UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

256 lines (199 loc) 6.69 kB
const $dom = require('../dom') const $window = require('../dom/window') const $elements = require('../dom/elements') const $actionability = require('./actionability') const create = (state) => { const documentHasFocus = () => { // hardcode document has focus as true // since the test should assume the window // is in focus the entire time return true } const fireBlur = (el) => { const win = $window.getWindowByElement(el) let hasBlurred = false // we need to bind to the blur event here // because some browsers will not ever fire // the blur event if the window itself is not // currently blured const cleanup = () => { return $elements.callNativeMethod(el, 'removeEventListener', 'blur', onBlur) } const onBlur = () => { return hasBlurred = true } // for simplicity we allow change events // to be triggered by a manual blur $actionability.dispatchPrimedChangeEvents(state) $elements.callNativeMethod(el, 'addEventListener', 'blur', onBlur) $elements.callNativeMethod(el, 'blur') cleanup() // body will never emit focus events // so we avoid simulating this if ($elements.isBody(el)) { return } // fallback if our focus event never fires // to simulate the focus + focusin if (!hasBlurred) { return simulateBlurEvent(el, win) } } const simulateBlurEvent = (el, win) => { // todo handle relatedTarget's per the spec const focusoutEvt = new FocusEvent('focusout', { bubbles: true, cancelable: false, view: win, relatedTarget: null, }) const blurEvt = new FocusEvent('blur', { bubble: false, cancelable: false, view: win, relatedTarget: null, }) el.dispatchEvent(blurEvt) return el.dispatchEvent(focusoutEvt) } const fireFocus = (el, opts) => { // body will never emit focus events (unless it's contenteditable) // so we avoid simulating this if ($elements.isBody(el) && !$elements.isContentEditable(el)) { return } // if we are focusing a different element // dispatch any primed change events // we have to do this because our blur // method might not get triggered if // our window is in focus since the // browser may fire blur events naturally $actionability.dispatchPrimedChangeEvents(state) const win = $window.getWindowByElement(el) // store the current focused element, since it will change when we call .focus() // // need to pass in el.ownerDocument to get the correct focused element // when el is in an iframe and the browser is not // in focus (https://github.com/cypress-io/cypress/issues/8111) const $focused = getFocused(el.ownerDocument) let hasFocused = false // we need to bind to the focus event here // because some browsers will not ever fire // the focus event if the window itself is not // currently focused const cleanup = () => { return $elements.callNativeMethod(el, 'removeEventListener', 'focus', onFocus) } const onFocus = () => { return hasFocused = true } $elements.callNativeMethod(el, 'addEventListener', 'focus', onFocus) $elements.callNativeMethod(el, 'focus', opts) cleanup() // fallback if our focus event never fires // to simulate the focus + focusin if (!hasFocused) { // only blur if we have a focused element AND its not // currently ourselves! if ($focused && $focused.get(0) !== el) { // additionally make sure that this isnt // the window, since that does not steal focus // or actually change the activeElement if (!$window.isWindow(el)) { fireBlur($focused.get(0)) } } return simulateFocusEvent(el, win) } } const simulateFocusEvent = (el, win) => { // todo handle relatedTarget's per the spec const focusinEvt = new FocusEvent('focusin', { bubbles: true, view: win, relatedTarget: null, }) const focusEvt = new FocusEvent('focus', { view: win, relatedTarget: null, }) // not fired in the correct order per w3c spec // because chrome chooses to fire focus before focusin // and since we have a simulation fallback we end up // doing it how chrome does it // http://www.w3.org/TR/DOM-Level-3-Events/#h-events-focusevent-event-order el.dispatchEvent(focusEvt) return el.dispatchEvent(focusinEvt) } const interceptFocus = (el) => { // normally programmatic focus calls cause "primed" focus/blur // events if the window is not in focus // so we fire fake events to act as if the window // is always in focus const $focused = getFocused() if ((!$focused || $focused[0] !== el) && $elements.isW3CFocusable(el)) { fireFocus(el) return } $elements.callNativeMethod(el, 'focus') } const interceptBlur = (el) => { // normally programmatic blur calls cause "primed" focus/blur // events if the window is not in focus // so we fire fake events to act as if the window // is always in focus. const $focused = getFocused() if ($focused && $focused[0] === el) { fireBlur(el) return } $elements.callNativeMethod(el, 'blur') } const needsFocus = ($elToFocus, $previouslyFocusedEl) => { const $focused = getFocused() // if we dont have a focused element // we know we want to fire a focus event if (!$focused) { return true } // if we didnt have a previously focused el // then always return true if (!$previouslyFocusedEl) { return true } // if we are attemping to focus a differnet element // than the one we currently have, we know we want // to fire a focus event if ($focused.get(0) !== $elToFocus.get(0)) { return true } // if our focused element isnt the same as the previously // focused el then what probably happened is our mouse // down event caused our element to receive focuse // without the browser sending the focus event // which happens when the window isnt in focus if ($previouslyFocusedEl.get(0) !== $focused.get(0)) { return true } return false } const getFocused = (document = state('document')) => { const { activeElement } = document if ($dom.isFocused(activeElement)) { return $dom.wrap(activeElement) } return null } return { fireBlur, fireFocus, needsFocus, getFocused, interceptFocus, interceptBlur, documentHasFocus, } } module.exports = { create, }