@crowdstrike/glide-core 0.5.1 → 0.6.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 (242) hide show
  1. package/README.md +11 -1
  2. package/dist/accordion.js +1 -1
  3. package/dist/accordion.styles.js +5 -4
  4. package/dist/accordion.test.basics.js +109 -0
  5. package/dist/accordion.test.events.js +39 -0
  6. package/dist/button-group.button.js +1 -1
  7. package/dist/button-group.button.styles.js +6 -5
  8. package/dist/button-group.button.test.basics.js +169 -0
  9. package/dist/button-group.button.test.events.js +73 -0
  10. package/dist/button-group.js +1 -1
  11. package/dist/button-group.styles.js +3 -3
  12. package/dist/button-group.test.basics.js +268 -0
  13. package/dist/button-group.test.events.js +291 -0
  14. package/dist/button.d.ts +3 -2
  15. package/dist/button.js +1 -1
  16. package/dist/button.styles.js +5 -5
  17. package/dist/button.test.basics.js +202 -0
  18. package/dist/button.test.events.js +25 -0
  19. package/dist/button.test.form.js +49 -0
  20. package/dist/checkbox-group.js +1 -1
  21. package/dist/checkbox-group.styles.js +2 -2
  22. package/dist/checkbox-group.test.basics.js +119 -0
  23. package/dist/checkbox-group.test.events.js +110 -0
  24. package/dist/checkbox-group.test.focus.js +45 -0
  25. package/dist/checkbox-group.test.form.js +130 -0
  26. package/dist/checkbox-group.test.validity.js +75 -0
  27. package/dist/checkbox.js +1 -1
  28. package/dist/checkbox.styles.js +7 -4
  29. package/dist/checkbox.test.basics.js +89 -0
  30. package/dist/checkbox.test.events.js +87 -0
  31. package/dist/checkbox.test.focus.js +38 -0
  32. package/dist/checkbox.test.form.js +115 -0
  33. package/dist/checkbox.test.states.js +62 -0
  34. package/dist/checkbox.test.validity.js +51 -0
  35. package/dist/drawer.d.ts +2 -2
  36. package/dist/drawer.js +1 -15
  37. package/dist/drawer.styles.js +14 -4
  38. package/dist/drawer.test.accessibility.js +22 -0
  39. package/dist/drawer.test.basics.js +43 -0
  40. package/dist/drawer.test.closing.js +37 -0
  41. package/dist/drawer.test.events.js +52 -0
  42. package/dist/drawer.test.floating-components.d.ts +1 -0
  43. package/dist/drawer.test.floating-components.js +51 -0
  44. package/dist/drawer.test.methods.js +34 -0
  45. package/dist/dropdown.d.ts +4 -2
  46. package/dist/dropdown.js +1 -1
  47. package/dist/dropdown.option.d.ts +0 -2
  48. package/dist/dropdown.option.js +1 -1
  49. package/dist/dropdown.option.styles.js +2 -2
  50. package/dist/dropdown.option.test.basics.js +59 -0
  51. package/dist/dropdown.option.test.basics.multiple.js +26 -0
  52. package/dist/dropdown.option.test.basics.single.js +20 -0
  53. package/dist/dropdown.option.test.events.js +27 -0
  54. package/dist/dropdown.option.test.focus.js +11 -0
  55. package/dist/dropdown.option.test.interactions.multiple.js +82 -0
  56. package/dist/dropdown.option.test.interactions.single.js +22 -0
  57. package/dist/dropdown.styles.js +26 -6
  58. package/dist/dropdown.test.basics.filterable.js +84 -0
  59. package/dist/dropdown.test.basics.js +233 -0
  60. package/dist/dropdown.test.basics.multiple.js +270 -0
  61. package/dist/dropdown.test.basics.single.js +79 -0
  62. package/dist/dropdown.test.events.js +268 -0
  63. package/dist/dropdown.test.events.multiple.js +130 -0
  64. package/dist/dropdown.test.focus.d.ts +1 -0
  65. package/dist/dropdown.test.focus.filterable.js +154 -0
  66. package/dist/dropdown.test.focus.js +28 -0
  67. package/dist/dropdown.test.focus.multiple.js +181 -0
  68. package/dist/dropdown.test.focus.single.js +53 -0
  69. package/dist/dropdown.test.form.js +140 -0
  70. package/dist/dropdown.test.form.multiple.js +149 -0
  71. package/dist/dropdown.test.form.single.js +128 -0
  72. package/dist/dropdown.test.interactions.filterable.js +449 -0
  73. package/dist/dropdown.test.interactions.js +446 -0
  74. package/dist/dropdown.test.interactions.multiple.js +908 -0
  75. package/dist/dropdown.test.interactions.single.js +466 -0
  76. package/dist/dropdown.test.validity.js +46 -0
  77. package/dist/icon-button.d.ts +3 -2
  78. package/dist/icon-button.js +1 -1
  79. package/dist/icon-button.styles.js +12 -12
  80. package/dist/icon-button.test.basics.js +110 -0
  81. package/dist/icons/checked.js +1 -1
  82. package/dist/icons/magnifying-glass.js +1 -1
  83. package/dist/input.js +1 -1
  84. package/dist/input.styles.js +4 -3
  85. package/dist/input.test.basics.js +169 -0
  86. package/dist/input.test.events.js +97 -0
  87. package/dist/input.test.focus.js +54 -0
  88. package/dist/input.test.form.js +56 -0
  89. package/dist/input.test.validity.js +50 -0
  90. package/dist/label.js +1 -1
  91. package/dist/label.styles.js +13 -15
  92. package/dist/label.test.basics.js +129 -0
  93. package/dist/library/expect-argument-error.js +1 -1
  94. package/dist/library/localize.d.ts +17 -0
  95. package/dist/library/localize.js +1 -0
  96. package/dist/library/ow.js +1 -1
  97. package/dist/library/ow.test.js +55 -0
  98. package/dist/library/set-containing-block.d.ts +15 -0
  99. package/dist/library/set-containing-block.js +1 -0
  100. package/dist/menu.button.d.ts +1 -2
  101. package/dist/menu.button.js +1 -1
  102. package/dist/menu.button.styles.js +3 -3
  103. package/dist/menu.button.test.basics.js +42 -0
  104. package/dist/menu.d.ts +7 -2
  105. package/dist/menu.js +1 -1
  106. package/dist/menu.link.d.ts +1 -2
  107. package/dist/menu.link.js +1 -1
  108. package/dist/menu.link.styles.js +3 -3
  109. package/dist/menu.link.test.basics.js +46 -0
  110. package/dist/menu.options.d.ts +22 -0
  111. package/dist/menu.options.js +1 -0
  112. package/dist/menu.options.styles.d.ts +2 -0
  113. package/dist/menu.options.styles.js +33 -0
  114. package/dist/menu.options.test.basics.d.ts +2 -0
  115. package/dist/menu.options.test.basics.js +43 -0
  116. package/dist/menu.stories.d.ts +1 -0
  117. package/dist/menu.styles.js +7 -31
  118. package/dist/menu.test.basics.d.ts +1 -0
  119. package/dist/menu.test.basics.js +183 -0
  120. package/dist/menu.test.focus.d.ts +0 -1
  121. package/dist/menu.test.focus.js +84 -0
  122. package/dist/menu.test.interactions.d.ts +1 -1
  123. package/dist/menu.test.interactions.js +664 -0
  124. package/dist/modal.icon-button.js +1 -1
  125. package/dist/modal.icon-button.styles.js +2 -2
  126. package/dist/modal.icon-button.test.basics.js +45 -0
  127. package/dist/modal.js +1 -15
  128. package/dist/modal.styles.js +4 -7
  129. package/dist/modal.tertiary-icon.d.ts +1 -0
  130. package/dist/modal.tertiary-icon.js +1 -1
  131. package/dist/modal.tertiary-icon.test.basics.js +59 -0
  132. package/dist/modal.test.accessibility.js +48 -0
  133. package/dist/modal.test.basics.js +203 -0
  134. package/dist/modal.test.close.js +38 -0
  135. package/dist/modal.test.events.js +110 -0
  136. package/dist/modal.test.floating-components.d.ts +1 -0
  137. package/dist/modal.test.floating-components.js +62 -0
  138. package/dist/modal.test.lock-scroll.js +76 -0
  139. package/dist/modal.test.methods.js +23 -0
  140. package/dist/modal.test.scrollbars.js +19 -0
  141. package/dist/radio-group.js +1 -1
  142. package/dist/radio-group.styles.js +20 -24
  143. package/dist/radio-group.test.basics.js +323 -0
  144. package/dist/radio-group.test.events.js +277 -0
  145. package/dist/radio-group.test.focus.js +75 -0
  146. package/dist/radio-group.test.form.js +104 -0
  147. package/dist/radio-group.test.validity.js +228 -0
  148. package/dist/radio.js +1 -1
  149. package/dist/radio.styles.js +14 -31
  150. package/dist/split-button.d.ts +27 -0
  151. package/dist/split-button.js +1 -0
  152. package/dist/split-button.stories.d.ts +17 -0
  153. package/dist/split-button.styles.d.ts +2 -0
  154. package/dist/split-button.styles.js +102 -0
  155. package/dist/split-button.test.basics.d.ts +1 -0
  156. package/dist/split-button.test.basics.js +99 -0
  157. package/dist/split-container.d.ts +31 -0
  158. package/dist/split-container.js +1 -0
  159. package/dist/split-container.styles.d.ts +2 -0
  160. package/dist/split-container.styles.js +134 -0
  161. package/dist/split-container.test.basics.d.ts +3 -0
  162. package/dist/split-container.test.basics.js +440 -0
  163. package/dist/split-container.test.interactions.d.ts +1 -0
  164. package/dist/split-container.test.interactions.js +20 -0
  165. package/dist/split-link.d.ts +25 -0
  166. package/dist/split-link.js +1 -0
  167. package/dist/split-link.test.basics.d.ts +1 -0
  168. package/dist/split-link.test.basics.js +92 -0
  169. package/dist/split-link.test.interactions.d.ts +1 -0
  170. package/dist/split-link.test.interactions.js +19 -0
  171. package/dist/status-indicator.js +1 -1
  172. package/dist/status-indicator.styles.js +2 -2
  173. package/dist/status-indicator.test.basics.js +102 -0
  174. package/dist/styles/focus-outline.js +1 -4
  175. package/dist/styles/variables.css +1 -1
  176. package/dist/styles/visually-hidden.js +1 -11
  177. package/dist/tab.group.js +1 -1
  178. package/dist/tab.group.styles.js +2 -2
  179. package/dist/tab.group.test.basics.js +185 -0
  180. package/dist/tab.js +1 -1
  181. package/dist/tab.panel.js +1 -1
  182. package/dist/tab.panel.styles.js +3 -3
  183. package/dist/tab.styles.js +80 -55
  184. package/dist/tab.test.basics.js +71 -0
  185. package/dist/tag.js +1 -1
  186. package/dist/tag.styles.js +4 -3
  187. package/dist/tag.test.basics.js +118 -0
  188. package/dist/tag.test.events.js +16 -0
  189. package/dist/tag.test.focus.js +11 -0
  190. package/dist/tag.test.translations.d.ts +1 -0
  191. package/dist/tag.test.translations.js +25 -0
  192. package/dist/textarea.js +2 -2
  193. package/dist/textarea.styles.js +5 -4
  194. package/dist/textarea.test.basics.js +140 -0
  195. package/dist/textarea.test.events.js +204 -0
  196. package/dist/textarea.test.form.js +70 -0
  197. package/dist/textarea.test.validity.js +83 -0
  198. package/dist/toasts.js +1 -1
  199. package/dist/toasts.styles.js +2 -2
  200. package/dist/toasts.test.basics.js +94 -0
  201. package/dist/toasts.toast.js +1 -1
  202. package/dist/toasts.toast.styles.js +5 -2
  203. package/dist/toasts.toast.test.basics.js +139 -0
  204. package/dist/toggle.js +1 -1
  205. package/dist/toggle.styles.js +3 -3
  206. package/dist/toggle.test.basics.js +68 -0
  207. package/dist/toggle.test.events.js +29 -0
  208. package/dist/toggle.test.focus.js +9 -0
  209. package/dist/toggle.test.states.js +43 -0
  210. package/dist/tooltip.d.ts +2 -0
  211. package/dist/tooltip.js +1 -1
  212. package/dist/tooltip.styles.js +5 -3
  213. package/dist/tooltip.test.basics.js +64 -0
  214. package/dist/tooltip.test.interactions.js +78 -0
  215. package/dist/translations/en.d.ts +3 -0
  216. package/dist/translations/en.js +1 -0
  217. package/dist/translations/fr.d.ts +3 -0
  218. package/dist/translations/fr.js +1 -0
  219. package/dist/translations/ja.d.ts +3 -0
  220. package/dist/translations/ja.js +1 -0
  221. package/dist/tree.d.ts +1 -0
  222. package/dist/tree.item.d.ts +3 -1
  223. package/dist/tree.item.icon-button.js +1 -1
  224. package/dist/tree.item.icon-button.styles.js +2 -2
  225. package/dist/tree.item.icon-button.test.basics.js +13 -0
  226. package/dist/tree.item.js +1 -1
  227. package/dist/tree.item.menu.d.ts +2 -0
  228. package/dist/tree.item.menu.js +1 -1
  229. package/dist/tree.item.menu.styles.js +2 -2
  230. package/dist/tree.item.menu.test.basics.js +33 -0
  231. package/dist/tree.item.styles.js +23 -8
  232. package/dist/tree.item.test.basics.js +102 -0
  233. package/dist/tree.js +1 -1
  234. package/dist/tree.stories.d.ts +1 -0
  235. package/dist/tree.styles.js +2 -2
  236. package/dist/tree.test.aria.js +86 -0
  237. package/dist/tree.test.basics.js +123 -0
  238. package/dist/tree.test.events.js +19 -0
  239. package/dist/tree.test.focus.js +261 -0
  240. package/package.json +25 -18
  241. /package/dist/{dropdown.option.test.focus.multiple.d.ts → dropdown.option.test.focus.d.ts} +0 -0
  242. /package/dist/{dropdown.option.test.focus.single.d.ts → dropdown.test.events.multiple.d.ts} +0 -0
@@ -0,0 +1,449 @@
1
+ import { assert, elementUpdated, expect, fixture, html, } from '@open-wc/testing';
2
+ import { sendKeys } from '@web/test-runner-commands';
3
+ import GlideCoreDropdown from './dropdown.js';
4
+ import GlideCoreDropdownOption from './dropdown.option.js';
5
+ GlideCoreDropdown.shadowRootOptions.mode = 'open';
6
+ GlideCoreDropdownOption.shadowRootOptions.mode = 'open';
7
+ const defaultSlot = html `
8
+ <glide-core-dropdown-option
9
+ label="One"
10
+ value="one"
11
+ ></glide-core-dropdown-option>
12
+
13
+ <glide-core-dropdown-option
14
+ label="Two"
15
+ value="two"
16
+ ></glide-core-dropdown-option>
17
+
18
+ <glide-core-dropdown-option
19
+ label="Three"
20
+ value="three"
21
+ ></glide-core-dropdown-option>
22
+
23
+ <glide-core-dropdown-option
24
+ label="Four"
25
+ value="four"
26
+ ></glide-core-dropdown-option>
27
+
28
+ <glide-core-dropdown-option
29
+ label="Five"
30
+ value="five"
31
+ ></glide-core-dropdown-option>
32
+
33
+ <glide-core-dropdown-option
34
+ label="Six"
35
+ value="six"
36
+ ></glide-core-dropdown-option>
37
+
38
+ <glide-core-dropdown-option
39
+ label="Seven"
40
+ value="seven"
41
+ ></glide-core-dropdown-option>
42
+
43
+ <glide-core-dropdown-option
44
+ label="Eight"
45
+ value="eight"
46
+ ></glide-core-dropdown-option>
47
+
48
+ <glide-core-dropdown-option
49
+ label="Nine"
50
+ value="nine"
51
+ ></glide-core-dropdown-option>
52
+
53
+ <glide-core-dropdown-option
54
+ label="Ten"
55
+ value="ten"
56
+ ></glide-core-dropdown-option>
57
+
58
+ <glide-core-dropdown-option
59
+ label="Eleven"
60
+ value="eleven"
61
+ ></glide-core-dropdown-option>
62
+ `;
63
+ it('opens on click', async () => {
64
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
65
+ ${defaultSlot}
66
+ </glide-core-dropdown>`);
67
+ // Calling `click()` would be sweet. The problem is it sets `event.detail` to `0`,
68
+ // which puts us in a guard in the event handler. `Event` has no `detail` property
69
+ // and would work. `CustomEvent` is used for completeness and to get us as close as
70
+ // possible to a real click. See the comment in the handler for more information.
71
+ component.shadowRoot
72
+ ?.querySelector('[data-test="input"]')
73
+ ?.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: 1 }));
74
+ expect(component.open).to.be.true;
75
+ });
76
+ it('filters', async () => {
77
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
78
+ ${defaultSlot}
79
+ </glide-core-dropdown>`);
80
+ component.focus();
81
+ await sendKeys({ type: ' one ' });
82
+ const options = [
83
+ ...component.querySelectorAll('glide-core-dropdown-option'),
84
+ ].filter(({ hidden }) => !hidden);
85
+ expect(options.length).to.equal(1);
86
+ });
87
+ it('clears the filter term when an option is selected', async () => {
88
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
89
+ ${defaultSlot}
90
+ </glide-core-dropdown>`);
91
+ component.focus();
92
+ await sendKeys({ type: ' one ' });
93
+ const option = [
94
+ ...component.querySelectorAll('glide-core-dropdown-option'),
95
+ ].find(({ hidden }) => !hidden);
96
+ option?.click();
97
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
98
+ const options = [
99
+ ...component.querySelectorAll('glide-core-dropdown-option'),
100
+ ].filter(({ hidden }) => !hidden);
101
+ expect(input?.value).to.equal('');
102
+ expect(options.length).to.equal(11);
103
+ });
104
+ it('shows the magnifying glass icon when there is a filter term', async () => {
105
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
106
+ ${defaultSlot}
107
+ </glide-core-dropdown>`);
108
+ component.focus();
109
+ await sendKeys({ type: ' one ' });
110
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
111
+ expect(icon?.checkVisibility()).to.be.true;
112
+ });
113
+ it('hides the magnifying glass icon when there is no filter term', async () => {
114
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
115
+ ${defaultSlot}
116
+ </glide-core-dropdown>`);
117
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
118
+ expect(icon?.checkVisibility()).to.be.not.ok;
119
+ });
120
+ it('hides the magnifying glass icon when an option is selected', async () => {
121
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
122
+ ${defaultSlot}
123
+ </glide-core-dropdown>`);
124
+ component.focus();
125
+ await sendKeys({ type: ' one ' });
126
+ const option = [
127
+ ...component.querySelectorAll('glide-core-dropdown-option'),
128
+ ].find(({ hidden }) => !hidden);
129
+ option?.click();
130
+ const icon = component?.shadowRoot?.querySelector('[data-test="magnifying-glass-icon"]');
131
+ await elementUpdated(component);
132
+ expect(icon?.checkVisibility()).to.be.not.ok;
133
+ });
134
+ it('does not filter on only whitespace', async () => {
135
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
136
+ ${defaultSlot}
137
+ </glide-core-dropdown>`);
138
+ component.focus();
139
+ await sendKeys({ type: ' ' });
140
+ const options = [
141
+ ...component.querySelectorAll('glide-core-dropdown-option'),
142
+ ].filter(({ hidden }) => !hidden);
143
+ expect(options.length).to.equal(11);
144
+ });
145
+ it('hides the options when all of them are filtered out', async () => {
146
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
147
+ ${defaultSlot}
148
+ </glide-core-dropdown>`);
149
+ component.focus();
150
+ await sendKeys({ type: 'twelve' });
151
+ const options = component.shadowRoot?.querySelector('[data-test="options"]');
152
+ expect(options?.dataset.testVisible).to.equal('false');
153
+ });
154
+ it('hides Select All when filtering', async () => {
155
+ const component = await fixture(html `<glide-core-dropdown
156
+ label="Label"
157
+ placeholder="Placeholder"
158
+ open
159
+ multiple
160
+ select-all
161
+ >
162
+ ${defaultSlot}
163
+ </glide-core-dropdown>`);
164
+ component.focus();
165
+ await sendKeys({ type: 'one' });
166
+ const selectAll = component.shadowRoot?.querySelector('[data-test="select-all"]');
167
+ expect(selectAll?.checkVisibility()).to.not.be.ok;
168
+ });
169
+ it('sets the first unfiltered option as active when the previously active option is filtered out', async () => {
170
+ const component = await fixture(html `<glide-core-dropdown
171
+ label="Label"
172
+ placeholder="Placeholder"
173
+ multiple
174
+ select-all
175
+ >
176
+ ${defaultSlot}
177
+ </glide-core-dropdown>`);
178
+ component.focus();
179
+ await sendKeys({ type: 'two' });
180
+ const option = [
181
+ ...component.querySelectorAll('glide-core-dropdown-option'),
182
+ ].find(({ hidden }) => !hidden);
183
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
184
+ expect(option?.privateActive).to.be.true;
185
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(option?.id);
186
+ });
187
+ it('updates `value` when an option `value` is changed programmatically', async () => {
188
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
189
+ ${defaultSlot}
190
+ </glide-core-dropdown>`);
191
+ const option = component.querySelector('glide-core-dropdown-option');
192
+ assert(option);
193
+ option.value = 'two';
194
+ expect(component.value).to.deep.equal(['two']);
195
+ });
196
+ it('deselects options on Backspace', async () => {
197
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
198
+ ${defaultSlot}
199
+ </glide-core-dropdown>`);
200
+ const options = component.querySelectorAll('glide-core-dropdown-option');
201
+ options[0].selected = true;
202
+ options[1].selected = true;
203
+ component.focus();
204
+ await sendKeys({ press: 'Backspace' });
205
+ expect(options[1].selected).to.be.false;
206
+ expect(options[0].selected).to.be.true;
207
+ await sendKeys({ press: 'Backspace' });
208
+ expect(options[0].selected).to.be.false;
209
+ });
210
+ it('uses the label of the selected option as a placeholder when not `multiple`', async () => {
211
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
212
+ ${defaultSlot}
213
+ </glide-core-dropdown>`);
214
+ const option = component?.querySelector('glide-core-dropdown-option');
215
+ option?.click();
216
+ await elementUpdated(component);
217
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
218
+ expect(input?.placeholder).to.equal(option?.label);
219
+ });
220
+ it('uses `placeholder` as a placeholder when `multiple` and an option is selected', async () => {
221
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" multiple>
222
+ ${defaultSlot}
223
+ </glide-core-dropdown>`);
224
+ component?.querySelector('glide-core-dropdown-option')?.click();
225
+ await elementUpdated(component);
226
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
227
+ expect(input?.placeholder).to.equal('Placeholder');
228
+ });
229
+ it('sets `aria-activedescendant` on option "mouseover"', async () => {
230
+ const component = await fixture(html `<glide-core-dropdown open> ${defaultSlot} </glide-core-dropdown>`);
231
+ const options = component.querySelectorAll('glide-core-dropdown-option');
232
+ options[1]?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
233
+ await elementUpdated(component);
234
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
235
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options[1].id);
236
+ });
237
+ it('sets `aria-activedescendant` on ArrowDown', async () => {
238
+ const component = await fixture(html `<glide-core-dropdown
239
+ label="Label"
240
+ placeholder="Placeholder"
241
+ open
242
+ multiple
243
+ >
244
+ ${defaultSlot}
245
+ </glide-core-dropdown>`);
246
+ const options = component.querySelectorAll('glide-core-dropdown-option');
247
+ options[0]?.focus();
248
+ await sendKeys({ press: 'ArrowDown' });
249
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
250
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options[1].id);
251
+ });
252
+ it('sets `aria-activedescendant` on ArrowUp', async () => {
253
+ const component = await fixture(html `<glide-core-dropdown
254
+ label="Label"
255
+ placeholder="Placeholder"
256
+ open
257
+ multiple
258
+ >
259
+ ${defaultSlot}
260
+ </glide-core-dropdown>`);
261
+ const options = component.querySelectorAll('glide-core-dropdown-option');
262
+ options[1]?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
263
+ options[1]?.focus();
264
+ await sendKeys({ press: 'ArrowUp' });
265
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
266
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options[0].id);
267
+ });
268
+ it('sets `aria-activedescendant` on Home', async () => {
269
+ const component = await fixture(html `<glide-core-dropdown
270
+ label="Label"
271
+ placeholder="Placeholder"
272
+ open
273
+ multiple
274
+ >
275
+ ${defaultSlot}
276
+ </glide-core-dropdown>`);
277
+ const options = component.querySelectorAll('glide-core-dropdown-option');
278
+ options[1]?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
279
+ options[1].focus();
280
+ await sendKeys({ press: 'Home' });
281
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
282
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options[0].id);
283
+ });
284
+ it('sets `aria-activedescendant` on PageUp', async () => {
285
+ const component = await fixture(html `<glide-core-dropdown
286
+ label="Label"
287
+ placeholder="Placeholder"
288
+ open
289
+ multiple
290
+ >
291
+ ${defaultSlot}
292
+ </glide-core-dropdown>`);
293
+ const options = component.querySelectorAll('glide-core-dropdown-option');
294
+ options[1].dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
295
+ options[1].focus();
296
+ await sendKeys({ press: 'PageUp' });
297
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
298
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options[0].id);
299
+ });
300
+ it('sets `aria-activedescendant` on ArrowUp + Meta', async () => {
301
+ const component = await fixture(html `<glide-core-dropdown
302
+ label="Label"
303
+ placeholder="Placeholder"
304
+ open
305
+ multiple
306
+ >
307
+ ${defaultSlot}
308
+ </glide-core-dropdown>`);
309
+ const options = component.querySelectorAll('glide-core-dropdown-option');
310
+ options[1]?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
311
+ options[1].focus();
312
+ await sendKeys({ down: 'Meta' });
313
+ await sendKeys({ press: 'ArrowUp' });
314
+ await sendKeys({ up: 'Meta' });
315
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
316
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options[0].id);
317
+ });
318
+ it('sets `aria-activedescendant` on open via click', async () => {
319
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
320
+ ${defaultSlot}
321
+ </glide-core-dropdown>`);
322
+ // Calling `click()` would be sweet. The problem is it sets `event.detail` to `0`,
323
+ // which puts us in a guard in the event handler. `Event` has no `detail` property
324
+ // and would work. `CustomEvent` is used for completeness and to get us as close as
325
+ // possible to a real click. See the comment in the handler for more information.
326
+ component.shadowRoot
327
+ ?.querySelector('[data-test="button"]')
328
+ ?.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: 1 }));
329
+ await elementUpdated(component);
330
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
331
+ const option = component.querySelector('glide-core-dropdown-option');
332
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(option?.id);
333
+ });
334
+ it('sets `aria-activedescendant` on open via Space', async () => {
335
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder">
336
+ ${defaultSlot}
337
+ </glide-core-dropdown>`);
338
+ component.focus();
339
+ await sendKeys({ press: ' ' });
340
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
341
+ const option = component.querySelector('glide-core-dropdown-option');
342
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(option?.id);
343
+ });
344
+ it('sets `aria-activedescendant` on End', async () => {
345
+ const component = await fixture(html `<glide-core-dropdown
346
+ label="Label"
347
+ placeholder="Placeholder"
348
+ open
349
+ multiple
350
+ >
351
+ ${defaultSlot}
352
+ </glide-core-dropdown>`);
353
+ // Made into an array because the linter forces `at(-1)` instead of
354
+ // `[options.length - 1]` but doesn't take into account that `options`
355
+ // isn't an actual array and doesn't have an `at()` method.
356
+ const options = [...component.querySelectorAll('glide-core-dropdown-option')];
357
+ options[0]?.focus();
358
+ await sendKeys({ press: 'End' });
359
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
360
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options.at(-1)?.id);
361
+ });
362
+ it('sets `aria-activedescendant` on PageDown', async () => {
363
+ const component = await fixture(html `<glide-core-dropdown
364
+ label="Label"
365
+ placeholder="Placeholder"
366
+ open
367
+ multiple
368
+ >
369
+ ${defaultSlot}
370
+ </glide-core-dropdown>`);
371
+ // Made into an array because the linter forces `at(-1)` instead of
372
+ // `[options.length - 1]` but doesn't take into account that `options`
373
+ // isn't an actual array and doesn't have an `at()` method.
374
+ const options = [...component.querySelectorAll('glide-core-dropdown-option')];
375
+ options[0]?.focus();
376
+ await sendKeys({ press: 'PageDown' });
377
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
378
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options.at(-1)?.id);
379
+ });
380
+ it('sets `aria-activedescendant` on Meta + ArrowDown', async () => {
381
+ const component = await fixture(html `<glide-core-dropdown
382
+ label="Label"
383
+ placeholder="Placeholder"
384
+ open
385
+ multiple
386
+ >
387
+ ${defaultSlot}
388
+ </glide-core-dropdown>`);
389
+ // Spread into an array because the linter forces `at(-1)` instead of
390
+ // `[options.length - 1]` but doesn't take into account that `options`
391
+ // isn't an actual array and doesn't have an `at()` method.
392
+ const options = [...component.querySelectorAll('glide-core-dropdown-option')];
393
+ options[0]?.focus();
394
+ await sendKeys({ down: 'Meta' });
395
+ await sendKeys({ press: 'ArrowDown' });
396
+ await sendKeys({ up: 'Meta' });
397
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
398
+ expect(input?.getAttribute('aria-activedescendant')).to.equal(options.at(-1)?.id);
399
+ });
400
+ it('sets `aria-activedescendant` when closed via click', async () => {
401
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
402
+ ${defaultSlot}
403
+ </glide-core-dropdown>`);
404
+ component.shadowRoot
405
+ ?.querySelector('[data-test="button"]')
406
+ ?.click();
407
+ await elementUpdated(component);
408
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
409
+ expect(input?.getAttribute('aria-activedescendant')).to.equal('');
410
+ });
411
+ it('sets `aria-activedescendant` when closed because it lost focus', async () => {
412
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
413
+ ${defaultSlot}
414
+ </glide-core-dropdown>`);
415
+ component.focus();
416
+ await sendKeys({ press: 'Tab' });
417
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
418
+ expect(input?.getAttribute('aria-activedescendant')).to.equal('');
419
+ });
420
+ it('sets `aria-activedescendant` when closed because something outside of it was clicked', async () => {
421
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
422
+ ${defaultSlot}
423
+ </glide-core-dropdown>`);
424
+ document.body.click();
425
+ await elementUpdated(component);
426
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
427
+ expect(input?.getAttribute('aria-activedescendant')).to.equal('');
428
+ });
429
+ it('sets `aria-activedescendant` when closed via Escape', async () => {
430
+ const component = await fixture(html `<glide-core-dropdown label="Label" placeholder="Placeholder" open>
431
+ ${defaultSlot}
432
+ </glide-core-dropdown>`);
433
+ component.focus();
434
+ await sendKeys({ press: 'Escape' });
435
+ const input = component.shadowRoot?.querySelector('[data-test="input"]');
436
+ expect(input?.getAttribute('aria-activedescendant')).to.equal('');
437
+ });
438
+ it('cannot be tabbed to when `disabled`', async () => {
439
+ await fixture(html `<glide-core-dropdown
440
+ label="Label"
441
+ placeholder="Placeholder"
442
+ multiple
443
+ disabled
444
+ >
445
+ ${defaultSlot}
446
+ </glide-core-dropdown>`);
447
+ await sendKeys({ down: 'Tab' });
448
+ expect(document.activeElement).to.equal(document.body);
449
+ });