lion/packages/combobox/test/lion-combobox.test.js
2020-10-15 11:40:25 +02:00

1226 lines
52 KiB
JavaScript

import '@lion/listbox/lion-option.js';
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
import sinon from 'sinon';
import '../lion-combobox.js';
import { LionOptions } from '@lion/listbox/src/LionOptions.js';
import { browserDetection, LitElement } from '@lion/core';
import { Required } from '@lion/form-core';
/**
* @typedef {import('../src/LionCombobox.js').LionCombobox} LionCombobox
* @typedef {import('../types/SelectionDisplay').SelectionDisplay} SelectionDisplay
*/
/**
* @param {LionCombobox} el
* @param {string} value
*/
function mimicUserTyping(el, value) {
el._inputNode.dispatchEvent(new Event('focusin', { bubbles: true }));
// eslint-disable-next-line no-param-reassign
el._inputNode.value = value;
el._inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
el._overlayInvokerNode.dispatchEvent(new Event('keydown'));
}
/**
* @param {LionCombobox} el
* @param {string[]} values
*/
async function mimicUserTypingAdvanced(el, values) {
const inputNode = /** @type {HTMLInputElement & {selectionStart:number, selectionEnd:number}} */ (el._inputNode);
inputNode.dispatchEvent(new Event('focusin', { bubbles: true }));
let hasSelection = inputNode.selectionStart !== inputNode.selectionEnd;
for (const key of values) {
// eslint-disable-next-line no-await-in-loop, no-loop-func
await new Promise(resolve => {
setTimeout(() => {
if (key === 'Backspace') {
if (hasSelection) {
inputNode.value =
inputNode.value.slice(0, inputNode.selectionStart) +
inputNode.value.slice(inputNode.selectionEnd, inputNode.value.length);
} else {
inputNode.value = inputNode.value.slice(0, -1);
}
} else if (hasSelection) {
inputNode.value =
inputNode.value.slice(0, inputNode.selectionStart) +
key +
inputNode.value.slice(inputNode.selectionEnd, inputNode.value.length);
} else {
inputNode.value += key;
}
hasSelection = false;
inputNode.dispatchEvent(new KeyboardEvent('keydown', { key }));
el._inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
resolve();
});
});
}
}
/**
* @param {LionCombobox} el
*/
function getFilteredOptionValues(el) {
const options = el.formElements;
/**
* @param {{ style: { display: string; }; }} option
*/
const filtered = options.filter(option => option.getAttribute('aria-hidden') !== 'true');
/**
* @param {{ value: any; }} option
*/
return filtered.map(option => option.value);
}
/**
* @param {{ autocomplete?:'none'|'list'|'both', matchMode?:'begin'|'all' }} [config]
*/
async function fruitFixture({ autocomplete, matchMode } = {}) {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
if (autocomplete) {
el.autocomplete = autocomplete;
}
if (matchMode) {
el.matchMode = matchMode;
}
await el.updateComplete;
return [el, el.formElements];
}
describe('lion-combobox', () => {
describe('Options', () => {
describe('showAllOnEmpty', () => {
it('hides options when text in input node is cleared after typing something by default', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
const options = el.formElements;
const visibleOptions = () => options.filter(o => o.getAttribute('aria-hidden') !== 'true');
async function performChecks() {
mimicUserTyping(el, 'c');
await el.updateComplete;
expect(visibleOptions().length).to.equal(4);
mimicUserTyping(el, '');
await el.updateComplete;
expect(visibleOptions().length).to.equal(0);
}
// FIXME: autocomplete 'none' should have this behavior as well
// el.autocomplete = 'none';
// await performChecks();
el.autocomplete = 'list';
await performChecks();
el.autocomplete = 'inline';
await performChecks();
el.autocomplete = 'both';
await performChecks();
});
it('keeps showing options when text in input node is cleared after typing something', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="list" show-all-on-empty>
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
const options = el.formElements;
const visibleOptions = () => options.filter(o => o.getAttribute('aria-hidden') !== 'true');
async function performChecks() {
mimicUserTyping(el, 'c');
await el.updateComplete;
expect(visibleOptions().length).to.equal(4);
mimicUserTyping(el, '');
await el.updateComplete;
expect(visibleOptions().length).to.equal(options.length);
}
el.autocomplete = 'none';
await performChecks();
el.autocomplete = 'list';
await performChecks();
el.autocomplete = 'inline';
await performChecks();
el.autocomplete = 'both';
await performChecks();
});
});
});
describe('Structure', () => {
it('has a listbox node', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
</lion-combobox>
`));
expect(el._listboxNode).to.exist;
expect(el._listboxNode).to.be.instanceOf(LionOptions);
expect(el.querySelector('[role=listbox]')).to.equal(el._listboxNode);
});
it('has a textbox element', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
</lion-combobox>
`));
expect(el._comboboxNode).to.exist;
expect(el.querySelector('[role=combobox]')).to.equal(el._comboboxNode);
});
});
describe('Values', () => {
it('syncs modelValue with textbox', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" .modelValue="${'10'}">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
</lion-combobox>
`));
expect(el._inputNode.value).to.equal('10');
el.modelValue = '20';
await el.updateComplete;
expect(el._inputNode.value).to.equal('20');
});
it('sets modelValue to empty string if no option is selected', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" .modelValue="${'Artichoke'}">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
expect(el.modelValue).to.equal('Artichoke');
expect(el.formElements[0].checked).to.be.true;
el.checkedIndex = -1;
await el.updateComplete;
expect(el.modelValue).to.equal('');
expect(el.formElements[0].checked).to.be.false;
});
it('sets modelValue to empty array if no option is selected for multiple choice', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" multiple-choice .modelValue="${['Artichoke']}">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
expect(el.modelValue).to.eql(['Artichoke']);
expect(el.formElements[0].checked).to.be.true;
el.checkedIndex = [];
await el.updateComplete;
expect(el.modelValue).to.eql([]);
expect(el.formElements[0].checked).to.be.false;
});
});
describe('Listbox visibility', () => {
it('does not show listbox on focusin', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" multiple-choice>
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
expect(el.opened).to.equal(false);
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
await el.updateComplete;
expect(el.opened).to.equal(false);
});
it('shows listbox again after select and char keydown', async () => {
/**
* Scenario:
* [1] user focuses textbox: listbox hidden
* [2] user types char: listbox shows
* [3] user selects "Artichoke": listbox closes, textbox gets value "Artichoke" and textbox
* still has focus
* [4] user changes textbox value to "Artichoke": the listbox should show again
*/
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
const options = el.formElements;
expect(el.opened).to.equal(false);
// step [1]
el._inputNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
await el.updateComplete;
expect(el.opened).to.equal(false);
// step [2]
mimicUserTyping(el, 'c');
await el.updateComplete;
expect(el.opened).to.equal(true);
// step [3]
options[0].click();
await el.updateComplete;
expect(el.opened).to.equal(false);
expect(document.activeElement).to.equal(el._inputNode);
// step [4]
el._inputNode.value = '';
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'c' }));
await el.updateComplete;
expect(el.opened).to.equal(true);
});
it('hides (and clears) listbox on [Escape]', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
// open
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
mimicUserTyping(el, 'art');
await el.updateComplete;
expect(el.opened).to.equal(true);
expect(el._inputNode.value).to.equal('Artichoke');
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
expect(el.opened).to.equal(false);
expect(el._inputNode.value).to.equal('');
});
it('hides listbox on [Tab]', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
// open
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
mimicUserTyping(el, 'art');
await el.updateComplete;
expect(el.opened).to.equal(true);
expect(el._inputNode.value).to.equal('Artichoke');
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
expect(el.opened).to.equal(false);
expect(el._inputNode.value).to.equal('Artichoke');
});
it('clears checkedIndex on empty text', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
// open
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
mimicUserTyping(el, 'art');
await el.updateComplete;
expect(el.opened).to.equal(true);
expect(el._inputNode.value).to.equal('Artichoke');
expect(el.checkedIndex).to.equal(0);
el._inputNode.value = '';
mimicUserTyping(el, '');
el.opened = false;
await el.updateComplete;
expect(el.checkedIndex).to.equal(-1);
});
describe('Accessibility', () => {
it('sets "aria-posinset" and "aria-setsize" on visible entries', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" multiple-choice>
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
const options = el.formElements;
expect(el.opened).to.equal(false);
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
mimicUserTyping(el, 'art');
await el.updateComplete;
expect(el.opened).to.equal(true);
const visibleOptions = options.filter(o => o.style.display !== 'none');
visibleOptions.forEach((o, i) => {
expect(o.getAttribute('aria-posinset')).to.equal(`${i + 1}`);
expect(o.getAttribute('aria-setsize')).to.equal(`${visibleOptions.length}`);
});
/**
* @param {{ style: { display: string; }; }} o
*/
const hiddenOptions = options.filter(o => o.style.display === 'none');
/**
* @param {{ hasAttribute: (arg0: string) => any; }} o
*/
hiddenOptions.forEach(o => {
expect(o.hasAttribute('aria-posinset')).to.equal(false);
expect(o.hasAttribute('aria-setsize')).to.equal(false);
});
});
/**
* Note that we use aria-hidden instead of 'display:none' to allow for animations
* (like fade in/out)
*/
it('sets aria-hidden="true" on hidden entries', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
const options = el.formElements;
expect(el.opened).to.equal(false);
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
mimicUserTyping(el, 'art');
expect(el.opened).to.equal(true);
await el.updateComplete;
const visibleOptions = options.filter(o => o.style.display !== 'none');
visibleOptions.forEach(o => {
expect(o.hasAttribute('aria-hidden')).to.be.false;
});
const hiddenOptions = options.filter(o => o.style.display === 'none');
hiddenOptions.forEach(o => {
expect(o.getAttribute('aria-hidden')).to.equal('true');
});
});
it('works with validation', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" .validators=${[new Required()]}>
<lion-option checked .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
expect(el.checkedIndex).to.equal(0);
// Simulate backspace deleting the char at the end of the string
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
el._inputNode.dispatchEvent(new Event('input'));
const arr = el._inputNode.value.split('');
arr.splice(el._inputNode.value.length - 1, 1);
el._inputNode.value = arr.join('');
await el.updateComplete;
el.dispatchEvent(new Event('blur'));
expect(el.checkedIndex).to.equal(-1);
await el.feedbackComplete;
expect(el.hasFeedbackFor).to.include('error');
expect(el.showsFeedbackFor).to.include('error');
});
});
});
// Notice that the LionComboboxInvoker always needs to be used in conjunction with the
// LionCombobox, and therefore will be tested integrated,
describe('Invoker component integration', () => {
describe('Accessibility', () => {
it('sets role="combobox" on textbox wrapper/listbox sibling', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
</lion-combobox>
`));
expect(el._comboboxNode.getAttribute('role')).to.equal('combobox');
});
it('makes sure listbox node is not focusable', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
<lion-option .choiceValue="${'20'}">Item 2</lion-option>
</lion-combobox>
`));
expect(el._listboxNode.hasAttribute('tabindex')).to.be.false;
});
});
});
describe('Selection display', () => {
class MySelectionDisplay extends LitElement {
/**
* @param {import('lit-element').PropertyValues } changedProperties
*/
onComboboxElementUpdated(changedProperties) {
if (
changedProperties.has('modelValue') &&
// @ts-ignore
this.comboboxElement.multipleChoice
) {
// do smth..
}
}
}
const selectionDisplayTag = unsafeStatic(defineCE(MySelectionDisplay));
it('stores internal reference _selectionDisplayNode in LionCombobox', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<${selectionDisplayTag} slot="selection-display"></${selectionDisplayTag}>
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
expect(el._selectionDisplayNode).to.equal(el.querySelector('[slot=selection-display]'));
});
it('sets a reference to combobox element in _selectionDisplayNode', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<${selectionDisplayTag} slot="selection-display"></${selectionDisplayTag}>
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
// @ts-ignore allow protected members
expect(el._selectionDisplayNode.comboboxElement).to.equal(el);
});
it('calls "onComboboxElementUpdated(changedProperties)" on "updated" in _selectionDisplayNode', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<${selectionDisplayTag} slot="selection-display"></${selectionDisplayTag}>
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
const spy = sinon.spy(el._selectionDisplayNode, 'onComboboxElementUpdated');
el.requestUpdate('modelValue');
await el.updateComplete;
expect(spy).to.have.been.calledOnce;
});
// TODO: put those in distinct file if ./docs/lion-combobox-selection-display.js is accessible
// and exposable
describe.skip('Selected chips display', () => {
// it('displays chips next to textbox, ordered based on user selection', async () => {
// const el = /** @type {LionCombobox} */ (await fixture(html`
// <lion-combobox name="foo" multiple-choice>
// <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
// <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
// <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
// <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
// </lion-combobox>
// `));
// const options = el.formElements;
// options[2].checked = true; // Chicory
// options[0].checked = true; // Artichoke
// options[1].checked = true; // Chard
// const chips = Array.from(el._comboboxNode.querySelectorAll('.selection-chip'));
// expect(chips.map(elm => elm.textContent)).to.eql(['Chicory', 'Artichoke', 'Chard']);
// expect(el._comboboxNode.selectedElements).to.eql([options[2], options[0], options[1]]);
// });
// it('stages deletable chips on [Backspace]', async () => {
// const el = /** @type {LionCombobox} */ (await fixture(html`
// <lion-combobox name="foo" multiple-choice>
// <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
// <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
// <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
// </lion-combobox>
// `));
// const options = el.formElements;
// options[0].checked = true; // Artichoke
// options[1].checked = true; // Chard
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
// el._inputNode.blur();
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
// });
// it('deletes staged chip on [Backspace]', async () => {
// const el = /** @type {LionCombobox} */ (await fixture(html`
// <lion-combobox name="foo" multiple-choice>
// <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
// <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
// <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
// </lion-combobox>
// `));
// const options = el.formElements;
// options[0].checked = true; // Artichoke
// options[1].checked = true; // Chard
// expect(el._comboboxNode.selectedElements).to.eql([options[0], options[1]]);
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
// expect(el._comboboxNode.selectedElements).to.eql([options[0], options[1]]);
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
// expect(el._comboboxNode.selectedElements).to.eql([options[0]]);
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
// el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
// expect(el._comboboxNode.selectedElements).to.eql([]);
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
// el._inputNode.blur();
// expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
// });
});
});
describe('Autocompletion', () => {
it('has autocomplete "both" by default', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
expect(el.autocomplete).to.equal('both');
});
it('filters options when autocomplete is "both"', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="both">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(getFilteredOptionValues(el)).to.eql(['Artichoke', 'Chard', 'Chicory']);
});
it('completes textbox when autocomplete is "both"', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="both">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el._inputNode.value).to.equal('Chard');
expect(el._inputNode.selectionStart).to.equal(2);
expect(el._inputNode.selectionEnd).to.equal(el._inputNode.value.length);
// We don't autocomplete when characters are removed
mimicUserTyping(el, 'c'); // The user pressed backspace (number of chars decreased)
expect(el._inputNode.value).to.equal('c');
expect(el._inputNode.selectionStart).to.equal(el._inputNode.value.length);
});
it('filters options when autocomplete is "list"', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="list">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(getFilteredOptionValues(el)).to.eql(['Artichoke', 'Chard', 'Chicory']);
expect(el._inputNode.value).to.equal('ch');
});
it('does not filter options when autocomplete is "none"', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="none">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(getFilteredOptionValues(el)).to.eql([
'Artichoke',
'Chard',
'Chicory',
'Victoria Plum',
]);
});
it('does not filter options when autocomplete is "inline"', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="inline">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(getFilteredOptionValues(el)).to.eql([
'Artichoke',
'Chard',
'Chicory',
'Victoria Plum',
]);
});
it('resets "checkedIndex" when going from matched to unmatched textbox value', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="inline">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el.checkedIndex).to.equal(1);
mimicUserTyping(el, 'cho');
await el.updateComplete;
expect(el.checkedIndex).to.equal(-1);
// Works for autocomplete 'both' as well.
const el2 = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="both">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el2, 'ch');
await el2.updateComplete;
expect(el2.checkedIndex).to.equal(1);
// Also works when 'diminishing amount of chars'
mimicUserTyping(el2, 'x');
await el2.updateComplete;
expect(el2.checkedIndex).to.equal(-1);
});
it('completes chars inside textbox', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="inline">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el._inputNode.value).to.equal('Chard');
expect(el._inputNode.selectionStart).to.equal('ch'.length);
expect(el._inputNode.selectionEnd).to.equal('Chard'.length);
await mimicUserTypingAdvanced(el, ['i', 'c']);
await el.updateComplete;
expect(el._inputNode.value).to.equal('Chicory');
expect(el._inputNode.selectionStart).to.equal('chic'.length);
expect(el._inputNode.selectionEnd).to.equal('Chicory'.length);
// Diminishing chars, but autocompleting
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el._inputNode.value).to.equal('ch');
expect(el._inputNode.selectionStart).to.equal('ch'.length);
expect(el._inputNode.selectionEnd).to.equal('ch'.length);
});
it('does autocompletion when adding chars', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="inline">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch'); // ch
await el.updateComplete; // Ch[ard]
expect(el.activeIndex).to.equal(1);
expect(el.checkedIndex).to.equal(1);
await mimicUserTypingAdvanced(el, ['i', 'c']); // Chic
await el.updateComplete; // Chic[ory]
expect(el.activeIndex).to.equal(2);
expect(el.checkedIndex).to.equal(2);
await mimicUserTypingAdvanced(el, ['Backspace', 'Backspace', 'Backspace', 'Backspace', 'h']); // Ch
await el.updateComplete; // Ch[ard]
expect(el.activeIndex).to.equal(1);
expect(el.checkedIndex).to.equal(1);
});
it('does autocompletion when changing the word', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="inline">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el.activeIndex).to.equal(1);
expect(el.checkedIndex).to.equal(1);
await mimicUserTypingAdvanced(el, 'ic'.split(''));
await el.updateComplete;
expect(el.activeIndex).to.equal(2);
expect(el.checkedIndex).to.equal(2);
// Diminishing chars, but autocompleting
mimicUserTyping(el, 'a');
await el.updateComplete;
expect(el.activeIndex).to.equal(0);
expect(el.checkedIndex).to.equal(0);
});
it('computation of "user intends autofill" works correctly afer autofill', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="inline">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el._inputNode.value).to.equal('Chard');
expect(el._inputNode.selectionStart).to.equal('ch'.length);
expect(el._inputNode.selectionEnd).to.equal('Chard'.length);
// Autocompletion happened. When we go backwards ('Char'), we should not
// autocomplete to 'Chard' anymore.
mimicUserTyping(el, 'Char');
await el.updateComplete;
expect(el._inputNode.value).to.equal('Char'); // so not 'Chard'
expect(el._inputNode.selectionStart).to.equal('Char'.length);
expect(el._inputNode.selectionEnd).to.equal('Char'.length);
});
it('highlights matching options', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" match-mode="all">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
const options = el.formElements;
mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch');
await el.updateComplete;
expect(options[0]).lightDom.to.equal(`<span aria-label="Artichoke">Arti<b>ch</b>oke</span>`);
expect(options[1]).lightDom.to.equal(`<span aria-label="Chard"><b>Ch</b>ard</span>`);
expect(options[2]).lightDom.to.equal(`<span aria-label="Chicory"><b>Ch</b>icory</span>`);
expect(options[3]).lightDom.to.equal(`Victoria Plum`);
mimicUserTyping(/** @type {LionCombobox} */ (el), 'D');
await el.updateComplete;
expect(options[0]).lightDom.to.equal(`Artichoke`);
expect(options[1]).lightDom.to.equal(`<span aria-label="Chard">Char<b>d</b></span>`);
expect(options[2]).lightDom.to.equal(`Chicory`);
expect(options[3]).lightDom.to.equal(`Victoria Plum`);
});
describe('Active index behavior', () => {
it('sets the active index to the closest match on open by default', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
expect(el.activeIndex).to.equal(1);
});
it('changes whether active index is set to the closest match automatically depending on autocomplete', async () => {
/**
* Automatic selection (setting activeIndex to closest matching option) in lion is set for inline & both autocomplete,
* because it is unavoidable there
* For list & none autocomplete, it is turned off and manual selection is required.
* TODO: Make this configurable for list & none autocomplete?
*/
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="none">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
// https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
// Example 1. List Autocomplete with Manual Selection:
// does not set active at all until user selects
el.autocomplete = 'none';
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(el.activeIndex).to.equal(-1);
expect(el.opened).to.be.true;
// https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
// Example 2. List Autocomplete with Automatic Selection:
// does not set active at all until user selects
el.autocomplete = 'list';
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(el.activeIndex).to.equal(-1);
expect(el.opened).to.be.true;
// https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
// Example 3. List with Inline Autocomplete (mostly, but with aria-autocomplete="inline")
el.autocomplete = 'inline';
mimicUserTyping(/** @type {LionCombobox} */ (el), '');
await el.updateComplete;
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
await el.updateComplete;
// TODO: enable this, so it does not open listbox and is different from [autocomplete=both]?
// expect(el.opened).to.be.false;
expect(el.activeIndex).to.equal(1);
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
await el.updateComplete;
expect(el.activeIndex).to.equal(-1);
expect(el.opened).to.be.false;
// https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
// Example 3. List with Inline Autocomplete
el.autocomplete = 'both';
mimicUserTyping(/** @type {LionCombobox} */ (el), '');
await el.updateComplete;
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(el.activeIndex).to.equal(1);
expect(el.opened).to.be.false;
});
it('sets the active index to the closest match on autocomplete', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="both">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
expect(el.activeIndex).to.equal(1);
mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch');
await el.updateComplete;
mimicUserTyping(/** @type {LionCombobox} */ (el), 'chi');
// Chard no longer matches, so should switch active to Chicory
await el.updateComplete;
expect(el.activeIndex).to.equal(2);
// select artichoke
mimicUserTyping(/** @type {LionCombobox} */ (el), 'artichoke');
await el.updateComplete;
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
mimicUserTyping(/** @type {LionCombobox} */ (el), '');
await el.updateComplete;
// change selection, active index should update to closest match
mimicUserTyping(/** @type {LionCombobox} */ (el), 'vic');
await el.updateComplete;
expect(el.activeIndex).to.equal(3);
});
it('supports clearing by [Escape] key and resets active state on all options', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
// Select something
mimicUserTyping(/** @type {LionCombobox} */ (el), 'cha');
await el.updateComplete;
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
expect(el.activeIndex).to.equal(1);
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
await el.updateComplete;
expect(el._inputNode.textContent).to.equal('');
el.formElements.forEach(option => expect(option.active).to.be.false);
// change selection, active index should update to closest match
mimicUserTyping(/** @type {LionCombobox} */ (el), 'vic');
await el.updateComplete;
expect(el.activeIndex).to.equal(3);
});
});
describe('Accessibility', () => {
it('synchronizes autocomplete option to textbox', async () => {
let el;
[el] = await fruitFixture({ autocomplete: 'both' });
expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('both');
[el] = await fruitFixture({ autocomplete: 'list' });
expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('list');
[el] = await fruitFixture({ autocomplete: 'none' });
expect(el._inputNode.getAttribute('aria-autocomplete')).to.equal('none');
});
it('updates aria-activedescendant on textbox node', async () => {
let el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="none">
<lion-option .choiceValue="${'Artichoke'}" id="artichoke-option">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}" id="chard-option">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(null);
expect(el.formElements[1].active).to.equal(false);
mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch');
await el.updateComplete;
expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(null);
el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(
'artichoke-option',
);
expect(el.formElements[1].active).to.equal(false);
el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" autocomplete="both" match-mode="begin">
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch');
await el.updateComplete;
expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(
el.formElements[1].id,
);
expect(el.formElements[1].active).to.equal(true);
el.autocomplete = 'list';
mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch');
await el.updateComplete;
expect(el._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(
el.formElements[1].id,
);
expect(el.formElements[1].active).to.equal(true);
});
it('adds aria-label to highlighted options', async () => {
const [el, options] = await fruitFixture({ autocomplete: 'both', matchMode: 'all' });
mimicUserTyping(/** @type {LionCombobox} */ (el), 'choke');
await el.updateComplete;
const labelledElement = options[0].querySelector('span[aria-label="Artichoke"]');
expect(labelledElement).to.not.be.null;
expect(labelledElement.innerText).to.equal('Artichoke');
});
});
});
describe('Accessibility', () => {
describe('Aria versions', () => {
it('[role=combobox] wraps input node in v1.1', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" ._ariaVersion="${'1.1'}">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
expect(el._comboboxNode.contains(el._inputNode)).to.be.true;
});
it('has one input node with [role=combobox] in v1.0', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" ._ariaVersion="${'1.0'}">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
expect(el._comboboxNode).to.equal(el._inputNode);
});
it('autodetects aria version and sets it to 1.1 on Chromium browsers', async () => {
const browserDetectionIsChromiumOriginal = browserDetection.isChromium;
browserDetection.isChromium = true;
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
expect(el._ariaVersion).to.equal('1.1');
browserDetection.isChromium = false;
const el2 = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo">
<lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
</lion-combobox>
`));
expect(el2._ariaVersion).to.equal('1.0');
// restore...
browserDetection.isChromium = browserDetectionIsChromiumOriginal;
});
});
});
describe('Multiple Choice', () => {
// TODO: possibly later share test with select-rich if it officially supports multipleChoice
it('does not close listbox on click/enter/space', async () => {
const el = /** @type {LionCombobox} */ (await fixture(html`
<lion-combobox name="foo" multiple-choice>
<lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
<lion-option .choiceValue="${'Chard'}">Chard</lion-option>
<lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
<lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
</lion-combobox>
`));
// activate opened listbox
el._comboboxNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
mimicUserTyping(el, 'ch');
await el.updateComplete;
expect(el.opened).to.equal(true);
const visibleOptions = el.formElements.filter(o => o.style.display !== 'none');
visibleOptions[0].click();
expect(el.opened).to.equal(true);
// visibleOptions[1].dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
// expect(el.opened).to.equal(true);
// visibleOptions[2].dispatchEvent(new KeyboardEvent('keyup', { key: ' ' }));
// expect(el.opened).to.equal(true);
});
});
describe('Match Mode', () => {
it('has a default value of "all"', async () => {
const [el] = await fruitFixture();
expect(el.matchMode).to.equal('all');
});
it('will suggest partial matches (in the middle of the word) when set to "all"', async () => {
const [el] = await fruitFixture();
mimicUserTyping(/** @type {LionCombobox} */ (el), 'c');
await el.updateComplete;
expect(getFilteredOptionValues(/** @type {LionCombobox} */ (el))).to.eql([
'Artichoke',
'Chard',
'Chicory',
'Victoria Plum',
]);
});
it('will only suggest beginning matches when set to "begin"', async () => {
const [el] = await fruitFixture({ matchMode: 'begin' });
mimicUserTyping(/** @type {LionCombobox} */ (el), 'ch');
await el.updateComplete;
expect(getFilteredOptionValues(/** @type {LionCombobox} */ (el))).to.eql([
'Chard',
'Chicory',
]);
});
describe('Subclassers', () => {
it('allows for custom matching functions', async () => {
const [el] = await fruitFixture();
/**
* @param {{ value: any; }} option
* @param {any} curValue
*/
function onlyExactMatches(option, curValue) {
return option.value === curValue;
}
el.matchCondition = onlyExactMatches;
mimicUserTyping(/** @type {LionCombobox} */ (el), 'Chicory');
await el.updateComplete;
expect(getFilteredOptionValues(/** @type {LionCombobox} */ (el))).to.eql(['Chicory']);
mimicUserTyping(/** @type {LionCombobox} */ (el), 'Chicor');
await el.updateComplete;
expect(getFilteredOptionValues(/** @type {LionCombobox} */ (el))).to.eql([]);
});
});
});
});