diff --git a/.changeset/wise-mirrors-grabberz.md b/.changeset/wise-mirrors-grabberz.md new file mode 100644 index 000000000..5cf3307a8 --- /dev/null +++ b/.changeset/wise-mirrors-grabberz.md @@ -0,0 +1,6 @@ +--- +'@lion/ui': patch +--- + +make web-test-runner statements checking `documentOrShadowRoot.activeElement` debuggable by exposing +a test-helper method `isActiveElement`. diff --git a/packages/ui/components/combobox/test/lion-combobox.test.js b/packages/ui/components/combobox/test/lion-combobox.test.js index f1e052f00..2a94f8636 100644 --- a/packages/ui/components/combobox/test/lion-combobox.test.js +++ b/packages/ui/components/combobox/test/lion-combobox.test.js @@ -1,20 +1,22 @@ -import { - getComboboxMembers, - getFilteredOptionValues, - mimicKeyPress, - mimicUserTyping, - mimicUserTypingAdvanced, -} from '@lion/ui/combobox-test-helpers.js'; +import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing'; +import { Required, Unparseable } from '@lion/ui/form-core.js'; +import { sendKeys } from '@web/test-runner-commands'; import { LionCombobox } from '@lion/ui/combobox.js'; import { browserDetection } from '@lion/ui/core.js'; import '@lion/ui/define/lion-combobox.js'; import '@lion/ui/define/lion-listbox.js'; import '@lion/ui/define/lion-option.js'; -import { Required, Unparseable } from '@lion/ui/form-core.js'; -import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing'; -import { sendKeys } from '@web/test-runner-commands'; import { LitElement } from 'lit'; import sinon from 'sinon'; +import { + getFilteredOptionValues, + mimicUserTypingAdvanced, + getComboboxMembers, + mimicUserTyping, + mimicKeyPress, +} from '@lion/ui/combobox-test-helpers.js'; + +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; /** * @typedef {import('../types/SelectionDisplay.js').SelectionDisplay} SelectionDisplay @@ -986,7 +988,7 @@ describe('lion-combobox', () => { options[0].click(); await el.updateComplete; expect(el.opened).to.equal(false); - expect(document.activeElement).to.equal(_inputNode); + expect(isActiveElement(_inputNode)).to.be.true; // step [4] await el.updateComplete; diff --git a/packages/ui/components/overlays/src/utils/get-deep-active-element.js b/packages/ui/components/core/src/getDeepActiveElement.js similarity index 100% rename from packages/ui/components/overlays/src/utils/get-deep-active-element.js rename to packages/ui/components/core/src/getDeepActiveElement.js diff --git a/packages/ui/components/core/test-helpers/isActiveElement.js b/packages/ui/components/core/test-helpers/isActiveElement.js new file mode 100644 index 000000000..759c8b649 --- /dev/null +++ b/packages/ui/components/core/test-helpers/isActiveElement.js @@ -0,0 +1,14 @@ +import { getDeepActiveElement } from '../src/getDeepActiveElement.js'; +/** + * Readable alternative for `expect(el).to.equal(document.activeElement);`. + * While this is readable by itself, it makes Web Test Runner hang completely in many occasions. + * Therefore it's better to write: + * `expect(isActiveElement(el)).to.be.true;` + * @param {Element} el + * @param {{deep?: boolean}} opts + * @returns {boolean} + */ +export function isActiveElement(el, { deep = false } = {}) { + const activeEl = deep ? getDeepActiveElement() : document.activeElement; + return el === activeEl; +} diff --git a/packages/ui/components/core/test/SlotMixin.test.js b/packages/ui/components/core/test/SlotMixin.test.js index 21b1f0c2a..ffdce2c0a 100644 --- a/packages/ui/components/core/test/SlotMixin.test.js +++ b/packages/ui/components/core/test/SlotMixin.test.js @@ -4,6 +4,7 @@ import { LitElement } from 'lit'; import sinon from 'sinon'; import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js'; +import { isActiveElement } from '../test-helpers/isActiveElement.js'; /** * @typedef {import('../types/SlotMixinTypes.js').SlotHost} SlotHost @@ -210,12 +211,12 @@ describe('SlotMixin', () => { ); const el = /** @type {* & SlotHost} */ (await fixture(`<${tag}>`)); el._focusableNode.focus(); - expect(document.activeElement).to.equal(el._focusableNode); + expect(isActiveElement(el._focusableNode)).to.be.true; el.currentValue = 1; await el.updateComplete; - expect(document.activeElement).to.equal(el._focusableNode); + expect(isActiveElement(el._focusableNode)).to.be.true; }); it('keeps focus after rerendering complex shadow root into slot', async () => { @@ -277,7 +278,7 @@ describe('SlotMixin', () => { el.currentValue = 1; await el.updateComplete; - expect(document.activeElement).to.equal(el._focusableNode); + expect(isActiveElement(el._focusableNode)).to.be.true; expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); }); @@ -335,12 +336,12 @@ describe('SlotMixin', () => { el._focusableNode._buttonNode.focus(); - expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); + expect(isActiveElement(el._focusableNode._buttonNode, { deep: true })).to.be.true; el.currentValue = 1; await el.updateComplete; - expect(document.activeElement).to.equal(el._focusableNode); + expect(isActiveElement(el._focusableNode)).to.be.true; expect(el._focusableNode.shadowRoot.activeElement).to.equal(el._focusableNode._buttonNode); }); diff --git a/packages/ui/components/dialog/test/lion-dialog.test.js b/packages/ui/components/dialog/test/lion-dialog.test.js index dcff2d60f..3838b6703 100644 --- a/packages/ui/components/dialog/test/lion-dialog.test.js +++ b/packages/ui/components/dialog/test/lion-dialog.test.js @@ -1,6 +1,7 @@ /* eslint-disable lit-a11y/no-autofocus */ import { expect, fixture as _fixture, html, unsafeStatic, aTimeout } from '@open-wc/testing'; import { runOverlayMixinSuite } from '../../overlays/test-suites/OverlayMixin.suite.js'; +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; import '@lion/ui/define/lion-dialog.js'; /** @@ -89,8 +90,8 @@ describe('lion-dialog', () => { const invokerNode = el._overlayInvokerNode; invokerNode.focus(); invokerNode.click(); - const contentNode = el.querySelector('[slot="content"]'); - expect(document.activeElement).to.equal(contentNode); + const contentNode = /** @type {Element} */ (el.querySelector('[slot="content"]')); + expect(isActiveElement(contentNode)).to.be.true; }); it('sets focus on autofocused element', async () => { @@ -107,8 +108,8 @@ describe('lion-dialog', () => { const invokerNode = el._overlayInvokerNode; invokerNode.focus(); invokerNode.click(); - const input = el.querySelector('input'); - expect(document.activeElement).to.equal(input); + const input = /** @type {Element} */ (el.querySelector('input')); + expect(isActiveElement(input)).to.be.true; }); it('with trapsKeyboardFocus set to false the focus stays on the invoker', async () => { @@ -125,7 +126,7 @@ describe('lion-dialog', () => { const invokerNode = el._overlayInvokerNode; invokerNode.focus(); invokerNode.click(); - expect(document.activeElement).to.equal(invokerNode); + expect(isActiveElement(invokerNode)).to.be.true; }); it('opened-changed event should send detail object with opened state', async () => { diff --git a/packages/ui/components/form-core/test-suites/NativeTextFieldMixin.suite.js b/packages/ui/components/form-core/test-suites/NativeTextFieldMixin.suite.js index 95f6fa318..d3249a390 100644 --- a/packages/ui/components/form-core/test-suites/NativeTextFieldMixin.suite.js +++ b/packages/ui/components/form-core/test-suites/NativeTextFieldMixin.suite.js @@ -1,14 +1,16 @@ -import { LitElement } from 'lit'; -import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js'; import { defineCE, expect, fixture, html, triggerFocusFor, unsafeStatic } from '@open-wc/testing'; -import { sendKeys } from '@web/test-runner-commands'; -import { spy } from 'sinon'; +import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js'; import { NativeTextFieldMixin } from '@lion/ui/form-core.js'; +import { sendKeys } from '@web/test-runner-commands'; import { browserDetection } from '@lion/ui/core.js'; +import { LitElement } from 'lit'; +import { spy } from 'sinon'; + +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; /** - * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost * @typedef {ArrayConstructor | ObjectConstructor | NumberConstructor | BooleanConstructor | StringConstructor | DateConstructor | 'iban' | 'email'} modelValueType + * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost */ /** @@ -56,7 +58,7 @@ export function runNativeTextFieldMixinSuite(customConfig) { const { _inputNode } = getFormControlMembers(el); await triggerFocusFor(el); await el.updateComplete; - expect(document.activeElement).to.equal(_inputNode); + expect(isActiveElement(_inputNode)).to.be.true; await sendKeys({ press: 'h', }); diff --git a/packages/ui/components/form-core/test/lion-field.test.js b/packages/ui/components/form-core/test/lion-field.test.js index 195d3c787..e49afdda8 100644 --- a/packages/ui/components/form-core/test/lion-field.test.js +++ b/packages/ui/components/form-core/test/lion-field.test.js @@ -1,27 +1,28 @@ -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; -import { Required, Validator } from '@lion/ui/form-core.js'; -import '@lion/ui/define/lion-field.js'; import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js'; import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; -import { - expect, - fixture, - html, - triggerBlurFor, - triggerFocusFor, - unsafeStatic, -} from '@open-wc/testing'; +import { Required, Validator } from '@lion/ui/form-core.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import '@lion/ui/define/lion-field.js'; import sinon from 'sinon'; +import { + triggerFocusFor, + triggerBlurFor, + unsafeStatic, + fixture, + expect, + html, +} from '@open-wc/testing'; + +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; /** - * @typedef {import('../src/LionField.js').LionField} LionField - * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost * @typedef {FormControlHost & HTMLElement & {_parentFormGroup?:HTMLElement, checked?:boolean}} FormControl + * @typedef {HTMLElement & {shadowRoot: HTMLElement, assignedNodes: Function}} ShadowHTMLElement + * @typedef {import('../types/FormControlMixinTypes.js').FormControlHost} FormControlHost + * @typedef {import('../src/LionField.js').LionField} LionField */ -/** @typedef {HTMLElement & {shadowRoot: HTMLElement, assignedNodes: Function}} ShadowHTMLElement */ - const tagString = 'lion-field'; const tag = unsafeStatic(tagString); const inputSlotString = ''; @@ -106,7 +107,7 @@ describe('', () => { await triggerFocusFor(el); - expect(document.activeElement).to.equal(_inputNode); + expect(isActiveElement(_inputNode)).to.be.true; expect(cbFocusHost.callCount).to.equal(1); expect(cbFocusNativeInput.callCount).to.equal(1); expect(cbBlurHost.callCount).to.equal(0); @@ -117,7 +118,7 @@ describe('', () => { expect(cbBlurNativeInput.callCount).to.equal(1); await triggerFocusFor(el); - expect(document.activeElement).to.equal(_inputNode); + expect(isActiveElement(_inputNode)).to.be.true; expect(cbFocusHost.callCount).to.equal(2); expect(cbFocusNativeInput.callCount).to.equal(2); diff --git a/packages/ui/components/form-integrations/test/dialog-integrations.test.js b/packages/ui/components/form-integrations/test/dialog-integrations.test.js index 0b5da6bc4..9e68e54a3 100644 --- a/packages/ui/components/form-integrations/test/dialog-integrations.test.js +++ b/packages/ui/components/form-integrations/test/dialog-integrations.test.js @@ -1,13 +1,14 @@ /* eslint-disable lit-a11y/no-autofocus */ import { expect, fixture } from '@open-wc/testing'; -import { html } from 'lit'; -import { getAllTagNames } from './helpers/helpers.js'; -import './helpers/umbrella-form.js'; -import '@lion/ui/define/lion-dialog.js'; import '@lion/ui/define/lion-checkbox.js'; +import '@lion/ui/define/lion-dialog.js'; import '@lion/ui/define/lion-option.js'; import '@lion/ui/define/lion-radio.js'; +import { html } from 'lit'; +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; +import { getAllTagNames } from './helpers/helpers.js'; +import './helpers/umbrella-form.js'; /** * @typedef {import('./helpers/umbrella-form.js').UmbrellaForm} UmbrellaForm * @typedef {import('../../dialog/src/LionDialog.js').LionDialog} LionDialog @@ -89,6 +90,6 @@ describe('Form inside dialog Integrations', () => { el._overlayInvokerNode.click(); const lionInput = el.querySelector('[name="input"]'); // @ts-expect-error [allow-protected-in-tests] - expect(document.activeElement).to.equal(lionInput._focusableNode); + expect(isActiveElement(lionInput._focusableNode)).to.be.true; }); }); diff --git a/packages/ui/components/form/test/lion-form.test.js b/packages/ui/components/form/test/lion-form.test.js index f1dcc0403..da824b43c 100644 --- a/packages/ui/components/form/test/lion-form.test.js +++ b/packages/ui/components/form/test/lion-form.test.js @@ -1,25 +1,27 @@ -import { LionFieldset } from '@lion/ui/fieldset.js'; -import '@lion/ui/define/lion-fieldset.js'; import { LionField, Required } from '@lion/ui/form-core.js'; -import '@lion/ui/define/lion-field.js'; import '@lion/ui/define/lion-validation-feedback.js'; +import { LionFieldset } from '@lion/ui/fieldset.js'; +import '@lion/ui/define/lion-checkbox-group.js'; +import '@lion/ui/define/lion-radio-group.js'; +import '@lion/ui/define/lion-fieldset.js'; +import '@lion/ui/define/lion-checkbox.js'; import '@lion/ui/define/lion-listbox.js'; import '@lion/ui/define/lion-option.js'; -import '@lion/ui/define/lion-checkbox-group.js'; -import '@lion/ui/define/lion-checkbox.js'; -import '@lion/ui/define/lion-radio-group.js'; +import '@lion/ui/define/lion-field.js'; import '@lion/ui/define/lion-radio.js'; import '@lion/ui/define/lion-form.js'; +import { spy } from 'sinon'; import { + fixture as _fixture, + unsafeStatic, aTimeout, defineCE, - expect, - fixture as _fixture, - html, oneEvent, - unsafeStatic, + expect, + html, } from '@open-wc/testing'; -import { spy } from 'sinon'; + +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; /** * @typedef {import('../src/LionForm.js').LionForm} LionForm @@ -225,7 +227,7 @@ describe('', () => { button.click(); expect(dispatchSpy.args[0][0].type).to.equal('submit'); // @ts-ignore [allow-protected] in test - expect(document.activeElement).to.equal(el.formElements[1]._inputNode); + expect(isActiveElement(el.formElements[1]._inputNode)).to.be.true; }); it('sets focus on submit to the first erroneous form element within a fieldset', async () => { @@ -248,7 +250,7 @@ describe('', () => { expect(dispatchSpy.args[0][0].type).to.equal('submit'); const fieldset = el.formElements[0]; // @ts-ignore [allow-protected] in test - expect(document.activeElement).to.equal(fieldset.formElements[1]._inputNode); + expect(isActiveElement(fieldset.formElements[1]._inputNode)).to.be.true; }); it('sets focus on submit to the first form element within a erroneous fieldset', async () => { @@ -268,7 +270,7 @@ describe('', () => { button.click(); expect(dispatchSpy.args[0][0].type).to.equal('submit'); const fieldset = el.formElements[0]; - expect(document.activeElement).to.equal(fieldset.formElements[0]._inputNode); + expect(isActiveElement(fieldset.formElements[0]._inputNode)).to.be.true; }); it('sets focus on submit to the first form element within a erroneous fieldset within another fieldset', async () => { @@ -290,7 +292,7 @@ describe('', () => { const childFieldsetEl = parentFieldSetEl.formElements[0]; const inputEl = childFieldsetEl.formElements[0]; button.click(); - expect(document.activeElement).to.equal(inputEl._focusableNode); + expect(isActiveElement(inputEl._focusableNode)).to.be.true; }); it('sets focus on submit to the first form element within a erroneous listbox', async () => { @@ -308,7 +310,7 @@ describe('', () => { const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); button.click(); const listboxEl = el.formElements[0]; - expect(document.activeElement).to.equal(listboxEl._inputNode); + expect(isActiveElement(listboxEl._inputNode)).to.be.true; }); it('sets focus on submit to the first form element within a erroneous listbox within a fieldset', async () => { @@ -329,7 +331,7 @@ describe('', () => { button.click(); const fieldsetEl = el.formElements[0]; const listboxEl = fieldsetEl.formElements[0]; - expect(document.activeElement).to.equal(listboxEl._inputNode); + expect(isActiveElement(listboxEl._inputNode)).to.be.true; }); it('sets focus on submit to the first form element within a erroneous checkbox-group', async () => { @@ -348,7 +350,7 @@ describe('', () => { const checkboxGroupEl = el.formElements[0]; const checkboxEl = checkboxGroupEl.formElements[0]; button.click(); - expect(document.activeElement).to.equal(checkboxEl._focusableNode); + expect(isActiveElement(checkboxEl._focusableNode)).to.be.true; }); it('sets focus on submit to the first form element within a erroneous radio-group', async () => { @@ -370,6 +372,6 @@ describe('', () => { const radioGroupEl = el.formElements[0]; const radioEl = radioGroupEl.formElements[0]; button.click(); - expect(document.activeElement).to.equal(radioEl._focusableNode); + expect(isActiveElement(radioEl._focusableNode)).to.be.true; }); }); diff --git a/packages/ui/components/overlays/src/utils/contain-focus.js b/packages/ui/components/overlays/src/utils/contain-focus.js index 3fdf6e0c0..67a5590ad 100644 --- a/packages/ui/components/overlays/src/utils/contain-focus.js +++ b/packages/ui/components/overlays/src/utils/contain-focus.js @@ -6,7 +6,7 @@ * and contains several bugs on IE11. */ -import { getDeepActiveElement } from './get-deep-active-element.js'; +import { getDeepActiveElement } from '../../../core/src/getDeepActiveElement.js'; import { getFocusableElements } from './get-focusable-elements.js'; import { deepContains } from './deep-contains.js'; import { keyCodes } from './key-codes.js'; diff --git a/packages/ui/components/overlays/test/OverlayController.test.js b/packages/ui/components/overlays/test/OverlayController.test.js index 7ce54420a..7b8640843 100644 --- a/packages/ui/components/overlays/test/OverlayController.test.js +++ b/packages/ui/components/overlays/test/OverlayController.test.js @@ -12,6 +12,7 @@ import { html, } from '@open-wc/testing'; +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; import { createShadowHost } from '../test-helpers/createShadowHost.js'; import { _adoptStyleUtils } from '../src/utils/adopt-styles.js'; import { simulateTab } from '../src/utils/simulate-tab.js'; @@ -574,7 +575,8 @@ describe('OverlayController', () => { trapsKeyboardFocus: true, }); await ctrl.show(); - expect(ctrl.contentNode).to.equal(document.activeElement); + + expect(isActiveElement(ctrl.contentNode)).to.be.true; }); it('keeps focus within the overlay e.g. you can not tab out by accident', async () => { @@ -1139,10 +1141,10 @@ describe('OverlayController', () => { await ctrl.show(); const input = /** @type {HTMLInputElement} */ (contentNode.querySelector('input')); input.focus(); - expect(document.activeElement).to.equal(input); + expect(isActiveElement(input)).to.be.true; await ctrl.hide(); - expect(document.activeElement).to.equal(document.body); + expect(isActiveElement(document.body)).to.be.true; }); it('supports elementToFocusAfterHide option to focus it when hiding', async () => { @@ -1159,10 +1161,10 @@ describe('OverlayController', () => { await ctrl.show(); const textarea = /** @type {HTMLTextAreaElement} */ (contentNode.querySelector('textarea')); textarea.focus(); - expect(document.activeElement).to.equal(textarea); + expect(isActiveElement(textarea)).to.be.true; await ctrl.hide(); - expect(document.activeElement).to.equal(input); + expect(isActiveElement(input)).to.be.true; expect(isInViewport(input)).to.be.true; }); @@ -1187,10 +1189,10 @@ describe('OverlayController', () => { await ctrl.show(); const textarea = /** @type {HTMLTextAreaElement} */ (contentNode.querySelector('textarea')); textarea.focus(); - expect(document.activeElement).to.equal(textarea); + expect(isActiveElement(textarea)).to.be.true; await ctrl.hide(); - expect(document.activeElement).to.equal(input); + expect(isActiveElement(input)).to.be.true; document.body.removeChild(shadowHost); }); @@ -1208,10 +1210,10 @@ describe('OverlayController', () => { await ctrl.show(); // an outside element has taken over focus outsideButton.focus(); - expect(document.activeElement).to.equal(outsideButton); + expect(isActiveElement(outsideButton)).to.be.true; await ctrl.hide(); - expect(document.activeElement).to.equal(outsideButton); + expect(isActiveElement(outsideButton)).to.be.true; }); it('allows to set elementToFocusAfterHide on show', async () => { @@ -1230,10 +1232,10 @@ describe('OverlayController', () => { await ctrl.show(input); const textarea = /** @type {HTMLTextAreaElement} */ (contentNode.querySelector('textarea')); textarea.focus(); - expect(document.activeElement).to.equal(textarea); + expect(isActiveElement(textarea)).to.be.true; await ctrl.hide(); - expect(document.activeElement).to.equal(input); + expect(isActiveElement(input)).to.be.true; }); }); diff --git a/packages/ui/components/overlays/test/utils-tests/contain-focus.test.js b/packages/ui/components/overlays/test/utils-tests/contain-focus.test.js index 2ad3c3fca..ce7e095aa 100644 --- a/packages/ui/components/overlays/test/utils-tests/contain-focus.test.js +++ b/packages/ui/components/overlays/test/utils-tests/contain-focus.test.js @@ -1,11 +1,12 @@ /* eslint-disable lit-a11y/no-autofocus */ import { expect, fixture, nextFrame } from '@open-wc/testing'; -import { html } from 'lit/static-html.js'; +import { getFocusableElements } from '@lion/ui/overlays.js'; import { renderLitAsNode } from '@lion/ui/helpers.js'; -import { getDeepActiveElement, getFocusableElements } from '@lion/ui/overlays.js'; +import { html } from 'lit/static-html.js'; -import { keyCodes } from '../../src/utils/key-codes.js'; +import { isActiveElement } from '../../../core/test-helpers/isActiveElement.js'; import { containFocus } from '../../src/utils/contain-focus.js'; +import { keyCodes } from '../../src/utils/key-codes.js'; function simulateTabWithinContainFocus() { const event = new CustomEvent('keydown', { detail: 0, bubbles: true }); @@ -87,7 +88,7 @@ describe('containFocus()', () => { const root = /** @type {HTMLElement} */ (document.getElementById('rootElement')); const { disconnect } = containFocus(root); - expect(getDeepActiveElement()).to.equal(root); + expect(isActiveElement(root, { deep: true })).to.be.true; expect(root.getAttribute('tabindex')).to.equal('-1'); expect(root.style.getPropertyValue('outline-style')).to.equal('none'); @@ -99,7 +100,7 @@ describe('containFocus()', () => { const el = /** @type {HTMLElement} */ (document.querySelector('input[autofocus]')); const { disconnect } = containFocus(el); - expect(getDeepActiveElement()).to.equal(el); + expect(isActiveElement(el, { deep: true })).to.be.true; disconnect(); }); @@ -113,7 +114,7 @@ describe('containFocus()', () => { /** @type {HTMLElement} */ (document.getElementById('outside-1')).focus(); simulateTabWithinContainFocus(); - expect(getDeepActiveElement()).to.equal(focusableElements[0]); + expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true; disconnect(); }); @@ -127,7 +128,7 @@ describe('containFocus()', () => { focusableElements[focusableElements.length - 1].focus(); simulateTabWithinContainFocus(); - expect(getDeepActiveElement()).to.equal(focusableElements[0]); + expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true; disconnect(); }); @@ -146,7 +147,7 @@ describe('containFocus()', () => { * actual tab key press. So the best we can do is if we didn't redirect focus * to the first element. */ - expect(getDeepActiveElement()).to.equal(focusableElements[2]); + expect(isActiveElement(focusableElements[2], { deep: true })).to.be.true; disconnect(); }); @@ -158,9 +159,10 @@ describe('containFocus()', () => { const { disconnect } = containFocus(root); focusableElements[2].focus(); - expect(getDeepActiveElement()).to.equal(focusableElements[2]); + expect(isActiveElement(focusableElements[2], { deep: true })).to.be.true; + document.body.click(); // this does not cause focusout event :( doesn't seem possible to mock - expect(getDeepActiveElement()).to.equal(root); + expect(isActiveElement(root, { deep: true })).to.be.true; disconnect(); }); @@ -185,11 +187,12 @@ describe('containFocus()', () => { // Simulate tab in window simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-1'))); - expect(getDeepActiveElement()).to.equal(focusableElements[0]); + expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true; // Simulate shift+tab in window simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-2'))); - expect(getDeepActiveElement()).to.equal(focusableElements[focusableElements.length - 1]); + expect(isActiveElement(focusableElements[focusableElements.length - 1], { deep: true })).to.be + .true; disconnect(); }); @@ -202,11 +205,12 @@ describe('containFocus()', () => { // Simulate tab in window simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-1'))); - expect(getDeepActiveElement()).to.equal(focusableElements[0]); + expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true; // Simulate shift+tab in window simulateTabInWindow(/** @type {HTMLElement} */ (document.getElementById('outside-2'))); - expect(getDeepActiveElement()).to.equal(focusableElements[focusableElements.length - 1]); + expect(isActiveElement(focusableElements[focusableElements.length - 1], { deep: true })).to.be + .true; disconnect(); }); @@ -219,11 +223,12 @@ describe('containFocus()', () => { // Simulate tab in window simulateTabInWindow(focusableElements[0]); - expect(getDeepActiveElement()).to.equal(focusableElements[0]); + expect(isActiveElement(focusableElements[0], { deep: true })).to.be.true; // Simulate shift+tab in window simulateTabInWindow(focusableElements[focusableElements.length - 1]); - expect(getDeepActiveElement()).to.equal(focusableElements[focusableElements.length - 1]); + expect(isActiveElement(focusableElements[focusableElements.length - 1], { deep: true })).to.be + .true; disconnect(); }); diff --git a/packages/ui/components/select-rich/test/lion-select-rich.test.js b/packages/ui/components/select-rich/test/lion-select-rich.test.js index a0db51cdf..fb7681bfa 100644 --- a/packages/ui/components/select-rich/test/lion-select-rich.test.js +++ b/packages/ui/components/select-rich/test/lion-select-rich.test.js @@ -1,30 +1,28 @@ -import { LitElement } from 'lit'; -import { LionOption } from '@lion/ui/listbox.js'; -import { OverlayController } from '@lion/ui/overlays.js'; -import { mimicClick } from '@lion/ui/overlays-test-helpers.js'; import { LionSelectInvoker, LionSelectRich } from '@lion/ui/select-rich.js'; - -import '@lion/ui/define/lion-option.js'; -import '@lion/ui/define/lion-listbox.js'; +import { getSelectRichMembers } from '@lion/ui/select-rich-test-helpers.js'; +import { mimicClick } from '@lion/ui/overlays-test-helpers.js'; +import { OverlayController } from '@lion/ui/overlays.js'; +import { LionOption } from '@lion/ui/listbox.js'; import '@lion/ui/define/lion-select-rich.js'; +import '@lion/ui/define/lion-listbox.js'; +import '@lion/ui/define/lion-option.js'; +import { LitElement } from 'lit'; import { + fixture as _fixture, + unsafeStatic, + nextFrame, aTimeout, defineCE, expect, - fixture as _fixture, html, - nextFrame, - unsafeStatic, } from '@open-wc/testing'; -import { getSelectRichMembers } from '@lion/ui/select-rich-test-helpers.js'; + +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; /** - * @typedef {import('../../listbox/src/LionOptions.js').LionOptions} LionOptions - * @typedef {import('../../listbox/types/ListboxMixinTypes.js').ListboxHost} ListboxHost * @typedef {import('../../form-core/types/FormControlMixinTypes.js').FormControlHost} FormControlHost - */ - -/** + * @typedef {import('../../listbox/types/ListboxMixinTypes.js').ListboxHost} ListboxHost + * @typedef {import('../../listbox/src/LionOptions.js').LionOptions} LionOptions * @typedef {import('lit').TemplateResult} TemplateResult */ @@ -478,7 +476,7 @@ describe('lion-select-rich', () => { el.opened = true; await el.updateComplete; - expect(document.activeElement).to.equal(_listboxNode); + expect(isActiveElement(_listboxNode)).to.be.true; el.opened = false; await el.updateComplete; diff --git a/packages/ui/components/switch/test/lion-switch.test.js b/packages/ui/components/switch/test/lion-switch.test.js index f3dfe2e10..359f846a9 100644 --- a/packages/ui/components/switch/test/lion-switch.test.js +++ b/packages/ui/components/switch/test/lion-switch.test.js @@ -1,16 +1,17 @@ +import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js'; import { expect, fixture as _fixture } from '@open-wc/testing'; -import { html } from 'lit/static-html.js'; -import sinon from 'sinon'; import { Validator } from '@lion/ui/form-core.js'; import { LionSwitch } from '@lion/ui/switch.js'; -import { getFormControlMembers } from '@lion/ui/form-core-test-helpers.js'; - +import { html } from 'lit/static-html.js'; import '@lion/ui/define/lion-switch.js'; +import sinon from 'sinon'; + +import { isActiveElement } from '../../core/test-helpers/isActiveElement.js'; /** * @typedef {import('../src/LionSwitchButton.js').LionSwitchButton} LionSwitchButton - * @typedef {import('lit').TemplateResult} TemplateResult * @typedef {import('@lion/ui/types/form-core.js').FormControlHost} FormControlHost + * @typedef {import('lit').TemplateResult} TemplateResult */ const IsTrue = class extends Validator { @@ -60,7 +61,7 @@ describe('lion-switch', () => { const { _inputNode, _labelNode } = getSwitchMembers(el); _labelNode.click(); - expect(document.activeElement).to.equal(_inputNode); + expect(isActiveElement(_inputNode)).to.be.true; }); it('clicking the label should not focus the toggle button when disabled', async () => { diff --git a/packages/ui/exports/overlays.js b/packages/ui/exports/overlays.js index db0a17179..f7813d061 100644 --- a/packages/ui/exports/overlays.js +++ b/packages/ui/exports/overlays.js @@ -11,11 +11,12 @@ export { withTooltipConfig } from '../components/overlays/src/configurations/wit export { containFocus, rotateFocus } from '../components/overlays/src/utils/contain-focus.js'; export { deepContains } from '../components/overlays/src/utils/deep-contains.js'; -export { getDeepActiveElement } from '../components/overlays/src/utils/get-deep-active-element.js'; +// re-export via this entrypoint for backwards compatibility +export { getDeepActiveElement } from '../components/core/src/getDeepActiveElement.js'; export { getFocusableElements } from '../components/overlays/src/utils/get-focusable-elements.js'; export { - setSiblingsInert, unsetSiblingsInert, + setSiblingsInert, } from '../components/overlays/src/utils/inert-siblings.js'; export { overlays } from '../components/overlays/src/singleton.js'; diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs index a37974224..033fab500 100644 --- a/web-test-runner.config.mjs +++ b/web-test-runner.config.mjs @@ -11,7 +11,8 @@ const groups = ( await optimisedGlob(['packages/*/test', 'packages/ui/components/**/test'], { onlyDirectories: true, }) -).map(dir => ({ name: dir.split('/').at(-2), files: `${dir}/**/*.test.js` })); +) // @ts-expect-error [update-es-version-later] + .map(dir => ({ name: dir.split('/').at(-2), files: `${dir}/**/*.test.js` })); // .filter(({name}) => name === 'overlays'); /** * @type {import('@web/test-runner').TestRunnerConfig['testRunnerHtml']}