feat: private and protected types form-core
This commit is contained in:
parent
cc02ae2450
commit
43e4bb810c
70 changed files with 1197 additions and 957 deletions
40
.changeset/stupid-cycles-sniff.md
Normal file
40
.changeset/stupid-cycles-sniff.md
Normal file
|
|
@ -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
|
||||
|
|
@ -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 "[]".');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ describe('<lion-checkbox-group>', () => {
|
|||
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`<lion-checkbox-group name="woof[]"></lion-checkbox-group>`);
|
||||
el.name = 'woof';
|
||||
let err;
|
||||
|
|
|
|||
|
|
@ -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>', () => {
|
|||
'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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
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('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
// 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('<lion-checkbox-indeterminate>', () => {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
</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`
|
||||
<lion-combobox name="foo" multiple-choice .modelValue="${['Artichoke']}">
|
||||
|
|
@ -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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
</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', () => {
|
|||
</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', () => {
|
|||
</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', () => {
|
|||
</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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</${tag}>
|
||||
`));
|
||||
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', () => {
|
|||
</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', () => {
|
|||
</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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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`
|
||||
<lion-combobox name="foo" ._ariaVersion="${'1.1'}">
|
||||
|
|
@ -728,12 +735,12 @@ describe('lion-combobox', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</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
|
||||
|
|
@ -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', () => {
|
|||
</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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</${tag}>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
</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', () => {
|
|||
</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', () => {
|
|||
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
|
||||
</lion-combobox>
|
||||
`));
|
||||
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', () => {
|
|||
</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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<import('../types/NativeTextFieldMixinTypes').NativeTextField>} superclass} superclass
|
||||
* @param {import('@open-wc/dedupe-mixin').Constructor<import('@lion/core').LitElement>} 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;
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ const ChoiceGroupMixinImplementation = superclass =>
|
|||
*/
|
||||
_triggerInitialModelValueChangedEvent() {
|
||||
this.registrationComplete.then(() => {
|
||||
this.__dispatchInitialModelValueChangedEvent();
|
||||
this._dispatchInitialModelValueChangedEvent();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
export {
|
||||
AlwaysInvalid,
|
||||
AlwaysValid,
|
||||
AsyncAlwaysValid,
|
||||
AsyncAlwaysInvalid,
|
||||
} from './test-helpers/ExampleValidators.js';
|
||||
21
packages/form-core/test-helpers/getFormControlMembers.js
Normal file
21
packages/form-core/test-helpers/getFormControlMembers.js
Normal file
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from './ExampleValidators.js';
|
||||
export * from './getFormControlMembers.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]
|
||||
|
|
|
|||
|
|
@ -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 <input>/<textarea>) which holds the formatted (view) value', async () => {
|
||||
const { _inputNode } = getFormControlMembers(fooFormat);
|
||||
fooFormat.modelValue = 'string';
|
||||
expect(fooFormat.formattedValue).to.equal('foo: string');
|
||||
expect(fooFormat.value).to.equal('foo: string');
|
||||
expect(fooFormat._inputNode.value).to.equal('foo: string');
|
||||
expect(_inputNode.value).to.equal('foo: string');
|
||||
});
|
||||
|
||||
it('works if there is no underlying _inputNode', async () => {
|
||||
|
|
@ -305,16 +308,17 @@ export function runFormatMixinSuite(customConfig) {
|
|||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(formatEl);
|
||||
|
||||
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
||||
const generatedModelValue = generateValueBasedOnType();
|
||||
mimicUserInput(formatEl, generatedViewValue);
|
||||
expect(formatEl._inputNode.value).to.not.equal(`foo: ${generatedModelValue}`);
|
||||
expect(_inputNode.value).to.not.equal(`foo: ${generatedModelValue}`);
|
||||
|
||||
// user leaves field
|
||||
formatEl._inputNode.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
|
||||
_inputNode.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
|
||||
await aTimeout(0);
|
||||
expect(formatEl._inputNode.value).to.equal(`foo: ${generatedModelValue}`);
|
||||
expect(_inputNode.value).to.equal(`foo: ${generatedModelValue}`);
|
||||
});
|
||||
|
||||
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => {
|
||||
|
|
@ -323,17 +327,20 @@ export function runFormatMixinSuite(customConfig) {
|
|||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
// The FormatMixin can be used in conjunction with the ValidateMixin, in which case
|
||||
// it can hold errorState (affecting the formatting)
|
||||
el.hasFeedbackFor = ['error'];
|
||||
|
||||
// users types value 'test'
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el._inputNode.value).to.not.equal('foo: test');
|
||||
expect(_inputNode.value).to.not.equal('foo: test');
|
||||
|
||||
// Now see the difference for an imperative change
|
||||
el.modelValue = 'test2';
|
||||
expect(el._inputNode.value).to.equal('foo: test2');
|
||||
expect(_inputNode.value).to.equal('foo: test2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -494,6 +501,8 @@ export function runFormatMixinSuite(customConfig) {
|
|||
</${tag}>
|
||||
`));
|
||||
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(preprocessorSpy.callCount).to.equal(1);
|
||||
|
||||
const parserSpy = sinon.spy(el, 'parser');
|
||||
|
|
@ -501,7 +510,7 @@ export function runFormatMixinSuite(customConfig) {
|
|||
|
||||
expect(preprocessorSpy.callCount).to.equal(2);
|
||||
expect(parserSpy.lastCall.args[0]).to.equal(val);
|
||||
expect(el._inputNode.value).to.equal(val);
|
||||
expect(_inputNode.value).to.equal(val);
|
||||
});
|
||||
|
||||
it('does not preprocess during composition', async () => {
|
||||
|
|
@ -510,13 +519,16 @@ export function runFormatMixinSuite(customConfig) {
|
|||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
const preprocessorSpy = sinon.spy(el, 'preprocessor');
|
||||
el._inputNode.dispatchEvent(new Event('compositionstart', { bubbles: true }));
|
||||
_inputNode.dispatchEvent(new Event('compositionstart', { bubbles: true }));
|
||||
mimicUserInput(el, '`');
|
||||
expect(preprocessorSpy.callCount).to.equal(0);
|
||||
// "à" would be sent by the browser after pressing "option + `", followed by "a"
|
||||
mimicUserInput(el, 'à');
|
||||
el._inputNode.dispatchEvent(new Event('compositionend', { bubbles: true }));
|
||||
_inputNode.dispatchEvent(new Event('compositionend', { bubbles: true }));
|
||||
expect(preprocessorSpy.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
unsafeStatic,
|
||||
} from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import { InteractionStateMixin } from '../src/InteractionStateMixin.js';
|
||||
import { ValidateMixin } from '../src/validate/ValidateMixin.js';
|
||||
import { MinLength } from '../src/validate/validators/StringValidators.js';
|
||||
|
|
@ -135,6 +136,7 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
const targetEl = el._inputNode || el;
|
||||
targetEl.dispatchEvent(new Event('focus', { bubbles: true }));
|
||||
el.modelValue = modelValue;
|
||||
// @ts-ignore [allow-protected] in test
|
||||
targetEl.dispatchEvent(new Event(el._leaveEvent, { bubbles: true }));
|
||||
};
|
||||
|
||||
|
|
@ -224,20 +226,22 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
const el = /** @type {IState} */ (await fixture(html`
|
||||
<${tag} .validators=${[new MinLength(3)]}></${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData).to.deep.equal([]);
|
||||
expect(_feedbackNode.feedbackData).to.deep.equal([]);
|
||||
|
||||
// has error but does not show/forward to component as showCondition is not met
|
||||
el.modelValue = '1';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData).to.deep.equal([]);
|
||||
expect(_feedbackNode.feedbackData).to.deep.equal([]);
|
||||
|
||||
el.submitted = true;
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.length).to.equal(1);
|
||||
expect(_feedbackNode.feedbackData?.length).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { aTimeout, defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import {
|
||||
MaxLength,
|
||||
|
|
@ -15,7 +16,7 @@ import {
|
|||
AlwaysValid,
|
||||
AsyncAlwaysInvalid,
|
||||
AsyncAlwaysValid,
|
||||
} from '../test-helpers.js';
|
||||
} from '../test-helpers/index.js';
|
||||
|
||||
/**
|
||||
* @param {{tagString?: string | null, lightDom?: string}} [customConfig]
|
||||
|
|
@ -153,6 +154,7 @@ export function runValidateMixinSuite(customConfig) {
|
|||
>${lightDom}</${tag}>
|
||||
`));
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const clearSpy = sinon.spy(el, '__clearValidationResults');
|
||||
const validateSpy = sinon.spy(el, 'validate');
|
||||
el.modelValue = 'x';
|
||||
|
|
@ -174,6 +176,7 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
<${tag} .validators=${[alwaysValid]}>${lightDom}</${tag}>
|
||||
`));
|
||||
// @ts-ignore [allow-private] in test
|
||||
const isEmptySpy = sinon.spy(el, '__isEmpty');
|
||||
const validateSpy = sinon.spy(el, 'validate');
|
||||
el.modelValue = '';
|
||||
|
|
@ -191,7 +194,9 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
<${tag} .validators=${[new AlwaysValid()]}>${lightDom}</${tag}>
|
||||
`));
|
||||
// @ts-ignore [allow-private] in test
|
||||
const isEmptySpy = sinon.spy(el, '__isEmpty');
|
||||
// @ts-ignore [allow-private] in test
|
||||
const syncSpy = sinon.spy(el, '__executeSyncValidators');
|
||||
el.modelValue = 'nonEmpty';
|
||||
expect(isEmptySpy.calledBefore(syncSpy)).to.be.true;
|
||||
|
|
@ -203,7 +208,9 @@ export function runValidateMixinSuite(customConfig) {
|
|||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
// @ts-ignore [allow-private] in test
|
||||
const syncSpy = sinon.spy(el, '__executeSyncValidators');
|
||||
// @ts-ignore [allow-private] in test
|
||||
const asyncSpy = sinon.spy(el, '__executeAsyncValidators');
|
||||
el.modelValue = 'nonEmpty';
|
||||
expect(syncSpy.calledBefore(asyncSpy)).to.be.true;
|
||||
|
|
@ -223,7 +230,9 @@ export function runValidateMixinSuite(customConfig) {
|
|||
</${tag}>
|
||||
`));
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const syncSpy = sinon.spy(el, '__executeSyncValidators');
|
||||
// @ts-ignore [allow-private] in test
|
||||
const resultSpy2 = sinon.spy(el, '__executeResultValidators');
|
||||
|
||||
el.modelValue = 'nonEmpty';
|
||||
|
|
@ -236,7 +245,9 @@ export function runValidateMixinSuite(customConfig) {
|
|||
</${tag}>
|
||||
`);
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const asyncSpy = sinon.spy(el, '__executeAsyncValidators');
|
||||
// @ts-ignore [allow-private] in test
|
||||
const resultSpy = sinon.spy(el, '__executeResultValidators');
|
||||
|
||||
el.modelValue = 'nonEmpty';
|
||||
|
|
@ -266,6 +277,7 @@ export function runValidateMixinSuite(customConfig) {
|
|||
</${tag}>
|
||||
`));
|
||||
el.modelValue = 'nonEmpty';
|
||||
// @ts-ignore [allow-private] in test
|
||||
const validateResolveSpy = sinon.spy(el, '__validateCompleteResolve');
|
||||
await el.validateComplete;
|
||||
expect(validateResolveSpy.callCount).to.equal(1);
|
||||
|
|
@ -610,10 +622,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${withSuccessTag}>
|
||||
`));
|
||||
// @ts-ignore [allow-private] in test
|
||||
const prevValidationResult = el.__prevValidationResult;
|
||||
// @ts-ignore [allow-private] in test
|
||||
const prevShownValidationResult = el.__prevShownValidationResult;
|
||||
const regularValidationResult = [
|
||||
// @ts-ignore [allow-private] in test
|
||||
...el.__syncValidationResult,
|
||||
// @ts-ignore [allow-private] in test
|
||||
...el.__asyncValidationResult,
|
||||
];
|
||||
|
||||
|
|
@ -643,6 +659,7 @@ export function runValidateMixinSuite(customConfig) {
|
|||
>${lightDom}</${tag}>
|
||||
`));
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const totalValidationResult = el.__validationResult;
|
||||
expect(totalValidationResult).to.eql([resultV, validator]);
|
||||
});
|
||||
|
|
@ -673,6 +690,7 @@ export function runValidateMixinSuite(customConfig) {
|
|||
`));
|
||||
const validator = /** @type {Validator} */ (el.validators.find(v => v instanceof Required));
|
||||
const executeSpy = sinon.spy(validator, 'execute');
|
||||
// @ts-ignore [allow-private] in test
|
||||
const privateIsEmptySpy = sinon.spy(el, '__isEmpty');
|
||||
el.modelValue = null;
|
||||
expect(executeSpy.callCount).to.equal(0);
|
||||
|
|
@ -722,9 +740,11 @@ export function runValidateMixinSuite(customConfig) {
|
|||
.modelValue=${''}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
expect(el._inputNode?.getAttribute('aria-required')).to.equal('true');
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_inputNode?.getAttribute('aria-required')).to.equal('true');
|
||||
el.validators = [];
|
||||
expect(el._inputNode?.getAttribute('aria-required')).to.be.null;
|
||||
expect(_inputNode?.getAttribute('aria-required')).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -776,17 +796,20 @@ export function runValidateMixinSuite(customConfig) {
|
|||
<${preconfTag}
|
||||
.validators=${[new MinLength(3)]}
|
||||
></${preconfTag}>`));
|
||||
const { _allValidators } = getFormControlMembers(el);
|
||||
|
||||
expect(el.validators.length).to.equal(1);
|
||||
expect(el.defaultValidators.length).to.equal(1);
|
||||
expect(el._allValidators.length).to.equal(2);
|
||||
expect(_allValidators.length).to.equal(2);
|
||||
|
||||
expect(el._allValidators[0] instanceof MinLength).to.be.true;
|
||||
expect(el._allValidators[1] instanceof AlwaysInvalid).to.be.true;
|
||||
expect(_allValidators[0] instanceof MinLength).to.be.true;
|
||||
expect(_allValidators[1] instanceof AlwaysInvalid).to.be.true;
|
||||
|
||||
el.validators = [new MaxLength(5)];
|
||||
expect(el._allValidators[0] instanceof MaxLength).to.be.true;
|
||||
expect(el._allValidators[1] instanceof AlwaysInvalid).to.be.true;
|
||||
const { _allValidators: _allValidatorsMl } = getFormControlMembers(el);
|
||||
|
||||
expect(_allValidatorsMl[0] instanceof MaxLength).to.be.true;
|
||||
expect(_allValidatorsMl[1] instanceof AlwaysInvalid).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -915,8 +938,9 @@ export function runValidateMixinSuite(customConfig) {
|
|||
.validators=${[new MinLength(3, { message: 'foo' })]}>
|
||||
<input slot="input">
|
||||
</${tag}>`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
if (el._inputNode) {
|
||||
if (_inputNode) {
|
||||
// @ts-expect-error
|
||||
const spy = sinon.spy(el._inputNode, 'setCustomValidity');
|
||||
el.modelValue = '';
|
||||
|
|
@ -1021,9 +1045,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
.modelValue=${'1'}
|
||||
>${lightDom}</${customTypeTag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.feedbackComplete;
|
||||
|
||||
const feedbackNode = /** @type {import('../src/validate/LionValidationFeedback').LionValidationFeedback} */ (el._feedbackNode);
|
||||
const feedbackNode =
|
||||
/** @type {import('../src/validate/LionValidationFeedback').LionValidationFeedback} */
|
||||
(_feedbackNode);
|
||||
const resultOrder = feedbackNode.feedbackData?.map(v => v.type);
|
||||
expect(resultOrder).to.deep.equal(['error', 'x', 'y']);
|
||||
|
||||
|
|
@ -1164,6 +1192,7 @@ export function runValidateMixinSuite(customConfig) {
|
|||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const spy = sinon.spy(el, '_updateShouldShowFeedbackFor');
|
||||
let counter = 0;
|
||||
// for ... of is already allowed we should update eslint...
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { LitElement } from '@lion/core';
|
|||
import { localize } from '@lion/localize';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import { DefaultSuccess, MinLength, Required, ValidateMixin, Validator } from '../index.js';
|
||||
import { AlwaysInvalid } from '../test-helpers.js';
|
||||
import { AlwaysInvalid } from '../test-helpers/index.js';
|
||||
|
||||
export function runValidateMixinFeedbackPart() {
|
||||
describe('Validity Feedback', () => {
|
||||
|
|
@ -121,11 +122,13 @@ export function runValidateMixinFeedbackPart() {
|
|||
.modelValue=${'cat'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
expect(el._feedbackNode.feedbackData).to.deep.equal([]);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.feedbackData).to.deep.equal([]);
|
||||
el.validators = [new AlwaysInvalid()];
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('Message for AlwaysInvalid');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('Message for AlwaysInvalid');
|
||||
});
|
||||
|
||||
it('has configurable feedback visibility hook', async () => {
|
||||
|
|
@ -136,14 +139,17 @@ export function runValidateMixinFeedbackPart() {
|
|||
.validators=${[new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('Message for AlwaysInvalid');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('Message for AlwaysInvalid');
|
||||
// @ts-ignore [allow-protected] in test
|
||||
el._prioritizeAndFilterFeedback = () => []; // filter out all errors
|
||||
await el.validate();
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData).to.deep.equal([]);
|
||||
expect(_feedbackNode.feedbackData).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('writes prioritized result to "._feedbackNode" based on Validator order', async () => {
|
||||
|
|
@ -154,9 +160,11 @@ export function runValidateMixinFeedbackPart() {
|
|||
.validators=${[new AlwaysInvalid(), new MinLength(4)]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('Message for AlwaysInvalid');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('Message for AlwaysInvalid');
|
||||
});
|
||||
|
||||
it('renders validation result to "._feedbackNode" when async messages are resolved', async () => {
|
||||
|
|
@ -178,13 +186,13 @@ export function runValidateMixinFeedbackPart() {
|
|||
.validators=${[new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
expect(el._feedbackNode.feedbackData).to.be.undefined;
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.feedbackData).to.be.undefined;
|
||||
unlockMessage();
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal(
|
||||
'this ends up in "._feedbackNode"',
|
||||
);
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('this ends up in "._feedbackNode"');
|
||||
});
|
||||
|
||||
// N.B. this replaces the 'config.hideFeedback' option we had before...
|
||||
|
|
@ -207,14 +215,13 @@ export function runValidateMixinFeedbackPart() {
|
|||
.validators=${[new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(el._feedbackNode.feedbackData).to.be.undefined;
|
||||
expect(_feedbackNode.feedbackData).to.be.undefined;
|
||||
unlockMessage();
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal(
|
||||
'this ends up in "._feedbackNode"',
|
||||
);
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('this ends up in "._feedbackNode"');
|
||||
});
|
||||
|
||||
it('supports custom element to render feedback', async () => {
|
||||
|
|
@ -257,20 +264,21 @@ export function runValidateMixinFeedbackPart() {
|
|||
<${customFeedbackTag} slot="feedback"><${customFeedbackTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(el._feedbackNode.localName).to.equal(customFeedbackTagString);
|
||||
expect(_feedbackNode.localName).to.equal(customFeedbackTagString);
|
||||
|
||||
el.modelValue = 'dog';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
await el._feedbackNode.updateComplete;
|
||||
expect(el._feedbackNode).shadowDom.to.equal('Custom for ContainsLowercaseA');
|
||||
await _feedbackNode.updateComplete;
|
||||
expect(_feedbackNode).shadowDom.to.equal('Custom for ContainsLowercaseA');
|
||||
|
||||
el.modelValue = 'cat';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
await el._feedbackNode.updateComplete;
|
||||
expect(el._feedbackNode).shadowDom.to.equal('Custom for AlwaysInvalid');
|
||||
await _feedbackNode.updateComplete;
|
||||
expect(_feedbackNode).shadowDom.to.equal('Custom for AlwaysInvalid');
|
||||
});
|
||||
|
||||
it('supports custom messages in Validator instance configuration object', async () => {
|
||||
|
|
@ -280,11 +288,12 @@ export function runValidateMixinFeedbackPart() {
|
|||
.validators=${[new MinLength(3, { getMessage: () => 'custom via config' })]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
el.modelValue = 'a';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('custom via config');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('custom via config');
|
||||
});
|
||||
|
||||
it('updates the feedback component when locale changes', async () => {
|
||||
|
|
@ -295,13 +304,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
.modelValue=${'1'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.length).to.equal(1);
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('Message for MinLength');
|
||||
expect(_feedbackNode.feedbackData?.length).to.equal(1);
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('Message for MinLength');
|
||||
|
||||
localize.locale = 'de-DE';
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('Nachricht für MinLength');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('Nachricht für MinLength');
|
||||
});
|
||||
|
||||
it('shows success message after fixing an error', async () => {
|
||||
|
|
@ -321,16 +332,17 @@ export function runValidateMixinFeedbackPart() {
|
|||
]}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
el.modelValue = 'a';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('Message for MinLength');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('Message for MinLength');
|
||||
|
||||
el.modelValue = 'abcd';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('This is a success message');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('This is a success message');
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
|
|
@ -342,7 +354,9 @@ export function runValidateMixinFeedbackPart() {
|
|||
.modelValue=${'a'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const inputNode = el._inputNode;
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
const inputNode = _inputNode;
|
||||
expect(inputNode.getAttribute('aria-invalid')).to.equal('false');
|
||||
|
||||
el.modelValue = '';
|
||||
|
|
@ -493,12 +507,13 @@ export function runValidateMixinFeedbackPart() {
|
|||
.modelValue=${'1'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
el.modelValue = '12345';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
|
||||
expect(el._feedbackNode.feedbackData).to.deep.equal([]);
|
||||
expect(_feedbackNode.feedbackData).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Required } from '@lion/form-core';
|
||||
import { LionInput } from '@lion/input';
|
||||
import { expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import { ChoiceInputMixin } from '../../src/choice-group/ChoiceInputMixin.js';
|
||||
|
||||
|
|
@ -86,9 +87,11 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(counter).to.equal(0);
|
||||
// Here we try to mimic user interaction by firing browser events
|
||||
const nativeInput = el._inputNode;
|
||||
const nativeInput = _inputNode;
|
||||
nativeInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); // fired by (at least) Chrome
|
||||
expect(counter).to.equal(0);
|
||||
nativeInput.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
|
|
@ -104,14 +107,16 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
const { _inputNode, _labelNode } = getFormControlMembers(el);
|
||||
|
||||
el.click();
|
||||
expect(spy.args[0][0].target).to.equal(el);
|
||||
expect(spy.callCount).to.equal(1);
|
||||
el._labelNode.click();
|
||||
expect(spy.args[1][0].target).to.equal(el._labelNode);
|
||||
_labelNode.click();
|
||||
expect(spy.args[1][0].target).to.equal(_labelNode);
|
||||
expect(spy.callCount).to.equal(2);
|
||||
el._inputNode.click();
|
||||
expect(spy.args[2][0].target).to.equal(el._inputNode);
|
||||
_inputNode.click();
|
||||
expect(spy.args[2][0].target).to.equal(_inputNode);
|
||||
expect(spy.callCount).to.equal(3);
|
||||
});
|
||||
|
||||
|
|
@ -126,7 +131,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
el._inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
_inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
expect(isTriggeredByUser).to.be.true;
|
||||
});
|
||||
|
||||
|
|
@ -134,6 +141,7 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
<${tag} .choiceValue=${'foo'} .validators=${[new Required()]}></${tag}>
|
||||
`));
|
||||
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates.error).to.exist;
|
||||
expect(el.validationStates.error.Required).to.exist;
|
||||
|
|
@ -156,19 +164,23 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
it('can be checked and unchecked programmatically', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(el.checked).to.be.false;
|
||||
el.checked = true;
|
||||
expect(el.checked).to.be.true;
|
||||
|
||||
await el.updateComplete;
|
||||
expect(el._inputNode.checked).to.be.true;
|
||||
expect(/** @type {HTMLInputElement} */ (_inputNode).checked).to.be.true;
|
||||
});
|
||||
|
||||
it('can be checked and unchecked via user interaction', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
el._inputNode.click();
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
_inputNode.click();
|
||||
expect(el.checked).to.be.true;
|
||||
el._inputNode.click();
|
||||
_inputNode.click();
|
||||
await el.updateComplete;
|
||||
if (el.type === 'checkbox') {
|
||||
expect(el.checked).to.be.false;
|
||||
|
|
@ -177,7 +189,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
it('can not toggle the checked state when disabled via user interaction', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag} disabled></${tag}>`));
|
||||
el._inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
_inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
expect(el.checked).to.be.false;
|
||||
});
|
||||
|
||||
|
|
@ -206,7 +220,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
));
|
||||
expect(el.checked).to.be.false;
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const spyModelCheckedToChecked = sinon.spy(el, '__syncModelCheckedToChecked');
|
||||
// @ts-ignore [allow-private] in test
|
||||
const spyCheckedToModel = sinon.spy(el, '__syncCheckedToModel');
|
||||
el.checked = true;
|
||||
expect(el.modelValue.checked).to.be.true;
|
||||
|
|
@ -234,14 +250,16 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
const { _inputNode: _inputNodeChecked } = getFormControlMembers(elChecked);
|
||||
|
||||
// Initial values
|
||||
expect(hasAttr(el)).to.equal(false, 'initial unchecked element');
|
||||
expect(hasAttr(elChecked)).to.equal(true, 'initial checked element');
|
||||
|
||||
// Via user interaction
|
||||
el._inputNode.click();
|
||||
elChecked._inputNode.click();
|
||||
_inputNode.click();
|
||||
_inputNodeChecked.click();
|
||||
await el.updateComplete;
|
||||
expect(el.checked).to.be.true;
|
||||
expect(hasAttr(el)).to.equal(true, 'user click checked');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { defineCE, expect, html, unsafeStatic, fixture } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import { LionInput } from '@lion/input';
|
||||
import '@lion/form-core/define';
|
||||
import { FormGroupMixin } from '../../src/form-group/FormGroupMixin.js';
|
||||
|
|
@ -67,20 +68,23 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
<${childTag} name="B" label="fieldB"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { _labelNode } = getFormControlMembers(el);
|
||||
|
||||
/**
|
||||
* @param {LionInput} formControl
|
||||
*/
|
||||
function getLabels(formControl) {
|
||||
return /** @type {string} */ (formControl._inputNode.getAttribute('aria-labelledby')).split(
|
||||
const control = getFormControlMembers(formControl);
|
||||
|
||||
return /** @type {string} */ (control._inputNode.getAttribute('aria-labelledby')).split(
|
||||
' ',
|
||||
);
|
||||
}
|
||||
const field1 = el.formElements[0];
|
||||
const field2 = el.formElements[1];
|
||||
|
||||
expect(getLabels(field1)).to.eql([field1._labelNode.id, el._labelNode.id]);
|
||||
expect(getLabels(field2)).to.eql([field2._labelNode.id, el._labelNode.id]);
|
||||
expect(getLabels(field1)).to.eql([field1._labelNode.id, _labelNode.id]);
|
||||
expect(getLabels(field2)).to.eql([field2._labelNode.id, _labelNode.id]);
|
||||
|
||||
// Test the cleanup on disconnected
|
||||
el.removeChild(field1);
|
||||
|
|
@ -277,10 +281,12 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
|
||||
// Check cleanup of FormGroup on disconnect
|
||||
const l2_g = /** @type {FormGroup} */ (childAriaFixture.querySelector('[name=l2_g]'));
|
||||
// @ts-ignore [allow-private] in test
|
||||
expect(l2_g.__descriptionElementsInParentChain.size).to.not.equal(0);
|
||||
// @ts-expect-error removeChild should always be inherited via LitElement?
|
||||
l2_g._parentFormGroup.removeChild(l2_g);
|
||||
await l2_g.updateComplete;
|
||||
// @ts-ignore [allow-private] in test
|
||||
expect(l2_g.__descriptionElementsInParentChain.size).to.equal(0);
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
// @ts-ignore
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import {
|
||||
defineCE,
|
||||
|
|
@ -11,10 +10,10 @@ import {
|
|||
aTimeout,
|
||||
} from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
// @ts-ignore
|
||||
import { IsNumber, Validator, LionField } from '@lion/form-core';
|
||||
import '@lion/form-core/define';
|
||||
import { FormGroupMixin } from '../../src/form-group/FormGroupMixin.js';
|
||||
import { getFormControlMembers } from '../../test-helpers/getFormControlMembers.js';
|
||||
|
||||
/**
|
||||
* @param {{ tagString?: string, childTagString?:string }} [cfg]
|
||||
|
|
@ -63,12 +62,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
const el1 = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag} label="foo">${inputSlots}</${tag}>`,
|
||||
));
|
||||
expect(el1.fieldName).to.equal(el1._labelNode.textContent);
|
||||
const { _labelNode: _labelNode1 } = getFormControlMembers(el1);
|
||||
expect(el1.fieldName).to.equal(_labelNode1.textContent);
|
||||
|
||||
const el2 = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}><label slot="label">bar</label>${inputSlots}</${tag}>`,
|
||||
));
|
||||
expect(el2.fieldName).to.equal(el2._labelNode.textContent);
|
||||
const { _labelNode: _labelNode2 } = getFormControlMembers(el2);
|
||||
expect(el2.fieldName).to.equal(_labelNode2.textContent);
|
||||
});
|
||||
|
||||
it(`has a fieldName based on the name if no label exists`, async () => {
|
||||
|
|
@ -82,17 +83,18 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
<${tag} label="foo" .fieldName="${'bar'}">${inputSlots}</${tag}>
|
||||
`));
|
||||
// @ts-ignore [allow-proteced] in test
|
||||
expect(el.__fieldName).to.equal(el.fieldName);
|
||||
});
|
||||
|
||||
// TODO: Tests below belong to FormRegistrarMixin. Preferably run suite integration test
|
||||
it(`${tagString} has an up to date list of every form element in .formElements`, async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`<${tag}>${inputSlots}</${tag}>`));
|
||||
// @ts-ignore
|
||||
// @ts-ignore [allow-proteced] in test
|
||||
expect(el.formElements._keys().length).to.equal(3);
|
||||
expect(el.formElements['hobbies[]'].length).to.equal(2);
|
||||
el.removeChild(el.formElements['hobbies[]'][0]);
|
||||
// @ts-ignore
|
||||
// @ts-ignore [allow-proteced] in test
|
||||
expect(el.formElements._keys().length).to.equal(3);
|
||||
expect(el.formElements['hobbies[]'].length).to.equal(1);
|
||||
});
|
||||
|
|
@ -207,16 +209,17 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
const newField = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${childTag} name="lastName"></${childTag}>`,
|
||||
));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el.formElements._keys().length).to.equal(3);
|
||||
|
||||
el.appendChild(newField);
|
||||
// @ts-ignore
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el.formElements._keys().length).to.equal(4);
|
||||
|
||||
el._inputNode.removeChild(newField);
|
||||
// @ts-ignore
|
||||
_inputNode.removeChild(newField);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el.formElements._keys().length).to.equal(3);
|
||||
});
|
||||
|
||||
|
|
@ -510,11 +513,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
it('sets touched when last field in fieldset left after focus', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`<${tag}>${inputSlots}</${tag}>`));
|
||||
|
||||
await triggerFocusFor(el.formElements['hobbies[]'][0]._inputNode);
|
||||
await triggerFocusFor(
|
||||
el.formElements['hobbies[]'][el.formElements['gender[]'].length - 1]._inputNode,
|
||||
const { _inputNode: hobbyInputNode } = getFormControlMembers(
|
||||
el.formElements['hobbies[]'][0],
|
||||
);
|
||||
const { _inputNode: genderInputNode } = getFormControlMembers(
|
||||
el.formElements['hobbies[]'][el.formElements['gender[]'].length - 1],
|
||||
);
|
||||
|
||||
await triggerFocusFor(hobbyInputNode);
|
||||
await triggerFocusFor(genderInputNode);
|
||||
const button = /** @type {FormGroup} */ (await fixture(html`<button></button>`));
|
||||
button.focus();
|
||||
|
||||
|
|
@ -925,12 +932,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
html`<${tag} touched dirty>${inputSlots}</${tag}>`,
|
||||
));
|
||||
// Safety check initially
|
||||
// @ts-ignore [allow-protected] in test
|
||||
el._setValueForAllFormElements('prefilled', true);
|
||||
expect(el.dirty).to.equal(true, '"dirty" initially');
|
||||
expect(el.touched).to.equal(true, '"touched" initially');
|
||||
expect(el.prefilled).to.equal(true, '"prefilled" initially');
|
||||
|
||||
// Reset all children states, with prefilled false
|
||||
// @ts-ignore [allow-protected] in test
|
||||
el._setValueForAllFormElements('modelValue', {});
|
||||
el.resetInteractionState();
|
||||
expect(el.dirty).to.equal(false, 'not "dirty" after reset');
|
||||
|
|
@ -938,6 +947,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expect(el.prefilled).to.equal(false, 'not "prefilled" after reset');
|
||||
|
||||
// Reset all children states with prefilled true
|
||||
// @ts-ignore [allow-protected] in test
|
||||
el._setValueForAllFormElements('modelValue', { checked: true }); // not prefilled
|
||||
el.resetInteractionState();
|
||||
expect(el.dirty).to.equal(false, 'not "dirty" after 2nd reset');
|
||||
|
|
@ -1024,6 +1034,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
`));
|
||||
await el.updateComplete;
|
||||
el.modelValue['child[]'] = ['foo2', 'bar2'];
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el._initialModelValue['child[]']).to.eql(['foo1', 'bar1']);
|
||||
});
|
||||
|
||||
|
|
@ -1040,6 +1051,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
</${childTag}>
|
||||
`));
|
||||
el.appendChild(childEl);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el._initialModelValue['child[]']).to.eql(['foo1', 'bar1']);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { defineCE, expect, fixture, html, oneEvent, unsafeStatic } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import { FocusMixin } from '../src/FocusMixin.js';
|
||||
|
||||
describe('FocusMixin', () => {
|
||||
|
|
@ -16,16 +17,19 @@ describe('FocusMixin', () => {
|
|||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
el.focus();
|
||||
expect(document.activeElement === el._inputNode).to.be.true;
|
||||
expect(document.activeElement === _inputNode).to.be.true;
|
||||
el.blur();
|
||||
expect(document.activeElement === el._inputNode).to.be.false;
|
||||
expect(document.activeElement === _inputNode).to.be.false;
|
||||
});
|
||||
|
||||
it('has an attribute focused when focused', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
|
||||
el.focus();
|
||||
await el.updateComplete;
|
||||
expect(el.hasAttribute('focused')).to.be.true;
|
||||
|
|
@ -39,10 +43,12 @@ describe('FocusMixin', () => {
|
|||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(el.focused).to.be.false;
|
||||
el._inputNode?.focus();
|
||||
_inputNode?.focus();
|
||||
expect(el.focused).to.be.true;
|
||||
el._inputNode?.blur();
|
||||
_inputNode?.blur();
|
||||
expect(el.focused).to.be.false;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { expect, html, defineCE, unsafeStatic, fixture } from '@open-wc/testing';
|
||||
import { LitElement } from '@lion/core';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import { FormControlMixin } from '../src/FormControlMixin.js';
|
||||
import { FormRegistrarMixin } from '../src/registration/FormRegistrarMixin.js';
|
||||
|
|
@ -118,15 +119,17 @@ describe('FormControlMixin', () => {
|
|||
</div>
|
||||
`));
|
||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||
const labelIdsBefore = /** @type {string} */ (el._inputNode.getAttribute('aria-labelledby'));
|
||||
const descriptionIdsBefore = /** @type {string} */ (el._inputNode.getAttribute(
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
const labelIdsBefore = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
const descriptionIdsBefore = /** @type {string} */ (_inputNode.getAttribute(
|
||||
'aria-describedby',
|
||||
));
|
||||
// Reconnect
|
||||
wrapper.removeChild(el);
|
||||
wrapper.appendChild(el);
|
||||
const labelIdsAfter = /** @type {string} */ (el._inputNode.getAttribute('aria-labelledby'));
|
||||
const descriptionIdsAfter = /** @type {string} */ (el._inputNode.getAttribute(
|
||||
const labelIdsAfter = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
const descriptionIdsAfter = /** @type {string} */ (_inputNode.getAttribute(
|
||||
'aria-describedby',
|
||||
));
|
||||
|
||||
|
|
@ -141,8 +144,10 @@ describe('FormControlMixin', () => {
|
|||
${inputSlot}
|
||||
</${tag}>
|
||||
`));
|
||||
const { _labelNode } = getFormControlMembers(el);
|
||||
|
||||
expect(spy).to.not.have.been.called;
|
||||
el._labelNode.click();
|
||||
_labelNode.click();
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
|
|
@ -231,6 +236,8 @@ describe('FormControlMixin', () => {
|
|||
<div id="additionalDescription"> Same for this </div>
|
||||
</div>`));
|
||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
// wait until the field element is done rendering
|
||||
await el.updateComplete;
|
||||
await el.updateComplete;
|
||||
|
|
@ -240,7 +247,7 @@ describe('FormControlMixin', () => {
|
|||
|
||||
// 1a. addToAriaLabelledBy()
|
||||
// Check if the aria attr is filled initially
|
||||
expect(/** @type {string} */ (el._inputNode.getAttribute('aria-labelledby'))).to.contain(
|
||||
expect(/** @type {string} */ (_inputNode.getAttribute('aria-labelledby'))).to.contain(
|
||||
`label-${inputId}`,
|
||||
);
|
||||
const additionalLabel = /** @type {HTMLElement} */ (wrapper.querySelector(
|
||||
|
|
@ -248,7 +255,7 @@ describe('FormControlMixin', () => {
|
|||
));
|
||||
el.addToAriaLabelledBy(additionalLabel);
|
||||
await el.updateComplete;
|
||||
let labelledbyAttr = /** @type {string} */ (el._inputNode.getAttribute('aria-labelledby'));
|
||||
let labelledbyAttr = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
// Now check if ids are added to the end (not overridden)
|
||||
expect(labelledbyAttr).to.contain(`additionalLabel`);
|
||||
// Should be placed in the end
|
||||
|
|
@ -259,13 +266,13 @@ describe('FormControlMixin', () => {
|
|||
// 1b. removeFromAriaLabelledBy()
|
||||
el.removeFromAriaLabelledBy(additionalLabel);
|
||||
await el.updateComplete;
|
||||
labelledbyAttr = /** @type {string} */ (el._inputNode.getAttribute('aria-labelledby'));
|
||||
labelledbyAttr = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
// Now check if ids are added to the end (not overridden)
|
||||
expect(labelledbyAttr).to.not.contain(`additionalLabel`);
|
||||
|
||||
// 2a. addToAriaDescribedBy()
|
||||
// Check if the aria attr is filled initially
|
||||
expect(/** @type {string} */ (el._inputNode.getAttribute('aria-describedby'))).to.contain(
|
||||
expect(/** @type {string} */ (_inputNode.getAttribute('aria-describedby'))).to.contain(
|
||||
`feedback-${inputId}`,
|
||||
);
|
||||
});
|
||||
|
|
@ -284,6 +291,7 @@ describe('FormControlMixin', () => {
|
|||
<div id="externalDescriptionB">should go after input internals</div>
|
||||
</div>`);
|
||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
// N.B. in real life we would never add the input to aria-describedby or -labelledby,
|
||||
// but this example purely demonstrates dom order is respected.
|
||||
|
|
@ -296,10 +304,10 @@ describe('FormControlMixin', () => {
|
|||
await el.updateComplete;
|
||||
|
||||
expect(
|
||||
/** @type {string} */ (el._inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||
).to.eql(['myInput', 'internalLabel']);
|
||||
expect(
|
||||
/** @type {string} */ (el._inputNode.getAttribute('aria-describedby')).split(' '),
|
||||
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
||||
).to.eql(['myInput', 'internalDescription']);
|
||||
|
||||
// cleanup
|
||||
|
|
@ -315,10 +323,10 @@ describe('FormControlMixin', () => {
|
|||
await el.updateComplete;
|
||||
|
||||
expect(
|
||||
/** @type {string} */ (el._inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||
).to.eql(['internalLabel', 'myInput']);
|
||||
expect(
|
||||
/** @type {string} */ (el._inputNode.getAttribute('aria-describedby')).split(' '),
|
||||
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
||||
).to.eql(['internalDescription', 'myInput']);
|
||||
});
|
||||
|
||||
|
|
@ -336,6 +344,7 @@ describe('FormControlMixin', () => {
|
|||
<div id="externalDescriptionB">should go after input internals</div>
|
||||
</div>`);
|
||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
// 1. addToAriaLabelledBy()
|
||||
const labelA = /** @type {HTMLElement} */ (wrapper.querySelector('#externalLabelA'));
|
||||
|
|
@ -346,7 +355,7 @@ describe('FormControlMixin', () => {
|
|||
await el.updateComplete;
|
||||
|
||||
expect(
|
||||
/** @type {string} */ (el._inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||
/** @type {string} */ (_inputNode.getAttribute('aria-labelledby')).split(' '),
|
||||
).to.eql(['internalLabel', 'externalLabelA', 'externalLabelB']);
|
||||
|
||||
// 2. addToAriaDescribedBy()
|
||||
|
|
@ -358,7 +367,7 @@ describe('FormControlMixin', () => {
|
|||
await el.updateComplete;
|
||||
|
||||
expect(
|
||||
/** @type {string} */ (el._inputNode.getAttribute('aria-describedby')).split(' '),
|
||||
/** @type {string} */ (_inputNode.getAttribute('aria-describedby')).split(' '),
|
||||
).to.eql(['internalDescription', 'externalDescriptionA', 'externalDescriptionB']);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
triggerFocusFor,
|
||||
unsafeStatic,
|
||||
} from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import '@lion/form-core/define-field';
|
||||
|
||||
|
|
@ -31,8 +32,10 @@ const inputSlot = unsafeHTML(inputSlotString);
|
|||
* @param {string} newViewValue
|
||||
*/
|
||||
function mimicUserInput(formControl, newViewValue) {
|
||||
const { _inputNode } = getFormControlMembers(formControl);
|
||||
|
||||
formControl.value = newViewValue; // eslint-disable-line no-param-reassign
|
||||
formControl._inputNode.dispatchEvent(new CustomEvent('input', { bubbles: true }));
|
||||
_inputNode.dispatchEvent(new CustomEvent('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -60,12 +63,15 @@ describe('<lion-field>', () => {
|
|||
const el1 = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag} label="foo">${inputSlot}</${tag}>`,
|
||||
));
|
||||
expect(el1.fieldName).to.equal(el1._labelNode.textContent);
|
||||
const { _labelNode: _labelNode1 } = getFormControlMembers(el1);
|
||||
|
||||
expect(el1.fieldName).to.equal(_labelNode1.textContent);
|
||||
|
||||
const el2 = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag}><label slot="label">bar</label>${inputSlot}</${tag}>`,
|
||||
));
|
||||
expect(el2.fieldName).to.equal(el2._labelNode.textContent);
|
||||
const { _labelNode: _labelNode2 } = getFormControlMembers(el2);
|
||||
expect(el2.fieldName).to.equal(_labelNode2.textContent);
|
||||
});
|
||||
|
||||
it(`has a fieldName based on the name if no label exists`, async () => {
|
||||
|
|
@ -79,23 +85,26 @@ describe('<lion-field>', () => {
|
|||
const el = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag} label="foo" .fieldName="${'bar'}">${inputSlot}</${tag}>`,
|
||||
));
|
||||
// @ts-ignore [allow-proteced] in test
|
||||
expect(el.__fieldName).to.equal(el.fieldName);
|
||||
});
|
||||
|
||||
it('fires focus/blur event on host and native input if focused/blurred', async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
const cbFocusHost = sinon.spy();
|
||||
el.addEventListener('focus', cbFocusHost);
|
||||
const cbFocusNativeInput = sinon.spy();
|
||||
el._inputNode.addEventListener('focus', cbFocusNativeInput);
|
||||
_inputNode.addEventListener('focus', cbFocusNativeInput);
|
||||
const cbBlurHost = sinon.spy();
|
||||
el.addEventListener('blur', cbBlurHost);
|
||||
const cbBlurNativeInput = sinon.spy();
|
||||
el._inputNode.addEventListener('blur', cbBlurNativeInput);
|
||||
_inputNode.addEventListener('blur', cbBlurNativeInput);
|
||||
|
||||
await triggerFocusFor(el);
|
||||
|
||||
expect(document.activeElement).to.equal(el._inputNode);
|
||||
expect(document.activeElement).to.equal(_inputNode);
|
||||
expect(cbFocusHost.callCount).to.equal(1);
|
||||
expect(cbFocusNativeInput.callCount).to.equal(1);
|
||||
expect(cbBlurHost.callCount).to.equal(0);
|
||||
|
|
@ -106,7 +115,7 @@ describe('<lion-field>', () => {
|
|||
expect(cbBlurNativeInput.callCount).to.equal(1);
|
||||
|
||||
await triggerFocusFor(el);
|
||||
expect(document.activeElement).to.equal(el._inputNode);
|
||||
expect(document.activeElement).to.equal(_inputNode);
|
||||
expect(cbFocusHost.callCount).to.equal(2);
|
||||
expect(cbFocusNativeInput.callCount).to.equal(2);
|
||||
|
||||
|
|
|
|||
|
|
@ -318,7 +318,9 @@ describe('SyncUpdatableMixin', () => {
|
|||
const tagString = defineCE(UpdatableImplementation);
|
||||
const tag = unsafeStatic(tagString);
|
||||
const el = /** @type {UpdatableImplementation} */ (fixtureSync(html`<${tag}></${tag}>`));
|
||||
// @ts-ignore [allow-private] in tests
|
||||
const ns = el.__SyncUpdatableNamespace;
|
||||
// @ts-ignore [allow-protected] in tests
|
||||
const updateSyncSpy = sinon.spy(el, 'updateSync');
|
||||
|
||||
expect(ns.connected).to.be.true;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { expect, fixture, html, unsafeStatic, defineCE } from '@open-wc/testing';
|
||||
import { LionField } from '@lion/form-core';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import { Required } from '../../src/validate/validators/Required.js';
|
||||
|
||||
/**
|
||||
|
|
@ -17,6 +18,7 @@ class RequiredElement extends LionField {
|
|||
super.connectedCallback();
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
get _inputNode() {
|
||||
return inputNodeTag || super._inputNode;
|
||||
}
|
||||
|
|
@ -37,7 +39,8 @@ describe('Required validation', async () => {
|
|||
inputNodeTag = /** @type {HTMLElementWithValue} */ (document.createElement(tagName));
|
||||
|
||||
validator.onFormControlConnect(el);
|
||||
expect(el._inputNode).to.have.attribute('aria-required', 'true');
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
expect(_inputNode).to.have.attribute('aria-required', 'true');
|
||||
});
|
||||
|
||||
// When incompatible tags are used, aria-required will not be added
|
||||
|
|
@ -46,7 +49,8 @@ describe('Required validation', async () => {
|
|||
inputNodeTag = /** @type {HTMLDivElementWithValue} */ (document.createElement('div'));
|
||||
|
||||
validator.onFormControlConnect(el);
|
||||
expect(el._inputNode).to.not.have.attribute('aria-required');
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
expect(_inputNode).to.not.have.attribute('aria-required');
|
||||
});
|
||||
it('get aria-required attribute if element is part of the right roles', async () => {
|
||||
const el = /** @type {FormControlHost & HTMLElement} */ (await fixture(
|
||||
|
|
@ -59,7 +63,8 @@ describe('Required validation', async () => {
|
|||
inputNodeTag.setAttribute('role', role);
|
||||
|
||||
validator.onFormControlConnect(el);
|
||||
expect(el._inputNode).to.have.attribute('aria-required', 'true');
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
expect(_inputNode).to.have.attribute('aria-required', 'true');
|
||||
});
|
||||
|
||||
// When incompatible roles are used, aria-required will not be added
|
||||
|
|
@ -69,6 +74,7 @@ describe('Required validation', async () => {
|
|||
inputNodeTag.setAttribute('role', 'group');
|
||||
|
||||
validator.onFormControlConnect(el);
|
||||
expect(el._inputNode).to.not.have.attribute('aria-required');
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
expect(_inputNode).to.not.have.attribute('aria-required');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import '@lion/form-core/define-validation-feedback';
|
||||
import { AlwaysInvalid, AlwaysValid } from '../../test-helpers.js';
|
||||
import { AlwaysInvalid, AlwaysValid } from '../../test-helpers/index.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../../src/validate/LionValidationFeedback').LionValidationFeedback} LionValidationFeedback
|
||||
|
|
|
|||
13
packages/form-core/types/FocusMixinTypes.d.ts
vendored
13
packages/form-core/types/FocusMixinTypes.d.ts
vendored
|
|
@ -4,16 +4,13 @@ import { FormControlHost } from './FormControlMixinTypes';
|
|||
|
||||
export declare class FocusHost {
|
||||
focused: boolean;
|
||||
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
|
||||
focus(): void;
|
||||
blur(): void;
|
||||
__onFocus(): void;
|
||||
__onBlur(): void;
|
||||
__registerEventsForFocusMixin(): void;
|
||||
__teardownEventsForFocusMixin(): void;
|
||||
|
||||
private __onFocus(): void;
|
||||
private __onBlur(): void;
|
||||
private __registerEventsForFocusMixin(): void;
|
||||
private __teardownEventsForFocusMixin(): void;
|
||||
}
|
||||
|
||||
export declare function FocusImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
119
packages/form-core/types/FormControlMixinTypes.d.ts
vendored
119
packages/form-core/types/FormControlMixinTypes.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import { LitElement, nothing, TemplateResult, CSSResultArray } from '@lion/core';
|
||||
import { SlotsMap, SlotHost } from '@lion/core/types/SlotMixinTypes';
|
||||
import { SlotHost } from '@lion/core/types/SlotMixinTypes';
|
||||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { DisabledHost } from '@lion/core/types/DisabledMixinTypes';
|
||||
import { FormRegisteringHost } from './registration/FormRegisteringMixinTypes';
|
||||
|
|
@ -66,12 +66,12 @@ export declare class FormControlHost {
|
|||
* controls until they are enabled.
|
||||
* (From: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly)
|
||||
*/
|
||||
public readOnly: boolean;
|
||||
readOnly: boolean;
|
||||
/**
|
||||
* The name the element will be registered with to the .formElements collection
|
||||
* of the parent.
|
||||
*/
|
||||
public name: string;
|
||||
name: string;
|
||||
/**
|
||||
* The model value is the result of the parser function(when available).
|
||||
* It should be considered as the internal value used for validation and reasoning/logic.
|
||||
|
|
@ -84,29 +84,56 @@ export declare class FormControlHost {
|
|||
* - For a number input: a formatted String '1.234,56' will be converted to a Number:
|
||||
* 1234.56
|
||||
*/
|
||||
public get modelValue(): any | Unparseable;
|
||||
public set modelValue(value: any | Unparseable);
|
||||
get modelValue(): any | Unparseable;
|
||||
set modelValue(value: any | Unparseable);
|
||||
/**
|
||||
* The label text for the input node.
|
||||
* When no light dom defined via [slot=label], this value will be used
|
||||
*/
|
||||
public get label(): string;
|
||||
public set label(arg: string);
|
||||
__label: string;
|
||||
get label(): string;
|
||||
set label(arg: string);
|
||||
/**
|
||||
* The helpt text for the input node.
|
||||
* When no light dom defined via [slot=help-text], this value will be used
|
||||
*/
|
||||
public get helpText(): string;
|
||||
public set helpText(arg: string);
|
||||
__helpText: string | undefined;
|
||||
public set fieldName(arg: string);
|
||||
public get fieldName(): string;
|
||||
__fieldName: string | undefined;
|
||||
get _inputNode(): HTMLElementWithValue;
|
||||
get _labelNode(): HTMLElement;
|
||||
get _helpTextNode(): HTMLElement;
|
||||
get _feedbackNode(): LionValidationFeedback;
|
||||
get helpText(): string;
|
||||
set helpText(arg: string);
|
||||
|
||||
set fieldName(arg: string);
|
||||
get fieldName(): string;
|
||||
|
||||
addToAriaLabelledBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
idPrefix?: string | undefined;
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
addToAriaDescribedBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
idPrefix?: string | undefined;
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
removeFromAriaLabelledBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
removeFromAriaDescribedBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
updated(changedProperties: import('@lion/core').PropertyValues): void;
|
||||
|
||||
protected get _inputNode(): HTMLElementWithValue | HTMLInputElement | HTMLTextAreaElement;
|
||||
protected get _labelNode(): HTMLElement;
|
||||
protected get _helpTextNode(): HTMLElement;
|
||||
protected get _feedbackNode(): LionValidationFeedback;
|
||||
protected _inputId: string;
|
||||
protected _ariaLabelledNodes: HTMLElement[];
|
||||
protected _ariaDescribedNodes: HTMLElement[];
|
||||
|
|
@ -125,11 +152,7 @@ export declare class FormControlHost {
|
|||
* to true to hide private internals in the formPath.
|
||||
*/
|
||||
protected _isRepropagationEndpoint: boolean;
|
||||
|
||||
connectedCallback(): void;
|
||||
updated(changedProperties: import('@lion/core').PropertyValues): void;
|
||||
|
||||
render(): TemplateResult;
|
||||
protected _parentFormGroup: FormControlHost | undefined;
|
||||
protected _groupOneTemplate(): TemplateResult;
|
||||
protected _groupTwoTemplate(): TemplateResult;
|
||||
protected _labelTemplate(): TemplateResult;
|
||||
|
|
@ -141,49 +164,29 @@ export declare class FormControlHost {
|
|||
protected _inputGroupSuffixTemplate(): TemplateResult | typeof nothing;
|
||||
protected _inputGroupAfterTemplate(): TemplateResult;
|
||||
protected _feedbackTemplate(): TemplateResult;
|
||||
|
||||
protected _triggerInitialModelValueChangedEvent(): void;
|
||||
protected _enhanceLightDomClasses(): void;
|
||||
protected _enhanceLightDomA11y(): void;
|
||||
protected _enhanceLightDomA11yForAdditionalSlots(additionalSlots?: string[]): void;
|
||||
__reflectAriaAttr(attrName: string, nodes: HTMLElement[], reorder: boolean | undefined): void;
|
||||
protected _isEmpty(modelValue?: any): boolean;
|
||||
protected _getAriaDescriptionElements(): HTMLElement[];
|
||||
public addToAriaLabelledBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
idPrefix?: string | undefined;
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
__reorderAriaLabelledNodes: boolean | undefined;
|
||||
public addToAriaDescribedBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
idPrefix?: string | undefined;
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
public removeFromAriaLabelledBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
public removeFromAriaDescribedBy(
|
||||
element: HTMLElement,
|
||||
customConfig?: {
|
||||
reorder?: boolean | undefined;
|
||||
},
|
||||
): void;
|
||||
__reorderAriaDescribedNodes: boolean | undefined;
|
||||
__getDirectSlotChild(slotName: string): HTMLElement | undefined;
|
||||
__dispatchInitialModelValueChangedEvent(): void;
|
||||
__repropagateChildrenInitialized: boolean | undefined;
|
||||
protected _dispatchInitialModelValueChangedEvent(): void;
|
||||
protected _onBeforeRepropagateChildrenValues(ev: CustomEvent): void;
|
||||
__repropagateChildrenValues(ev: CustomEvent): void;
|
||||
protected _parentFormGroup: FormControlHost | undefined;
|
||||
protected _repropagationCondition(target: FormControlHost): boolean;
|
||||
|
||||
private __helpText: string | undefined;
|
||||
private __label: string;
|
||||
private __fieldName: string | undefined;
|
||||
private __reorderAriaLabelledNodes: boolean | undefined;
|
||||
private __reflectAriaAttr(
|
||||
attrName: string,
|
||||
nodes: HTMLElement[],
|
||||
reorder: boolean | undefined,
|
||||
): void;
|
||||
private __reorderAriaDescribedNodes: boolean | undefined;
|
||||
private __getDirectSlotChild(slotName: string): HTMLElement | undefined;
|
||||
private __repropagateChildrenInitialized: boolean | undefined;
|
||||
private __repropagateChildrenValues(ev: CustomEvent): void;
|
||||
}
|
||||
|
||||
export declare function FormControlImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
32
packages/form-core/types/FormatMixinTypes.d.ts
vendored
32
packages/form-core/types/FormatMixinTypes.d.ts
vendored
|
|
@ -5,36 +5,32 @@ import { ValidateHost } from './validate/ValidateMixinTypes';
|
|||
import { FormControlHost } from './FormControlMixinTypes';
|
||||
|
||||
export declare class FormatHost {
|
||||
formattedValue: string;
|
||||
serializedValue: string;
|
||||
formatOn: string;
|
||||
formatOptions: FormatNumberOptions;
|
||||
__preventRecursiveTrigger: boolean;
|
||||
__isHandlingUserInput: boolean;
|
||||
|
||||
parser(v: string, opts: FormatNumberOptions): unknown;
|
||||
formatter(v: unknown, opts?: FormatNumberOptions): string;
|
||||
serializer(v: unknown): string;
|
||||
deserializer(v: string): unknown;
|
||||
preprocessor(v: string): string;
|
||||
|
||||
formattedValue: string;
|
||||
serializedValue: string;
|
||||
formatOn: string;
|
||||
formatOptions: FormatNumberOptions;
|
||||
get value(): string;
|
||||
set value(value: string);
|
||||
|
||||
_calculateValues(opts: { source: 'model' | 'serialized' | 'formatted' | null }): void;
|
||||
private __callParser(value: string | undefined): object;
|
||||
__callFormatter(): string;
|
||||
protected _isHandlingUserInput: boolean;
|
||||
protected _calculateValues(opts: { source: 'model' | 'serialized' | 'formatted' | null }): void;
|
||||
protected _onModelValueChanged(arg: { modelValue: unknown }): void;
|
||||
_dispatchModelValueChangedEvent(): void;
|
||||
protected _dispatchModelValueChangedEvent(): void;
|
||||
protected _syncValueUpwards(): void;
|
||||
_reflectBackFormattedValueToUser(): void;
|
||||
_reflectBackFormattedValueDebounced(): void;
|
||||
_reflectBackOn(): boolean;
|
||||
protected _reflectBackFormattedValueToUser(): void;
|
||||
protected _reflectBackFormattedValueDebounced(): void;
|
||||
protected _reflectBackOn(): boolean;
|
||||
protected _proxyInputEvent(): void;
|
||||
_onUserInputChanged(): void;
|
||||
protected _onUserInputChanged(): void;
|
||||
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
private __preventRecursiveTrigger: boolean;
|
||||
private __callParser(value: string | undefined): object;
|
||||
private __callFormatter(): string;
|
||||
}
|
||||
|
||||
export declare function FormatImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -19,25 +19,18 @@ export declare class InteractionStateHost {
|
|||
touched: boolean;
|
||||
dirty: boolean;
|
||||
submitted: boolean;
|
||||
_leaveEvent: string;
|
||||
_valueChangedEvent: string;
|
||||
initInteractionState(): void;
|
||||
resetInteractionState(): void;
|
||||
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
|
||||
initInteractionState(): void;
|
||||
resetInteractionState(): void;
|
||||
_iStateOnLeave(): void;
|
||||
_iStateOnValueChange(): void;
|
||||
_onTouchedChanged(): void;
|
||||
_onDirtyChanged(): void;
|
||||
|
||||
showFeedbackConditionFor(
|
||||
type: string,
|
||||
meta: InteractionStates,
|
||||
currentCondition: Function,
|
||||
): boolean;
|
||||
_feedbackConditionMeta: InteractionStates;
|
||||
protected _leaveEvent: string;
|
||||
protected _valueChangedEvent: string;
|
||||
protected _iStateOnLeave(): void;
|
||||
protected _iStateOnValueChange(): void;
|
||||
protected _onTouchedChanged(): void;
|
||||
protected _onDirtyChanged(): void;
|
||||
}
|
||||
|
||||
export declare function InteractionStateImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -1,22 +1,32 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { LionField } from '@lion/form-core/src/LionField';
|
||||
// import { LionField } from '@lion/form-core/src/LionField';
|
||||
import { LitElement } from '@lion/core';
|
||||
import { FocusHost } from '@lion/form-core/types/FocusMixinTypes';
|
||||
import { FormControlHost } from '@lion/form-core/types/FormControlMixinTypes';
|
||||
|
||||
export declare class NativeTextField extends LionField {
|
||||
get _inputNode(): HTMLTextAreaElement | HTMLInputElement;
|
||||
}
|
||||
// export declare class NativeTextField extends LionField {
|
||||
// protected get _inputNode(): HTMLTextAreaElement | HTMLInputElement;
|
||||
// }
|
||||
|
||||
export declare class NativeTextFieldHost {
|
||||
// protected get _inputNode(): HTMLTextAreaElement | HTMLInputElement;
|
||||
|
||||
get selectionStart(): number;
|
||||
set selectionStart(value: number);
|
||||
get selectionEnd(): number;
|
||||
set selectionEnd(value: number);
|
||||
}
|
||||
|
||||
export declare function NativeTextFieldImplementation<T extends Constructor<NativeTextField>>(
|
||||
export declare function NativeTextFieldImplementation<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
): T &
|
||||
Constructor<NativeTextFieldHost> &
|
||||
Pick<typeof NativeTextFieldHost, keyof typeof NativeTextFieldHost> &
|
||||
Pick<typeof NativeTextField, keyof typeof NativeTextField>;
|
||||
Constructor<FocusHost> &
|
||||
Pick<typeof FocusHost, keyof typeof FocusHost> &
|
||||
Constructor<FormControlHost> &
|
||||
Pick<typeof FormControlHost, keyof typeof FormControlHost>;
|
||||
// &
|
||||
// Pick<typeof NativeTextField, keyof typeof NativeTextField>;
|
||||
|
||||
export type NativeTextFieldMixin = typeof NativeTextFieldImplementation;
|
||||
|
|
|
|||
|
|
@ -6,50 +6,27 @@ import { InteractionStateHost } from '../InteractionStateMixinTypes';
|
|||
|
||||
export declare class ChoiceGroupHost {
|
||||
multipleChoice: boolean;
|
||||
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
|
||||
get modelValue(): any;
|
||||
|
||||
set modelValue(value: any);
|
||||
|
||||
get serializedValue(): string;
|
||||
|
||||
set serializedValue(value: string);
|
||||
|
||||
get formattedValue(): string;
|
||||
|
||||
set formattedValue(value: string);
|
||||
|
||||
connectedCallback(): void;
|
||||
|
||||
disconnectedCallback(): void;
|
||||
|
||||
addFormElement(child: FormControlHost, indexToInsertAt: number): void;
|
||||
|
||||
clear(): void;
|
||||
|
||||
protected _triggerInitialModelValueChangedEvent(): void;
|
||||
|
||||
_getFromAllFormElements(property: string, filterCondition: Function): void;
|
||||
|
||||
_throwWhenInvalidChildModelValue(child: FormControlHost): void;
|
||||
|
||||
protected _isEmpty(): void;
|
||||
|
||||
_checkSingleChoiceElements(ev: Event): void;
|
||||
|
||||
protected _getCheckedElements(): void;
|
||||
|
||||
_setCheckedElements(value: any, check: boolean): void;
|
||||
|
||||
__setChoiceGroupTouched(): void;
|
||||
|
||||
__delegateNameAttribute(child: FormControlHost): void;
|
||||
|
||||
protected _onBeforeRepropagateChildrenValues(ev: Event): void;
|
||||
protected _oldModelValue: any;
|
||||
protected _triggerInitialModelValueChangedEvent(): void;
|
||||
protected _getFromAllFormElements(property: string, filterCondition: Function): void;
|
||||
protected _throwWhenInvalidChildModelValue(child: FormControlHost): void;
|
||||
protected _isEmpty(): void;
|
||||
protected _checkSingleChoiceElements(ev: Event): void;
|
||||
protected _getCheckedElements(): void;
|
||||
protected _setCheckedElements(value: any, check: boolean): void;
|
||||
protected _onBeforeRepropagateChildrenValues(ev: Event): void;
|
||||
|
||||
private __setChoiceGroupTouched(): void;
|
||||
private __delegateNameAttribute(child: FormControlHost): void;
|
||||
}
|
||||
|
||||
export declare function ChoiceGroupImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -15,64 +15,37 @@ export interface ChoiceInputSerializedValue {
|
|||
}
|
||||
|
||||
export declare class ChoiceInputHost {
|
||||
type: string;
|
||||
serializedValue: ChoiceInputSerializedValue;
|
||||
checked: boolean;
|
||||
get modelValue(): ChoiceInputModelValue;
|
||||
set modelValue(value: ChoiceInputModelValue);
|
||||
serializedValue: ChoiceInputSerializedValue;
|
||||
|
||||
checked: boolean;
|
||||
|
||||
get choiceValue(): any;
|
||||
|
||||
set choiceValue(value: any);
|
||||
|
||||
protected requestUpdateInternal(name: string, oldValue: any): void;
|
||||
|
||||
firstUpdated(changedProperties: Map<string, any>): void;
|
||||
|
||||
updated(changedProperties: Map<string, any>): void;
|
||||
|
||||
static get styles(): CSSResultArray;
|
||||
parser(): any;
|
||||
formatter(modelValue: ChoiceInputModelValue): string;
|
||||
|
||||
render(): TemplateResult;
|
||||
|
||||
_choiceGraphicTemplate(): TemplateResult;
|
||||
protected _afterTemplate(): TemplateResult;
|
||||
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
|
||||
_preventDuplicateLabelClick(ev: Event): void;
|
||||
|
||||
_syncNameToParentFormGroup(): void;
|
||||
|
||||
_toggleChecked(ev: Event): void;
|
||||
|
||||
__syncModelCheckedToChecked(checked: boolean): void;
|
||||
|
||||
__syncCheckedToModel(checked: boolean): void;
|
||||
|
||||
__syncCheckedToInputElement(): void;
|
||||
|
||||
__isHandlingUserInput: boolean;
|
||||
protected _isHandlingUserInput: boolean;
|
||||
protected get _inputNode(): HTMLElement;
|
||||
|
||||
protected _proxyInputEvent(): void;
|
||||
|
||||
protected requestUpdateInternal(name: string, oldValue: any): void;
|
||||
protected _choiceGraphicTemplate(): TemplateResult;
|
||||
protected _afterTemplate(): TemplateResult;
|
||||
protected _preventDuplicateLabelClick(ev: Event): void;
|
||||
protected _syncNameToParentFormGroup(): void;
|
||||
protected _toggleChecked(ev: Event): void;
|
||||
protected _onModelValueChanged(
|
||||
newV: { modelValue: ChoiceInputModelValue },
|
||||
oldV: { modelValue: ChoiceInputModelValue },
|
||||
): void;
|
||||
|
||||
parser(): any;
|
||||
|
||||
formatter(modelValue: ChoiceInputModelValue): string;
|
||||
|
||||
protected _isEmpty(): void;
|
||||
|
||||
protected _syncValueUpwards(): void;
|
||||
|
||||
type: string;
|
||||
|
||||
get _inputNode(): HTMLElement;
|
||||
private __syncModelCheckedToChecked(checked: boolean): void;
|
||||
private __syncCheckedToModel(checked: boolean): void;
|
||||
private __syncCheckedToInputElement(): void;
|
||||
}
|
||||
|
||||
export declare function ChoiceInputImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -7,24 +7,26 @@ import { FormRegistrarHost } from '../registration/FormRegistrarMixinTypes';
|
|||
import { ValidateHost } from '../validate/ValidateMixinTypes';
|
||||
|
||||
export declare class FormGroupHost {
|
||||
protected static _addDescriptionElementIdsToField(): void;
|
||||
get _inputNode(): HTMLElement;
|
||||
submitGroup(): void;
|
||||
resetGroup(): void;
|
||||
prefilled: boolean;
|
||||
touched: boolean;
|
||||
dirty: boolean;
|
||||
submitted: boolean;
|
||||
serializedValue: { [key: string]: any };
|
||||
get modelValue(): { [x: string]: any };
|
||||
set modelValue(value: { [x: string]: any });
|
||||
formattedValue: string;
|
||||
children: Array<HTMLElement & FormControlHost>;
|
||||
_initialModelValue: { [x: string]: any };
|
||||
_setValueForAllFormElements(property: string, value: any): void;
|
||||
get modelValue(): { [x: string]: any };
|
||||
set modelValue(value: { [x: string]: any });
|
||||
resetInteractionState(): void;
|
||||
clearGroup(): void;
|
||||
__descriptionElementsInParentChain: Set<HTMLElement>;
|
||||
submitGroup(): void;
|
||||
resetGroup(): void;
|
||||
|
||||
protected _initialModelValue: { [x: string]: any };
|
||||
protected get _inputNode(): HTMLElement;
|
||||
protected static _addDescriptionElementIdsToField(): void;
|
||||
|
||||
protected _setValueForAllFormElements(property: string, value: any): void;
|
||||
private __descriptionElementsInParentChain: Set<HTMLElement>;
|
||||
}
|
||||
|
||||
export declare function FormGroupImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ import { FormRegistrarHost } from './FormRegistrarMixinTypes';
|
|||
import { LitElement } from '@lion/core';
|
||||
|
||||
export declare class FormRegisteringHost {
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
name: string;
|
||||
protected _parentFormGroup: FormRegistrarHost | undefined;
|
||||
public name: string;
|
||||
}
|
||||
|
||||
export declare function FormRegisteringImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export declare class ElementWithParentFormGroup {
|
|||
}
|
||||
|
||||
export declare class FormRegistrarHost {
|
||||
protected _isFormOrFieldset: boolean;
|
||||
formElements: FormControlsCollection & { [x: string]: any };
|
||||
addFormElement(
|
||||
child:
|
||||
|
|
@ -19,10 +18,11 @@ export declare class FormRegistrarHost {
|
|||
indexToInsertAt?: number,
|
||||
): void;
|
||||
removeFormElement(child: FormRegisteringHost): void;
|
||||
_onRequestToAddFormElement(e: CustomEvent): void;
|
||||
isRegisteredFormElement(el: FormControlHost): boolean;
|
||||
registrationComplete: Promise<boolean>;
|
||||
initComplete: Promise<boolean>;
|
||||
protected _isFormOrFieldset: boolean;
|
||||
protected _onRequestToAddFormElement(e: CustomEvent): void;
|
||||
protected _completeRegistration(): void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { LitElement } from '@lion/core';
|
|||
|
||||
export declare class FormRegistrarPortalHost {
|
||||
registrationTarget: HTMLElement;
|
||||
__redispatchEventForFormRegistrarPortalMixin(ev: CustomEvent): void;
|
||||
private __redispatchEventForFormRegistrarPortalMixin(ev: CustomEvent): void;
|
||||
}
|
||||
|
||||
export declare function FormRegistrarPortalImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -10,13 +10,10 @@ export declare interface SyncUpdatableNamespace {
|
|||
}
|
||||
|
||||
export declare class SyncUpdatableHost {
|
||||
static __syncUpdatableHasChanged(name: string, newValue: any, oldValue: any): boolean;
|
||||
updateSync(name: string, oldValue: any): void;
|
||||
__syncUpdatableInitialize(): void;
|
||||
__SyncUpdatableNamespace: SyncUpdatableNamespace;
|
||||
|
||||
firstUpdated(changedProperties: PropertyValues): void;
|
||||
disconnectedCallback(): void;
|
||||
protected updateSync(name: string, oldValue: any): void;
|
||||
private __syncUpdatableInitialize(): void;
|
||||
private __SyncUpdatableNamespace: SyncUpdatableNamespace;
|
||||
private static __syncUpdatableHasChanged(name: string, newValue: any, oldValue: any): boolean;
|
||||
}
|
||||
|
||||
export type SyncUpdatableHostType = typeof SyncUpdatableHost;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { DisabledHost } from '@lion/core/types/DisabledMixinTypes';
|
||||
import { SlotsMap, SlotHost } from '@lion/core/types/SlotMixinTypes';
|
||||
import { SlotHost } from '@lion/core/types/SlotMixinTypes';
|
||||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { ScopedElementsHost } from '@open-wc/scoped-elements/src/types';
|
||||
import { FormControlHost } from '../FormControlMixinTypes';
|
||||
|
|
@ -26,51 +26,47 @@ export declare class ValidateHost {
|
|||
validationStates: { [key: string]: { [key: string]: Object } };
|
||||
isPending: boolean;
|
||||
defaultValidators: Validator[];
|
||||
_visibleMessagesAmount: number;
|
||||
fieldName: string;
|
||||
|
||||
static validationTypes: string[];
|
||||
_feedbackNode: LionValidationFeedback;
|
||||
_allValidators: Validator[];
|
||||
|
||||
__syncValidationResult: Validator[];
|
||||
__asyncValidationResult: Validator[];
|
||||
__validationResult: Validator[];
|
||||
__prevValidationResult: Validator[];
|
||||
__prevShownValidationResult: Validator[];
|
||||
|
||||
connectedCallback(): void;
|
||||
disconnectedCallback(): void;
|
||||
firstUpdated(changedProperties: import('@lion/core').PropertyValues): void;
|
||||
updateSync(name: string, oldValue: unknown): void;
|
||||
updated(changedProperties: import('@lion/core').PropertyValues): void;
|
||||
|
||||
validate(opts?: { clearCurrentResult?: boolean }): void;
|
||||
__storePrevResult(): void;
|
||||
__executeValidators(): void;
|
||||
validateComplete: Promise<void>;
|
||||
feedbackComplete: Promise<void>;
|
||||
__validateCompleteResolve(): void;
|
||||
__executeSyncValidators(
|
||||
|
||||
static validationTypes: string[];
|
||||
|
||||
validate(opts?: { clearCurrentResult?: boolean }): void;
|
||||
|
||||
protected _visibleMessagesAmount: number;
|
||||
protected _allValidators: Validator[];
|
||||
protected get _feedbackNode(): LionValidationFeedback;
|
||||
|
||||
protected _updateFeedbackComponent(): void;
|
||||
protected _showFeedbackConditionFor(type: string, meta: object): boolean;
|
||||
protected _hasFeedbackVisibleFor(type: string): boolean;
|
||||
protected _updateShouldShowFeedbackFor(): void;
|
||||
protected _prioritizeAndFilterFeedback(opts: { validationResult: Validator[] }): Validator[];
|
||||
protected updateSync(name: string, oldValue: unknown): void;
|
||||
|
||||
private __syncValidationResult: Validator[];
|
||||
private __asyncValidationResult: Validator[];
|
||||
private __validationResult: Validator[];
|
||||
private __prevValidationResult: Validator[];
|
||||
private __prevShownValidationResult: Validator[];
|
||||
|
||||
private __storePrevResult(): void;
|
||||
private __executeValidators(): void;
|
||||
private __validateCompleteResolve(): void;
|
||||
private __executeSyncValidators(
|
||||
syncValidators: Validator[],
|
||||
value: unknown,
|
||||
opts: { hasAsync: boolean },
|
||||
): void;
|
||||
__executeAsyncValidators(asyncValidators: Validator[], value: unknown): void;
|
||||
__executeResultValidators(regularValidationResult: Validator[]): Validator[];
|
||||
__finishValidation(options: { source: 'sync' | 'async'; hasAsync?: boolean }): void;
|
||||
__clearValidationResults(): void;
|
||||
__onValidatorUpdated(e: Event | CustomEvent): void;
|
||||
__setupValidators(): void;
|
||||
__isEmpty(v: unknown): boolean;
|
||||
__getFeedbackMessages(validators: Validator[]): Promise<FeedbackMessage[]>;
|
||||
_updateFeedbackComponent(): void;
|
||||
_showFeedbackConditionFor(type: string, meta: object): boolean;
|
||||
showFeedbackConditionFor(type: string, meta: object, currentCondition: Function): boolean;
|
||||
_hasFeedbackVisibleFor(type: string): boolean;
|
||||
_updateShouldShowFeedbackFor(): void;
|
||||
_prioritizeAndFilterFeedback(opts: { validationResult: Validator[] }): Validator[];
|
||||
_feedbackConditionMeta: object;
|
||||
private __executeAsyncValidators(asyncValidators: Validator[], value: unknown): void;
|
||||
private __executeResultValidators(regularValidationResult: Validator[]): Validator[];
|
||||
private __finishValidation(options: { source: 'sync' | 'async'; hasAsync?: boolean }): void;
|
||||
private __clearValidationResults(): void;
|
||||
private __onValidatorUpdated(e: Event | CustomEvent): void;
|
||||
private __setupValidators(): void;
|
||||
private __isEmpty(v: unknown): boolean;
|
||||
private __getFeedbackMessages(validators: Validator[]): Promise<FeedbackMessage[]>;
|
||||
}
|
||||
|
||||
export declare function ValidateImplementation<T extends Constructor<LitElement>>(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Required, DefaultSuccess, Validator } from '@lion/form-core';
|
|||
import { loadDefaultFeedbackMessages } from '@lion/validate-messages';
|
||||
import { LionInput } from '@lion/input';
|
||||
import sinon from 'sinon';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
|
||||
describe('Form Validation Integrations', () => {
|
||||
const lightDom = '';
|
||||
|
|
@ -49,8 +50,9 @@ describe('Form Validation Integrations', () => {
|
|||
]}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(el._feedbackNode.feedbackData?.length).to.equal(0);
|
||||
expect(_feedbackNode.feedbackData?.length).to.equal(0);
|
||||
|
||||
el.modelValue = 'w';
|
||||
el.touched = true;
|
||||
|
|
@ -61,7 +63,7 @@ describe('Form Validation Integrations', () => {
|
|||
el.modelValue = 'warn';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('warning');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('warning');
|
||||
|
||||
el.modelValue = 'war';
|
||||
await el.updateComplete;
|
||||
|
|
@ -76,14 +78,14 @@ describe('Form Validation Integrations', () => {
|
|||
'Changed!',
|
||||
'Ok, correct.',
|
||||
]).to.include(
|
||||
/** @type {{ message: string ;type: string; validator?: Validator | undefined;}[]} */ (el
|
||||
._feedbackNode.feedbackData)[0].message,
|
||||
/** @type {{ message: string ;type: string; validator?: Validator | undefined;}[]} */
|
||||
(_feedbackNode.feedbackData)[0].message,
|
||||
);
|
||||
|
||||
el.modelValue = '';
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(el._feedbackNode.feedbackData?.[0].message).to.equal('error');
|
||||
expect(_feedbackNode.feedbackData?.[0].message).to.equal('error');
|
||||
|
||||
el.modelValue = 'war';
|
||||
await el.updateComplete;
|
||||
|
|
@ -98,13 +100,15 @@ describe('Form Validation Integrations', () => {
|
|||
'Changed!',
|
||||
'Ok, correct.',
|
||||
]).to.include(
|
||||
/** @type {{ message: string ;type: string; validator?: Validator | undefined;}[]} */ (el
|
||||
._feedbackNode.feedbackData)[0].message,
|
||||
/** @type {{ message: string ;type: string; validator?: Validator | undefined;}[]} */
|
||||
(_feedbackNode.feedbackData)[0].message,
|
||||
);
|
||||
|
||||
// Check that change in focused or other interaction states does not refresh the success message
|
||||
// without a change in validation results
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const spy = sinon.spy(el, '_updateFeedbackComponent');
|
||||
// @ts-ignore [allow-protected] in test
|
||||
el._updateShouldShowFeedbackFor();
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import '@lion/fieldset/define';
|
|||
import '@lion/form/define';
|
||||
import '@lion/form-core/define';
|
||||
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/core').LitElement} LitElement
|
||||
* @typedef {import('@lion/form-core').LionField} LionField
|
||||
|
|
@ -129,7 +131,7 @@ const choiceGroupDispatchesCountOnFirstPaint = (groupTagname, itemTagname, count
|
|||
it(getFirstPaintTitle(count), async () => {
|
||||
const spy = sinon.spy();
|
||||
await fixture(html`
|
||||
<${groupTag} @model-value-changed="${spy}">
|
||||
<${groupTag} @model-value-changed="${spy}" name="group[]">
|
||||
<${itemTag} .choiceValue="${'option1'}"></${itemTag}>
|
||||
<${itemTag} .choiceValue="${'option2'}"></${itemTag}>
|
||||
<${itemTag} .choiceValue="${'option3'}"></${itemTag}>
|
||||
|
|
@ -151,7 +153,7 @@ const choiceGroupDispatchesCountOnInteraction = (groupTagname, itemTagname, coun
|
|||
it(getInteractionTitle(count), async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = await fixture(html`
|
||||
<${groupTag}>
|
||||
<${groupTag} name="group[]">
|
||||
<${itemTag} .choiceValue="${'option1'}"></${itemTag}>
|
||||
<${itemTag} .choiceValue="${'option2'}"></${itemTag}>
|
||||
<${itemTag} .choiceValue="${'option3'}"></${itemTag}>
|
||||
|
|
@ -417,14 +419,15 @@ describe('detail.isTriggeredByUser', () => {
|
|||
* @param {string | undefined} [triggerType]
|
||||
*/
|
||||
function mimicUserInput(el, newViewValue, triggerType) {
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
const type = detectType(el);
|
||||
let userInputEv;
|
||||
if (type === 'RegularField') {
|
||||
userInputEv = el._inputNode.tagName === 'SELECT' ? 'change' : 'input';
|
||||
userInputEv = _inputNode.tagName === 'SELECT' ? 'change' : 'input';
|
||||
el.value = newViewValue; // eslint-disable-line no-param-reassign
|
||||
el._inputNode.dispatchEvent(new Event(userInputEv, { bubbles: true }));
|
||||
_inputNode.dispatchEvent(new Event(userInputEv, { bubbles: true }));
|
||||
} else if (type === 'ChoiceField') {
|
||||
el._inputNode.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
_inputNode.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else if (type === 'OptionChoiceField') {
|
||||
if (!triggerType) {
|
||||
el.dispatchEvent(new Event('click', { bubbles: true }));
|
||||
|
|
@ -456,8 +459,9 @@ describe('detail.isTriggeredByUser', () => {
|
|||
childrenEl = await fixture(html`<input slot="input" />`);
|
||||
}
|
||||
|
||||
const name = controlName === 'checkbox-group' ? 'test[]' : 'test';
|
||||
const el = /** @type {LitElement & FormControl & {value: string} & {registrationComplete: Promise<boolean>} & {formElements: Array.<FormControl & {value: string}>}} */ (await fixture(
|
||||
html`<${tag}>${childrenEl}</${tag}>`,
|
||||
html`<${tag} name="${name}">${childrenEl}</${tag}>`,
|
||||
));
|
||||
await el.registrationComplete;
|
||||
el.addEventListener('model-value-changed', spy);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import { html } from '@lion/core';
|
|||
import { localize } from '@lion/localize';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { aTimeout, expect, fixture } from '@open-wc/testing';
|
||||
import { getInputMembers } from '@lion/input/test-helpers';
|
||||
import '@lion/input-amount/define';
|
||||
import { formatAmount } from '../src/formatters.js';
|
||||
import { parseAmount } from '../src/parsers.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/input/src/LionInput').LionInput} LionInput
|
||||
* @typedef {import('../src/LionInputAmount').LionInputAmount} LionInputAmount
|
||||
*/
|
||||
|
||||
|
|
@ -73,14 +75,16 @@ describe('<lion-input-amount>', () => {
|
|||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount></lion-input-amount>`,
|
||||
));
|
||||
expect(el._inputNode.getAttribute('inputmode')).to.equal('decimal');
|
||||
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
|
||||
expect(_inputNode.getAttribute('inputmode')).to.equal('decimal');
|
||||
});
|
||||
|
||||
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount></lion-input-amount>`,
|
||||
));
|
||||
expect(el._inputNode.type).to.equal('text');
|
||||
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
|
||||
expect(_inputNode.type).to.equal('text');
|
||||
});
|
||||
|
||||
it('shows no currency by default', async () => {
|
||||
|
|
@ -143,7 +147,8 @@ describe('<lion-input-amount>', () => {
|
|||
`<lion-input-amount currency="EUR"></lion-input-amount>`,
|
||||
));
|
||||
expect(el._currencyDisplayNode?.getAttribute('data-label')).to.be.not.null;
|
||||
expect(el._inputNode.getAttribute('aria-labelledby')).to.contain(el._currencyDisplayNode?.id);
|
||||
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
|
||||
expect(_inputNode.getAttribute('aria-labelledby')).to.contain(el._currencyDisplayNode?.id);
|
||||
});
|
||||
|
||||
it('adds an aria-label to currency slot', async () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { localize } from '@lion/localize';
|
|||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { MaxDate } from '@lion/form-core';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { getInputMembers } from '@lion/input/test-helpers';
|
||||
import '@lion/input-date/define';
|
||||
|
||||
/**
|
||||
|
|
@ -23,7 +24,8 @@ describe('<lion-input-date>', () => {
|
|||
|
||||
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
||||
const el = await fixture(html`<lion-input-date></lion-input-date>`);
|
||||
expect(el._inputNode.type).to.equal('text');
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
expect(_inputNode.type).to.equal('text');
|
||||
});
|
||||
|
||||
it('has validator "isDate" applied by default', async () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
|
||||
import { getInputMembers } from '@lion/input/test-helpers';
|
||||
import '@lion/input-email/define';
|
||||
|
||||
/**
|
||||
|
|
@ -11,7 +11,8 @@ const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionInputEmai
|
|||
describe('<lion-input-email>', () => {
|
||||
it('has a type = text', async () => {
|
||||
const el = await fixture(`<lion-input-email></lion-input-email>`);
|
||||
expect(el._inputNode.type).to.equal('text');
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
expect(_inputNode.type).to.equal('text');
|
||||
});
|
||||
|
||||
it('has validator "IsEmail" applied by default', async () => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from '@lion/core';
|
||||
|
||||
import { getInputMembers } from '@lion/input/test-helpers';
|
||||
import { IsCountryIBAN } from '../src/validators.js';
|
||||
import { formatIBAN } from '../src/formatters.js';
|
||||
import { parseIBAN } from '../src/parsers.js';
|
||||
|
||||
import '@lion/input-iban/define';
|
||||
|
||||
/**
|
||||
|
|
@ -26,7 +25,8 @@ describe('<lion-input-iban>', () => {
|
|||
|
||||
it('has a type = text', async () => {
|
||||
const el = await fixture(`<lion-input-iban></lion-input-iban>`);
|
||||
expect(el._inputNode.type).to.equal('text');
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
expect(_inputNode.type).to.equal('text');
|
||||
});
|
||||
|
||||
it('has validator "IsIBAN" applied by default', async () => {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
"exports": {
|
||||
".": "./index.js",
|
||||
"./define": "./lion-input.js",
|
||||
"./docs/": "./docs/"
|
||||
"./docs/": "./docs/",
|
||||
"./test-helpers": "./test-helpers/index.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import { LionField, NativeTextFieldMixin } from '@lion/form-core';
|
|||
*
|
||||
* @customElement lion-input
|
||||
*/
|
||||
export class LionInput extends NativeTextFieldMixin(
|
||||
/** @type {typeof import('@lion/form-core/types/NativeTextFieldMixinTypes').NativeTextField} */ (LionField),
|
||||
) {
|
||||
export class LionInput extends NativeTextFieldMixin(LionField) {
|
||||
/** @type {any} */
|
||||
static get properties() {
|
||||
return {
|
||||
|
|
@ -50,6 +48,10 @@ export class LionInput extends NativeTextFieldMixin(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {HTMLInputElement}
|
||||
* @protected
|
||||
*/
|
||||
get _inputNode() {
|
||||
return /** @type {HTMLInputElement} */ (super._inputNode); // casts type
|
||||
}
|
||||
|
|
|
|||
17
packages/input/test-helpers/getInputMembers.js
Normal file
17
packages/input/test-helpers/getInputMembers.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionInput').LionInput} LionInput
|
||||
* @typedef {import('@lion/form-core/types/FormControlMixinTypes').FormControlHost} FormControlHost
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param { LionInput } el
|
||||
*/
|
||||
export function getInputMembers(el) {
|
||||
const obj = getFormControlMembers(/** @type { * & FormControlHost } */ (el));
|
||||
return {
|
||||
...obj,
|
||||
_inputNode: /** @type {HTMLInputElement} */ (obj._inputNode),
|
||||
};
|
||||
}
|
||||
1
packages/input/test-helpers/index.js
Normal file
1
packages/input/test-helpers/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { getInputMembers } from './getInputMembers.js';
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Validator } from '@lion/form-core';
|
||||
import { expect, fixture, html, unsafeStatic, triggerFocusFor, aTimeout } from '@open-wc/testing';
|
||||
|
||||
import { getInputMembers } from '../test-helpers/index.js';
|
||||
import '@lion/input/define';
|
||||
|
||||
/**
|
||||
|
|
@ -13,43 +13,51 @@ const tag = unsafeStatic(tagString);
|
|||
describe('<lion-input>', () => {
|
||||
it('delegates readOnly property and readonly attribute', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag} readonly></${tag}>`));
|
||||
expect(el._inputNode.readOnly).to.equal(true);
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
expect(_inputNode.readOnly).to.equal(true);
|
||||
el.readOnly = false;
|
||||
await el.updateComplete;
|
||||
expect(el.readOnly).to.equal(false);
|
||||
expect(el._inputNode.readOnly).to.equal(false);
|
||||
expect(_inputNode.readOnly).to.equal(false);
|
||||
});
|
||||
|
||||
it('delegates value attribute', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag} value="prefilled"></${tag}>`));
|
||||
expect(el._inputNode.getAttribute('value')).to.equal('prefilled');
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
expect(_inputNode.getAttribute('value')).to.equal('prefilled');
|
||||
});
|
||||
|
||||
it('can be disabled via attribute', async () => {
|
||||
const elDisabled = /** @type {LionInput} */ (await fixture(html`<${tag} disabled></${tag}>`));
|
||||
expect(elDisabled.disabled).to.equal(true);
|
||||
expect(elDisabled._inputNode.disabled).to.equal(true);
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag} disabled></${tag}>`));
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
expect(el.disabled).to.equal(true);
|
||||
expect(_inputNode.disabled).to.equal(true);
|
||||
});
|
||||
|
||||
it('can be disabled via property', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
el.disabled = true;
|
||||
await el.updateComplete;
|
||||
expect(el._inputNode.disabled).to.equal(true);
|
||||
expect(_inputNode.disabled).to.equal(true);
|
||||
});
|
||||
|
||||
// TODO: Add test that css pointerEvents is none if disabled.
|
||||
it('is disabled when disabled property is passed', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
expect(el._inputNode.hasAttribute('disabled')).to.equal(false);
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
expect(_inputNode.hasAttribute('disabled')).to.equal(false);
|
||||
|
||||
el.disabled = true;
|
||||
await el.updateComplete;
|
||||
await aTimeout(0);
|
||||
|
||||
expect(el._inputNode.hasAttribute('disabled')).to.equal(true);
|
||||
const disabledel = /** @type {LionInput} */ (await fixture(html`<${tag} disabled></${tag}>`));
|
||||
expect(disabledel._inputNode.hasAttribute('disabled')).to.equal(true);
|
||||
expect(_inputNode.hasAttribute('disabled')).to.equal(true);
|
||||
const disabledEl = /** @type {LionInput} */ (await fixture(html`<${tag} disabled></${tag}>`));
|
||||
const { _inputNode: _inputNodeDisabled } = getInputMembers(disabledEl);
|
||||
expect(_inputNodeDisabled.hasAttribute('disabled')).to.equal(true);
|
||||
});
|
||||
|
||||
it('reads initial value from attribute value', async () => {
|
||||
|
|
@ -80,54 +88,64 @@ describe('<lion-input>', () => {
|
|||
// This is necessary for security, so that _inputNodes autocomplete can be set to 'off'
|
||||
it('delegates autocomplete property', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
expect(el._inputNode.autocomplete).to.equal('');
|
||||
expect(el._inputNode.hasAttribute('autocomplete')).to.be.false;
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
expect(_inputNode.autocomplete).to.equal('');
|
||||
expect(_inputNode.hasAttribute('autocomplete')).to.be.false;
|
||||
el.autocomplete = 'off';
|
||||
await el.updateComplete;
|
||||
expect(el._inputNode.autocomplete).to.equal('off');
|
||||
expect(el._inputNode.getAttribute('autocomplete')).to.equal('off');
|
||||
expect(_inputNode.autocomplete).to.equal('off');
|
||||
expect(_inputNode.getAttribute('autocomplete')).to.equal('off');
|
||||
});
|
||||
|
||||
it('preserves the caret position on value change for native text fields (input|textarea)', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
await triggerFocusFor(el);
|
||||
await el.updateComplete;
|
||||
el._inputNode.value = 'hello world';
|
||||
el._inputNode.selectionStart = 2;
|
||||
el._inputNode.selectionEnd = 2;
|
||||
_inputNode.value = 'hello world';
|
||||
_inputNode.selectionStart = 2;
|
||||
_inputNode.selectionEnd = 2;
|
||||
el.value = 'hey there universe';
|
||||
expect(el._inputNode.selectionStart).to.equal(2);
|
||||
expect(el._inputNode.selectionEnd).to.equal(2);
|
||||
expect(_inputNode.selectionStart).to.equal(2);
|
||||
expect(_inputNode.selectionEnd).to.equal(2);
|
||||
});
|
||||
|
||||
it('automatically creates an <input> element if not provided by user', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`
|
||||
<${tag}></${tag}>
|
||||
`));
|
||||
expect(el.querySelector('input')).to.equal(el._inputNode);
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
expect(el.querySelector('input')).to.equal(_inputNode);
|
||||
});
|
||||
|
||||
it('has a type which is reflected to an attribute and is synced down to the native input', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
expect(el.type).to.equal('text');
|
||||
expect(el.getAttribute('type')).to.equal('text');
|
||||
expect(el._inputNode.getAttribute('type')).to.equal('text');
|
||||
expect(_inputNode.getAttribute('type')).to.equal('text');
|
||||
|
||||
el.type = 'foo';
|
||||
await el.updateComplete;
|
||||
expect(el.getAttribute('type')).to.equal('foo');
|
||||
expect(el._inputNode.getAttribute('type')).to.equal('foo');
|
||||
expect(_inputNode.getAttribute('type')).to.equal('foo');
|
||||
});
|
||||
|
||||
it('has an attribute that can be used to set the placeholder text of the input', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag} placeholder="text"></${tag}>`));
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
expect(el.getAttribute('placeholder')).to.equal('text');
|
||||
expect(el._inputNode.getAttribute('placeholder')).to.equal('text');
|
||||
expect(_inputNode.getAttribute('placeholder')).to.equal('text');
|
||||
|
||||
el.placeholder = 'foo';
|
||||
await el.updateComplete;
|
||||
expect(el.getAttribute('placeholder')).to.equal('foo');
|
||||
expect(el._inputNode.getAttribute('placeholder')).to.equal('foo');
|
||||
expect(_inputNode.getAttribute('placeholder')).to.equal('foo');
|
||||
});
|
||||
|
||||
it('should remove validation when disabled state toggles', async () => {
|
||||
|
|
@ -162,10 +180,12 @@ describe('<lion-input>', () => {
|
|||
describe('Delegation', () => {
|
||||
it('delegates property value', async () => {
|
||||
const el = /** @type {LionInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
expect(el._inputNode.value).to.equal('');
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
expect(_inputNode.value).to.equal('');
|
||||
el.value = 'one';
|
||||
expect(el.value).to.equal('one');
|
||||
expect(el._inputNode.value).to.equal('one');
|
||||
expect(_inputNode.value).to.equal('one');
|
||||
});
|
||||
|
||||
it('delegates property selectionStart and selectionEnd', async () => {
|
||||
|
|
@ -174,11 +194,12 @@ describe('<lion-input>', () => {
|
|||
.modelValue=${'Some text to select'}
|
||||
></${tag}>
|
||||
`));
|
||||
const { _inputNode } = getInputMembers(el);
|
||||
|
||||
el.selectionStart = 5;
|
||||
el.selectionEnd = 12;
|
||||
expect(el._inputNode.selectionStart).to.equal(5);
|
||||
expect(el._inputNode.selectionEnd).to.equal(12);
|
||||
expect(_inputNode.selectionStart).to.equal(5);
|
||||
expect(_inputNode.selectionEnd).to.equal(12);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
"customElementsManifest": "custom-elements.json",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./test-helpers": "./test-helpers/index.js",
|
||||
"./test-suites": "./test-suites/index.js",
|
||||
"./define": "./define.js",
|
||||
"./define-listbox": "./lion-listbox.js",
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export class LionOption extends DisabledMixin(ChoiceInputMixin(FormRegisteringMi
|
|||
return;
|
||||
}
|
||||
const parentForm = /** @type {unknown} */ (this._parentFormGroup);
|
||||
this.__isHandlingUserInput = true;
|
||||
this._isHandlingUserInput = true;
|
||||
if (parentForm && /** @type {ChoiceGroupHost} */ (parentForm).multipleChoice) {
|
||||
this.checked = !this.checked;
|
||||
this.active = !this.active;
|
||||
|
|
@ -136,6 +136,6 @@ export class LionOption extends DisabledMixin(ChoiceInputMixin(FormRegisteringMi
|
|||
this.checked = true;
|
||||
this.active = true;
|
||||
}
|
||||
this.__isHandlingUserInput = false;
|
||||
this._isHandlingUserInput = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -528,11 +528,11 @@ const ListboxMixinImplementation = superclass =>
|
|||
return;
|
||||
}
|
||||
|
||||
this.__isHandlingUserInput = true;
|
||||
this._isHandlingUserInput = true;
|
||||
setTimeout(() => {
|
||||
// Since we can't control when subclasses are done handling keyboard input, we
|
||||
// schedule a timeout to reset __isHandlingUserInput
|
||||
this.__isHandlingUserInput = false;
|
||||
// schedule a timeout to reset _isHandlingUserInput
|
||||
this._isHandlingUserInput = false;
|
||||
});
|
||||
|
||||
const { key } = ev;
|
||||
|
|
@ -638,11 +638,11 @@ const ListboxMixinImplementation = superclass =>
|
|||
return;
|
||||
}
|
||||
|
||||
this.__isHandlingUserInput = true;
|
||||
this._isHandlingUserInput = true;
|
||||
setTimeout(() => {
|
||||
// Since we can't control when subclasses are done handling keyboard input, we
|
||||
// schedule a timeout to reset __isHandlingUserInput
|
||||
this.__isHandlingUserInput = false;
|
||||
// schedule a timeout to reset _isHandlingUserInput
|
||||
this._isHandlingUserInput = false;
|
||||
});
|
||||
|
||||
const { key } = ev;
|
||||
|
|
@ -769,7 +769,7 @@ const ListboxMixinImplementation = superclass =>
|
|||
new CustomEvent('model-value-changed', {
|
||||
detail: /** @type {ModelValueEventDetails} */ ({
|
||||
formPath: ev.detail.formPath,
|
||||
isTriggeredByUser: ev.detail.isTriggeredByUser || this.__isHandlingUserInput,
|
||||
isTriggeredByUser: ev.detail.isTriggeredByUser || this._isHandlingUserInput,
|
||||
element: ev.target,
|
||||
}),
|
||||
}),
|
||||
|
|
|
|||
21
packages/listbox/test-helpers/getListboxMembers.js
Normal file
21
packages/listbox/test-helpers/getListboxMembers.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/listbox/src/LionOptions').LionOptions} LionOptions
|
||||
* @typedef {import('@lion/listbox/types/ListboxMixinTypes').ListboxHost} ListboxHost
|
||||
* @typedef {import('@lion/form-core/types/FormControlMixinTypes').FormControlHost} FormControlHost
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param { ListboxHost } el
|
||||
*/
|
||||
export function getListboxMembers(el) {
|
||||
const obj = getFormControlMembers(/** @type { * & FormControlHost } */ (el));
|
||||
// eslint-disable-next-line no-return-assign
|
||||
return {
|
||||
...obj,
|
||||
_inputNode: /** @type {* & LionOptions} */ (obj._inputNode),
|
||||
// @ts-ignore [allow-protected] in test
|
||||
...{ _listboxNode: el._listboxNode, _activeDescendantOwnerNode: el._activeDescendantOwnerNode },
|
||||
};
|
||||
}
|
||||
1
packages/listbox/test-helpers/index.js
Normal file
1
packages/listbox/test-helpers/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { getListboxMembers } from './getListboxMembers.js';
|
||||
|
|
@ -4,6 +4,7 @@ import { LionOptions } from '@lion/listbox';
|
|||
import '@lion/listbox/define';
|
||||
import { expect, fixture as _fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import { getListboxMembers } from '../test-helpers/index.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionListbox').LionListbox} LionListbox
|
||||
|
|
@ -23,22 +24,22 @@ function mimicKeyPress(el, key) {
|
|||
el.dispatchEvent(new KeyboardEvent('keyup', { key }));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LionListbox} lionListboxEl
|
||||
*/
|
||||
function getProtectedMembers(lionListboxEl) {
|
||||
// @ts-ignore protected members allowed in test
|
||||
const {
|
||||
_inputNode: input,
|
||||
_activeDescendantOwnerNode: activeDescendantOwner,
|
||||
_listboxNode: listbox,
|
||||
} = lionListboxEl;
|
||||
return {
|
||||
input,
|
||||
activeDescendantOwner,
|
||||
listbox,
|
||||
};
|
||||
}
|
||||
// /**
|
||||
// * @param {LionListbox} lionListboxEl
|
||||
// */
|
||||
// function getProtectedMembers(lionListboxEl) {
|
||||
// // @ts-ignore protected members allowed in test
|
||||
// const {
|
||||
// _inputNode: input,
|
||||
// _activeDescendantOwnerNode: activeDescendantOwner,
|
||||
// _listboxNode: listbox,
|
||||
// } = lionListboxEl;
|
||||
// return {
|
||||
// input,
|
||||
// activeDescendantOwner,
|
||||
// listbox,
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param { {tagString?:string, optionTagString?:string} } [customConfig]
|
||||
|
|
@ -253,14 +254,16 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
const el1 = await fixture(html`
|
||||
<${tag} label="foo"></${tag}>
|
||||
`);
|
||||
expect(el1.fieldName).to.equal(el1._labelNode.textContent);
|
||||
const { _labelNode: _labelNode1 } = getListboxMembers(el1);
|
||||
expect(el1.fieldName).to.equal(_labelNode1.textContent);
|
||||
|
||||
const el2 = await fixture(html`
|
||||
<${tag}>
|
||||
<label slot="label">bar</label>
|
||||
</${tag}>
|
||||
`);
|
||||
expect(el2.fieldName).to.equal(el2._labelNode.textContent);
|
||||
const { _labelNode: _labelNode2 } = getListboxMembers(el2);
|
||||
expect(el2.fieldName).to.equal(_labelNode2.textContent);
|
||||
});
|
||||
|
||||
it(`has a fieldName based on the name if no label exists`, async () => {
|
||||
|
|
@ -274,6 +277,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
const el = await fixture(html`
|
||||
<${tag} label="foo" .fieldName="${'bar'}"></${tag}>
|
||||
`);
|
||||
// @ts-ignore [allow-proteced] in test
|
||||
expect(el.__fieldName).to.equal(el.fieldName);
|
||||
});
|
||||
|
||||
|
|
@ -370,9 +374,9 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${30} id="predefined">Item 3</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
expect(el.querySelectorAll('lion-option')[0].id).to.exist;
|
||||
expect(el.querySelectorAll('lion-option')[1].id).to.exist;
|
||||
expect(el.querySelectorAll('lion-option')[2].id).to.equal('predefined');
|
||||
expect(el.querySelectorAll(cfg.optionTagString)[0].id).to.exist;
|
||||
expect(el.querySelectorAll(cfg.optionTagString)[1].id).to.exist;
|
||||
expect(el.querySelectorAll(cfg.optionTagString)[2].id).to.equal('predefined');
|
||||
});
|
||||
|
||||
it('has a reference to the active option', async () => {
|
||||
|
|
@ -382,19 +386,19 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${'20'} checked id="second">Item 2</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { activeDescendantOwner } = getProtectedMembers(el);
|
||||
const { _activeDescendantOwnerNode } = getListboxMembers(el);
|
||||
|
||||
expect(activeDescendantOwner.getAttribute('aria-activedescendant')).to.be.null;
|
||||
expect(_activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.be.null;
|
||||
await el.updateComplete;
|
||||
|
||||
// Normalize
|
||||
el.activeIndex = 0;
|
||||
await el.updateComplete;
|
||||
expect(activeDescendantOwner.getAttribute('aria-activedescendant')).to.equal('first');
|
||||
mimicKeyPress(activeDescendantOwner, 'ArrowDown');
|
||||
// activeDescendantOwner.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
|
||||
expect(_activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal('first');
|
||||
mimicKeyPress(_activeDescendantOwnerNode, 'ArrowDown');
|
||||
// _activeDescendantOwnerNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
|
||||
await el.updateComplete;
|
||||
expect(activeDescendantOwner.getAttribute('aria-activedescendant')).to.equal('second');
|
||||
expect(_activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal('second');
|
||||
});
|
||||
|
||||
it('puts "aria-setsize" on all options to indicate the total amount of options', async () => {
|
||||
|
|
@ -542,21 +546,21 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize
|
||||
el.activeIndex = 0;
|
||||
const options = el.formElements;
|
||||
// mimicKeyPress(listbox, 'ArrowUp');
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowUp');
|
||||
mimicKeyPress(_listboxNode, 'ArrowUp');
|
||||
|
||||
expect(options[0].active).to.be.true;
|
||||
expect(options[1].active).to.be.false;
|
||||
expect(options[2].active).to.be.false;
|
||||
el.activeIndex = 2;
|
||||
// mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
|
||||
expect(options[0].active).to.be.false;
|
||||
expect(options[1].active).to.be.false;
|
||||
|
|
@ -571,26 +575,27 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { _inputNode } = getListboxMembers(el);
|
||||
|
||||
el._inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
_inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
// Normalize
|
||||
el.activeIndex = 0;
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
|
||||
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' }));
|
||||
mimicKeyPress(el._inputNode, 'ArrowUp');
|
||||
mimicKeyPress(_inputNode, 'ArrowUp');
|
||||
|
||||
await el.updateComplete;
|
||||
expect(el.activeIndex).to.equal(2);
|
||||
|
||||
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
|
||||
mimicKeyPress(el._inputNode, 'ArrowDown');
|
||||
mimicKeyPress(_inputNode, 'ArrowDown');
|
||||
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
// Extra check: regular navigation
|
||||
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
|
||||
mimicKeyPress(el._inputNode, 'ArrowDown');
|
||||
mimicKeyPress(_inputNode, 'ArrowDown');
|
||||
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
});
|
||||
|
|
@ -605,14 +610,14 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize suite
|
||||
el.activeIndex = 0;
|
||||
const options = el.formElements;
|
||||
el.checkedIndex = 0;
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
expect(options[1].checked).to.be.true;
|
||||
});
|
||||
});
|
||||
|
|
@ -628,21 +633,21 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize suite
|
||||
el.activeIndex = 0;
|
||||
const options = el.formElements;
|
||||
el.checkedIndex = 0;
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(listbox, ' ');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, ' ');
|
||||
|
||||
expect(options[1].checked).to.be.true;
|
||||
el.checkedIndex = 0;
|
||||
// @ts-ignore allow protected member access in test
|
||||
el._listboxReceivesNoFocus = true;
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(listbox, ' ');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, ' ');
|
||||
|
||||
expect(options[1].checked).to.be.false;
|
||||
});
|
||||
|
|
@ -683,7 +688,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${'40'}>Item 4</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// @ts-ignore allow protected members in tests
|
||||
if (el._listboxReceivesNoFocus) {
|
||||
|
|
@ -691,9 +696,9 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
}
|
||||
|
||||
el.activeIndex = 2;
|
||||
mimicKeyPress(listbox, 'Home');
|
||||
mimicKeyPress(_listboxNode, 'Home');
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
mimicKeyPress(listbox, 'End');
|
||||
mimicKeyPress(_listboxNode, 'End');
|
||||
expect(el.activeIndex).to.equal(3);
|
||||
});
|
||||
it('navigates through open lists with [ArrowDown] [ArrowUp] keys activates the option', async () => {
|
||||
|
|
@ -704,7 +709,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${'Item 3'}>Item 3</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize across listbox/select-rich/combobox
|
||||
el.activeIndex = 0;
|
||||
|
|
@ -713,10 +718,10 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
el.selectionFollowsFocus = false;
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
expect(el.checkedIndex).to.equal(-1);
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
expect(el.checkedIndex).to.equal(-1);
|
||||
mimicKeyPress(listbox, 'ArrowUp');
|
||||
mimicKeyPress(_listboxNode, 'ArrowUp');
|
||||
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
expect(el.checkedIndex).to.equal(-1);
|
||||
|
|
@ -731,7 +736,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
expect(el.orientation).to.equal('vertical');
|
||||
const options = el.formElements;
|
||||
|
|
@ -742,23 +747,23 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
expect(options[0].active).to.be.true;
|
||||
expect(options[1].active).to.be.false;
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(options[0].active).to.be.false;
|
||||
expect(options[1].active).to.be.true;
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowUp');
|
||||
mimicKeyPress(_listboxNode, 'ArrowUp');
|
||||
|
||||
expect(options[0].active).to.be.true;
|
||||
expect(options[1].active).to.be.false;
|
||||
|
||||
// No response to horizontal arrows...
|
||||
mimicKeyPress(listbox, 'ArrowRight');
|
||||
mimicKeyPress(_listboxNode, 'ArrowRight');
|
||||
|
||||
expect(options[0].active).to.be.true;
|
||||
expect(options[1].active).to.be.false;
|
||||
|
||||
el.activeIndex = 1;
|
||||
mimicKeyPress(listbox, 'ArrowLeft');
|
||||
mimicKeyPress(_listboxNode, 'ArrowLeft');
|
||||
|
||||
expect(options[0].active).to.be.false;
|
||||
expect(options[1].active).to.be.true;
|
||||
|
|
@ -771,7 +776,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
expect(el.orientation).to.equal('horizontal');
|
||||
|
||||
|
|
@ -780,20 +785,20 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
|
||||
await el.updateComplete;
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowRight');
|
||||
mimicKeyPress(_listboxNode, 'ArrowRight');
|
||||
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowLeft');
|
||||
mimicKeyPress(_listboxNode, 'ArrowLeft');
|
||||
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
|
||||
// No response to vertical arrows...
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
|
||||
el.activeIndex = 1;
|
||||
mimicKeyPress(listbox, 'ArrowUp');
|
||||
mimicKeyPress(_listboxNode, 'ArrowUp');
|
||||
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
});
|
||||
|
|
@ -806,8 +811,8 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
expect(listbox.getAttribute('aria-orientation')).to.equal('horizontal');
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
expect(_listboxNode.getAttribute('aria-orientation')).to.equal('horizontal');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -838,7 +843,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Victoria Plum'}">Victoria Plum</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
const options = el.formElements;
|
||||
|
||||
// @ts-ignore feature detection select-rich
|
||||
|
|
@ -864,13 +869,13 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
|
||||
// Enter
|
||||
el.activeIndex = 0;
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
el.activeIndex = 1;
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
expect(options[0].checked).to.equal(true);
|
||||
expect(el.modelValue).to.eql(['Artichoke', 'Chard']);
|
||||
// also deselect
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
expect(options[0].checked).to.equal(true);
|
||||
expect(el.modelValue).to.eql(['Artichoke']);
|
||||
|
||||
|
|
@ -885,15 +890,15 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
|
||||
// Space
|
||||
el.activeIndex = 0;
|
||||
mimicKeyPress(listbox, ' ');
|
||||
mimicKeyPress(_listboxNode, ' ');
|
||||
|
||||
el.activeIndex = 1;
|
||||
mimicKeyPress(listbox, ' ');
|
||||
mimicKeyPress(_listboxNode, ' ');
|
||||
|
||||
expect(options[0].checked).to.equal(true);
|
||||
expect(el.modelValue).to.eql(['Artichoke', 'Chard']);
|
||||
// also deselect
|
||||
mimicKeyPress(listbox, ' ');
|
||||
mimicKeyPress(_listboxNode, ' ');
|
||||
|
||||
expect(options[0].checked).to.equal(true);
|
||||
expect(el.modelValue).to.eql(['Artichoke']);
|
||||
|
|
@ -907,8 +912,8 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
expect(listbox.getAttribute('aria-multiselectable')).to.equal('true');
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
expect(_listboxNode.getAttribute('aria-multiselectable')).to.equal('true');
|
||||
});
|
||||
|
||||
it('does not allow "selectionFollowsFocus"', async () => {
|
||||
|
|
@ -918,11 +923,11 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue="${'Chard'}">Chard</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox, input } = getProtectedMembers(el);
|
||||
const { _listboxNode, _inputNode } = getListboxMembers(el);
|
||||
|
||||
input.focus();
|
||||
listbox.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
|
||||
expect(listbox.getAttribute('aria-multiselectable')).to.equal('true');
|
||||
_inputNode.focus();
|
||||
_listboxNode.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' }));
|
||||
expect(_listboxNode.getAttribute('aria-multiselectable')).to.equal('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -950,7 +955,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
</${tag}>
|
||||
`));
|
||||
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
const options = el.formElements;
|
||||
// Normalize start values between listbox, slect and combobox and test interaction below
|
||||
el.activeIndex = 0;
|
||||
|
|
@ -958,11 +963,11 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
expect(el.activeIndex).to.equal(0);
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
expectOnlyGivenOneOptionToBeChecked(options, 0);
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
expect(el.checkedIndex).to.equal(1);
|
||||
expectOnlyGivenOneOptionToBeChecked(options, 1);
|
||||
mimicKeyPress(listbox, 'ArrowUp');
|
||||
mimicKeyPress(_listboxNode, 'ArrowUp');
|
||||
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
|
|
@ -990,7 +995,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
</${tag}>
|
||||
`));
|
||||
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
const options = el.formElements;
|
||||
// Normalize start values between listbox, slect and combobox and test interaction below
|
||||
el.activeIndex = 0;
|
||||
|
|
@ -998,12 +1003,12 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
expect(el.activeIndex).to.equal(0);
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
expectOnlyGivenOneOptionToBeChecked(options, 0);
|
||||
mimicKeyPress(listbox, 'ArrowRight');
|
||||
mimicKeyPress(_listboxNode, 'ArrowRight');
|
||||
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
expect(el.checkedIndex).to.equal(1);
|
||||
expectOnlyGivenOneOptionToBeChecked(options, 1);
|
||||
mimicKeyPress(listbox, 'ArrowLeft');
|
||||
mimicKeyPress(_listboxNode, 'ArrowLeft');
|
||||
|
||||
expect(el.activeIndex).to.equal(0);
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
|
|
@ -1018,7 +1023,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${'40'}>Item 4</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// @ts-ignore allow protected
|
||||
if (el._listboxReceivesNoFocus) {
|
||||
|
|
@ -1026,9 +1031,9 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
}
|
||||
|
||||
expect(el.modelValue).to.equal('30');
|
||||
mimicKeyPress(listbox, 'Home');
|
||||
mimicKeyPress(_listboxNode, 'Home');
|
||||
expect(el.modelValue).to.equal('10');
|
||||
mimicKeyPress(listbox, 'End');
|
||||
mimicKeyPress(_listboxNode, 'End');
|
||||
expect(el.modelValue).to.equal('40');
|
||||
});
|
||||
});
|
||||
|
|
@ -1041,11 +1046,11 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} checked .choiceValue=${'20'}>Item 2</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
const { checkedIndex } = el;
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.checkedIndex).to.equal(checkedIndex);
|
||||
});
|
||||
|
||||
|
|
@ -1096,16 +1101,16 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
</${tag}>
|
||||
`);
|
||||
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize activeIndex across multiple implementers of ListboxMixinSuite
|
||||
el.activeIndex = 0;
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
// Checked index stays where it was
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
});
|
||||
|
|
@ -1118,16 +1123,16 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
</${tag}>
|
||||
`);
|
||||
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
// Normalize activeIndex across multiple implementers of ListboxMixinSuite
|
||||
el.activeIndex = 0;
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.activeIndex).to.equal(1);
|
||||
expect(el.checkedIndex).to.equal(-1);
|
||||
|
||||
mimicKeyPress(listbox, 'ArrowDown');
|
||||
mimicKeyPress(_listboxNode, 'ArrowDown');
|
||||
expect(el.activeIndex).to.equal(2);
|
||||
expect(el.checkedIndex).to.equal(2);
|
||||
});
|
||||
|
|
@ -1141,11 +1146,11 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${20} id="myId">Item 2</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { activeDescendantOwner } = getProtectedMembers(el);
|
||||
const { _activeDescendantOwnerNode } = getListboxMembers(el);
|
||||
|
||||
const opt = el.formElements[1];
|
||||
opt.active = true;
|
||||
expect(activeDescendantOwner.getAttribute('aria-activedescendant')).to.equal('myId');
|
||||
expect(_activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal('myId');
|
||||
});
|
||||
|
||||
it('can set checked state', async () => {
|
||||
|
|
@ -1326,15 +1331,15 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
<${optionTag} .choiceValue=${20}>Item 2</${optionTag}>
|
||||
</${tag}>
|
||||
`);
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
expect(listbox).to.exist;
|
||||
expect(listbox).to.be.instanceOf(LionOptions);
|
||||
expect(el.querySelector('[role=listbox]')).to.equal(listbox);
|
||||
expect(_listboxNode).to.exist;
|
||||
expect(_listboxNode).to.be.instanceOf(LionOptions);
|
||||
expect(el.querySelector('[role=listbox]')).to.equal(_listboxNode);
|
||||
|
||||
expect(el.formElements.length).to.equal(2);
|
||||
expect(listbox.children.length).to.equal(2);
|
||||
expect(listbox.children[0].tagName).to.equal(cfg.optionTagString.toUpperCase());
|
||||
expect(_listboxNode.children.length).to.equal(2);
|
||||
expect(_listboxNode.children[0].tagName).to.equal(cfg.optionTagString.toUpperCase());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1347,14 +1352,14 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
</${tag}>
|
||||
`);
|
||||
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
el.activeIndex = 1;
|
||||
|
||||
// Allow options that behave like anchors (think of Google Search) to trigger the anchor behavior
|
||||
const activeOption = el.formElements[1];
|
||||
const clickSpy = sinon.spy(activeOption, 'click');
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
|
||||
expect(clickSpy).to.have.been.calledOnce;
|
||||
});
|
||||
|
|
@ -1367,14 +1372,14 @@ export function runListboxMixinSuite(customConfig = {}) {
|
|||
</${tag}>
|
||||
`);
|
||||
|
||||
const { listbox } = getProtectedMembers(el);
|
||||
const { _listboxNode } = getListboxMembers(el);
|
||||
|
||||
el.activeIndex = 0;
|
||||
|
||||
const activeOption = el.formElements[0];
|
||||
const clickSpy = sinon.spy(activeOption, 'click');
|
||||
|
||||
mimicKeyPress(listbox, 'Enter');
|
||||
mimicKeyPress(_listboxNode, 'Enter');
|
||||
|
||||
expect(clickSpy).to.not.have.been.called;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
".": "./index.js",
|
||||
"./test-suites": "./test-suites/index.js",
|
||||
"./translations/*": "./translations/*",
|
||||
"./test-helpers": "./test-helpers.js",
|
||||
"./test-helpers": "./test-helpers/index.js",
|
||||
"./docs/": "./docs/"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export { mimicClick } from './test-helpers/mimicClick.js';
|
||||
1
packages/overlays/test-helpers/index.js
Normal file
1
packages/overlays/test-helpers/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { mimicClick } from './mimicClick.js';
|
||||
|
|
@ -8,7 +8,7 @@ import { OverlayController } from '../src/OverlayController.js';
|
|||
import { overlays } from '../src/overlays.js';
|
||||
import { keyCodes } from '../src/utils/key-codes.js';
|
||||
import { simulateTab } from '../src/utils/simulate-tab.js';
|
||||
import { mimicClick } from '../test-helpers.js';
|
||||
import { mimicClick } from '../test-helpers/index.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/OverlayConfig').OverlayConfig} OverlayConfig
|
||||
|
|
|
|||
|
|
@ -13,10 +13,29 @@ import {
|
|||
fixture as _fixture,
|
||||
} from '@open-wc/testing';
|
||||
import { LionSelectInvoker, LionSelectRich } from '@lion/select-rich';
|
||||
|
||||
import '@lion/core/differentKeyEventNamesShimIE';
|
||||
import '@lion/listbox/define';
|
||||
import '@lion/select-rich/define';
|
||||
import { getListboxMembers } from '@lion/listbox/test-helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/listbox/src/LionOptions').LionOptions} LionOptions
|
||||
* @typedef {import('@lion/listbox/types/ListboxMixinTypes').ListboxHost} ListboxHost
|
||||
* @typedef {import('@lion/form-core/types/FormControlMixinTypes').FormControlHost} FormControlHost
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param { LionSelectRich } el
|
||||
*/
|
||||
function getSelectRichMembers(el) {
|
||||
const obj = getListboxMembers(el);
|
||||
// eslint-disable-next-line no-return-assign
|
||||
return {
|
||||
...obj,
|
||||
// @ts-ignore [allow-protected] in test
|
||||
...{ _invokerNode: el._invokerNode, _overlayCtrl: el._overlayCtrl },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||
|
|
@ -24,43 +43,14 @@ import '@lion/select-rich/define';
|
|||
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionSelectRich>} */ (_fixture);
|
||||
|
||||
/**
|
||||
* @param {LionSelectRich} lionSelectEl
|
||||
*/
|
||||
function getProtectedMembers(lionSelectEl) {
|
||||
// @ts-ignore protected members allowed in test
|
||||
const {
|
||||
_invokerNode: invoker,
|
||||
// @ts-ignore
|
||||
_feedbackNode: feedback,
|
||||
// @ts-ignore
|
||||
_labelNode: label,
|
||||
// @ts-ignore
|
||||
_helpTextNode: helpText,
|
||||
// @ts-ignore
|
||||
_listboxNode: listbox,
|
||||
// @ts-ignore
|
||||
_overlayCtrl: overlay,
|
||||
} = lionSelectEl;
|
||||
return {
|
||||
invoker,
|
||||
feedback,
|
||||
label,
|
||||
helpText,
|
||||
listbox,
|
||||
overlay,
|
||||
};
|
||||
}
|
||||
|
||||
describe('lion-select-rich', () => {
|
||||
it('clicking the label should focus the invoker', async () => {
|
||||
const el = await fixture(html` <lion-select-rich label="foo"> </lion-select-rich> `);
|
||||
expect(document.activeElement === document.body).to.be.true;
|
||||
const { label } = getProtectedMembers(el);
|
||||
label.click();
|
||||
const { _labelNode, _invokerNode } = getSelectRichMembers(el);
|
||||
_labelNode.click();
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(document.activeElement === el._invokerNode).to.be.true;
|
||||
expect(document.activeElement === _invokerNode).to.be.true;
|
||||
});
|
||||
|
||||
it('checks the first enabled option', async () => {
|
||||
|
|
@ -95,19 +85,18 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { invoker } = getProtectedMembers(el);
|
||||
expect(invoker.selectedElement).to.be.undefined;
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
expect(_invokerNode.selectedElement).to.be.undefined;
|
||||
expect(el.modelValue).to.equal('');
|
||||
});
|
||||
|
||||
describe('Invoker', () => {
|
||||
it('generates an lion-select-invoker if no invoker is provided', async () => {
|
||||
const el = await fixture(html` <lion-select-rich> </lion-select-rich> `);
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode).to.exist;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.tagName).to.include('LION-SELECT-INVOKER');
|
||||
expect(_invokerNode).to.exist;
|
||||
expect(_invokerNode).to.be.instanceOf(LionSelectInvoker);
|
||||
});
|
||||
|
||||
it('sets the first option as the selectedElement if no option is checked', async () => {
|
||||
|
|
@ -117,9 +106,9 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
const options = el.formElements;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.selectedElement).dom.to.equal(options[0]);
|
||||
expect(_invokerNode.selectedElement).dom.to.equal(options[0]);
|
||||
});
|
||||
|
||||
it('syncs the selected element to the invoker', async () => {
|
||||
|
|
@ -129,14 +118,13 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20} checked>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
const options = el.querySelectorAll('lion-option');
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.selectedElement).dom.to.equal(options[1]);
|
||||
expect(_invokerNode.selectedElement).dom.to.equal(options[1]);
|
||||
|
||||
el.checkedIndex = 0;
|
||||
await el.updateComplete;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.selectedElement).dom.to.equal(options[0]);
|
||||
expect(_invokerNode.selectedElement).dom.to.equal(options[0]);
|
||||
});
|
||||
|
||||
it('delegates readonly to the invoker', async () => {
|
||||
|
|
@ -146,10 +134,9 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
expect(el.hasAttribute('readonly')).to.be.true;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.hasAttribute('readonly')).to.be.true;
|
||||
expect(_invokerNode.hasAttribute('readonly')).to.be.true;
|
||||
});
|
||||
|
||||
it('delegates singleOption to the invoker', async () => {
|
||||
|
|
@ -158,10 +145,9 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
expect(el.singleOption).to.be.true;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.hasAttribute('single-option')).to.be.true;
|
||||
expect(_invokerNode.hasAttribute('single-option')).to.be.true;
|
||||
});
|
||||
|
||||
it('updates the invoker when the selected element is the same but the modelValue was updated asynchronously', async () => {
|
||||
|
|
@ -189,14 +175,16 @@ describe('lion-select-rich', () => {
|
|||
</lion-select-rich>
|
||||
`);
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.shadowRoot.firstElementChild.textContent).to.equal('10');
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
const firstChild = /** @type {HTMLElement} */ (
|
||||
/** @type {ShadowRoot} */ (_invokerNode.shadowRoot).firstElementChild
|
||||
);
|
||||
expect(firstChild.textContent).to.equal('10');
|
||||
|
||||
firstOption.modelValue = { value: 30, checked: true };
|
||||
await firstOption.updateComplete;
|
||||
await el.updateComplete;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.shadowRoot.firstElementChild.textContent).to.equal('30');
|
||||
expect(firstChild.textContent).to.equal('30');
|
||||
});
|
||||
|
||||
// FIXME: wrong values in safari/webkit even though this passes in the "real" debug browsers
|
||||
|
|
@ -210,20 +198,21 @@ describe('lion-select-rich', () => {
|
|||
el.opened = true;
|
||||
const options = el.formElements;
|
||||
await el.updateComplete;
|
||||
const { invoker } = getProtectedMembers(el);
|
||||
expect(invoker.clientWidth).to.equal(options[1].clientWidth);
|
||||
const { _invokerNode, _inputNode } = getSelectRichMembers(el);
|
||||
|
||||
expect(_invokerNode.clientWidth).to.equal(options[1].clientWidth);
|
||||
|
||||
const newOption = /** @type {LionOption} */ (document.createElement('lion-option'));
|
||||
newOption.choiceValue = 30;
|
||||
newOption.textContent = '30 with longer label';
|
||||
|
||||
el._inputNode.appendChild(newOption);
|
||||
_inputNode.appendChild(newOption);
|
||||
await el.updateComplete;
|
||||
expect(invoker.clientWidth).to.equal(options[2].clientWidth);
|
||||
expect(_invokerNode.clientWidth).to.equal(options[2].clientWidth);
|
||||
|
||||
el._inputNode.removeChild(newOption);
|
||||
_inputNode.removeChild(newOption);
|
||||
await el.updateComplete;
|
||||
expect(invoker.clientWidth).to.equal(options[1].clientWidth);
|
||||
expect(_invokerNode.clientWidth).to.equal(options[1].clientWidth);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -235,16 +224,16 @@ describe('lion-select-rich', () => {
|
|||
|
||||
it('shows/hides the listbox via opened attribute', async () => {
|
||||
const el = await fixture(html` <lion-select-rich></lion-select-rich> `);
|
||||
const { _overlayCtrl } = getSelectRichMembers(el);
|
||||
|
||||
el.opened = true;
|
||||
await el.updateComplete;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._overlayCtrl.isShown).to.be.true;
|
||||
expect(_overlayCtrl.isShown).to.be.true;
|
||||
|
||||
el.opened = false;
|
||||
await el.updateComplete;
|
||||
await el.updateComplete; // safari takes a little longer
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._overlayCtrl.isShown).to.be.false;
|
||||
expect(_overlayCtrl.isShown).to.be.false;
|
||||
});
|
||||
|
||||
it('syncs opened state with overlay shown', async () => {
|
||||
|
|
@ -262,21 +251,18 @@ describe('lion-select-rich', () => {
|
|||
|
||||
it('will focus the listbox on open and invoker on close', async () => {
|
||||
const el = await fixture(html` <lion-select-rich></lion-select-rich> `);
|
||||
// @ts-ignore allow protected access in tests
|
||||
await el._overlayCtrl.show();
|
||||
const { _overlayCtrl, _listboxNode, _invokerNode } = getSelectRichMembers(el);
|
||||
|
||||
await _overlayCtrl.show();
|
||||
await el.updateComplete;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(document.activeElement === el._listboxNode).to.be.true;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(document.activeElement === el._invokerNode).to.be.false;
|
||||
expect(document.activeElement === _listboxNode).to.be.true;
|
||||
expect(document.activeElement === _invokerNode).to.be.false;
|
||||
|
||||
el.opened = false;
|
||||
await el.updateComplete;
|
||||
await el.updateComplete; // safari takes a little longer
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(document.activeElement === el._listboxNode).to.be.false;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(document.activeElement === el._invokerNode).to.be.true;
|
||||
expect(document.activeElement === _listboxNode).to.be.false;
|
||||
expect(document.activeElement === _invokerNode).to.be.true;
|
||||
});
|
||||
|
||||
it('opens the listbox with checked option as active', async () => {
|
||||
|
|
@ -286,8 +272,9 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20} checked>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
// @ts-ignore allow protected access in tests
|
||||
await el._overlayCtrl.show();
|
||||
const { _overlayCtrl } = getSelectRichMembers(el);
|
||||
|
||||
await _overlayCtrl.show();
|
||||
await el.updateComplete;
|
||||
const options = el.formElements;
|
||||
|
||||
|
|
@ -302,6 +289,7 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20} checked>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _invokerNode: _invokerNodeReadOnly } = getSelectRichMembers(elReadOnly);
|
||||
|
||||
const elDisabled = await fixture(html`
|
||||
<lion-select-rich disabled>
|
||||
|
|
@ -309,25 +297,24 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20} checked>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _invokerNode: _invokerNodeDisabled } = getSelectRichMembers(elDisabled);
|
||||
|
||||
const elSingleoption = await fixture(html`
|
||||
<lion-select-rich>
|
||||
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _invokerNode: _invokerNodeSingleOption } = getSelectRichMembers(elSingleoption);
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
elReadOnly._invokerNode.click();
|
||||
_invokerNodeReadOnly.click();
|
||||
await elReadOnly.updateComplete;
|
||||
expect(elReadOnly.opened).to.be.false;
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
elDisabled._invokerNode.click();
|
||||
_invokerNodeDisabled.click();
|
||||
await elDisabled.updateComplete;
|
||||
expect(elDisabled.opened).to.be.false;
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
elSingleoption._invokerNode.click();
|
||||
_invokerNodeSingleOption.click();
|
||||
await elSingleoption.updateComplete;
|
||||
expect(elSingleoption.opened).to.be.false;
|
||||
});
|
||||
|
|
@ -340,14 +327,13 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _overlayCtrl } = getSelectRichMembers(el);
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('min');
|
||||
expect(_overlayCtrl.inheritsReferenceWidth).to.equal('min');
|
||||
el.opened = true;
|
||||
await el.updateComplete;
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('min');
|
||||
expect(_overlayCtrl.inheritsReferenceWidth).to.equal('min');
|
||||
});
|
||||
|
||||
it('should override the inheritsWidth prop when no default selected feature is used', async () => {
|
||||
|
|
@ -359,20 +345,17 @@ describe('lion-select-rich', () => {
|
|||
</lion-select-rich>
|
||||
`);
|
||||
|
||||
const { overlay } = getProtectedMembers(el);
|
||||
const { _overlayCtrl } = getSelectRichMembers(el);
|
||||
|
||||
// The default is min, so we override that behavior here
|
||||
// @ts-ignore allow protected access in tests
|
||||
overlay.updateConfig({ inheritsReferenceWidth: 'full' });
|
||||
_overlayCtrl.updateConfig({ inheritsReferenceWidth: 'full' });
|
||||
el._initialInheritsReferenceWidth = 'full';
|
||||
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(overlay.inheritsReferenceWidth).to.equal('full');
|
||||
expect(_overlayCtrl.inheritsReferenceWidth).to.equal('full');
|
||||
el.opened = true;
|
||||
await el.updateComplete;
|
||||
// Opens while hasNoDefaultSelected = true, so we expect an override
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(overlay.inheritsReferenceWidth).to.equal('min');
|
||||
expect(_overlayCtrl.inheritsReferenceWidth).to.equal('min');
|
||||
|
||||
// Emulate selecting hotpink, it closing, and opening it again
|
||||
el.modelValue = 'hotpink';
|
||||
|
|
@ -382,11 +365,10 @@ describe('lion-select-rich', () => {
|
|||
el.opened = true;
|
||||
await el.updateComplete;
|
||||
await el.updateComplete; // safari takes a little longer
|
||||
await overlay._showComplete;
|
||||
await _overlayCtrl._showComplete;
|
||||
|
||||
// noDefaultSelected will now flip the override back to what was the initial reference width
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('full');
|
||||
expect(_overlayCtrl.inheritsReferenceWidth).to.equal('full');
|
||||
});
|
||||
|
||||
it('should have singleOption only if there is exactly one option', async () => {
|
||||
|
|
@ -396,27 +378,26 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
|
||||
const { _inputNode, _invokerNode } = getSelectRichMembers(el);
|
||||
|
||||
expect(el.singleOption).to.be.false;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.singleOption).to.be.false;
|
||||
expect(_invokerNode.singleOption).to.be.false;
|
||||
|
||||
const optionELm = el.formElements[0];
|
||||
// @ts-ignore allow protected access in tests
|
||||
optionELm.parentNode.removeChild(optionELm);
|
||||
el.requestUpdate();
|
||||
await el.updateComplete;
|
||||
expect(el.singleOption).to.be.true;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.singleOption).to.be.true;
|
||||
expect(_invokerNode.singleOption).to.be.true;
|
||||
|
||||
const newOption = /** @type {LionOption} */ (document.createElement('lion-option'));
|
||||
newOption.choiceValue = 30;
|
||||
el._inputNode.appendChild(newOption);
|
||||
_inputNode.appendChild(newOption);
|
||||
el.requestUpdate();
|
||||
await el.updateComplete;
|
||||
expect(el.singleOption).to.be.false;
|
||||
// @ts-ignore allow protected access in tests
|
||||
expect(el._invokerNode.singleOption).to.be.false;
|
||||
expect(_invokerNode.singleOption).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -432,32 +413,32 @@ describe('lion-select-rich', () => {
|
|||
describe('Keyboard navigation', () => {
|
||||
it('opens the listbox with [Enter] key via click handler', async () => {
|
||||
const el = await fixture(html` <lion-select-rich> </lion-select-rich> `);
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._invokerNode.click();
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
_invokerNode.click();
|
||||
await aTimeout(0);
|
||||
expect(el.opened).to.be.true;
|
||||
});
|
||||
|
||||
it('opens the listbox with [ ](Space) key via click handler', async () => {
|
||||
const el = await fixture(html` <lion-select-rich> </lion-select-rich> `);
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._invokerNode.click();
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
_invokerNode.click();
|
||||
await aTimeout(0);
|
||||
expect(el.opened).to.be.true;
|
||||
});
|
||||
|
||||
it('closes the listbox with [Escape] key once opened', async () => {
|
||||
const el = await fixture(html` <lion-select-rich opened> </lion-select-rich> `);
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
||||
const { _listboxNode } = getSelectRichMembers(el);
|
||||
_listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
||||
expect(el.opened).to.be.false;
|
||||
});
|
||||
|
||||
it('closes the listbox with [Tab] key once opened', async () => {
|
||||
const el = await fixture(html` <lion-select-rich opened> </lion-select-rich> `);
|
||||
// tab can only be caught via keydown
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
|
||||
const { _listboxNode } = getSelectRichMembers(el);
|
||||
_listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
|
||||
expect(el.opened).to.be.false;
|
||||
});
|
||||
});
|
||||
|
|
@ -466,8 +447,8 @@ describe('lion-select-rich', () => {
|
|||
it('opens the listbox via click on invoker', async () => {
|
||||
const el = await fixture(html` <lion-select-rich> </lion-select-rich> `);
|
||||
expect(el.opened).to.be.false;
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._invokerNode.click();
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
_invokerNode.click();
|
||||
await nextFrame(); // reflection of click takes some time
|
||||
expect(el.opened).to.be.true;
|
||||
});
|
||||
|
|
@ -487,8 +468,8 @@ describe('lion-select-rich', () => {
|
|||
describe('Keyboard navigation Windows', () => {
|
||||
it('closes the listbox with [Enter] key once opened', async () => {
|
||||
const el = await fixture(html` <lion-select-rich opened> </lion-select-rich> `);
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
|
||||
const { _listboxNode } = getSelectRichMembers(el);
|
||||
_listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
|
||||
expect(el.opened).to.be.false;
|
||||
});
|
||||
});
|
||||
|
|
@ -501,12 +482,12 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { _listboxNode } = getSelectRichMembers(el);
|
||||
|
||||
// changes active but not checked
|
||||
el.activeIndex = 1;
|
||||
expect(el.checkedIndex).to.equal(0);
|
||||
// @ts-ignore allow protected access in tests
|
||||
el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
|
||||
_listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
|
||||
expect(el.opened).to.be.false;
|
||||
expect(el.checkedIndex).to.equal(1);
|
||||
});
|
||||
|
|
@ -538,26 +519,26 @@ describe('lion-select-rich', () => {
|
|||
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
||||
</lion-select-rich>
|
||||
`);
|
||||
const { invoker, feedback, label, helpText } = getProtectedMembers(el);
|
||||
const { _invokerNode, _feedbackNode, _labelNode, _helpTextNode } = getSelectRichMembers(el);
|
||||
|
||||
expect(invoker.getAttribute('aria-labelledby')).to.contain(label.id);
|
||||
expect(invoker.getAttribute('aria-labelledby')).to.contain(invoker.id);
|
||||
expect(invoker.getAttribute('aria-describedby')).to.contain(helpText.id);
|
||||
expect(invoker.getAttribute('aria-describedby')).to.contain(feedback.id);
|
||||
expect(invoker.getAttribute('aria-haspopup')).to.equal('listbox');
|
||||
expect(_invokerNode.getAttribute('aria-labelledby')).to.contain(_labelNode.id);
|
||||
expect(_invokerNode.getAttribute('aria-labelledby')).to.contain(_invokerNode.id);
|
||||
expect(_invokerNode.getAttribute('aria-describedby')).to.contain(_helpTextNode.id);
|
||||
expect(_invokerNode.getAttribute('aria-describedby')).to.contain(_feedbackNode.id);
|
||||
expect(_invokerNode.getAttribute('aria-haspopup')).to.equal('listbox');
|
||||
});
|
||||
|
||||
it('notifies when the listbox is expanded or not', async () => {
|
||||
// smoke test for overlay functionality
|
||||
const el = await fixture(html` <lion-select-rich> </lion-select-rich> `);
|
||||
const { invoker } = getProtectedMembers(el);
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
|
||||
expect(invoker.getAttribute('aria-expanded')).to.equal('false');
|
||||
expect(_invokerNode.getAttribute('aria-expanded')).to.equal('false');
|
||||
el.opened = true;
|
||||
await el.updateComplete;
|
||||
await el.updateComplete; // need 2 awaits as overlay.show is an async function
|
||||
|
||||
expect(invoker.getAttribute('aria-expanded')).to.equal('true');
|
||||
expect(_invokerNode.getAttribute('aria-expanded')).to.equal('true');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -617,21 +598,21 @@ describe('lion-select-rich', () => {
|
|||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector('lion-select-rich')
|
||||
);
|
||||
|
||||
const { invoker, listbox } = getProtectedMembers(selectRich);
|
||||
const { _invokerNode, _listboxNode } = getSelectRichMembers(selectRich);
|
||||
|
||||
expect(selectRich.checkedIndex).to.equal(1);
|
||||
expect(selectRich.modelValue).to.equal('hotpink');
|
||||
expect(/** @type {LionOption} */ (invoker.selectedElement).value).to.equal('hotpink');
|
||||
expect(/** @type {LionOption} */ (_invokerNode.selectedElement).value).to.equal('hotpink');
|
||||
|
||||
const newOption = /** @type {LionOption} */ (document.createElement('lion-option'));
|
||||
newOption.modelValue = { checked: false, value: 'blue' };
|
||||
newOption.textContent = 'Blue';
|
||||
const hotpinkEl = listbox.children[1];
|
||||
const hotpinkEl = _listboxNode.children[1];
|
||||
hotpinkEl.insertAdjacentElement('beforebegin', newOption);
|
||||
|
||||
expect(selectRich.checkedIndex).to.equal(2);
|
||||
expect(selectRich.modelValue).to.equal('hotpink');
|
||||
expect(/** @type {LionOption} */ (invoker.selectedElement).value).to.equal('hotpink');
|
||||
expect(/** @type {LionOption} */ (_invokerNode.selectedElement).value).to.equal('hotpink');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -669,11 +650,11 @@ describe('lion-select-rich', () => {
|
|||
</${mySelectTag}>
|
||||
`);
|
||||
await el.updateComplete;
|
||||
// @ts-ignore allow protected member access in tests
|
||||
expect(el._overlayCtrl.placementMode).to.equal('global');
|
||||
const { _overlayCtrl } = getSelectRichMembers(el);
|
||||
|
||||
expect(_overlayCtrl.placementMode).to.equal('global');
|
||||
el.dispatchEvent(new Event('switch'));
|
||||
// @ts-ignore allow protected member access in tests
|
||||
expect(el._overlayCtrl.placementMode).to.equal('local');
|
||||
expect(_overlayCtrl.placementMode).to.equal('local');
|
||||
});
|
||||
|
||||
it('supports putting a placeholder template when there is no default selection initially', async () => {
|
||||
|
|
@ -707,10 +688,10 @@ describe('lion-select-rich', () => {
|
|||
|
||||
</${selectTag}>
|
||||
`);
|
||||
const { invoker } = getProtectedMembers(el);
|
||||
const { _invokerNode } = getSelectRichMembers(el);
|
||||
|
||||
expect(
|
||||
/** @type {ShadowRoot} */ (invoker.shadowRoot).getElementById('content-wrapper'),
|
||||
/** @type {ShadowRoot} */ (_invokerNode.shadowRoot).getElementById('content-wrapper'),
|
||||
).dom.to.equal(`<div id="content-wrapper">Please select an option..</div>`);
|
||||
expect(el.modelValue).to.equal('');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ export class LionSwitch extends ScopedElementsMixin(ChoiceInputMixin(LionField))
|
|||
/**
|
||||
* Input node here is the lion-switch-button, which is not compatible with LionField _inputNode --> HTMLInputElement
|
||||
* Therefore we do a full override and typecast to an intersection type that includes LionSwitchButton
|
||||
* @returns {LionSwitchButton}
|
||||
* @type {LionSwitchButton}
|
||||
* @protected
|
||||
*/
|
||||
// @ts-ignore [editor]: prevents vscode from complaining
|
||||
get _inputNode() {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ import sinon from 'sinon';
|
|||
import { Validator } from '@lion/form-core';
|
||||
import { LionSwitch } from '@lion/switch';
|
||||
import '@lion/switch/define';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionSwitchButton').LionSwitchButton} LionSwitchButton
|
||||
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||
* @typedef {import('@lion/form-core/types/FormControlMixinTypes').FormControlHost} FormControlHost
|
||||
*/
|
||||
|
||||
const IsTrue = class extends Validator {
|
||||
|
|
@ -19,13 +22,11 @@ const IsTrue = class extends Validator {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {LionSwitch} lionSwitchEl
|
||||
* @param { LionSwitch } el
|
||||
*/
|
||||
function getProtectedMembers(lionSwitchEl) {
|
||||
return {
|
||||
// @ts-ignore
|
||||
inputNode: lionSwitchEl._inputNode,
|
||||
};
|
||||
function getSwitchMembers(el) {
|
||||
const obj = getFormControlMembers(/** @type { * & FormControlHost } */ (el));
|
||||
return { ...obj, _inputNode: /** @type {LionSwitchButton} */ (obj._inputNode) };
|
||||
}
|
||||
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionSwitch>} */ (_fixture);
|
||||
|
|
@ -38,40 +39,46 @@ describe('lion-switch', () => {
|
|||
|
||||
it('clicking the label should toggle the checked state', async () => {
|
||||
const el = await fixture(html`<lion-switch label="Enable Setting"></lion-switch>`);
|
||||
el._labelNode.click();
|
||||
const { _labelNode } = getFormControlMembers(el);
|
||||
_labelNode.click();
|
||||
expect(el.checked).to.be.true;
|
||||
el._labelNode.click();
|
||||
_labelNode.click();
|
||||
expect(el.checked).to.be.false;
|
||||
});
|
||||
|
||||
it('clicking the label should not toggle the checked state when disabled', async () => {
|
||||
const el = await fixture(html`<lion-switch disabled label="Enable Setting"></lion-switch>`);
|
||||
el._labelNode.click();
|
||||
const { _labelNode } = getFormControlMembers(el);
|
||||
_labelNode.click();
|
||||
expect(el.checked).to.be.false;
|
||||
});
|
||||
|
||||
it('clicking the label should focus the toggle button', async () => {
|
||||
const el = await fixture(html`<lion-switch label="Enable Setting"></lion-switch>`);
|
||||
el._labelNode.click();
|
||||
expect(document.activeElement).to.equal(el._inputNode);
|
||||
const { _inputNode, _labelNode } = getSwitchMembers(el);
|
||||
|
||||
_labelNode.click();
|
||||
expect(document.activeElement).to.equal(_inputNode);
|
||||
});
|
||||
|
||||
it('clicking the label should not focus the toggle button when disabled', async () => {
|
||||
const el = await fixture(html`<lion-switch disabled label="Enable Setting"></lion-switch>`);
|
||||
el._labelNode.click();
|
||||
expect(document.activeElement).to.not.equal(el._inputNode);
|
||||
const { _inputNode, _labelNode } = getSwitchMembers(el);
|
||||
|
||||
_labelNode.click();
|
||||
expect(document.activeElement).to.not.equal(_inputNode);
|
||||
});
|
||||
|
||||
it('should sync its "disabled" state to child button', async () => {
|
||||
const el = await fixture(html`<lion-switch disabled></lion-switch>`);
|
||||
const { inputNode } = getProtectedMembers(el);
|
||||
expect(inputNode.disabled).to.be.true;
|
||||
expect(inputNode.hasAttribute('disabled')).to.be.true;
|
||||
const { _inputNode } = getSwitchMembers(el);
|
||||
expect(_inputNode.disabled).to.be.true;
|
||||
expect(_inputNode.hasAttribute('disabled')).to.be.true;
|
||||
el.disabled = false;
|
||||
await el.updateComplete;
|
||||
await el.updateComplete; // safari takes longer
|
||||
expect(inputNode.disabled).to.be.false;
|
||||
expect(inputNode.hasAttribute('disabled')).to.be.false;
|
||||
expect(_inputNode.disabled).to.be.false;
|
||||
expect(_inputNode.hasAttribute('disabled')).to.be.false;
|
||||
});
|
||||
|
||||
it('is hidden when attribute hidden is true', async () => {
|
||||
|
|
@ -81,9 +88,9 @@ describe('lion-switch', () => {
|
|||
|
||||
it('should sync its "checked" state to child button', async () => {
|
||||
const uncheckedEl = await fixture(html`<lion-switch></lion-switch>`);
|
||||
const { inputNode: uncheckeInputNode } = getProtectedMembers(uncheckedEl);
|
||||
const { _inputNode: uncheckeInputNode } = getSwitchMembers(uncheckedEl);
|
||||
const checkedEl = await fixture(html`<lion-switch checked></lion-switch>`);
|
||||
const { inputNode: checkeInputNode } = getProtectedMembers(checkedEl);
|
||||
const { _inputNode: checkeInputNode } = getSwitchMembers(checkedEl);
|
||||
expect(uncheckeInputNode.checked).to.be.false;
|
||||
expect(checkeInputNode.checked).to.be.true;
|
||||
uncheckedEl.checked = true;
|
||||
|
|
@ -96,8 +103,8 @@ describe('lion-switch', () => {
|
|||
|
||||
it('should sync "checked" state received from child button', async () => {
|
||||
const el = await fixture(html`<lion-switch></lion-switch>`);
|
||||
const { inputNode } = getProtectedMembers(el);
|
||||
const button = inputNode;
|
||||
const { _inputNode } = getSwitchMembers(el);
|
||||
const button = _inputNode;
|
||||
expect(el.checked).to.be.false;
|
||||
button.click();
|
||||
expect(el.checked).to.be.true;
|
||||
|
|
@ -123,10 +130,10 @@ describe('lion-switch', () => {
|
|||
it('should dispatch "checked-changed" event when toggled via button or label', async () => {
|
||||
const handlerSpy = sinon.spy();
|
||||
const el = await fixture(html`<lion-switch .choiceValue=${'foo'}></lion-switch>`);
|
||||
const { inputNode } = getProtectedMembers(el);
|
||||
const { _inputNode, _labelNode } = getSwitchMembers(el);
|
||||
el.addEventListener('checked-changed', handlerSpy);
|
||||
inputNode.click();
|
||||
el._labelNode.click();
|
||||
_inputNode.click();
|
||||
_labelNode.click();
|
||||
await el.updateComplete;
|
||||
expect(handlerSpy.callCount).to.equal(2);
|
||||
const checkCall = /** @param {import('sinon').SinonSpyCall} call */ call => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { css } from '@lion/core';
|
|||
class LionFieldWithTextArea extends LionField {
|
||||
/**
|
||||
* @returns {HTMLTextAreaElement}
|
||||
* @protected
|
||||
*/
|
||||
get _inputNode() {
|
||||
return /** @type {HTMLTextAreaElement} */ (Array.from(this.children).find(
|
||||
|
|
@ -21,6 +22,7 @@ class LionFieldWithTextArea extends LionField {
|
|||
* @customElement lion-textarea
|
||||
*/
|
||||
export class LionTextarea extends NativeTextFieldMixin(LionFieldWithTextArea) {
|
||||
/** @type {any} */
|
||||
static get properties() {
|
||||
return {
|
||||
maxRows: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { aTimeout, expect, fixture as _fixture, html } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import '@lion/textarea/define';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionTextarea').LionTextarea} LionTextarea
|
||||
|
|
@ -9,16 +10,6 @@ import '@lion/textarea/define';
|
|||
|
||||
const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionTextarea>} */ (_fixture);
|
||||
|
||||
/**
|
||||
* @param {LionTextarea} lionTextareaEl
|
||||
*/
|
||||
function getProtectedMembers(lionTextareaEl) {
|
||||
const { _inputNode: input } = lionTextareaEl;
|
||||
return {
|
||||
input,
|
||||
};
|
||||
}
|
||||
|
||||
function hasBrowserResizeSupport() {
|
||||
const textarea = document.createElement('textarea');
|
||||
return textarea.style.resize !== undefined;
|
||||
|
|
@ -41,25 +32,25 @@ describe('<lion-textarea>', () => {
|
|||
|
||||
it('has .readOnly=false .rows=2 and rows="2" by default', async () => {
|
||||
const el = await fixture(`<lion-textarea>foo</lion-textarea>`);
|
||||
const { input } = getProtectedMembers(el);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(el.rows).to.equal(2);
|
||||
expect(el.getAttribute('rows')).to.be.equal('2');
|
||||
// @ts-ignore
|
||||
expect(input.rows).to.equal(2);
|
||||
expect(input.getAttribute('rows')).to.be.equal('2');
|
||||
expect(_inputNode.rows).to.equal(2);
|
||||
expect(_inputNode.getAttribute('rows')).to.be.equal('2');
|
||||
expect(el.readOnly).to.be.false;
|
||||
expect(input.hasAttribute('readonly')).to.be.false;
|
||||
expect(_inputNode.hasAttribute('readonly')).to.be.false;
|
||||
});
|
||||
|
||||
it('sync rows down to the native textarea', async () => {
|
||||
const el = await fixture(`<lion-textarea rows="8">foo</lion-textarea>`);
|
||||
const { input } = getProtectedMembers(el);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
expect(el.rows).to.equal(8);
|
||||
expect(el.getAttribute('rows')).to.be.equal('8');
|
||||
// @ts-ignore
|
||||
expect(input.rows).to.equal(8);
|
||||
expect(input.getAttribute('rows')).to.be.equal('8');
|
||||
expect(_inputNode.rows).to.equal(8);
|
||||
expect(_inputNode.getAttribute('rows')).to.be.equal('8');
|
||||
});
|
||||
|
||||
it('sync readOnly to the native textarea', async () => {
|
||||
|
|
@ -74,8 +65,8 @@ describe('<lion-textarea>', () => {
|
|||
}
|
||||
|
||||
const el = await fixture(`<lion-textarea></lion-textarea>`);
|
||||
const { input } = getProtectedMembers(el);
|
||||
const computedStyle = window.getComputedStyle(input);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
const computedStyle = window.getComputedStyle(_inputNode);
|
||||
expect(computedStyle.resize).to.equal('none');
|
||||
});
|
||||
|
||||
|
|
@ -155,14 +146,14 @@ describe('<lion-textarea>', () => {
|
|||
|
||||
it('has an attribute that can be used to set the placeholder text of the textarea', async () => {
|
||||
const el = await fixture(`<lion-textarea placeholder="text"></lion-textarea>`);
|
||||
const { input } = getProtectedMembers(el);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
expect(el.getAttribute('placeholder')).to.equal('text');
|
||||
expect(input.getAttribute('placeholder')).to.equal('text');
|
||||
expect(_inputNode.getAttribute('placeholder')).to.equal('text');
|
||||
|
||||
el.placeholder = 'foo';
|
||||
await el.updateComplete;
|
||||
expect(el.getAttribute('placeholder')).to.equal('foo');
|
||||
expect(input.getAttribute('placeholder')).to.equal('foo');
|
||||
expect(_inputNode.getAttribute('placeholder')).to.equal('foo');
|
||||
});
|
||||
|
||||
it('fires resize textarea when a visibility change has been detected', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue