@lion/ui 0.18.0-menu-system.4 → 0.18.0-menu-system.5

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 (81) hide show
  1. package/components/collapsible/test-suites/DisclosureMixin.suite.js +30 -0
  2. package/components/menu/src/InteractiveListMixin.js +56 -23
  3. package/components/menu/src/LionMenu.js +1 -1
  4. package/components/menu/src/LionToolbar.js +1 -1
  5. package/components/menu/src/utils/listItemInteractions.js +25 -6
  6. package/components/menu/test/lion-menu.test.js +2 -1
  7. package/components/menu/test-suites/InteractiveListMixin.suite.js +164 -2
  8. package/custom-elements.json +263 -19
  9. package/dist-types/components/collapsible/test-suites/DisclosureMixin.suite.d.ts +2 -0
  10. package/dist-types/components/collapsible/test-suites/DisclosureMixin.suite.d.ts.map +1 -0
  11. package/dist-types/components/menu/src/InteractiveListMixin.d.ts +11 -5
  12. package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +1 -1
  13. package/dist-types/components/menu/src/LionMenu.d.ts +4 -2
  14. package/dist-types/components/menu/src/LionMenu.d.ts.map +1 -1
  15. package/dist-types/components/menu/src/LionMenuOverlay.d.ts +8 -4
  16. package/dist-types/components/menu/src/LionMenuOverlay.d.ts.map +1 -1
  17. package/dist-types/components/menu/src/LionTree.d.ts +4 -2
  18. package/dist-types/components/menu/src/LionTree.d.ts.map +1 -1
  19. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts +4 -2
  20. package/dist-types/components/menu/src/MultiLevelListMixin.d.ts.map +1 -1
  21. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +8 -2
  22. package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +1 -1
  23. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +6 -5
  24. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -1
  25. package/dist-types/exports/menu-test-suites.d.ts +2 -0
  26. package/dist-types/exports/menu-test-suites.d.ts.map +1 -0
  27. package/dist-types/tsconfig.tsbuildinfo +1 -1
  28. package/docs/components/checkbox-group/overview.md +2 -2
  29. package/docs/components/collapsible/overview.md +1 -1
  30. package/docs/components/combobox/overview.md +1 -1
  31. package/docs/components/combobox/use-cases.md +1 -1
  32. package/docs/components/dialog/overview.md +1 -1
  33. package/docs/components/fieldset/overview.md +5 -5
  34. package/docs/components/form/overview.md +3 -3
  35. package/docs/components/form/use-cases.md +2 -2
  36. package/docs/components/icon/use-cases.md +1 -1
  37. package/docs/components/input/overview.md +1 -1
  38. package/docs/components/input-amount/overview.md +3 -3
  39. package/docs/components/input-date/overview.md +3 -3
  40. package/docs/components/input-datepicker/overview.md +5 -5
  41. package/docs/components/input-email/overview.md +2 -2
  42. package/docs/components/input-file/overview.md +1 -1
  43. package/docs/components/input-iban/overview.md +2 -2
  44. package/docs/components/input-range/overview.md +2 -2
  45. package/docs/components/input-stepper/overview.md +1 -1
  46. package/docs/components/input-tel/overview.md +1 -1
  47. package/docs/components/input-tel/use-cases.md +6 -6
  48. package/docs/components/input-tel-dropdown/overview.md +1 -1
  49. package/docs/components/listbox/use-cases.md +1 -1
  50. package/docs/components/menu/api-table.md +197 -192
  51. package/docs/components/menu/overview.md +39 -39
  52. package/docs/components/radio-group/overview.md +3 -3
  53. package/docs/components/select/use-cases.md +1 -1
  54. package/docs/components/select-rich/use-cases.md +2 -2
  55. package/docs/components/tabs/examples.md +1 -1
  56. package/docs/fundamentals/index.md +4 -4
  57. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  58. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  59. package/docs/fundamentals/rationales/accessibility.md +1 -1
  60. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  61. package/docs/fundamentals/systems/core/overview.md +1 -1
  62. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  63. package/docs/fundamentals/systems/form/model-value.md +1 -1
  64. package/docs/fundamentals/systems/form/overview.md +28 -28
  65. package/docs/fundamentals/systems/form/validate.md +2 -2
  66. package/docs/fundamentals/systems/localize/overview.md +4 -4
  67. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  68. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  69. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  70. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  71. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  72. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  73. package/docs/guides/how-to/create-a-form.md +4 -4
  74. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  75. package/docs/guides/how-to/index.md +1 -1
  76. package/docs/guides/index.md +8 -8
  77. package/docs/guides/principles/index.md +5 -5
  78. package/docs/guides/principles/styling.md +1 -1
  79. package/exports/menu-test-suites.js +2 -0
  80. package/menu-test-suites.js +2 -0
  81. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ // import { LitElement } from 'lit';
2
+ // import { expect, html, fixture as _fixture, unsafeStatic, defineCE } from '@open-wc/testing';
3
+ // import { DisclosureMixin } from '../src/DisclosureMixin.js';
4
+
5
+ // import '@lion/ui/define/lion-menu.js';
6
+ // import '@lion/ui/define/lion-item.js';
7
+
8
+ // class DisclosureClass extends DisclosureMixin(LitElement) {}
9
+
10
+ // /**
11
+ // * @typedef {import('lit').TemplateResult} TemplateResult
12
+ // */
13
+
14
+ // const fixture = /** @type {(arg: TemplateResult) => Promise<LionMenu>} */ (_fixture);
15
+
16
+ // /**
17
+ // * @param { {tagString?:string, itemTagString?:string} } [customConfig]
18
+ // */
19
+ // export function runDisclosureMixinSuite(customConfig = {}) {
20
+ // const cfg = {
21
+ // tagString: 'lion-menu',
22
+ // ...customConfig,
23
+ // };
24
+
25
+ // const tagString = cfg.tagString || defineCE(DisclosureClass);
26
+ // const tag = unsafeStatic(tagString);
27
+
28
+ // describe('DisclosureMixin', () => {
29
+ // });
30
+ // }
@@ -22,7 +22,7 @@ import {
22
22
 
23
23
  // TODO: consider renaming to FocusGroupMixin
24
24
 
25
- // TODO: make all available in controller/directve (same logic with an elegent prop-to-host-mapping)
25
+ // TODO: make all available in controller/directive (same logic with an elegant prop-to-host-mapping)
26
26
 
27
27
  /**
28
28
  * @param {Element} potentialFocusable
@@ -55,13 +55,25 @@ function isFocusableElement(potentialFocusable) {
55
55
  export class LionItem extends LitElement {
56
56
  /** @type {any} */
57
57
  static get properties() {
58
- return { type: String };
58
+ return {
59
+ type: { type: String },
60
+ };
59
61
  }
60
62
 
61
63
  get _invokerNode() {
62
- return Array.from(this.children).find(child => child.slot === 'invoker');
64
+ return /** @type {HTMLElement[]} */ (Array.from(this.children)).find(
65
+ child => child.slot === 'invoker',
66
+ );
63
67
  }
64
68
 
69
+ static styles = [
70
+ css`
71
+ :host {
72
+ display: block;
73
+ }
74
+ `,
75
+ ];
76
+
65
77
  constructor() {
66
78
  super();
67
79
  /** @type {InteractiveListItemRole |''} */
@@ -81,14 +93,6 @@ export class LionItem extends LitElement {
81
93
  }
82
94
  }
83
95
 
84
- static styles = [
85
- css`
86
- :host {
87
- display: block;
88
- }
89
- `,
90
- ];
91
-
92
96
  render() {
93
97
  return html`
94
98
  <slot name="invoker"></slot>
@@ -332,11 +336,10 @@ const InteractiveListMixinImplementation = superclass =>
332
336
  setCheckedIndex(index) {
333
337
  const item = this.listItems[index];
334
338
  if (!item) return;
335
-
336
339
  if (!this.multipleChoice) {
337
340
  // Uncheck all
338
341
  this.listItems.forEach(listItem => {
339
- setChecked(listItem, false);
342
+ setChecked(listItem, true);
340
343
  });
341
344
  setChecked(this.listItems[index]);
342
345
  } else {
@@ -377,7 +380,7 @@ const InteractiveListMixinImplementation = superclass =>
377
380
  * - [role="menubar"](https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html)
378
381
  * - [role="tree"](https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-1/treeview-1b.html)
379
382
  * For disclosure menus, we need keyboard navigation, but treat every list item
380
- * (more precisely its anchor child) as a tab stop. Also we apply aria-current
383
+ * (more precisely its anchor child) as a tab stop.
381
384
  * @type {'activedescendant'|'roving-tabindex'|'tabbable-disclosure'|'none'}
382
385
  */
383
386
  this._activeMode = 'activedescendant';
@@ -426,6 +429,16 @@ const InteractiveListMixinImplementation = superclass =>
426
429
  this._onListFocusIn = this._onListFocusIn.bind(this);
427
430
  }
428
431
 
432
+ connectedCallback() {
433
+ super.connectedCallback();
434
+ window.addEventListener('popstate', this._onPopState);
435
+ }
436
+
437
+ disconnectedCallback() {
438
+ super.disconnectedCallback();
439
+ window.removeEventListener('popstate', this._onPopState);
440
+ }
441
+
429
442
  /**
430
443
  * @param {import('lit').PropertyValues } changedProperties
431
444
  */
@@ -461,7 +474,6 @@ const InteractiveListMixinImplementation = superclass =>
461
474
  * we search for a[href]
462
475
  */
463
476
  if (node.getAttribute('role') === 'group') {
464
- // console.debug('checking children of', node, node.children);
465
477
  // @ts-ignore - HTMLCollection is iterable
466
478
  for (const child of node.children) {
467
479
  // @ts-ignore
@@ -568,6 +580,15 @@ const InteractiveListMixinImplementation = superclass =>
568
580
  if (!this.noPreselect && this.checkedIndex === -1) {
569
581
  this.checkedIndex = 0;
570
582
  }
583
+ newItems.forEach(item => {
584
+ if (!item.hasAttribute('checked')) {
585
+ setChecked(item, true);
586
+ } else {
587
+ setChecked(item);
588
+ }
589
+ });
590
+
591
+ this.__syncCurrentPageWithLocationHref(window.location);
571
592
  }
572
593
 
573
594
  /**
@@ -706,16 +727,8 @@ const InteractiveListMixinImplementation = superclass =>
706
727
  // const item = /** @type {HTMLElement} */ (target.closest('[role]'));
707
728
  // @ts-ignore - target is HTMLElement
708
729
  const foundIndex = this.listItems.indexOf(target);
709
- // console.debug('focusin', target, foundIndex, this.listItems);
710
- const previousActiveIndex = this.activeIndex;
711
730
  if (foundIndex > -1 && foundIndex !== this.activeIndex) {
712
731
  this.activeIndex = foundIndex;
713
- if (this._activeMode === 'tabbable-disclosure') {
714
- const previousActiveItem = this.listItems[previousActiveIndex];
715
- previousActiveItem?.removeAttribute('aria-current');
716
- const activeItem = this.listItems[this.activeIndex];
717
- activeItem.setAttribute('aria-current', 'page');
718
- }
719
732
  }
720
733
  }
721
734
 
@@ -800,6 +813,26 @@ const InteractiveListMixinImplementation = superclass =>
800
813
  _getPreviousEnabledOption(currentIndex, offset = -1) {
801
814
  return this.__getNextOption(currentIndex, offset);
802
815
  }
816
+
817
+ /**
818
+ * @param {Location} location
819
+ */
820
+ __syncCurrentPageWithLocationHref(location) {
821
+ this.listItems.forEach(item => {
822
+ if (/** @type {HTMLAnchorElement} */ (item).href) {
823
+ if (location.href.includes(/** @type {HTMLAnchorElement} */ (item).href)) {
824
+ setChecked(item);
825
+ } else {
826
+ setChecked(item, true);
827
+ }
828
+ }
829
+ });
830
+ }
831
+
832
+ /** @protected */
833
+ _onPopState = () => {
834
+ this.__syncCurrentPageWithLocationHref(document.location);
835
+ };
803
836
  };
804
837
  // @ts-ignore
805
838
  export const InteractiveListMixin = dedupeMixin(InteractiveListMixinImplementation);
@@ -189,7 +189,7 @@ export class LionMenu extends MultiLevelListMixin(LitElement) {
189
189
  if (!multiple) {
190
190
  // Uncheck all
191
191
  listItemsWithinGroup.forEach(listItem => {
192
- setChecked(listItem, false);
192
+ setChecked(listItem, true);
193
193
  });
194
194
  setChecked(this.listItems[index]);
195
195
  } else {
@@ -60,7 +60,7 @@ export class LionToolbar extends LionMenu {
60
60
  if (!multiple) {
61
61
  // Uncheck all
62
62
  listItemsWithinGroup.forEach(listItem => {
63
- setChecked(listItem, false);
63
+ setChecked(listItem, true);
64
64
  });
65
65
  setChecked(this.listItems[index]);
66
66
  } else {
@@ -3,6 +3,10 @@
3
3
  * 'smart' (LionOption) and 'regular' items
4
4
  */
5
5
 
6
+ /**
7
+ * @typedef {import('@lion/ui/checkbox-group.js').LionCheckboxIndeterminate} LionCheckboxIndeterminate
8
+ */
9
+
6
10
  /**
7
11
  * @param {HTMLElement} item
8
12
  */
@@ -34,18 +38,33 @@ export function toggleDisabled(item) {
34
38
  * @param {HTMLElement} item
35
39
  */
36
40
  export function isChecked(item) {
37
- return item.hasAttribute('checked') || item.getAttribute('aria-selected') === 'true';
41
+ return item.hasAttribute('checked');
38
42
  }
39
43
 
40
44
  /**
41
45
  * @param {HTMLElement} item
42
- * @param {boolean} [shouldSet=false]
46
+ * @param {Object} [options]
47
+ * @param {boolean} [options.unset=false]
48
+ * @param {'page'|'mixed'} [options.text] - optional text to set for aria-checked/aria-selected/aria-current (e.g. "mixed")
43
49
  */
44
- export function setChecked(item, shouldSet = true) {
45
- const ariaAttr = item.getAttribute('role') === 'option' ? 'selected' : 'checked';
46
- if (shouldSet) {
50
+ export function setChecked(item, unset = false) {
51
+ const role = item.getAttribute('role');
52
+ let ariaAttr = 'current';
53
+ if (role?.endsWith('checkbox') || role?.endsWith('radio')) {
54
+ ariaAttr = 'checked';
55
+ } else if (role === 'option' || role === 'treeitem') {
56
+ ariaAttr = 'selected';
57
+ }
58
+ let text;
59
+ if (/** @type {HTMLAnchorElement} */ (item).href) {
60
+ text = 'page';
61
+ }
62
+ if (/** @type {LionCheckboxIndeterminate} */ (item).mixedState) {
63
+ text = 'mixed';
64
+ }
65
+ if (!unset) {
47
66
  item.setAttribute('checked', '');
48
- item.setAttribute(`aria-${ariaAttr}`, 'true');
67
+ item.setAttribute(`aria-${ariaAttr}`, text || 'true');
49
68
  } else {
50
69
  item.removeAttribute('checked');
51
70
  item.setAttribute(`aria-${ariaAttr}`, 'false');
@@ -1,3 +1,4 @@
1
1
  import { runInteractiveListMixinSuite } from '../test-suites/InteractiveListMixin.suite.js';
2
+ import '@lion/ui/define/lion-menu.js';
2
3
 
3
- runInteractiveListMixinSuite();
4
+ runInteractiveListMixinSuite({ tagString: 'lion-menu' });
@@ -1,5 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
- import { expect, html, fixture, unsafeStatic, defineCE } from '@open-wc/testing';
2
+ import { expect, html, fixture as _fixture, unsafeStatic, defineCE } from '@open-wc/testing';
3
3
  import { InteractiveListMixin } from '../src/InteractiveListMixin.js';
4
4
 
5
5
  class InteractiveListClass extends InteractiveListMixin(LitElement) {}
@@ -20,10 +20,15 @@ const supportedRoles = [
20
20
  ];
21
21
 
22
22
  /**
23
- * @typedef {Object} TestConfig
23
+ * @typedef {import('../src/LionMenu.js').LionMenu} LionMenu
24
+ * @typedef {import('lit').TemplateResult} TemplateResult
25
+ *
26
+ * * @typedef {Object} TestConfig
24
27
  * @property {string} tagString
25
28
  */
26
29
 
30
+ const fixture = /** @type {(arg: TemplateResult) => Promise<LionMenu>} */ (_fixture);
31
+
27
32
  /**
28
33
  * @param {TestConfig} [customConfig]
29
34
  */
@@ -122,6 +127,163 @@ export function runInteractiveListMixinSuite(customConfig) {
122
127
  });
123
128
  });
124
129
 
130
+ describe('Programmatic interaction', () => {
131
+ // If we have the item line directly in the fixture the linting rule lit-a11y/role-has-required-aria-attrs
132
+ // will fail. Inside our code we set the required aria attributes, but the linter doesn't know that.
133
+ const item = /** @param {number} i, @param {string} role */ (i, role) => html`
134
+ <div role="${role}" id="item${i}">Item ${i}</div>
135
+ `;
136
+
137
+ it('set checked state sets aria-current attribute with role="listitem"', async () => {
138
+ const el = await fixture(html`
139
+ <${tag}>
140
+ ${item(1, 'listitem')}
141
+ ${item(2, 'listitem')}
142
+ </${tag}>
143
+ `);
144
+ el.setCheckedIndex(1);
145
+ const item1 = el.listItems[1];
146
+ expect(item1.getAttribute('aria-current')).to.equal('true');
147
+ el.setCheckedIndex(0);
148
+ expect(item1.getAttribute('aria-current')).to.equal('false');
149
+ });
150
+
151
+ it('set checked state sets aria-selected attribute with role="option"', async () => {
152
+ const el = await fixture(html`
153
+ <${tag}>
154
+ ${item(1, 'option')}
155
+ ${item(2, 'option')}
156
+ </${tag}>
157
+ `);
158
+ console.debug('listItems', el.listItems);
159
+ const item1 = el.listItems[1];
160
+ expect(item1.getAttribute('aria-selected')).to.equal('false');
161
+
162
+ el.setCheckedIndex(1);
163
+ expect(item1.getAttribute('aria-selected')).to.equal('true');
164
+
165
+ el.setCheckedIndex(0);
166
+ expect(item1.getAttribute('aria-selected')).to.equal('false');
167
+ });
168
+
169
+ it('set checked state sets aria-selected attribute with role="treeitem"', async () => {
170
+ const el = await fixture(html`
171
+ <${tag}>
172
+ ${item(1, 'treeitem')}
173
+ ${item(2, 'treeitem')}
174
+ </${tag}>
175
+ `);
176
+ el.setCheckedIndex(1);
177
+ // @ts-ignore - test element with InteractiveListMixin properties
178
+ const item1 = el.listItems[1];
179
+ expect(item1.getAttribute('aria-selected')).to.equal('true');
180
+ el.setCheckedIndex(0);
181
+ expect(item1.getAttribute('aria-selected')).to.equal('false');
182
+ });
183
+
184
+ it('set checked state sets aria-current attribute with role="menuitem"', async () => {
185
+ const el = await fixture(html`
186
+ <${tag}>
187
+ ${item(1, 'menuitem')}
188
+ ${item(2, 'menuitem')}
189
+ </${tag}>
190
+ `);
191
+ el.setCheckedIndex(1);
192
+ const item1 = el.listItems[1];
193
+ expect(item1.getAttribute('aria-current')).to.equal('true');
194
+ el.setCheckedIndex(0);
195
+ expect(item1.getAttribute('aria-current')).to.equal('false');
196
+ });
197
+
198
+ it('set checked state sets aria-checked attribute with role="menuitemradio"', async () => {
199
+ const el = await fixture(html`
200
+ <${tag}>
201
+ ${item(1, 'menuitemradio')}
202
+ ${item(2, 'menuitemradio')}
203
+ </${tag}>
204
+ `);
205
+ el.setCheckedIndex(1);
206
+ const item1 = el.listItems[1];
207
+ expect(item1.getAttribute('aria-checked')).to.equal('true');
208
+ el.setCheckedIndex(0);
209
+ expect(item1.getAttribute('aria-checked')).to.equal('false');
210
+ });
211
+
212
+ it('set checked state sets aria-checked attribute with role="menuitemcheckbox"', async () => {
213
+ const el = await fixture(html`
214
+ <${tag}>
215
+ ${item(1, 'menuitemcheckbox')}
216
+ ${item(2, 'menuitemcheckbox')}
217
+ </${tag}>
218
+ `);
219
+ el.setCheckedIndex(1);
220
+ const item1 = el.listItems[1];
221
+ expect(item1.getAttribute('aria-checked')).to.equal('true');
222
+ el.setCheckedIndex(0);
223
+ // As this is multiple choice it doesn't unset the checked state
224
+ expect(item1.getAttribute('aria-checked')).to.equal('true');
225
+ });
226
+
227
+ it('can set checked state to "page" for role="listitem"', async () => {
228
+ const el = await fixture(html`
229
+ <${tag}>
230
+ <a role="listitem" id="item1" href="#">Item 1</a>
231
+ <a role="listitem" id="item2" href="#">Item 2</a>
232
+ </${tag}>
233
+ `);
234
+ el.setCheckedIndex(1);
235
+ const item1 = el.listItems[1];
236
+ expect(item1.getAttribute('aria-current')).to.equal('page');
237
+ el.setCheckedIndex(0);
238
+ expect(item1.getAttribute('aria-current')).to.equal('false');
239
+ });
240
+
241
+ it('can set checked state to "mixed" for role="menuitemcheckbox"', async () => {
242
+ const el = await fixture(html`
243
+ <${tag}>
244
+ ${item(1, 'menuitemcheckbox')}
245
+ ${item(2, 'menuitemcheckbox')}
246
+ </${tag}>
247
+ `);
248
+ const item1 = el.listItems[1];
249
+ // @ts-ignore - not yet supported property
250
+ item1.mixedState = true;
251
+ el.setCheckedIndex(1);
252
+ expect(item1.getAttribute('aria-checked')).to.equal('mixed');
253
+ el.setCheckedIndex(0);
254
+ expect(item1.getAttribute('aria-checked')).to.equal('mixed');
255
+ });
256
+
257
+ it('does not allow to set checkedIndex or activeIndex to be out of bound', async () => {
258
+ const el = await fixture(html`
259
+ <${tag} has-no-default-selected autocomplete="list">
260
+ <div role="menuitem" id="item1">Item 1</div>
261
+ </${tag}>
262
+ `);
263
+ expect(() => {
264
+ el.checkedIndex = -1;
265
+ el.checkedIndex = 1;
266
+ }).to.not.throw();
267
+ expect(el.checkedIndex).to.equal(-1);
268
+ });
269
+
270
+ it('unsets checked on other options when option becomes checked', async () => {
271
+ const el = await fixture(html`
272
+ <${tag}>
273
+ <div role="menuitem" id="item1">Item 1</div>
274
+ <div role="menuitem" id="item2">Item 2</div>
275
+ </${tag}>
276
+ `);
277
+ const { listItems } = el;
278
+ el.setCheckedIndex(0);
279
+ expect(listItems[0].hasAttribute('checked')).to.be.true;
280
+ expect(listItems[1].hasAttribute('checked')).to.be.false;
281
+ el.setCheckedIndex(1);
282
+ expect(listItems[0].hasAttribute('checked')).to.be.false;
283
+ expect(listItems[1].hasAttribute('checked')).to.be.true;
284
+ });
285
+ });
286
+
125
287
  // Copy tests from ListboxMixin. Later, make this mixin a fundament of the ListboxMixin
126
288
 
127
289
  // orientation