From 43e4bb810c22555127ba3d110378202eb65548fa Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Mon, 12 Apr 2021 08:56:56 +0200 Subject: [PATCH] feat: private and protected types form-core --- .changeset/stupid-cycles-sniff.md | 40 ++ .../checkbox-group/src/LionCheckboxGroup.js | 14 +- .../test/lion-checkbox-group.test.js | 2 +- .../test/lion-checkbox-indeterminate.test.js | 99 ++--- packages/combobox/src/LionCombobox.js | 1 + packages/combobox/test/lion-combobox.test.js | 347 +++++++++--------- packages/form-core/src/FormControlMixin.js | 8 +- packages/form-core/src/FormatMixin.js | 14 +- .../form-core/src/NativeTextFieldMixin.js | 14 +- .../src/choice-group/ChoiceGroupMixin.js | 2 +- .../src/choice-group/ChoiceInputMixin.js | 4 +- .../src/form-group/FormGroupMixin.js | 3 +- .../form-core/src/utils/SyncUpdatableMixin.js | 2 + .../src/validate/validators/Required.js | 18 +- packages/form-core/test-helpers.js | 6 - .../test-helpers/getFormControlMembers.js | 21 ++ packages/form-core/test-helpers/index.js | 1 + .../FormRegistrationMixins.suite.js | 4 - .../test-suites/FormatMixin.suite.js | 30 +- .../InteractionStateMixin.suite.js | 10 +- .../test-suites/ValidateMixin.suite.js | 49 ++- .../ValidateMixinFeedbackPart.suite.js | 69 ++-- .../choice-group/ChoiceInputMixin.suite.js | 42 ++- .../form-group/FormGroupMixin-input.suite.js | 12 +- .../form-group/FormGroupMixin.suite.js | 40 +- packages/form-core/test/FocusMixin.test.js | 14 +- .../form-core/test/FormControlMixin.test.js | 39 +- packages/form-core/test/lion-field.test.js | 23 +- .../test/utils/SyncUpdatableMixin.test.js | 2 + .../form-core/test/validate/Required.test.js | 14 +- .../validate/lion-validation-feedback.test.js | 2 +- packages/form-core/types/FocusMixinTypes.d.ts | 13 +- .../types/FormControlMixinTypes.d.ts | 119 +++--- .../form-core/types/FormatMixinTypes.d.ts | 32 +- .../types/InteractionStateMixinTypes.d.ts | 23 +- .../types/NativeTextFieldMixinTypes.d.ts | 22 +- .../choice-group/ChoiceGroupMixinTypes.d.ts | 45 +-- .../choice-group/ChoiceInputMixinTypes.d.ts | 59 +-- .../types/form-group/FormGroupMixinTypes.d.ts | 20 +- .../FormRegisteringMixinTypes.d.ts | 4 +- .../registration/FormRegistrarMixinTypes.d.ts | 4 +- .../FormRegistrarPortalMixinTypes.d.ts | 2 +- .../types/utils/SyncUpdatableMixinTypes.d.ts | 11 +- .../types/validate/ValidateMixinTypes.d.ts | 74 ++-- .../test/form-validation-integrations.test.js | 18 +- .../test/model-value-consistency.test.js | 16 +- .../test/lion-input-amount.test.js | 11 +- .../input-date/test/lion-input-date.test.js | 4 +- .../input-email/test/lion-input-email.test.js | 5 +- .../input-iban/test/lion-input-iban.test.js | 6 +- packages/input/package.json | 3 +- packages/input/src/LionInput.js | 8 +- .../input/test-helpers/getInputMembers.js | 17 + packages/input/test-helpers/index.js | 1 + packages/input/test/lion-input.test.js | 81 ++-- packages/listbox/package.json | 1 + packages/listbox/src/LionOption.js | 4 +- packages/listbox/src/ListboxMixin.js | 14 +- .../listbox/test-helpers/getListboxMembers.js | 21 ++ packages/listbox/test-helpers/index.js | 1 + .../listbox/test-suites/ListboxMixin.suite.js | 209 ++++++----- packages/overlays/package.json | 2 +- packages/overlays/test-helpers.js | 1 - packages/overlays/test-helpers/index.js | 1 + .../overlays/test/OverlayController.test.js | 2 +- .../select-rich/test/lion-select-rich.test.js | 257 ++++++------- packages/switch/src/LionSwitch.js | 3 +- packages/switch/test/lion-switch.test.js | 57 +-- packages/textarea/src/LionTextarea.js | 2 + packages/textarea/test/lion-textarea.test.js | 35 +- 70 files changed, 1197 insertions(+), 957 deletions(-) create mode 100644 .changeset/stupid-cycles-sniff.md delete mode 100644 packages/form-core/test-helpers.js create mode 100644 packages/form-core/test-helpers/getFormControlMembers.js create mode 100644 packages/input/test-helpers/getInputMembers.js create mode 100644 packages/input/test-helpers/index.js create mode 100644 packages/listbox/test-helpers/getListboxMembers.js create mode 100644 packages/listbox/test-helpers/index.js delete mode 100644 packages/overlays/test-helpers.js create mode 100644 packages/overlays/test-helpers/index.js diff --git a/.changeset/stupid-cycles-sniff.md b/.changeset/stupid-cycles-sniff.md new file mode 100644 index 000000000..ecd1c5d87 --- /dev/null +++ b/.changeset/stupid-cycles-sniff.md @@ -0,0 +1,40 @@ +--- +'@lion/ajax': minor +'@lion/button': minor +'@lion/checkbox-group': minor +'@lion/combobox': minor +'@lion/core': minor +'@lion/form-core': minor +'@lion/form-integrations': minor +'@lion/input': minor +'@lion/input-amount': minor +'@lion/input-date': minor +'@lion/input-email': minor +'@lion/input-iban': minor +'@lion/input-stepper': minor +'@lion/listbox': minor +'@lion/localize': minor +'@lion/overlays': minor +'@lion/select-rich': minor +'@lion/switch': minor +'@lion/textarea': minor +'@lion/fieldset': minor +'@lion/form': minor +'@lion/input-datepicker': minor +'@lion/input-range': minor +'@lion/radio-group': minor +'@lion/select': minor +'@lion/tooltip': minor +--- + +Type fixes and enhancements: + +- all protected/private entries added to form-core type definitions, and their dependents were fixed +- a lot @ts-expect-error and @ts-ignore (all `get slots()` and `get modelValue()` issues are fixed) +- categorized @ts-expect-error / @ts-ignore into: + - [external]: when a 3rd party didn't ship types (could also be browser specs) + - [allow-protected]: when we are allowed to know about protected methods. For instance when code + resides in the same package + - [allow-private]: when we need to check a private value inside a test + - [allow]: miscellaneous allows + - [editor]: when the editor complains, but the cli/ci doesn't diff --git a/packages/checkbox-group/src/LionCheckboxGroup.js b/packages/checkbox-group/src/LionCheckboxGroup.js index 646082d4a..6ad4b89f2 100644 --- a/packages/checkbox-group/src/LionCheckboxGroup.js +++ b/packages/checkbox-group/src/LionCheckboxGroup.js @@ -10,11 +10,11 @@ export class LionCheckboxGroup extends ChoiceGroupMixin(FormGroupMixin(LitElemen this.multipleChoice = true; } - // /** @param {import('@lion/core').PropertyValues } changedProperties */ - // updated(changedProperties) { - // super.updated(changedProperties); - // if (changedProperties.has('name') && !String(this.name).match(/\[\]$/)) { - // // throw new Error('Names should end in "[]".'); - // } - // } + /** @param {import('@lion/core').PropertyValues } changedProperties */ + updated(changedProperties) { + super.updated(changedProperties); + if (changedProperties.has('name') && !String(this.name).match(/\[\]$/)) { + throw new Error('Names should end in "[]".'); + } + } } diff --git a/packages/checkbox-group/test/lion-checkbox-group.test.js b/packages/checkbox-group/test/lion-checkbox-group.test.js index c771f42f1..847128a60 100644 --- a/packages/checkbox-group/test/lion-checkbox-group.test.js +++ b/packages/checkbox-group/test/lion-checkbox-group.test.js @@ -105,7 +105,7 @@ describe('', () => { await expect(el).to.be.accessible(); }); - it.skip("should throw exception if name doesn't end in []", async () => { + it("should throw exception if name doesn't end in []", async () => { const el = await fixture(html``); el.name = 'woof'; let err; diff --git a/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js b/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js index 6415bb3c1..7187b8caf 100644 --- a/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js +++ b/packages/checkbox-group/test/lion-checkbox-indeterminate.test.js @@ -1,4 +1,5 @@ import { expect, fixture, html } from '@open-wc/testing'; +import { getFormControlMembers } from '@lion/form-core/test-helpers'; import '@lion/checkbox-group/define'; /** @@ -9,10 +10,14 @@ import '@lion/checkbox-group/define'; /** * @param {LionCheckboxIndeterminate} el */ -function getProtectedMembers(el) { +function getCheckboxIndeterminateMembers(el) { + const obj = getFormControlMembers(el); return { - // @ts-ignore - subCheckboxes: el._subCheckboxes, + ...obj, + ...{ + // @ts-ignore [allow-protected] in test + _subCheckboxes: el._subCheckboxes, + }, }; } @@ -103,10 +108,10 @@ describe('', () => { 'lion-checkbox-indeterminate', )); - const { subCheckboxes } = getProtectedMembers(elIndeterminate); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); // Act - subCheckboxes[0].checked = true; + _subCheckboxes[0].checked = true; await el.updateComplete; // Assert @@ -127,12 +132,12 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); - const { subCheckboxes } = getProtectedMembers(elIndeterminate); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); // Act - subCheckboxes[0].checked = true; - subCheckboxes[1].checked = true; - subCheckboxes[2].checked = true; + _subCheckboxes[0].checked = true; + _subCheckboxes[1].checked = true; + _subCheckboxes[2].checked = true; await el.updateComplete; // Assert @@ -154,17 +159,17 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); // Act - elIndeterminate._inputNode.click(); + _inputNode.click(); await elIndeterminate.updateComplete; - const { subCheckboxes } = getProtectedMembers(elIndeterminate); // Assert expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false; - expect(subCheckboxes[0].hasAttribute('checked')).to.be.true; - expect(subCheckboxes[1].hasAttribute('checked')).to.be.true; - expect(subCheckboxes[2].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true; }); it('should sync all children when parent is checked (from unchecked to checked)', async () => { @@ -181,17 +186,17 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); // Act - elIndeterminate._inputNode.click(); + _inputNode.click(); await elIndeterminate.updateComplete; - const { subCheckboxes } = getProtectedMembers(elIndeterminate); // Assert expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false; - expect(subCheckboxes[0].hasAttribute('checked')).to.be.true; - expect(subCheckboxes[1].hasAttribute('checked')).to.be.true; - expect(subCheckboxes[2].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true; }); it('should sync all children when parent is checked (from checked to unchecked)', async () => { @@ -208,17 +213,17 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); + const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate); // Act - elIndeterminate._inputNode.click(); + _inputNode.click(); await elIndeterminate.updateComplete; - const elProts = getProtectedMembers(elIndeterminate); // Assert expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false; - expect(elProts.subCheckboxes[0].hasAttribute('checked')).to.be.false; - expect(elProts.subCheckboxes[1].hasAttribute('checked')).to.be.false; - expect(elProts.subCheckboxes[2].hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[0].hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false; + expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false; }); it('should work as expected with siblings checkbox-indeterminate', async () => { @@ -258,27 +263,28 @@ describe('', () => { const elFirstIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( '#first-checkbox-indeterminate', )); + const elSecondIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( '#second-checkbox-indeterminate', )); + const elFirstSubCheckboxes = getCheckboxIndeterminateMembers(elFirstIndeterminate); + const elSecondSubCheckboxes = getCheckboxIndeterminateMembers(elSecondIndeterminate); + // Act - check the first sibling - elFirstIndeterminate._inputNode.click(); + elFirstSubCheckboxes._inputNode.click(); await elFirstIndeterminate.updateComplete; await elSecondIndeterminate.updateComplete; - const elFirstSubCheckboxes = getProtectedMembers(elFirstIndeterminate); - const elSecondSubCheckboxes = getProtectedMembers(elSecondIndeterminate); - // Assert - the second sibling should not be affected expect(elFirstIndeterminate.hasAttribute('indeterminate')).to.be.false; - expect(elFirstSubCheckboxes.subCheckboxes[0].hasAttribute('checked')).to.be.true; - expect(elFirstSubCheckboxes.subCheckboxes[1].hasAttribute('checked')).to.be.true; - expect(elFirstSubCheckboxes.subCheckboxes[2].hasAttribute('checked')).to.be.true; + expect(elFirstSubCheckboxes._subCheckboxes[0].hasAttribute('checked')).to.be.true; + expect(elFirstSubCheckboxes._subCheckboxes[1].hasAttribute('checked')).to.be.true; + expect(elFirstSubCheckboxes._subCheckboxes[2].hasAttribute('checked')).to.be.true; - expect(elSecondSubCheckboxes.subCheckboxes[0].hasAttribute('checked')).to.be.false; - expect(elSecondSubCheckboxes.subCheckboxes[1].hasAttribute('checked')).to.be.false; + expect(elSecondSubCheckboxes._subCheckboxes[0].hasAttribute('checked')).to.be.false; + expect(elSecondSubCheckboxes._subCheckboxes[1].hasAttribute('checked')).to.be.false; }); it('should work as expected with nested indeterminate checkboxes', async () => { @@ -322,12 +328,13 @@ describe('', () => { const elParentIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( '#parent-checkbox-indeterminate', )); - const elNestedSubCheckboxes = getProtectedMembers(elNestedIndeterminate); - const elParentSubCheckboxes = getProtectedMembers(elParentIndeterminate); + const elNestedSubCheckboxes = getCheckboxIndeterminateMembers(elNestedIndeterminate); + const elParentSubCheckboxes = getCheckboxIndeterminateMembers(elParentIndeterminate); // Act - check a nested checkbox if (elNestedIndeterminate) { - elNestedSubCheckboxes.subCheckboxes[0]._inputNode.click(); + // @ts-ignore [allow-protected] in test + elNestedSubCheckboxes._subCheckboxes[0]._inputNode.click(); } await el.updateComplete; @@ -336,8 +343,10 @@ describe('', () => { expect(elParentIndeterminate?.hasAttribute('indeterminate')).to.be.true; // Act - check all nested checkbox - if (elNestedIndeterminate) elNestedSubCheckboxes.subCheckboxes[1]._inputNode.click(); - if (elNestedIndeterminate) elNestedSubCheckboxes.subCheckboxes[2]._inputNode.click(); + // @ts-ignore [allow-protected] in test + if (elNestedIndeterminate) elNestedSubCheckboxes._subCheckboxes[1]._inputNode.click(); + // @ts-ignore [allow-protected] in test + if (elNestedIndeterminate) elNestedSubCheckboxes._subCheckboxes[2]._inputNode.click(); await el.updateComplete; // Assert @@ -348,10 +357,12 @@ describe('', () => { // Act - finally check all remaining checkbox if (elParentIndeterminate) { - elParentSubCheckboxes.subCheckboxes[0]._inputNode.click(); + // @ts-ignore [allow-protected] in test + elParentSubCheckboxes._subCheckboxes[0]._inputNode.click(); } if (elParentIndeterminate) { - elParentSubCheckboxes.subCheckboxes[1]._inputNode.click(); + // @ts-ignore [allow-protected] in test + elParentSubCheckboxes._subCheckboxes[1]._inputNode.click(); } await el.updateComplete; @@ -383,12 +394,12 @@ describe('', () => { const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector( 'lion-checkbox-indeterminate', )); - const { subCheckboxes } = getProtectedMembers(elIndeterminate); + const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate); // Act - subCheckboxes[0].checked = true; - subCheckboxes[1].checked = true; - subCheckboxes[2].checked = true; + _subCheckboxes[0].checked = true; + _subCheckboxes[1].checked = true; + _subCheckboxes[2].checked = true; await el.updateComplete; // Assert diff --git a/packages/combobox/src/LionCombobox.js b/packages/combobox/src/LionCombobox.js index 387a00ab0..d9faa5d9c 100644 --- a/packages/combobox/src/LionCombobox.js +++ b/packages/combobox/src/LionCombobox.js @@ -180,6 +180,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * @configure FormControlMixin * Will tell FormControlMixin that a11y wrt labels / descriptions / feedback * should be applied here. + * @protected */ get _inputNode() { if (this._ariaVersion === '1.1') { diff --git a/packages/combobox/test/lion-combobox.test.js b/packages/combobox/test/lion-combobox.test.js index 927c02fdc..d80415cf4 100644 --- a/packages/combobox/test/lion-combobox.test.js +++ b/packages/combobox/test/lion-combobox.test.js @@ -4,34 +4,41 @@ import sinon from 'sinon'; import '@lion/combobox/define'; import { LionOptions } from '@lion/listbox'; import { browserDetection, LitElement } from '@lion/core'; +import { getListboxMembers } from '@lion/listbox/test-helpers'; import { Required } from '@lion/form-core'; import { LionCombobox } from '../src/LionCombobox.js'; /** * @typedef {import('../types/SelectionDisplay').SelectionDisplay} SelectionDisplay + * @typedef {import('@lion/listbox/types/ListboxMixinTypes').ListboxHost} ListboxHost + * @typedef {import('@lion/form-core/types/FormControlMixinTypes').FormControlHost} FormControlHost */ /** - * @param {LionCombobox} el + * @param { LionCombobox } el */ - -function getProtectedMembers(el) { - // @ts-ignore - const { - _comboboxNode: comboboxNode, - _inputNode: inputNode, - _listboxNode: listboxNode, - _selectionDisplayNode: selectionDisplayNode, - _activeDescendantOwnerNode: activeDescendantOwnerNode, - _ariaVersion: ariaVersion, - } = el; +function getComboboxMembers(el) { + const obj = getListboxMembers(el); return { - comboboxNode, - inputNode, - listboxNode, - selectionDisplayNode, - activeDescendantOwnerNode, - ariaVersion, + ...obj, + ...{ + // @ts-ignore [allow-protected] in test + _invokerNode: el._invokerNode, + // @ts-ignore [allow-protected] in test + _overlayCtrl: el._overlayCtrl, + // @ts-ignore [allow-protected] in test + _comboboxNode: el._comboboxNode, + // @ts-ignore [allow-protected] in test + _inputNode: el._inputNode, + // @ts-ignore [allow-protected] in test + _listboxNode: el._listboxNode, + // @ts-ignore [allow-protected] in test + _selectionDisplayNode: el._selectionDisplayNode, + // @ts-ignore [allow-protected] in test + _activeDescendantOwnerNode: el._activeDescendantOwnerNode, + // @ts-ignore [allow-protected] in test + _ariaVersion: el._ariaVersion, + }, }; } @@ -40,13 +47,13 @@ function getProtectedMembers(el) { * @param {string} value */ function mimicUserTyping(el, value) { - const { inputNode } = getProtectedMembers(el); - inputNode.dispatchEvent(new Event('focusin', { bubbles: true })); + const { _inputNode } = getComboboxMembers(el); + _inputNode.dispatchEvent(new Event('focusin', { bubbles: true })); // eslint-disable-next-line no-param-reassign - inputNode.value = value; - inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true })); - inputNode.dispatchEvent(new KeyboardEvent('keyup', { key: value })); - inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: value })); + _inputNode.value = value; + _inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true })); + _inputNode.dispatchEvent(new KeyboardEvent('keyup', { key: value })); + _inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: value })); } /** @@ -63,8 +70,8 @@ function mimicKeyPress(el, key) { * @param {string[]} values */ async function mimicUserTypingAdvanced(el, values) { - const { inputNode } = getProtectedMembers(el); - const inputNodeLoc = /** @type {HTMLInputElement & {selectionStart:number, selectionEnd:number}} */ (inputNode); + const { _inputNode } = getComboboxMembers(el); + const inputNodeLoc = /** @type {HTMLInputElement & {selectionStart:number, selectionEnd:number}} */ (_inputNode); inputNodeLoc.dispatchEvent(new Event('focusin', { bubbles: true })); for (const key of values) { @@ -226,10 +233,10 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); expect(el.opened).to.be.false; - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.be.true; }); @@ -244,11 +251,11 @@ describe('lion-combobox', () => { Item 2 `)); - const { listboxNode } = getProtectedMembers(el); + const { _listboxNode } = getComboboxMembers(el); - expect(listboxNode).to.exist; - expect(listboxNode).to.be.instanceOf(LionOptions); - expect(el.querySelector('[role=listbox]')).to.equal(listboxNode); + expect(_listboxNode).to.exist; + expect(_listboxNode).to.be.instanceOf(LionOptions); + expect(el.querySelector('[role=listbox]')).to.equal(_listboxNode); }); it('has a textbox element', async () => { @@ -258,10 +265,10 @@ describe('lion-combobox', () => { Item 2 `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); - expect(comboboxNode).to.exist; - expect(el.querySelector('[role=combobox]')).to.equal(comboboxNode); + expect(_comboboxNode).to.exist; + expect(el.querySelector('[role=combobox]')).to.equal(_comboboxNode); }); }); @@ -273,13 +280,13 @@ describe('lion-combobox', () => { Item 2 `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); - expect(inputNode.value).to.equal('10'); + expect(_inputNode.value).to.equal('10'); el.modelValue = '20'; await el.updateComplete; - expect(inputNode.value).to.equal('20'); + expect(_inputNode.value).to.equal('20'); }); it('sets modelValue to empty string if no option is selected', async () => { @@ -328,11 +335,11 @@ describe('lion-combobox', () => { `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); el.clear(); expect(el.modelValue).to.equal(''); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); const el2 = /** @type {LionCombobox} */ (await fixture(html` @@ -345,7 +352,7 @@ describe('lion-combobox', () => { el2.clear(); expect(el2.modelValue).to.eql([]); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); }); }); @@ -359,10 +366,10 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); expect(el.opened).to.equal(false); - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.equal(false); }); @@ -385,12 +392,12 @@ describe('lion-combobox', () => { `)); const options = el.formElements; - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); expect(el.opened).to.equal(false); // step [1] - inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.equal(false); @@ -403,7 +410,7 @@ describe('lion-combobox', () => { options[0].click(); await el.updateComplete; expect(el.opened).to.equal(false); - expect(document.activeElement).to.equal(inputNode); + expect(document.activeElement).to.equal(_inputNode); // step [4] await el.updateComplete; @@ -422,19 +429,19 @@ describe('lion-combobox', () => { `)); - const { comboboxNode, inputNode } = getProtectedMembers(el); + const { _comboboxNode, _inputNode } = getComboboxMembers(el); // open - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; expect(el.opened).to.equal(true); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); - inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); + _inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(el.opened).to.equal(false); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); }); it('hides overlay on [Tab]', async () => { @@ -447,19 +454,19 @@ describe('lion-combobox', () => { `)); - const { comboboxNode, inputNode } = getProtectedMembers(el); + const { _comboboxNode, _inputNode } = getComboboxMembers(el); // open - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; expect(el.opened).to.equal(true); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); - mimicKeyPress(inputNode, 'Tab'); + mimicKeyPress(_inputNode, 'Tab'); expect(el.opened).to.equal(false); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); }); it('clears checkedIndex on empty text', async () => { @@ -472,15 +479,15 @@ describe('lion-combobox', () => { `)); - const { comboboxNode, inputNode } = getProtectedMembers(el); + const { _comboboxNode, _inputNode } = getComboboxMembers(el); // open - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; expect(el.opened).to.equal(true); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); expect(el.checkedIndex).to.equal(0); mimicUserTyping(el, ''); @@ -510,10 +517,10 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); expect(el.opened).to.equal(false); - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); await el.updateComplete; expect(el.opened).to.equal(true); }); @@ -603,10 +610,10 @@ describe('lion-combobox', () => { `)); const options = el.formElements; - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); expect(el.opened).to.equal(false); - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); await el.updateComplete; @@ -644,10 +651,10 @@ describe('lion-combobox', () => { `)); const options = el.formElements; - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); expect(el.opened).to.equal(false); - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'art'); expect(el.opened).to.equal(true); @@ -672,15 +679,15 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); expect(el.checkedIndex).to.equal(0); // Simulate backspace deleting the char at the end of the string - mimicKeyPress(inputNode, 'Backspace'); - inputNode.dispatchEvent(new Event('input')); - const arr = inputNode.value.split(''); - arr.splice(inputNode.value.length - 1, 1); - inputNode.value = arr.join(''); + mimicKeyPress(_inputNode, 'Backspace'); + _inputNode.dispatchEvent(new Event('input')); + const arr = _inputNode.value.split(''); + arr.splice(_inputNode.value.length - 1, 1); + _inputNode.value = arr.join(''); await el.updateComplete; el.dispatchEvent(new Event('blur')); @@ -703,9 +710,9 @@ describe('lion-combobox', () => { Item 2 `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); - expect(comboboxNode.getAttribute('role')).to.equal('combobox'); + expect(_comboboxNode.getAttribute('role')).to.equal('combobox'); }); it('sets aria-expanded to element with role="combobox" in wai-aria 1.0 and 1.1', async () => { @@ -715,12 +722,12 @@ describe('lion-combobox', () => { Item 2 `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); - expect(comboboxNode.getAttribute('aria-expanded')).to.equal('false'); + expect(_comboboxNode.getAttribute('aria-expanded')).to.equal('false'); el.opened = true; await el.updateComplete; - expect(comboboxNode.getAttribute('aria-expanded')).to.equal('true'); + expect(_comboboxNode.getAttribute('aria-expanded')).to.equal('true'); const el2 = /** @type {LionCombobox} */ (await fixture(html` @@ -728,12 +735,12 @@ describe('lion-combobox', () => { Item 2 `)); - const { comboboxNode: comboboxNode2 } = getProtectedMembers(el2); + const { _comboboxNode: comboboxNode2 } = getComboboxMembers(el2); expect(comboboxNode2.getAttribute('aria-expanded')).to.equal('false'); el2.opened = true; await el2.updateComplete; - expect(comboboxNode.getAttribute('aria-expanded')).to.equal('true'); + expect(_comboboxNode.getAttribute('aria-expanded')).to.equal('true'); }); it('makes sure listbox node is not focusable', async () => { @@ -743,9 +750,9 @@ describe('lion-combobox', () => { Item 2 `)); - const { listboxNode } = getProtectedMembers(el); + const { _listboxNode } = getComboboxMembers(el); - expect(listboxNode.hasAttribute('tabindex')).to.be.false; + expect(_listboxNode.hasAttribute('tabindex')).to.be.false; }); }); }); @@ -774,9 +781,9 @@ describe('lion-combobox', () => { Item 1 `)); - const { selectionDisplayNode } = getProtectedMembers(el); + const { _selectionDisplayNode } = getComboboxMembers(el); - expect(selectionDisplayNode).to.equal(el.querySelector('[slot=selection-display]')); + expect(_selectionDisplayNode).to.equal(el.querySelector('[slot=selection-display]')); }); it('sets a reference to combobox element in _selectionDisplayNode', async () => { @@ -904,16 +911,16 @@ describe('lion-combobox', () => { `)); mimicUserTyping(el, 'ch'); await el.updateComplete; - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); - expect(inputNode.value).to.equal('Chard'); - expect(inputNode.selectionStart).to.equal(2); - expect(inputNode.selectionEnd).to.equal(inputNode.value.length); + expect(_inputNode.value).to.equal('Chard'); + expect(_inputNode.selectionStart).to.equal(2); + expect(_inputNode.selectionEnd).to.equal(_inputNode.value.length); // We don't autocomplete when characters are removed mimicUserTyping(el, 'c'); // The user pressed backspace (number of chars decreased) - expect(inputNode.value).to.equal('c'); - expect(inputNode.selectionStart).to.equal(inputNode.value.length); + expect(_inputNode.value).to.equal('c'); + expect(_inputNode.selectionStart).to.equal(_inputNode.value.length); }); it('filters options when autocomplete is "list"', async () => { @@ -925,12 +932,12 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); mimicUserTyping(el, 'ch'); await el.updateComplete; expect(getFilteredOptionValues(el)).to.eql(['Artichoke', 'Chard', 'Chicory']); - expect(inputNode.value).to.equal('ch'); + expect(_inputNode.value).to.equal('ch'); }); it('does not filter options when autocomplete is "none"', async () => { @@ -1018,26 +1025,26 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(inputNode.value).to.equal('Chard'); - expect(inputNode.selectionStart).to.equal('ch'.length); - expect(inputNode.selectionEnd).to.equal('Chard'.length); + expect(_inputNode.value).to.equal('Chard'); + expect(_inputNode.selectionStart).to.equal('ch'.length); + expect(_inputNode.selectionEnd).to.equal('Chard'.length); await mimicUserTypingAdvanced(el, ['i', 'c']); await el.updateComplete; - expect(inputNode.value).to.equal('Chicory'); - expect(inputNode.selectionStart).to.equal('chic'.length); - expect(inputNode.selectionEnd).to.equal('Chicory'.length); + expect(_inputNode.value).to.equal('Chicory'); + expect(_inputNode.selectionStart).to.equal('chic'.length); + expect(_inputNode.selectionEnd).to.equal('Chicory'.length); // Diminishing chars, but autocompleting mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(inputNode.value).to.equal('ch'); - expect(inputNode.selectionStart).to.equal('ch'.length); - expect(inputNode.selectionEnd).to.equal('ch'.length); + expect(_inputNode.value).to.equal('ch'); + expect(_inputNode.selectionStart).to.equal('ch'.length); + expect(_inputNode.selectionEnd).to.equal('ch'.length); }); it('synchronizes textbox on overlay close', async () => { @@ -1049,8 +1056,8 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); - expect(inputNode.value).to.equal(''); + const { _inputNode } = getComboboxMembers(el); + expect(_inputNode.value).to.equal(''); /** * @param {'none' | 'list' | 'inline' | 'both'} autocomplete @@ -1066,7 +1073,7 @@ describe('lion-combobox', () => { el.setCheckedIndex(index); el.opened = false; await el.updateComplete; - expect(inputNode.value).to.equal(valueOnClose); + expect(_inputNode.value).to.equal(valueOnClose); } await performChecks('none', 0, 'Artichoke'); @@ -1091,8 +1098,8 @@ describe('lion-combobox', () => { `)); - const { inputNode } = getProtectedMembers(el); - expect(inputNode.value).to.equal(''); + const { _inputNode } = getComboboxMembers(el); + expect(_inputNode.value).to.equal(''); /** * @param {'none' | 'list' | 'inline' | 'both'} autocomplete @@ -1108,7 +1115,7 @@ describe('lion-combobox', () => { el.setCheckedIndex(index); el.opened = false; await el.updateComplete; - expect(inputNode.value).to.equal(valueOnClose); + expect(_inputNode.value).to.equal(valueOnClose); } await performChecks('none', 0, ''); @@ -1185,21 +1192,21 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); mimicUserTyping(el, 'ch'); await el.updateComplete; - expect(inputNode.value).to.equal('Chard'); - expect(inputNode.selectionStart).to.equal('Ch'.length); - expect(inputNode.selectionEnd).to.equal('Chard'.length); + expect(_inputNode.value).to.equal('Chard'); + expect(_inputNode.selectionStart).to.equal('Ch'.length); + expect(_inputNode.selectionEnd).to.equal('Chard'.length); // Autocompletion happened. When we go backwards ('Ch[ard]' => 'Ch'), we should not // autocomplete to 'Chard' anymore. await mimicUserTypingAdvanced(el, ['Backspace']); await el.updateComplete; - expect(inputNode.value).to.equal('Ch'); // so not 'Chard' - expect(inputNode.selectionStart).to.equal('Ch'.length); - expect(inputNode.selectionEnd).to.equal('Ch'.length); + expect(_inputNode.value).to.equal('Ch'); // so not 'Chard' + expect(_inputNode.selectionStart).to.equal('Ch'.length); + expect(_inputNode.selectionEnd).to.equal('Ch'.length); }); describe('Subclassers', () => { @@ -1265,29 +1272,29 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'none'; el.setCheckedIndex(0); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'list'; el.setCheckedIndex(0); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'inline'; el.setCheckedIndex(0); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); el.setCheckedIndex(-1); el.autocomplete = 'both'; el.setCheckedIndex(0); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); }); it('synchronizes last index to textbox when autocomplete is "inline" or "both" when multipleChoice', async () => { @@ -1299,35 +1306,35 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); - expect(inputNode.value).to.eql(''); + expect(_inputNode.value).to.eql(''); el.setCheckedIndex(-1); el.autocomplete = 'none'; el.setCheckedIndex([0]); el.setCheckedIndex([1]); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'list'; el.setCheckedIndex([0]); el.setCheckedIndex([1]); - expect(inputNode.value).to.equal(''); + expect(_inputNode.value).to.equal(''); el.setCheckedIndex(-1); el.autocomplete = 'inline'; el.setCheckedIndex([0]); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); el.setCheckedIndex([1]); - expect(inputNode.value).to.equal('Chard'); + expect(_inputNode.value).to.equal('Chard'); el.setCheckedIndex(-1); el.autocomplete = 'both'; el.setCheckedIndex([0]); - expect(inputNode.value).to.equal('Artichoke'); + expect(_inputNode.value).to.equal('Artichoke'); el.setCheckedIndex([1]); - expect(inputNode.value).to.equal('Chard'); + expect(_inputNode.value).to.equal('Chard'); }); describe('Subclassers', () => { @@ -1360,27 +1367,27 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); el.setCheckedIndex(-1); el.autocomplete = 'none'; el.setCheckedIndex([0]); - expect(inputNode.value).to.equal('Artichoke--multi'); + expect(_inputNode.value).to.equal('Artichoke--multi'); el.setCheckedIndex(-1); el.autocomplete = 'list'; el.setCheckedIndex([0]); - expect(inputNode.value).to.equal('Artichoke--multi'); + expect(_inputNode.value).to.equal('Artichoke--multi'); el.setCheckedIndex(-1); el.autocomplete = 'inline'; el.setCheckedIndex([0]); - expect(inputNode.value).to.equal('Artichoke--multi'); + expect(_inputNode.value).to.equal('Artichoke--multi'); el.setCheckedIndex(-1); el.autocomplete = 'both'; el.setCheckedIndex([0]); - expect(inputNode.value).to.equal('Artichoke--multi'); + expect(_inputNode.value).to.equal('Artichoke--multi'); }); }); @@ -1414,7 +1421,7 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); /** * @param {LionCombobox} elm @@ -1442,7 +1449,7 @@ describe('lion-combobox', () => { expect(el.activeIndex).to.equal(-1); expect(el.opened).to.be.true; - mimicKeyPress(inputNode, 'Enter'); + mimicKeyPress(_inputNode, 'Enter'); expect(el.opened).to.be.false; expect(el.activeIndex).to.equal(-1); @@ -1456,7 +1463,7 @@ describe('lion-combobox', () => { expect(el.opened).to.be.true; expect(el.activeIndex).to.equal(-1); - mimicKeyPress(inputNode, 'Enter'); + mimicKeyPress(_inputNode, 'Enter'); expect(el.activeIndex).to.equal(-1); expect(el.opened).to.be.false; @@ -1473,7 +1480,7 @@ describe('lion-combobox', () => { expect(el.activeIndex).to.equal(1); - mimicKeyPress(inputNode, 'Enter'); + mimicKeyPress(_inputNode, 'Enter'); await el.updateComplete; await el.updateComplete; @@ -1487,7 +1494,7 @@ describe('lion-combobox', () => { await el.updateComplete; mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha'); await el.updateComplete; - mimicKeyPress(inputNode, 'Enter'); + mimicKeyPress(_inputNode, 'Enter'); expect(el.activeIndex).to.equal(1); expect(el.opened).to.be.false; }); @@ -1501,7 +1508,7 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha'); await el.updateComplete; @@ -1518,7 +1525,7 @@ describe('lion-combobox', () => { // select artichoke mimicUserTyping(/** @type {LionCombobox} */ (el), 'artichoke'); await el.updateComplete; - mimicKeyPress(inputNode, 'Enter'); + mimicKeyPress(_inputNode, 'Enter'); mimicUserTyping(/** @type {LionCombobox} */ (el), ''); await el.updateComplete; @@ -1537,17 +1544,17 @@ describe('lion-combobox', () => { Victoria Plum `)); - const { inputNode } = getProtectedMembers(el); + const { _inputNode } = getComboboxMembers(el); // Select something mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha'); await el.updateComplete; - mimicKeyPress(inputNode, 'Enter'); + mimicKeyPress(_inputNode, 'Enter'); expect(el.activeIndex).to.equal(1); - mimicKeyPress(inputNode, 'Escape'); + mimicKeyPress(_inputNode, 'Escape'); await el.updateComplete; - expect(inputNode.textContent).to.equal(''); + expect(_inputNode.textContent).to.equal(''); el.formElements.forEach(option => expect(option.active).to.be.false); @@ -1562,13 +1569,25 @@ describe('lion-combobox', () => { it('synchronizes autocomplete option to textbox', async () => { let el; [el] = await fruitFixture({ autocomplete: 'both' }); - expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('both'); + expect( + getComboboxMembers(/** @type {LionCombobox} */ (el))._inputNode.getAttribute( + 'aria-autocomplete', + ), + ).to.equal('both'); [el] = await fruitFixture({ autocomplete: 'list' }); - expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('list'); + expect( + getComboboxMembers(/** @type {LionCombobox} */ (el))._inputNode.getAttribute( + 'aria-autocomplete', + ), + ).to.equal('list'); [el] = await fruitFixture({ autocomplete: 'none' }); - expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('none'); + expect( + getComboboxMembers(/** @type {LionCombobox} */ (el))._inputNode.getAttribute( + 'aria-autocomplete', + ), + ).to.equal('none'); }); it('updates aria-activedescendant on textbox node', async () => { @@ -1581,21 +1600,21 @@ describe('lion-combobox', () => { `)); - const elProts = getProtectedMembers(el); + const elProts = getComboboxMembers(el); - expect(elProts.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + expect(elProts._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( null, ); expect(el.formElements[1].active).to.equal(false); mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch'); await el.updateComplete; - expect(elProts.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + expect(elProts._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( null, ); // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })); - mimicKeyPress(elProts.inputNode, 'ArrowDown'); - expect(elProts.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + mimicKeyPress(elProts._inputNode, 'ArrowDown'); + expect(elProts._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( 'artichoke-option', ); expect(el.formElements[1].active).to.equal(false); @@ -1609,11 +1628,11 @@ describe('lion-combobox', () => { `)); - const el2Prots = getProtectedMembers(el2); + const el2Prots = getComboboxMembers(el2); mimicUserTyping(/** @type {LionCombobox} */ (el2), 'ch'); await el2.updateComplete; - expect(el2Prots.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + expect(el2Prots._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( el2.formElements[1].id, ); expect(el2.formElements[1].active).to.equal(true); @@ -1621,7 +1640,7 @@ describe('lion-combobox', () => { el2.autocomplete = 'list'; mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch'); await el2.updateComplete; - expect(el2Prots.activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( + expect(el2Prots._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal( el2.formElements[1].id, ); expect(el2.formElements[1].active).to.equal(true); @@ -1646,9 +1665,9 @@ describe('lion-combobox', () => { Item 1 `)); - const { comboboxNode, inputNode } = getProtectedMembers(el); + const { _comboboxNode, _inputNode } = getComboboxMembers(el); - expect(comboboxNode.contains(inputNode)).to.be.true; + expect(_comboboxNode.contains(_inputNode)).to.be.true; }); it('has one input node with [role=combobox] in v1.0', async () => { @@ -1657,9 +1676,9 @@ describe('lion-combobox', () => { Item 1 `)); - const { comboboxNode, inputNode } = getProtectedMembers(el); + const { _comboboxNode, _inputNode } = getComboboxMembers(el); - expect(comboboxNode).to.equal(inputNode); + expect(_comboboxNode).to.equal(_inputNode); }); it('autodetects aria version and sets it to 1.1 on Chromium browsers', async () => { @@ -1671,9 +1690,9 @@ describe('lion-combobox', () => { Item 1 `)); - const elProts = getProtectedMembers(el); + const elProts = getComboboxMembers(el); - expect(elProts.ariaVersion).to.equal('1.1'); + expect(elProts._ariaVersion).to.equal('1.1'); browserDetection.isChromium = false; const el2 = /** @type {LionCombobox} */ (await fixture(html` @@ -1681,9 +1700,9 @@ describe('lion-combobox', () => { Item 1 `)); - const el2Prots = getProtectedMembers(el2); + const el2Prots = getComboboxMembers(el2); - expect(el2Prots.ariaVersion).to.equal('1.0'); + expect(el2Prots._ariaVersion).to.equal('1.0'); // restore... browserDetection.isChromium = browserDetectionIsChromiumOriginal; @@ -1703,10 +1722,10 @@ describe('lion-combobox', () => { `)); - const { comboboxNode } = getProtectedMembers(el); + const { _comboboxNode } = getComboboxMembers(el); // activate opened listbox - comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); + _comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); mimicUserTyping(el, 'ch'); await el.updateComplete; diff --git a/packages/form-core/src/FormControlMixin.js b/packages/form-core/src/FormControlMixin.js index 858c3323e..19feadbcc 100644 --- a/packages/form-core/src/FormControlMixin.js +++ b/packages/form-core/src/FormControlMixin.js @@ -183,6 +183,7 @@ const FormControlMixinImplementation = superclass => }; } + /** @protected */ get _inputNode() { return /** @type {HTMLElementWithValue} */ (this.__getDirectSlotChild('input')); } @@ -195,6 +196,9 @@ const FormControlMixinImplementation = superclass => return /** @type {HTMLElement} */ (this.__getDirectSlotChild('help-text')); } + /** + * @protected + */ get _feedbackNode() { return /** @type {LionValidationFeedback} */ (this.__getDirectSlotChild('feedback')); } @@ -280,7 +284,7 @@ const FormControlMixinImplementation = superclass => /** @protected */ _triggerInitialModelValueChangedEvent() { - this.__dispatchInitialModelValueChangedEvent(); + this._dispatchInitialModelValueChangedEvent(); } /** @protected */ @@ -779,7 +783,7 @@ const FormControlMixinImplementation = superclass => ); } - __dispatchInitialModelValueChangedEvent() { + _dispatchInitialModelValueChangedEvent() { // When we are not a fieldset / choice-group, we don't need to wait for our children // to send a unified event if (this._repropagationRole === 'child') { diff --git a/packages/form-core/src/FormatMixin.js b/packages/form-core/src/FormatMixin.js index dadb8e284..35c494b9f 100644 --- a/packages/form-core/src/FormatMixin.js +++ b/packages/form-core/src/FormatMixin.js @@ -18,7 +18,7 @@ import { ValidateMixin } from './validate/ValidateMixin.js'; // - simplify _calculateValues: recursive trigger lock can be omitted, since need for connecting // the loop via sync observers is not needed anymore. // - consider `formatOn` as an overridable function, by default something like: -// `(!__isHandlingUserInput || !hasError) && !focused` +// `(!_isHandlingUserInput || !hasError) && !focused` // This would allow for more advanced scenarios, like formatting an input whenever it becomes valid. // This would make formattedValue as a concept obsolete, since for maximum flexibility, the // formattedValue condition needs to be evaluated right before syncing back to the view @@ -281,7 +281,7 @@ const FormatMixinImplementation = superclass => // - Why check for this.hasError? // We only want to format values that are considered valid. For best UX, // we only 'reward' valid inputs. - // - Why check for __isHandlingUserInput? + // - Why check for _isHandlingUserInput? // Downwards sync is prevented whenever we are in an `@user-input-changed` flow, [2]. // If we are in a 'imperatively set `.modelValue`' flow, [1], we want to reflect back // the value, no matter what. @@ -290,7 +290,7 @@ const FormatMixinImplementation = superclass => // input into `._inputNode` with modelValue as input) if ( - this.__isHandlingUserInput && + this._isHandlingUserInput && this.hasFeedbackFor && this.hasFeedbackFor.length && this.hasFeedbackFor.includes('error') && @@ -333,7 +333,7 @@ const FormatMixinImplementation = superclass => bubbles: true, detail: /** @type { ModelValueEventDetails } */ ({ formPath: [this], - isTriggeredByUser: Boolean(this.__isHandlingUserInput), + isTriggeredByUser: Boolean(this._isHandlingUserInput), }), }), ); @@ -376,7 +376,7 @@ const FormatMixinImplementation = superclass => * @protected */ _reflectBackOn() { - return !this.__isHandlingUserInput; + return !this._isHandlingUserInput; } // This can be called whenever the view value should be updated. Dependent on component type @@ -397,9 +397,9 @@ const FormatMixinImplementation = superclass => _onUserInputChanged() { // Upwards syncing. Most properties are delegated right away, value is synced to // `LionField`, to be able to act on (imperatively set) value changes - this.__isHandlingUserInput = true; + this._isHandlingUserInput = true; this._syncValueUpwards(); - this.__isHandlingUserInput = false; + this._isHandlingUserInput = false; } /** diff --git a/packages/form-core/src/NativeTextFieldMixin.js b/packages/form-core/src/NativeTextFieldMixin.js index 367e2790c..4c32bc071 100644 --- a/packages/form-core/src/NativeTextFieldMixin.js +++ b/packages/form-core/src/NativeTextFieldMixin.js @@ -1,12 +1,22 @@ import { dedupeMixin } from '@lion/core'; +import { FormControlMixin } from './FormControlMixin.js'; +import { FocusMixin } from './FocusMixin.js'; /** * @typedef {import('../types/NativeTextFieldMixinTypes').NativeTextFieldMixin} NativeTextFieldMixin * @type {NativeTextFieldMixin} - * @param {import('@open-wc/dedupe-mixin').Constructor} superclass} superclass + * @param {import('@open-wc/dedupe-mixin').Constructor} superclass} superclass */ const NativeTextFieldMixinImplementation = superclass => - class NativeTextFieldMixin extends superclass { + class NativeTextFieldMixin extends FocusMixin(FormControlMixin(superclass)) { + /** + * @protected + * @type {HTMLInputElement | HTMLTextAreaElement} + */ + get _inputNode() { + return /** @type {HTMLInputElement | HTMLTextAreaElement} */ (super._inputNode); + } + /** @type {number} */ get selectionStart() { const native = this._inputNode; diff --git a/packages/form-core/src/choice-group/ChoiceGroupMixin.js b/packages/form-core/src/choice-group/ChoiceGroupMixin.js index a5986102c..4c21ae9ea 100644 --- a/packages/form-core/src/choice-group/ChoiceGroupMixin.js +++ b/packages/form-core/src/choice-group/ChoiceGroupMixin.js @@ -200,7 +200,7 @@ const ChoiceGroupMixinImplementation = superclass => */ _triggerInitialModelValueChangedEvent() { this.registrationComplete.then(() => { - this.__dispatchInitialModelValueChangedEvent(); + this._dispatchInitialModelValueChangedEvent(); }); } diff --git a/packages/form-core/src/choice-group/ChoiceInputMixin.js b/packages/form-core/src/choice-group/ChoiceInputMixin.js index 0f7cbf271..adb4ed66f 100644 --- a/packages/form-core/src/choice-group/ChoiceInputMixin.js +++ b/packages/form-core/src/choice-group/ChoiceInputMixin.js @@ -234,9 +234,9 @@ const ChoiceInputMixinImplementation = superclass => if (this.disabled) { return; } - this.__isHandlingUserInput = true; + this._isHandlingUserInput = true; this.checked = !this.checked; - this.__isHandlingUserInput = false; + this._isHandlingUserInput = false; } // TODO: make this less fuzzy by applying these methods in LionRadio and LionCheckbox diff --git a/packages/form-core/src/form-group/FormGroupMixin.js b/packages/form-core/src/form-group/FormGroupMixin.js index 393fc1da7..aecdf93d4 100644 --- a/packages/form-core/src/form-group/FormGroupMixin.js +++ b/packages/form-core/src/form-group/FormGroupMixin.js @@ -77,6 +77,7 @@ const FormGroupMixinImplementation = superclass => }; } + /** @protected */ get _inputNode() { return this; } @@ -183,7 +184,7 @@ const FormGroupMixinImplementation = superclass => */ _triggerInitialModelValueChangedEvent() { this.registrationComplete.then(() => { - this.__dispatchInitialModelValueChangedEvent(); + this._dispatchInitialModelValueChangedEvent(); }); } diff --git a/packages/form-core/src/utils/SyncUpdatableMixin.js b/packages/form-core/src/utils/SyncUpdatableMixin.js index 6f8832d01..485ff139e 100644 --- a/packages/form-core/src/utils/SyncUpdatableMixin.js +++ b/packages/form-core/src/utils/SyncUpdatableMixin.js @@ -80,6 +80,7 @@ const SyncUpdatableMixinImplementation = superclass => // Empty queue... if (ns.queue) { Array.from(ns.queue).forEach(name => { + // @ts-ignore [allow-private] in test if (ctor.__syncUpdatableHasChanged(name, this[name], undefined)) { this.updateSync(name, undefined); } @@ -105,6 +106,7 @@ const SyncUpdatableMixinImplementation = superclass => // Makes sure that we only initialize one time, with most up to date value ns.queue.add(name); } // After connectedCallback: guarded proxy to updateSync + // @ts-ignore [allow-private] in test else if (ctor.__syncUpdatableHasChanged(name, this[name], oldValue)) { this.updateSync(name, oldValue); } diff --git a/packages/form-core/src/validate/validators/Required.js b/packages/form-core/src/validate/validators/Required.js index 3eeac9da3..2bbe142bf 100644 --- a/packages/form-core/src/validate/validators/Required.js +++ b/packages/form-core/src/validate/validators/Required.js @@ -45,14 +45,15 @@ export class Required extends Validator { /** * @param {FormControlHost & HTMLElement} formControl */ + // @ts-ignore [allow-protected] we are allowed to know FormControl protcected props in form-core // eslint-disable-next-line class-methods-use-this - onFormControlConnect(formControl) { - if (formControl._inputNode) { - const role = formControl._inputNode.getAttribute('role') || ''; - const elementTagName = formControl._inputNode.tagName.toLowerCase(); + onFormControlConnect({ _inputNode: inputNode }) { + if (inputNode) { + const role = inputNode.getAttribute('role') || ''; + const elementTagName = inputNode.tagName.toLowerCase(); const ctor = /** @type {typeof Required} */ (this.constructor); if (ctor._compatibleRoles.includes(role) || ctor._compatibleTags.includes(elementTagName)) { - formControl._inputNode.setAttribute('aria-required', 'true'); + inputNode.setAttribute('aria-required', 'true'); } } } @@ -60,10 +61,11 @@ export class Required extends Validator { /** * @param {FormControlHost & HTMLElement} formControl */ + // @ts-ignore [allow-protected] we are allowed to know FormControl protcected props in form-core // eslint-disable-next-line class-methods-use-this - onFormControlDisconnect(formControl) { - if (formControl._inputNode) { - formControl._inputNode.removeAttribute('aria-required'); + onFormControlDisconnect({ _inputNode: inputNode }) { + if (inputNode) { + inputNode.removeAttribute('aria-required'); } } } diff --git a/packages/form-core/test-helpers.js b/packages/form-core/test-helpers.js deleted file mode 100644 index 75c4e8750..000000000 --- a/packages/form-core/test-helpers.js +++ /dev/null @@ -1,6 +0,0 @@ -export { - AlwaysInvalid, - AlwaysValid, - AsyncAlwaysValid, - AsyncAlwaysInvalid, -} from './test-helpers/ExampleValidators.js'; diff --git a/packages/form-core/test-helpers/getFormControlMembers.js b/packages/form-core/test-helpers/getFormControlMembers.js new file mode 100644 index 000000000..aae6c009b --- /dev/null +++ b/packages/form-core/test-helpers/getFormControlMembers.js @@ -0,0 +1,21 @@ +/** + * @typedef {import('../types/FormControlMixinTypes').FormControlHost} FormControlHost + * @typedef {import('../types/validate/ValidateMixinTypes').ValidateHost} ValidateHost + */ + +/** + * Exposes private and protected FormControl members + * @param {FormControlHost} el + */ +export function getFormControlMembers(el) { + // @ts-ignore [allow-protected] in test + // eslint-disable-next-line + const { _inputNode, _helpTextNode, _labelNode, _feedbackNode, _allValidators } = el; + return { + _inputNode, + _helpTextNode, + _labelNode, + _feedbackNode, + _allValidators: /** @type {* & ValidateHost} */ (el)._allValidators, + }; +} diff --git a/packages/form-core/test-helpers/index.js b/packages/form-core/test-helpers/index.js index 6d9e74290..fcc2d3d72 100644 --- a/packages/form-core/test-helpers/index.js +++ b/packages/form-core/test-helpers/index.js @@ -1 +1,2 @@ export * from './ExampleValidators.js'; +export * from './getFormControlMembers.js'; diff --git a/packages/form-core/test-suites/FormRegistrationMixins.suite.js b/packages/form-core/test-suites/FormRegistrationMixins.suite.js index 567867fe9..0f73a2ff1 100644 --- a/packages/form-core/test-suites/FormRegistrationMixins.suite.js +++ b/packages/form-core/test-suites/FormRegistrationMixins.suite.js @@ -4,10 +4,6 @@ import { FormRegisteringMixin } from '../src/registration/FormRegisteringMixin.j import { FormRegistrarMixin } from '../src/registration/FormRegistrarMixin.js'; import { FormRegistrarPortalMixin } from '../src/registration/FormRegistrarPortalMixin.js'; -/** - * @typedef {import('../types/registration/FormRegistrarMixinTypes').FormRegistrarHost} FormRegistrarHost - */ - /** * @typedef {Object} customConfig * @property {typeof LitElement|undefined} [baseElement] diff --git a/packages/form-core/test-suites/FormatMixin.suite.js b/packages/form-core/test-suites/FormatMixin.suite.js index 906a3a409..f222ac7e3 100644 --- a/packages/form-core/test-suites/FormatMixin.suite.js +++ b/packages/form-core/test-suites/FormatMixin.suite.js @@ -4,8 +4,10 @@ import { aTimeout, defineCE, expect, fixture, html, unsafeStatic } from '@open-w import sinon from 'sinon'; import { FormatMixin } from '../src/FormatMixin.js'; import { Unparseable, Validator } from '../index.js'; +import { getFormControlMembers } from '../test-helpers/getFormControlMembers.js'; /** + * @typedef {import('../types/FormControlMixinTypes').FormControlHost} FormControlHost * @typedef {ArrayConstructor | ObjectConstructor | NumberConstructor | BooleanConstructor | StringConstructor | DateConstructor | 'iban' | 'email'} modelValueType */ @@ -284,10 +286,11 @@ export function runFormatMixinSuite(customConfig) { describe('View value', () => { it('has an input node (like /