@lion/ui 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +222 -0
  2. package/components/accordion/src/LionAccordion.js +2 -2
  3. package/components/accordion/test/lion-accordion.test.js +31 -0
  4. package/components/combobox/src/LionCombobox.js +179 -17
  5. package/components/combobox/src/validators.js +18 -0
  6. package/components/combobox/test/lion-combobox.test.js +290 -172
  7. package/components/combobox/test/validators.test.js +62 -0
  8. package/components/combobox/test-helpers/combobox-helpers.js +125 -0
  9. package/components/combobox/translations/bg.js +3 -0
  10. package/components/combobox/translations/cs.js +3 -0
  11. package/components/combobox/translations/de.js +3 -0
  12. package/components/combobox/translations/en.js +3 -0
  13. package/components/combobox/translations/es.js +3 -0
  14. package/components/combobox/translations/fr.js +3 -0
  15. package/components/combobox/translations/hu.js +3 -0
  16. package/components/combobox/translations/it.js +3 -0
  17. package/components/combobox/translations/nl.js +3 -0
  18. package/components/combobox/translations/pl.js +3 -0
  19. package/components/combobox/translations/ro.js +3 -0
  20. package/components/combobox/translations/ru.js +3 -0
  21. package/components/combobox/translations/sk.js +3 -0
  22. package/components/combobox/translations/uk.js +3 -0
  23. package/components/combobox/translations/zh.js +3 -0
  24. package/components/form-core/types/choice-group/ChoiceGroupMixinTypes.ts +4 -3
  25. package/components/input-datepicker/src/LionInputDatepicker.js +9 -1
  26. package/components/input-iban/translations/de.js +1 -1
  27. package/components/listbox/src/ListboxMixin.js +6 -0
  28. package/components/listbox/test-suites/ListboxMixin.suite.js +13 -7
  29. package/components/overlays/src/OverlayController.js +2 -1
  30. package/components/overlays/test/OverlayController.test.js +1 -1
  31. package/components/switch/src/LionSwitch.js +0 -3
  32. package/components/switch/src/LionSwitchButton.js +14 -1
  33. package/components/switch/test/lion-switch-button.test.js +15 -1
  34. package/components/switch/test/lion-switch.test.js +21 -6
  35. package/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.js +11 -8
  36. package/components/validate-messages/translations/bg.js +4 -0
  37. package/components/validate-messages/translations/cs.js +2 -0
  38. package/components/validate-messages/translations/de.js +4 -0
  39. package/components/validate-messages/translations/en.js +2 -0
  40. package/components/validate-messages/translations/es.js +4 -0
  41. package/components/validate-messages/translations/fr.js +4 -0
  42. package/components/validate-messages/translations/hu.js +2 -0
  43. package/components/validate-messages/translations/it.js +4 -0
  44. package/components/validate-messages/translations/nl.js +4 -0
  45. package/components/validate-messages/translations/pl.js +2 -0
  46. package/components/validate-messages/translations/ro.js +4 -0
  47. package/components/validate-messages/translations/ru.js +4 -0
  48. package/components/validate-messages/translations/sk.js +2 -0
  49. package/components/validate-messages/translations/uk.js +4 -0
  50. package/components/validate-messages/translations/zh.js +2 -0
  51. package/custom-elements.json +1850 -115
  52. package/dist-types/components/combobox/src/LionCombobox.d.ts +37 -13
  53. package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
  54. package/dist-types/components/combobox/src/validators.d.ts +13 -0
  55. package/dist-types/components/combobox/src/validators.d.ts.map +1 -0
  56. package/dist-types/components/combobox/test/lion-combobox.test.d.ts +0 -2
  57. package/dist-types/components/combobox/test/lion-combobox.test.d.ts.map +1 -1
  58. package/dist-types/components/combobox/test/validators.test.d.ts +2 -0
  59. package/dist-types/components/combobox/test/validators.test.d.ts.map +1 -0
  60. package/dist-types/components/combobox/test-helpers/combobox-helpers.d.ts +41 -0
  61. package/dist-types/components/combobox/test-helpers/combobox-helpers.d.ts.map +1 -0
  62. package/dist-types/components/combobox/translations/bg.d.ts +5 -0
  63. package/dist-types/components/combobox/translations/bg.d.ts.map +1 -0
  64. package/dist-types/components/combobox/translations/cs.d.ts +5 -0
  65. package/dist-types/components/combobox/translations/cs.d.ts.map +1 -0
  66. package/dist-types/components/combobox/translations/de.d.ts +5 -0
  67. package/dist-types/components/combobox/translations/de.d.ts.map +1 -0
  68. package/dist-types/components/combobox/translations/en.d.ts +5 -0
  69. package/dist-types/components/combobox/translations/en.d.ts.map +1 -0
  70. package/dist-types/components/combobox/translations/es.d.ts +5 -0
  71. package/dist-types/components/combobox/translations/es.d.ts.map +1 -0
  72. package/dist-types/components/combobox/translations/fr.d.ts +5 -0
  73. package/dist-types/components/combobox/translations/fr.d.ts.map +1 -0
  74. package/dist-types/components/combobox/translations/hu.d.ts +5 -0
  75. package/dist-types/components/combobox/translations/hu.d.ts.map +1 -0
  76. package/dist-types/components/combobox/translations/it.d.ts +5 -0
  77. package/dist-types/components/combobox/translations/it.d.ts.map +1 -0
  78. package/dist-types/components/combobox/translations/nl.d.ts +5 -0
  79. package/dist-types/components/combobox/translations/nl.d.ts.map +1 -0
  80. package/dist-types/components/combobox/translations/pl.d.ts +5 -0
  81. package/dist-types/components/combobox/translations/pl.d.ts.map +1 -0
  82. package/dist-types/components/combobox/translations/ro.d.ts +5 -0
  83. package/dist-types/components/combobox/translations/ro.d.ts.map +1 -0
  84. package/dist-types/components/combobox/translations/ru.d.ts +5 -0
  85. package/dist-types/components/combobox/translations/ru.d.ts.map +1 -0
  86. package/dist-types/components/combobox/translations/sk.d.ts +5 -0
  87. package/dist-types/components/combobox/translations/sk.d.ts.map +1 -0
  88. package/dist-types/components/combobox/translations/uk.d.ts +5 -0
  89. package/dist-types/components/combobox/translations/uk.d.ts.map +1 -0
  90. package/dist-types/components/combobox/translations/zh.d.ts +5 -0
  91. package/dist-types/components/combobox/translations/zh.d.ts.map +1 -0
  92. package/dist-types/components/form-core/types/choice-group/ChoiceGroupMixinTypes.d.ts +4 -3
  93. package/dist-types/components/form-core/types/choice-group/ChoiceGroupMixinTypes.d.ts.map +1 -1
  94. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +4 -0
  95. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  96. package/dist-types/components/listbox/src/ListboxMixin.d.ts.map +1 -1
  97. package/dist-types/components/listbox/test-suites/ListboxMixin.suite.d.ts.map +1 -1
  98. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  99. package/dist-types/components/switch/src/LionSwitch.d.ts +0 -1
  100. package/dist-types/components/switch/src/LionSwitch.d.ts.map +1 -1
  101. package/dist-types/components/switch/src/LionSwitchButton.d.ts +3 -0
  102. package/dist-types/components/switch/src/LionSwitchButton.d.ts.map +1 -1
  103. package/dist-types/components/switch/test/lion-switch.test.d.ts +1 -1
  104. package/dist-types/components/switch/test/lion-switch.test.d.ts.map +1 -1
  105. package/dist-types/components/validate-messages/src/loadDefaultFeedbackMessagesNoSideEffects.d.ts.map +1 -1
  106. package/dist-types/components/validate-messages/translations/bg-BG.d.ts +2 -0
  107. package/dist-types/components/validate-messages/translations/bg.d.ts +3 -0
  108. package/dist-types/components/validate-messages/translations/cs-CZ.d.ts +2 -0
  109. package/dist-types/components/validate-messages/translations/cs.d.ts +3 -0
  110. package/dist-types/components/validate-messages/translations/de-DE.d.ts +2 -0
  111. package/dist-types/components/validate-messages/translations/de.d.ts +3 -0
  112. package/dist-types/components/validate-messages/translations/en-AU.d.ts +2 -0
  113. package/dist-types/components/validate-messages/translations/en-GB.d.ts +2 -0
  114. package/dist-types/components/validate-messages/translations/en-PH.d.ts +2 -0
  115. package/dist-types/components/validate-messages/translations/en-US.d.ts +2 -0
  116. package/dist-types/components/validate-messages/translations/en.d.ts +3 -0
  117. package/dist-types/components/validate-messages/translations/es-ES.d.ts +2 -0
  118. package/dist-types/components/validate-messages/translations/es.d.ts +3 -0
  119. package/dist-types/components/validate-messages/translations/fr-BE.d.ts +2 -0
  120. package/dist-types/components/validate-messages/translations/fr-FR.d.ts +2 -0
  121. package/dist-types/components/validate-messages/translations/fr.d.ts +3 -0
  122. package/dist-types/components/validate-messages/translations/hu-HU.d.ts +2 -0
  123. package/dist-types/components/validate-messages/translations/hu.d.ts +3 -0
  124. package/dist-types/components/validate-messages/translations/it-IT.d.ts +2 -0
  125. package/dist-types/components/validate-messages/translations/it.d.ts +3 -0
  126. package/dist-types/components/validate-messages/translations/nl-BE.d.ts +2 -0
  127. package/dist-types/components/validate-messages/translations/nl-NL.d.ts +2 -0
  128. package/dist-types/components/validate-messages/translations/nl.d.ts +3 -0
  129. package/dist-types/components/validate-messages/translations/pl-PL.d.ts +2 -0
  130. package/dist-types/components/validate-messages/translations/pl.d.ts +3 -0
  131. package/dist-types/components/validate-messages/translations/ro-RO.d.ts +2 -0
  132. package/dist-types/components/validate-messages/translations/ro.d.ts +3 -0
  133. package/dist-types/components/validate-messages/translations/ru-RU.d.ts +2 -0
  134. package/dist-types/components/validate-messages/translations/ru.d.ts +3 -0
  135. package/dist-types/components/validate-messages/translations/sk-SK.d.ts +2 -0
  136. package/dist-types/components/validate-messages/translations/sk.d.ts +3 -0
  137. package/dist-types/components/validate-messages/translations/uk-UA.d.ts +2 -0
  138. package/dist-types/components/validate-messages/translations/uk.d.ts +3 -0
  139. package/dist-types/components/validate-messages/translations/zh.d.ts +3 -0
  140. package/dist-types/exports/combobox-test-helpers.d.ts +2 -0
  141. package/dist-types/exports/combobox-test-helpers.d.ts.map +1 -0
  142. package/dist-types/exports/combobox.d.ts +1 -0
  143. package/dist-types/tsconfig.tsbuildinfo +1 -1
  144. package/docs/components/checkbox-group/overview.md +2 -2
  145. package/docs/components/collapsible/overview.md +1 -1
  146. package/docs/components/combobox/overview.md +12 -2
  147. package/docs/components/combobox/use-cases.md +36 -1
  148. package/docs/components/dialog/overview.md +1 -1
  149. package/docs/components/fieldset/overview.md +5 -5
  150. package/docs/components/form/overview.md +3 -3
  151. package/docs/components/icon/use-cases.md +1 -1
  152. package/docs/components/input/overview.md +1 -1
  153. package/docs/components/input-amount/overview.md +3 -3
  154. package/docs/components/input-date/overview.md +3 -3
  155. package/docs/components/input-datepicker/overview.md +5 -5
  156. package/docs/components/input-email/overview.md +2 -2
  157. package/docs/components/input-iban/overview.md +2 -2
  158. package/docs/components/input-range/overview.md +2 -2
  159. package/docs/components/input-stepper/overview.md +1 -1
  160. package/docs/components/input-tel/overview.md +1 -1
  161. package/docs/components/input-tel/use-cases.md +6 -6
  162. package/docs/components/input-tel-dropdown/overview.md +1 -1
  163. package/docs/components/radio-group/overview.md +3 -3
  164. package/docs/components/select-rich/use-cases.md +1 -1
  165. package/docs/components/tabs/examples.md +1 -1
  166. package/docs/fundamentals/index.md +4 -4
  167. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  168. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  169. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  170. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  171. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  172. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  173. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  174. package/docs/fundamentals/systems/core/overview.md +1 -1
  175. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  176. package/docs/fundamentals/systems/form/model-value.md +1 -1
  177. package/docs/fundamentals/systems/form/overview.md +28 -28
  178. package/docs/fundamentals/systems/form/validate.md +1 -1
  179. package/docs/fundamentals/systems/localize/overview.md +4 -4
  180. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  181. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  182. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  183. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  184. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  185. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  186. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  187. package/docs/guides/index.md +8 -8
  188. package/docs/guides/principles/index.md +5 -5
  189. package/docs/guides/principles/styling.md +1 -1
  190. package/exports/combobox-test-helpers.js +1 -0
  191. package/exports/combobox.js +1 -0
  192. package/package.json +2 -1
@@ -1,141 +1,25 @@
1
- import '@lion/ui/define/lion-combobox.js';
2
- import { LitElement } from 'lit';
1
+ import {
2
+ getComboboxMembers,
3
+ getFilteredOptionValues,
4
+ mimicKeyPress,
5
+ mimicUserTyping,
6
+ mimicUserTypingAdvanced,
7
+ } from '@lion/ui/combobox-test-helpers.js';
8
+ import { LionCombobox } from '@lion/ui/combobox.js';
3
9
  import { browserDetection } from '@lion/ui/core.js';
4
- import { Required } from '@lion/ui/form-core.js';
10
+ import '@lion/ui/define/lion-combobox.js';
5
11
  import '@lion/ui/define/lion-listbox.js';
6
12
  import '@lion/ui/define/lion-option.js';
7
- import { getListboxMembers } from '@lion/ui/listbox-test-helpers.js';
13
+ import { Required, Unparseable } from '@lion/ui/form-core.js';
8
14
  import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
15
+ import { LitElement } from 'lit';
9
16
  import sinon from 'sinon';
10
- import { LionCombobox } from '@lion/ui/combobox.js';
11
17
 
12
18
  /**
13
19
  * @typedef {import('../types/SelectionDisplay.js').SelectionDisplay} SelectionDisplay
14
- * @typedef {import('../../listbox/types/ListboxMixinTypes.js').ListboxHost} ListboxHost
15
- * @typedef {import('../../form-core/types/FormControlMixinTypes.js').FormControlHost} FormControlHost
16
20
  * @typedef {import('@lion/ui/listbox.js').LionOption} LionOption
17
21
  */
18
22
 
19
- /**
20
- * @param { LionCombobox } el
21
- */
22
- function getComboboxMembers(el) {
23
- const obj = getListboxMembers(el);
24
- return {
25
- ...obj,
26
- ...{
27
- // @ts-ignore [allow-protected] in test
28
- _invokerNode: el._invokerNode,
29
- // @ts-ignore [allow-protected] in test
30
- _overlayCtrl: el._overlayCtrl,
31
- // @ts-ignore [allow-protected] in test
32
- _comboboxNode: el._comboboxNode,
33
- // @ts-ignore [allow-protected] in test
34
- _inputNode: el._inputNode,
35
- // @ts-ignore [allow-protected] in test
36
- _listboxNode: el._listboxNode,
37
- // @ts-ignore [allow-protected] in test
38
- _selectionDisplayNode: el._selectionDisplayNode,
39
- // @ts-ignore [allow-protected] in test
40
- _activeDescendantOwnerNode: el._activeDescendantOwnerNode,
41
- // @ts-ignore [allow-protected] in test
42
- _ariaVersion: el._ariaVersion,
43
- },
44
- };
45
- }
46
-
47
- /**
48
- * @param {LionCombobox} el
49
- * @param {string} value
50
- */
51
- // TODO: add keys that actually make sense...
52
- function mimicUserTyping(el, value) {
53
- const { _inputNode } = getComboboxMembers(el);
54
- _inputNode.dispatchEvent(new Event('focusin', { bubbles: true }));
55
- // eslint-disable-next-line no-param-reassign
56
- _inputNode.value = value;
57
- _inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
58
- _inputNode.dispatchEvent(new KeyboardEvent('keyup', { key: value }));
59
- _inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: value }));
60
- }
61
-
62
- /**
63
- * @param {HTMLElement} el
64
- * @param {string} key
65
- */
66
- function mimicKeyPress(el, key) {
67
- el.dispatchEvent(new KeyboardEvent('keydown', { key }));
68
- el.dispatchEvent(new KeyboardEvent('keyup', { key }));
69
- }
70
-
71
- /**
72
- * @param {LionCombobox} el
73
- * @param {string[]} values
74
- */
75
- async function mimicUserTypingAdvanced(el, values) {
76
- const { _inputNode } = getComboboxMembers(el);
77
- _inputNode.dispatchEvent(new Event('focusin', { bubbles: true }));
78
-
79
- for (const key of values) {
80
- // eslint-disable-next-line no-await-in-loop, no-loop-func
81
- await new Promise(resolve => {
82
- const hasSelection = _inputNode.selectionStart !== _inputNode.selectionEnd;
83
-
84
- if (key === 'Backspace') {
85
- if (hasSelection) {
86
- _inputNode.value =
87
- _inputNode.value.slice(
88
- 0,
89
- _inputNode.selectionStart ? _inputNode.selectionStart : undefined,
90
- ) +
91
- _inputNode.value.slice(
92
- _inputNode.selectionEnd ? _inputNode.selectionEnd : undefined,
93
- _inputNode.value.length,
94
- );
95
- } else {
96
- _inputNode.value = _inputNode.value.slice(0, -1);
97
- }
98
- } else if (hasSelection) {
99
- _inputNode.value =
100
- _inputNode.value.slice(
101
- 0,
102
- _inputNode.selectionStart ? _inputNode.selectionStart : undefined,
103
- ) +
104
- key +
105
- _inputNode.value.slice(
106
- _inputNode.selectionEnd ? _inputNode.selectionEnd : undefined,
107
- _inputNode.value.length,
108
- );
109
- } else {
110
- _inputNode.value += key;
111
- }
112
-
113
- mimicKeyPress(_inputNode, key);
114
- _inputNode.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
115
-
116
- el.updateComplete.then(() => {
117
- // @ts-ignore
118
- resolve();
119
- });
120
- });
121
- }
122
- }
123
-
124
- /**
125
- * @param {LionCombobox} el
126
- */
127
- function getFilteredOptionValues(el) {
128
- const options = el.formElements;
129
- /**
130
- * @param {{ style: { display: string; }; }} option
131
- */
132
- const filtered = options.filter(option => option.getAttribute('aria-hidden') !== 'true');
133
- /**
134
- * @param {{ value: any; }} option
135
- */
136
- return filtered.map(option => option.value);
137
- }
138
-
139
23
  /**
140
24
  * @param {{ autocomplete?:'none'|'list'|'both', matchMode?:'begin'|'all' }} config
141
25
  */
@@ -387,6 +271,22 @@ describe('lion-combobox', () => {
387
271
  expect(_comboboxNode).to.exist;
388
272
  expect(el.querySelector('[role=combobox]')).to.equal(_comboboxNode);
389
273
  });
274
+
275
+ it('has validator "MatchesOption" applied by default', async () => {
276
+ const el = /** @type {LionCombobox} */ (
277
+ await fixture(html`
278
+ <lion-combobox name="foo">
279
+ <lion-option .choiceValue="${'10'}" checked>Item 1</lion-option>
280
+ <lion-option .choiceValue="${'20'}">Item 2</lion-option>
281
+ </lion-combobox>
282
+ `)
283
+ );
284
+ mimicUserTyping(el, '30');
285
+ await el.updateComplete;
286
+ expect(el.hasFeedbackFor).to.include('error');
287
+ expect(el.validationStates).to.have.property('error');
288
+ expect(el.validationStates.error).to.have.property('MatchesOption');
289
+ });
390
290
  });
391
291
 
392
292
  describe('Values', () => {
@@ -408,7 +308,7 @@ describe('lion-combobox', () => {
408
308
  expect(_inputNode.value).to.equal('20');
409
309
  });
410
310
 
411
- it('sets modelValue to empty string if no option is selected', async () => {
311
+ it('sets modelValue to Unparseable if no option is selected', async () => {
412
312
  const el = /** @type {LionCombobox} */ (
413
313
  await fixture(html`
414
314
  <lion-combobox name="foo" .modelValue="${'Artichoke'}">
@@ -419,12 +319,59 @@ describe('lion-combobox', () => {
419
319
  </lion-combobox>
420
320
  `)
421
321
  );
322
+ const { _inputNode } = getComboboxMembers(el);
422
323
 
423
324
  expect(el.modelValue).to.equal('Artichoke');
424
325
  expect(el.formElements[0].checked).to.be.true;
425
- el.checkedIndex = -1;
326
+ el.setCheckedIndex(-1);
327
+ el.__shouldAutocompleteNextUpdate = true;
426
328
  await el.updateComplete;
427
- expect(el.modelValue).to.equal('');
329
+ expect(el.modelValue instanceof Unparseable).to.be.true;
330
+ expect(el.modelValue.viewValue).to.equal('Artichoke');
331
+ expect(el.formElements[0].checked).to.be.false;
332
+
333
+ el.setCheckedIndex(-1);
334
+ _inputNode.value = 'Foo';
335
+ el.__shouldAutocompleteNextUpdate = true;
336
+ await el.updateComplete;
337
+ expect(el.modelValue instanceof Unparseable).to.be.true;
338
+ expect(el.modelValue.viewValue).to.equal('Foo');
339
+ expect(el.formElements[0].checked).to.be.false;
340
+
341
+ el.setCheckedIndex(0);
342
+ el.__shouldAutocompleteNextUpdate = true;
343
+ await el.updateComplete;
344
+ expect(el.modelValue instanceof Unparseable).to.be.false;
345
+ expect(el.modelValue).to.equal('Artichoke');
346
+ expect(el.formElements[0].checked).to.be.true;
347
+ });
348
+
349
+ it('sets modelValue to _inputNode.value if no option is selected when requireOptionMatch is false', async () => {
350
+ const el = /** @type {LionCombobox} */ (
351
+ await fixture(html`
352
+ <lion-combobox name="foo" .modelValue="${'Artichoke'}" .requireOptionMatch="${false}">
353
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
354
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
355
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
356
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
357
+ </lion-combobox>
358
+ `)
359
+ );
360
+ const { _inputNode } = getComboboxMembers(el);
361
+
362
+ expect(el.modelValue).to.equal('Artichoke');
363
+ expect(el.formElements[0].checked).to.be.true;
364
+ el.setCheckedIndex(-1);
365
+ el.__shouldAutocompleteNextUpdate = true;
366
+ await el.updateComplete;
367
+ expect(el.modelValue).to.equal('Artichoke');
368
+ expect(el.formElements[0].checked).to.be.true;
369
+
370
+ el.setCheckedIndex(-1);
371
+ _inputNode.value = 'Foo';
372
+ el.__shouldAutocompleteNextUpdate = true;
373
+ await el.updateComplete;
374
+ expect(el.modelValue).to.equal('Foo');
428
375
  expect(el.formElements[0].checked).to.be.false;
429
376
  });
430
377
 
@@ -439,15 +386,37 @@ describe('lion-combobox', () => {
439
386
  </lion-combobox>
440
387
  `)
441
388
  );
442
-
443
- expect(el.modelValue).to.eql(['Artichoke']);
389
+ expect(el.modelValue).to.deep.equal(['Artichoke']);
444
390
  expect(el.formElements[0].checked).to.be.true;
445
- el.checkedIndex = [];
391
+ el.setCheckedIndex([]);
446
392
  await el.updateComplete;
447
- expect(el.modelValue).to.eql([]);
393
+ expect(el.modelValue).to.deep.equal([]);
448
394
  expect(el.formElements[0].checked).to.be.false;
449
395
  });
450
396
 
397
+ it('multiple choice and requireOptionMatch is false do not work together yet', async () => {
398
+ const errorMessage = `multipleChoice and requireOptionMatch=false can't be used at the same time (yet).`;
399
+ let error;
400
+ const el = /** @type {LionCombobox} */ (
401
+ await fixture(html`
402
+ <lion-combobox name="foo" multiple-choice>
403
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
404
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
405
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
406
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
407
+ </lion-combobox>
408
+ `)
409
+ );
410
+ try {
411
+ el.requireOptionMatch = false;
412
+ await el.updateComplete;
413
+ } catch (err) {
414
+ error = err;
415
+ }
416
+ expect(error).to.be.instanceOf(Error);
417
+ expect(/** @type {Error} */ (error).message).to.equal(errorMessage);
418
+ });
419
+
451
420
  it('clears modelValue and textbox value on clear()', async () => {
452
421
  const el = /** @type {LionCombobox} */ (
453
422
  await fixture(html`
@@ -479,7 +448,43 @@ describe('lion-combobox', () => {
479
448
 
480
449
  el2.clear();
481
450
  expect(el2.modelValue).to.eql([]);
482
- expect(_inputNode.value).to.equal('');
451
+ // @ts-ignore [allow-protected] in test
452
+ expect(el2._inputNode.value).to.equal('');
453
+ });
454
+
455
+ it('resets modelValue and textbox value on reset()', async () => {
456
+ const el = /** @type {LionCombobox} */ (
457
+ await fixture(html`
458
+ <lion-combobox name="foo" .modelValue="${'Artichoke'}">
459
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
460
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
461
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
462
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
463
+ </lion-combobox>
464
+ `)
465
+ );
466
+
467
+ const { _inputNode } = getComboboxMembers(el);
468
+ el.modelValue = 'Chard';
469
+ el.reset();
470
+ expect(el.modelValue).to.equal('Artichoke');
471
+ expect(_inputNode.value).to.equal('Artichoke');
472
+
473
+ const el2 = /** @type {LionCombobox} */ (
474
+ await fixture(html`
475
+ <lion-combobox name="foo" multiple-choice .modelValue="${['Artichoke']}">
476
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
477
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
478
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
479
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
480
+ </lion-combobox>
481
+ `)
482
+ );
483
+ el2.modelValue = ['Artichoke', 'Chard'];
484
+ el2.reset();
485
+ expect(el2.modelValue).to.deep.equal(['Artichoke']);
486
+ // @ts-ignore [allow-protected] in test
487
+ expect(el2._inputNode.value).to.equal('Artichoke');
483
488
  });
484
489
 
485
490
  it('syncs textbox to modelValue', async () => {
@@ -493,27 +498,123 @@ describe('lion-combobox', () => {
493
498
  );
494
499
  const { _inputNode } = getComboboxMembers(el);
495
500
 
496
- async function performChecks() {
501
+ /** @param {string} autocompleteMode */
502
+ async function performChecks(autocompleteMode) {
497
503
  el.formElements[0].click();
498
504
  await el.updateComplete;
499
505
 
500
506
  // FIXME: fix properly for Webkit
501
- // expect(_inputNode.value).to.equal('Aha');
502
- expect(el.checkedIndex).to.equal(0);
507
+ // expect(_inputNode.value).to.equal('Aha', `autocomplete mode ${autocompleteMode}`);
508
+ expect(el.checkedIndex).to.equal(0, `autocomplete mode ${autocompleteMode}`);
503
509
 
504
510
  mimicUserTyping(el, 'Ah');
505
511
  await el.updateComplete;
506
- expect(_inputNode.value).to.equal('Ah');
512
+ expect(_inputNode.value).to.equal('Ah', `autocomplete mode ${autocompleteMode}`);
507
513
 
508
514
  await el.updateComplete;
509
- expect(el.checkedIndex).to.equal(-1);
515
+ expect(el.checkedIndex).to.equal(-1, `autocomplete mode ${autocompleteMode}`);
510
516
  }
511
517
 
512
518
  el.autocomplete = 'none';
513
- await performChecks();
519
+ await performChecks('none');
514
520
 
515
521
  el.autocomplete = 'list';
516
- await performChecks();
522
+ await performChecks('list');
523
+ });
524
+
525
+ it('works with Required validator', async () => {
526
+ const el = /** @type {LionCombobox} */ (
527
+ await fixture(html`
528
+ <lion-combobox name="foo" .validators=${[new Required()]}>
529
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
530
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
531
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
532
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
533
+ </lion-combobox>
534
+ `)
535
+ );
536
+ el.submitted = true;
537
+ await el.feedbackComplete;
538
+ expect(el.hasFeedbackFor).to.include('error', 'hasFeedbackFor');
539
+ await el.feedbackComplete;
540
+ expect(el.showsFeedbackFor).to.include('error', 'showsFeedbackFor');
541
+ });
542
+
543
+ it('allows a value outside of the option list when requireOptionMatch is false', async () => {
544
+ const el = /** @type {LionCombobox} */ (
545
+ await fixture(html`
546
+ <lion-combobox name="foo" .validators=${[new Required()]}>
547
+ <lion-option checked .choiceValue="${'Artichoke'}">Artichoke</lion-option>
548
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
549
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
550
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
551
+ </lion-combobox>
552
+ `)
553
+ );
554
+ el.requireOptionMatch = false;
555
+ const { _inputNode } = getComboboxMembers(el);
556
+ expect(el.checkedIndex).to.equal(0);
557
+
558
+ mimicUserTyping(el, 'Foo');
559
+ _inputNode.dispatchEvent(new Event('input'));
560
+ await el.updateComplete;
561
+
562
+ expect(el.checkedIndex).to.equal(-1);
563
+ expect(el.modelValue).to.equal('Foo');
564
+ expect(_inputNode.value).to.equal('Foo');
565
+ });
566
+
567
+ it("when removing a letter it won't select the option", async () => {
568
+ // We don't autocomplete when characters are removed
569
+ const el = /** @type {LionCombobox} */ (
570
+ await fixture(html`
571
+ <lion-combobox name="foo">
572
+ <lion-option checked .choiceValue="${'Artichoke'}">Artichoke</lion-option>
573
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
574
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
575
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
576
+ </lion-combobox>
577
+ `)
578
+ );
579
+ const { _inputNode } = getComboboxMembers(el);
580
+ expect(el.checkedIndex).to.equal(0);
581
+
582
+ // Simulate backspace deleting the char at the end of the string
583
+ mimicKeyPress(_inputNode, 'Backspace');
584
+ _inputNode.dispatchEvent(new Event('input'));
585
+ const arr = _inputNode.value.split('');
586
+ arr.splice(_inputNode.value.length - 1, 1);
587
+ _inputNode.value = arr.join('');
588
+ await el.updateComplete;
589
+ el.dispatchEvent(new Event('blur'));
590
+
591
+ expect(el.checkedIndex).to.equal(-1);
592
+ expect(el.modelValue instanceof Unparseable).to.be.true;
593
+ expect(el.modelValue.viewValue).to.equal('Artichok');
594
+ });
595
+
596
+ it('allows the user to override the parser', async () => {
597
+ const el = /** @type {LionCombobox} */ (
598
+ await fixture(html`
599
+ <lion-combobox
600
+ name="foo"
601
+ .parser="${/** @param {string} value */ value => value.replace(/[0-9]/g, '')}"
602
+ >
603
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
604
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
605
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
606
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
607
+ </lion-combobox>
608
+ `)
609
+ );
610
+ const { _inputNode } = getComboboxMembers(el);
611
+
612
+ el.setCheckedIndex(-1);
613
+ _inputNode.value = 'Foo123';
614
+ el.__shouldAutocompleteNextUpdate = true;
615
+ await el.updateComplete;
616
+ expect(el.modelValue).to.equal('Foo');
617
+ expect(el.formElements[0].checked).to.be.false;
517
618
  });
518
619
  });
519
620
 
@@ -670,6 +771,25 @@ describe('lion-combobox', () => {
670
771
  expect(el.checkedIndex).to.equal(-1);
671
772
  });
672
773
 
774
+ it('has the correct Overlay Config defined', async () => {
775
+ const el = /** @type {LionCombobox} */ (
776
+ await fixture(html`
777
+ <lion-combobox name="foo" .showAllOnEmpty="${true}">
778
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
779
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
780
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
781
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
782
+ </lion-combobox>
783
+ `)
784
+ );
785
+ // @ts-ignore [allow-protected] in test
786
+ expect(el._defineOverlayConfig().elementToFocusAfterHide).to.equal(undefined);
787
+ // @ts-ignore [allow-protected] in test
788
+ expect(el._defineOverlayConfig().invokerNode).to.equal(el._inputNode);
789
+ // @ts-ignore [allow-protected] in test
790
+ expect(el._defineOverlayConfig().visibilityTriggerFunction).to.equal(undefined);
791
+ });
792
+
673
793
  // NB: If this becomes a suite, move to separate file
674
794
  describe('Subclassers', () => {
675
795
  it('allows to control overlay visibility via "_showOverlayCondition"', async () => {
@@ -855,7 +975,7 @@ describe('lion-combobox', () => {
855
975
  });
856
976
  });
857
977
 
858
- it('works with validation', async () => {
978
+ it('dropdown has a label', async () => {
859
979
  const el = /** @type {LionCombobox} */ (
860
980
  await fixture(html`
861
981
  <lion-combobox name="foo" .validators=${[new Required()]}>
@@ -866,22 +986,10 @@ describe('lion-combobox', () => {
866
986
  </lion-combobox>
867
987
  `)
868
988
  );
869
- const { _inputNode } = getComboboxMembers(el);
870
- expect(el.checkedIndex).to.equal(0);
871
-
872
- // Simulate backspace deleting the char at the end of the string
873
- mimicKeyPress(_inputNode, 'Backspace');
874
- _inputNode.dispatchEvent(new Event('input'));
875
- const arr = _inputNode.value.split('');
876
- arr.splice(_inputNode.value.length - 1, 1);
877
- _inputNode.value = arr.join('');
878
- await el.updateComplete;
879
- el.dispatchEvent(new Event('blur'));
880
-
881
- expect(el.checkedIndex).to.equal(-1);
882
- await el.feedbackComplete;
883
- expect(el.hasFeedbackFor).to.include('error');
884
- expect(el.showsFeedbackFor).to.include('error');
989
+ const { _overlayCtrl } = getComboboxMembers(el);
990
+ expect(_overlayCtrl.contentWrapperNode.getAttribute('aria-label')).to.equal(
991
+ 'Combobox options popup',
992
+ );
885
993
  });
886
994
  });
887
995
  });
@@ -1342,6 +1450,7 @@ describe('lion-combobox', () => {
1342
1450
  `)
1343
1451
  );
1344
1452
  const { _inputNode } = getComboboxMembers(el);
1453
+ await el.updateComplete;
1345
1454
  expect(_inputNode.value).to.equal('');
1346
1455
 
1347
1456
  /**
@@ -1356,9 +1465,10 @@ describe('lion-combobox', () => {
1356
1465
  await el.updateComplete;
1357
1466
  el.autocomplete = autocomplete;
1358
1467
  el.setCheckedIndex(index);
1468
+ await el.updateComplete;
1359
1469
  el.opened = false;
1360
1470
  await el.updateComplete;
1361
- expect(_inputNode.value).to.equal(valueOnClose);
1471
+ expect(_inputNode.value).to.equal(valueOnClose, autocomplete);
1362
1472
  }
1363
1473
 
1364
1474
  await performChecks('none', 0, 'Artichoke');
@@ -1617,7 +1727,7 @@ describe('lion-combobox', () => {
1617
1727
 
1618
1728
  const el = /** @type {LionCombobox} */ (
1619
1729
  await fixture(html`
1620
- <${tag} name="foo" autocomplete="list" opened>
1730
+ <${tag} name="foo" opened>
1621
1731
  <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
1622
1732
  <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
1623
1733
  <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
@@ -1909,7 +2019,10 @@ describe('lion-combobox', () => {
1909
2019
  */
1910
2020
  // eslint-disable-next-line class-methods-use-this
1911
2021
  _getTextboxValueFromOption(option) {
1912
- return option.label;
2022
+ if (option && option.label) {
2023
+ return option.label;
2024
+ }
2025
+ return this.modelValue;
1913
2026
  }
1914
2027
  }
1915
2028
  const tagName = defineCE(X);
@@ -1956,7 +2069,10 @@ describe('lion-combobox', () => {
1956
2069
  */
1957
2070
  // eslint-disable-next-line class-methods-use-this
1958
2071
  _getTextboxValueFromOption(option) {
1959
- return option.label;
2072
+ if (option && option.label) {
2073
+ return option.label;
2074
+ }
2075
+ return this.modelValue;
1960
2076
  }
1961
2077
  }
1962
2078
  const tagName = defineCE(X);
@@ -2029,27 +2145,29 @@ describe('lion-combobox', () => {
2029
2145
  );
2030
2146
  const { _inputNode } = getComboboxMembers(el);
2031
2147
 
2032
- async function performChecks() {
2033
- el.formElements[0].click();
2148
+ /** @param {string} autocompleteMode */
2149
+ async function performChecks(autocompleteMode) {
2034
2150
  await el.updateComplete;
2035
2151
 
2152
+ el.formElements[0].click();
2153
+
2036
2154
  // FIXME: fix properly for Webkit
2037
- // expect(_inputNode.value).to.equal('Aha');
2038
- expect(el.checkedIndex).to.equal(0);
2155
+ // expect(_inputNode.value).to.equal('Aha', autocompleteMode);
2156
+ expect(el.checkedIndex).to.equal(0, autocompleteMode);
2039
2157
 
2040
2158
  mimicUserTyping(el, 'Arti');
2041
2159
  await el.updateComplete;
2042
- expect(_inputNode.value).to.equal('Arti');
2160
+ expect(_inputNode.value).to.equal('Arti', autocompleteMode);
2043
2161
 
2044
2162
  await el.updateComplete;
2045
- expect(el.checkedIndex).to.equal(-1);
2163
+ expect(el.checkedIndex).to.equal(-1, autocompleteMode);
2046
2164
  }
2047
2165
 
2048
2166
  el.autocomplete = 'none';
2049
- await performChecks();
2167
+ await performChecks('none');
2050
2168
 
2051
2169
  el.autocomplete = 'list';
2052
- await performChecks();
2170
+ await performChecks('list');
2053
2171
  });
2054
2172
  });
2055
2173
 
@@ -2192,7 +2310,7 @@ describe('lion-combobox', () => {
2192
2310
  // eslint-disable-next-line no-param-reassign
2193
2311
  elm.activeIndex = -1;
2194
2312
  // eslint-disable-next-line no-param-reassign
2195
- elm.checkedIndex = -1;
2313
+ elm.setCheckedIndex(-1);
2196
2314
  // eslint-disable-next-line no-param-reassign
2197
2315
  elm.opened = true;
2198
2316
  await elm.updateComplete;
@@ -0,0 +1,62 @@
1
+ import { mimicUserTyping } from '@lion/ui/combobox-test-helpers.js';
2
+ import { MatchesOption } from '@lion/ui/combobox.js';
3
+ import '@lion/ui/define/lion-combobox.js';
4
+ import '@lion/ui/define/lion-option.js';
5
+ import { expect, fixture, html } from '@open-wc/testing';
6
+
7
+ /**
8
+ * @typedef {import('@lion/ui/combobox.js').LionCombobox} LionCombobox
9
+ */
10
+
11
+ describe('MatchesOption validation', () => {
12
+ it('is enabled when an invalid value is set', async () => {
13
+ let isEnabled;
14
+ const el = /** @type {LionCombobox} */ (
15
+ await fixture(html`
16
+ <lion-combobox name="foo">
17
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
18
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
19
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
20
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
21
+ </lion-combobox>
22
+ `)
23
+ );
24
+ const config = {};
25
+ config.node = el;
26
+ const validator = new MatchesOption();
27
+
28
+ mimicUserTyping(el, 'Artichoke');
29
+ await el.updateComplete;
30
+
31
+ isEnabled = validator.execute('Artichoke', undefined, config);
32
+ expect(isEnabled).to.be.false;
33
+
34
+ mimicUserTyping(el, 'Foo');
35
+ await el.updateComplete;
36
+
37
+ isEnabled = validator.execute('Foo', undefined, config);
38
+ expect(isEnabled).to.be.true;
39
+ });
40
+
41
+ it('is not enabled when empty is set', async () => {
42
+ const el = /** @type {LionCombobox} */ (
43
+ await fixture(html`
44
+ <lion-combobox name="foo">
45
+ <lion-option .choiceValue="${'Artichoke'}">Artichoke</lion-option>
46
+ <lion-option .choiceValue="${'Chard'}">Chard</lion-option>
47
+ <lion-option .choiceValue="${'Chicory'}">Chicory</lion-option>
48
+ <lion-option .choiceValue="${'Victoria Plum'}">Victoria Plum</lion-option>
49
+ </lion-combobox>
50
+ `)
51
+ );
52
+ const config = {};
53
+ config.node = el;
54
+ const validator = new MatchesOption();
55
+
56
+ el.modelValue = '';
57
+ await el.updateComplete;
58
+
59
+ const isEnabled = validator.execute('', undefined, config);
60
+ expect(isEnabled).to.be.false;
61
+ });
62
+ });