kempo-ui 0.0.40 → 0.0.41
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.
- package/dist/components/Dropdown.js +123 -0
- package/dist/components/ShadowComponent.js +1 -1
- package/dist/components/Spinner.js +113 -0
- package/docs/components/dropdown.html +282 -0
- package/docs/components/spinner.html +214 -0
- package/docs/index.html +12 -0
- package/docs/nav-1.inc.html +2 -0
- package/docs/src/components/Dropdown.js +123 -0
- package/docs/src/components/ShadowComponent.js +1 -1
- package/docs/src/components/Spinner.js +113 -0
- package/package.json +1 -1
- package/src/components/Dropdown.js +358 -0
- package/src/components/Spinner.js +152 -0
- package/tests/components/Dropdown.browser-test.js +557 -0
- package/tests/components/HybridComponent.browser-test.js +8 -2
- package/tests/components/ShadowComponent.browser-test.js +6 -6
- package/tests/components/Spinner.browser-test.js +335 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import Dropdown from '../../src/components/Dropdown.js';
|
|
2
|
+
|
|
3
|
+
const createDropdown = async (options = {}) => {
|
|
4
|
+
const container = document.createElement('div');
|
|
5
|
+
container.innerHTML = `
|
|
6
|
+
<k-dropdown
|
|
7
|
+
${options.openDirection ? `open-direction="${options.openDirection}"` : ''}
|
|
8
|
+
${options.opened ? 'opened' : ''}
|
|
9
|
+
${options.closeOnSelect === false ? 'close-on-select="false"' : ''}
|
|
10
|
+
${options.closeOnClickOutside === false ? 'close-on-click-outside="false"' : ''}
|
|
11
|
+
>
|
|
12
|
+
<button slot="trigger">Trigger</button>
|
|
13
|
+
<button data-value="item1">Item 1</button>
|
|
14
|
+
<button data-value="item2">Item 2</button>
|
|
15
|
+
<button data-value="item3">Item 3</button>
|
|
16
|
+
${options.withDisabled ? '<button data-value="disabled" disabled>Disabled</button>' : ''}
|
|
17
|
+
${options.withDivider ? '<hr /><button data-value="item4">Item 4</button>' : ''}
|
|
18
|
+
${options.withLinks ? '<a href="#link1">Link 1</a><a href="#link2">Link 2</a>' : ''}
|
|
19
|
+
</k-dropdown>
|
|
20
|
+
`;
|
|
21
|
+
document.body.appendChild(container);
|
|
22
|
+
const dropdown = container.querySelector('k-dropdown');
|
|
23
|
+
await dropdown.updateComplete;
|
|
24
|
+
return { container, dropdown };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const cleanup = (container) => {
|
|
28
|
+
if(container && container.parentNode) {
|
|
29
|
+
container.parentNode.removeChild(container);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
/*
|
|
35
|
+
Element Creation
|
|
36
|
+
*/
|
|
37
|
+
'should create dropdown element': async ({pass, fail}) => {
|
|
38
|
+
const { container, dropdown } = await createDropdown();
|
|
39
|
+
if(!dropdown) {
|
|
40
|
+
cleanup(container);
|
|
41
|
+
return fail('Dropdown element should be created');
|
|
42
|
+
}
|
|
43
|
+
if(!(dropdown instanceof Dropdown)) {
|
|
44
|
+
cleanup(container);
|
|
45
|
+
return fail('Element should be instance of Dropdown');
|
|
46
|
+
}
|
|
47
|
+
cleanup(container);
|
|
48
|
+
pass('Dropdown element created correctly');
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
'should have shadow root': async ({pass, fail}) => {
|
|
52
|
+
const { container, dropdown } = await createDropdown();
|
|
53
|
+
if(!dropdown.shadowRoot) {
|
|
54
|
+
cleanup(container);
|
|
55
|
+
return fail('Dropdown should have shadow root');
|
|
56
|
+
}
|
|
57
|
+
cleanup(container);
|
|
58
|
+
pass('Dropdown has shadow root');
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
'should be closed by default': async ({pass, fail}) => {
|
|
62
|
+
const { container, dropdown } = await createDropdown();
|
|
63
|
+
if(dropdown.opened !== false) {
|
|
64
|
+
cleanup(container);
|
|
65
|
+
return fail(`Expected opened to be false, got ${dropdown.opened}`);
|
|
66
|
+
}
|
|
67
|
+
cleanup(container);
|
|
68
|
+
pass('Dropdown is closed by default');
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
'should have default openDirection of down left': async ({pass, fail}) => {
|
|
72
|
+
const { container, dropdown } = await createDropdown();
|
|
73
|
+
if(dropdown.openDirection !== 'down left') {
|
|
74
|
+
cleanup(container);
|
|
75
|
+
return fail(`Expected openDirection to be 'down left', got ${dropdown.openDirection}`);
|
|
76
|
+
}
|
|
77
|
+
cleanup(container);
|
|
78
|
+
pass('Default openDirection is down left');
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
Property Reflection
|
|
83
|
+
*/
|
|
84
|
+
'should reflect opened attribute': async ({pass, fail}) => {
|
|
85
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
86
|
+
if(!dropdown.hasAttribute('opened')) {
|
|
87
|
+
cleanup(container);
|
|
88
|
+
return fail('Dropdown should have opened attribute');
|
|
89
|
+
}
|
|
90
|
+
cleanup(container);
|
|
91
|
+
pass('Opened attribute reflects correctly');
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
'should reflect open-direction attribute': async ({pass, fail}) => {
|
|
95
|
+
const { container, dropdown } = await createDropdown({ openDirection: 'up right' });
|
|
96
|
+
if(dropdown.getAttribute('open-direction') !== 'up right') {
|
|
97
|
+
cleanup(container);
|
|
98
|
+
return fail('open-direction attribute should be up right');
|
|
99
|
+
}
|
|
100
|
+
cleanup(container);
|
|
101
|
+
pass('open-direction attribute reflects correctly');
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
Public Methods - open()
|
|
106
|
+
*/
|
|
107
|
+
'should have open method': async ({pass, fail}) => {
|
|
108
|
+
const { container, dropdown } = await createDropdown();
|
|
109
|
+
if(typeof dropdown.open !== 'function') {
|
|
110
|
+
cleanup(container);
|
|
111
|
+
return fail('Dropdown should have open method');
|
|
112
|
+
}
|
|
113
|
+
cleanup(container);
|
|
114
|
+
pass('Dropdown has open method');
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
'open() should set opened to true': async ({pass, fail}) => {
|
|
118
|
+
const { container, dropdown } = await createDropdown();
|
|
119
|
+
dropdown.open();
|
|
120
|
+
if(dropdown.opened !== true) {
|
|
121
|
+
cleanup(container);
|
|
122
|
+
return fail(`Expected opened to be true after open(), got ${dropdown.opened}`);
|
|
123
|
+
}
|
|
124
|
+
cleanup(container);
|
|
125
|
+
pass('open() sets opened to true');
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
'open() should return this for chaining': async ({pass, fail}) => {
|
|
129
|
+
const { container, dropdown } = await createDropdown();
|
|
130
|
+
const result = dropdown.open();
|
|
131
|
+
if(result !== dropdown) {
|
|
132
|
+
cleanup(container);
|
|
133
|
+
return fail('open() should return this');
|
|
134
|
+
}
|
|
135
|
+
cleanup(container);
|
|
136
|
+
pass('open() returns this');
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/*
|
|
140
|
+
Public Methods - close()
|
|
141
|
+
*/
|
|
142
|
+
'should have close method': async ({pass, fail}) => {
|
|
143
|
+
const { container, dropdown } = await createDropdown();
|
|
144
|
+
if(typeof dropdown.close !== 'function') {
|
|
145
|
+
cleanup(container);
|
|
146
|
+
return fail('Dropdown should have close method');
|
|
147
|
+
}
|
|
148
|
+
cleanup(container);
|
|
149
|
+
pass('Dropdown has close method');
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
'close() should set opened to false': async ({pass, fail}) => {
|
|
153
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
154
|
+
dropdown.close();
|
|
155
|
+
if(dropdown.opened !== false) {
|
|
156
|
+
cleanup(container);
|
|
157
|
+
return fail(`Expected opened to be false after close(), got ${dropdown.opened}`);
|
|
158
|
+
}
|
|
159
|
+
cleanup(container);
|
|
160
|
+
pass('close() sets opened to false');
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
'close() should return this for chaining': async ({pass, fail}) => {
|
|
164
|
+
const { container, dropdown } = await createDropdown();
|
|
165
|
+
const result = dropdown.close();
|
|
166
|
+
if(result !== dropdown) {
|
|
167
|
+
cleanup(container);
|
|
168
|
+
return fail('close() should return this');
|
|
169
|
+
}
|
|
170
|
+
cleanup(container);
|
|
171
|
+
pass('close() returns this');
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
Public Methods - toggle()
|
|
176
|
+
*/
|
|
177
|
+
'should have toggle method': async ({pass, fail}) => {
|
|
178
|
+
const { container, dropdown } = await createDropdown();
|
|
179
|
+
if(typeof dropdown.toggle !== 'function') {
|
|
180
|
+
cleanup(container);
|
|
181
|
+
return fail('Dropdown should have toggle method');
|
|
182
|
+
}
|
|
183
|
+
cleanup(container);
|
|
184
|
+
pass('Dropdown has toggle method');
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
'toggle() should open when closed': async ({pass, fail}) => {
|
|
188
|
+
const { container, dropdown } = await createDropdown();
|
|
189
|
+
dropdown.toggle();
|
|
190
|
+
if(dropdown.opened !== true) {
|
|
191
|
+
cleanup(container);
|
|
192
|
+
return fail(`Expected opened to be true after toggle(), got ${dropdown.opened}`);
|
|
193
|
+
}
|
|
194
|
+
cleanup(container);
|
|
195
|
+
pass('toggle() opens when closed');
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
'toggle() should close when open': async ({pass, fail}) => {
|
|
199
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
200
|
+
dropdown.toggle();
|
|
201
|
+
if(dropdown.opened !== false) {
|
|
202
|
+
cleanup(container);
|
|
203
|
+
return fail(`Expected opened to be false after toggle(), got ${dropdown.opened}`);
|
|
204
|
+
}
|
|
205
|
+
cleanup(container);
|
|
206
|
+
pass('toggle() closes when open');
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/*
|
|
210
|
+
Events
|
|
211
|
+
*/
|
|
212
|
+
'should dispatch opened event when opening': async ({pass, fail}) => {
|
|
213
|
+
const { container, dropdown } = await createDropdown();
|
|
214
|
+
let eventFired = false;
|
|
215
|
+
dropdown.addEventListener('opened', () => { eventFired = true; });
|
|
216
|
+
dropdown.open();
|
|
217
|
+
await dropdown.updateComplete;
|
|
218
|
+
if(!eventFired) {
|
|
219
|
+
cleanup(container);
|
|
220
|
+
return fail('opened event should be fired');
|
|
221
|
+
}
|
|
222
|
+
cleanup(container);
|
|
223
|
+
pass('opened event dispatched');
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
'should dispatch closed event when closing': async ({pass, fail}) => {
|
|
227
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
228
|
+
let eventFired = false;
|
|
229
|
+
dropdown.addEventListener('closed', () => { eventFired = true; });
|
|
230
|
+
dropdown.close();
|
|
231
|
+
await dropdown.updateComplete;
|
|
232
|
+
if(!eventFired) {
|
|
233
|
+
cleanup(container);
|
|
234
|
+
return fail('closed event should be fired');
|
|
235
|
+
}
|
|
236
|
+
cleanup(container);
|
|
237
|
+
pass('closed event dispatched');
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
'should dispatch select event when button item clicked': async ({pass, fail}) => {
|
|
241
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
242
|
+
let eventFired = false;
|
|
243
|
+
let eventDetail = null;
|
|
244
|
+
dropdown.addEventListener('select', e => {
|
|
245
|
+
eventFired = true;
|
|
246
|
+
eventDetail = e.detail;
|
|
247
|
+
});
|
|
248
|
+
const item = dropdown.querySelector('button[data-value="item1"]');
|
|
249
|
+
item.click();
|
|
250
|
+
if(!eventFired) {
|
|
251
|
+
cleanup(container);
|
|
252
|
+
return fail('select event should be fired');
|
|
253
|
+
}
|
|
254
|
+
if(eventDetail.value !== 'item1') {
|
|
255
|
+
cleanup(container);
|
|
256
|
+
return fail(`Expected value to be 'item1', got ${eventDetail.value}`);
|
|
257
|
+
}
|
|
258
|
+
cleanup(container);
|
|
259
|
+
pass('select event dispatched with correct value');
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
'select event should include item reference': async ({pass, fail}) => {
|
|
263
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
264
|
+
let eventDetail = null;
|
|
265
|
+
dropdown.addEventListener('select', e => { eventDetail = e.detail; });
|
|
266
|
+
const item = dropdown.querySelector('button[data-value="item2"]');
|
|
267
|
+
item.click();
|
|
268
|
+
if(eventDetail.item !== item) {
|
|
269
|
+
cleanup(container);
|
|
270
|
+
return fail('Event detail should include item reference');
|
|
271
|
+
}
|
|
272
|
+
cleanup(container);
|
|
273
|
+
pass('select event includes item reference');
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
/*
|
|
277
|
+
Close Behavior
|
|
278
|
+
*/
|
|
279
|
+
'should close on select by default': async ({pass, fail}) => {
|
|
280
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
281
|
+
const item = dropdown.querySelector('button[data-value="item1"]');
|
|
282
|
+
item.click();
|
|
283
|
+
if(dropdown.opened !== false) {
|
|
284
|
+
cleanup(container);
|
|
285
|
+
return fail('Dropdown should close after selection');
|
|
286
|
+
}
|
|
287
|
+
cleanup(container);
|
|
288
|
+
pass('Dropdown closes on select');
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
'should not close on select when close-on-select is false': async ({pass, fail}) => {
|
|
292
|
+
const { container, dropdown } = await createDropdown({ opened: true, closeOnSelect: false });
|
|
293
|
+
const item = dropdown.querySelector('button[data-value="item1"]');
|
|
294
|
+
item.click();
|
|
295
|
+
if(dropdown.opened !== true) {
|
|
296
|
+
cleanup(container);
|
|
297
|
+
return fail('Dropdown should remain open when close-on-select is false');
|
|
298
|
+
}
|
|
299
|
+
cleanup(container);
|
|
300
|
+
pass('Dropdown stays open when close-on-select is false');
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
'should close on Escape key': async ({pass, fail}) => {
|
|
304
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
305
|
+
const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
|
|
306
|
+
document.dispatchEvent(event);
|
|
307
|
+
if(dropdown.opened !== false) {
|
|
308
|
+
cleanup(container);
|
|
309
|
+
return fail('Dropdown should close on Escape');
|
|
310
|
+
}
|
|
311
|
+
cleanup(container);
|
|
312
|
+
pass('Dropdown closes on Escape');
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
/*
|
|
316
|
+
Trigger Click
|
|
317
|
+
*/
|
|
318
|
+
'should toggle on trigger click': async ({pass, fail}) => {
|
|
319
|
+
const { container, dropdown } = await createDropdown();
|
|
320
|
+
const trigger = dropdown.querySelector('[slot="trigger"]');
|
|
321
|
+
trigger.click();
|
|
322
|
+
if(dropdown.opened !== true) {
|
|
323
|
+
cleanup(container);
|
|
324
|
+
return fail('Dropdown should open on trigger click');
|
|
325
|
+
}
|
|
326
|
+
cleanup(container);
|
|
327
|
+
pass('Dropdown opens on trigger click');
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
Slotted Content
|
|
332
|
+
*/
|
|
333
|
+
'should style slotted buttons': async ({pass, fail}) => {
|
|
334
|
+
const { container, dropdown } = await createDropdown();
|
|
335
|
+
const buttons = dropdown.querySelectorAll('button:not([slot])');
|
|
336
|
+
if(buttons.length !== 3) {
|
|
337
|
+
cleanup(container);
|
|
338
|
+
return fail(`Expected 3 button items, got ${buttons.length}`);
|
|
339
|
+
}
|
|
340
|
+
cleanup(container);
|
|
341
|
+
pass('Slotted buttons exist');
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
'should style slotted links': async ({pass, fail}) => {
|
|
345
|
+
const { container, dropdown } = await createDropdown({ withLinks: true });
|
|
346
|
+
const links = dropdown.querySelectorAll('a');
|
|
347
|
+
if(links.length !== 2) {
|
|
348
|
+
cleanup(container);
|
|
349
|
+
return fail(`Expected 2 link items, got ${links.length}`);
|
|
350
|
+
}
|
|
351
|
+
cleanup(container);
|
|
352
|
+
pass('Slotted links exist');
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
'should style slotted hr as divider': async ({pass, fail}) => {
|
|
356
|
+
const { container, dropdown } = await createDropdown({ withDivider: true });
|
|
357
|
+
const divider = dropdown.querySelector('hr');
|
|
358
|
+
if(!divider) {
|
|
359
|
+
cleanup(container);
|
|
360
|
+
return fail('HR divider should exist');
|
|
361
|
+
}
|
|
362
|
+
cleanup(container);
|
|
363
|
+
pass('HR divider exists');
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
/*
|
|
367
|
+
Keyboard Navigation
|
|
368
|
+
*/
|
|
369
|
+
'ArrowDown should move focus to next item': async ({pass, fail}) => {
|
|
370
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
371
|
+
const items = dropdown.querySelectorAll('button:not([slot])');
|
|
372
|
+
items[0].focus();
|
|
373
|
+
const event = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true });
|
|
374
|
+
document.dispatchEvent(event);
|
|
375
|
+
if(document.activeElement !== items[1]) {
|
|
376
|
+
cleanup(container);
|
|
377
|
+
return fail('Focus should move to next item');
|
|
378
|
+
}
|
|
379
|
+
cleanup(container);
|
|
380
|
+
pass('ArrowDown moves focus to next item');
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
'ArrowUp should move focus to previous item': async ({pass, fail}) => {
|
|
384
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
385
|
+
const items = dropdown.querySelectorAll('button:not([slot])');
|
|
386
|
+
items[1].focus();
|
|
387
|
+
const event = new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true });
|
|
388
|
+
document.dispatchEvent(event);
|
|
389
|
+
if(document.activeElement !== items[0]) {
|
|
390
|
+
cleanup(container);
|
|
391
|
+
return fail('Focus should move to previous item');
|
|
392
|
+
}
|
|
393
|
+
cleanup(container);
|
|
394
|
+
pass('ArrowUp moves focus to previous item');
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
'ArrowDown should wrap to first item': async ({pass, fail}) => {
|
|
398
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
399
|
+
const items = dropdown.querySelectorAll('button:not([slot])');
|
|
400
|
+
items[2].focus();
|
|
401
|
+
const event = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true });
|
|
402
|
+
document.dispatchEvent(event);
|
|
403
|
+
if(document.activeElement !== items[0]) {
|
|
404
|
+
cleanup(container);
|
|
405
|
+
return fail('Focus should wrap to first item');
|
|
406
|
+
}
|
|
407
|
+
cleanup(container);
|
|
408
|
+
pass('ArrowDown wraps to first item');
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
'ArrowUp should wrap to last item': async ({pass, fail}) => {
|
|
412
|
+
const { container, dropdown } = await createDropdown({ opened: true });
|
|
413
|
+
const items = dropdown.querySelectorAll('button:not([slot])');
|
|
414
|
+
items[0].focus();
|
|
415
|
+
const event = new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true });
|
|
416
|
+
document.dispatchEvent(event);
|
|
417
|
+
if(document.activeElement !== items[2]) {
|
|
418
|
+
cleanup(container);
|
|
419
|
+
return fail('Focus should wrap to last item');
|
|
420
|
+
}
|
|
421
|
+
cleanup(container);
|
|
422
|
+
pass('ArrowUp wraps to last item');
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
/*
|
|
426
|
+
Shadow DOM Structure
|
|
427
|
+
*/
|
|
428
|
+
'should render trigger slot': async ({pass, fail}) => {
|
|
429
|
+
const { container, dropdown } = await createDropdown();
|
|
430
|
+
const triggerSlot = dropdown.shadowRoot.querySelector('slot[name="trigger"]');
|
|
431
|
+
if(!triggerSlot) {
|
|
432
|
+
cleanup(container);
|
|
433
|
+
return fail('Trigger slot should be rendered');
|
|
434
|
+
}
|
|
435
|
+
cleanup(container);
|
|
436
|
+
pass('Trigger slot rendered');
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
'should render menu container': async ({pass, fail}) => {
|
|
440
|
+
const { container, dropdown } = await createDropdown();
|
|
441
|
+
const menu = dropdown.shadowRoot.querySelector('#menu');
|
|
442
|
+
if(!menu) {
|
|
443
|
+
cleanup(container);
|
|
444
|
+
return fail('Menu container should be rendered');
|
|
445
|
+
}
|
|
446
|
+
cleanup(container);
|
|
447
|
+
pass('Menu container rendered');
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
'menu should have role menu': async ({pass, fail}) => {
|
|
451
|
+
const { container, dropdown } = await createDropdown();
|
|
452
|
+
const menu = dropdown.shadowRoot.querySelector('#menu');
|
|
453
|
+
if(menu.getAttribute('role') !== 'menu') {
|
|
454
|
+
cleanup(container);
|
|
455
|
+
return fail('Menu should have role menu');
|
|
456
|
+
}
|
|
457
|
+
cleanup(container);
|
|
458
|
+
pass('Menu has correct role');
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
/*
|
|
462
|
+
Method Chaining
|
|
463
|
+
*/
|
|
464
|
+
'should support method chaining': async ({pass, fail}) => {
|
|
465
|
+
const { container, dropdown } = await createDropdown();
|
|
466
|
+
dropdown.open().close().toggle();
|
|
467
|
+
if(dropdown.opened !== true) {
|
|
468
|
+
cleanup(container);
|
|
469
|
+
return fail('Expected opened to be true after chain');
|
|
470
|
+
}
|
|
471
|
+
cleanup(container);
|
|
472
|
+
pass('Method chaining works correctly');
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
/*
|
|
476
|
+
Multiple Dropdowns
|
|
477
|
+
*/
|
|
478
|
+
'clicking another dropdown trigger should close this one': async ({pass, fail}) => {
|
|
479
|
+
const container1 = document.createElement('div');
|
|
480
|
+
container1.innerHTML = `
|
|
481
|
+
<k-dropdown id="dd1">
|
|
482
|
+
<button slot="trigger">Dropdown 1</button>
|
|
483
|
+
<button data-value="1">Item 1</button>
|
|
484
|
+
</k-dropdown>
|
|
485
|
+
`;
|
|
486
|
+
const container2 = document.createElement('div');
|
|
487
|
+
container2.innerHTML = `
|
|
488
|
+
<k-dropdown id="dd2">
|
|
489
|
+
<button slot="trigger">Dropdown 2</button>
|
|
490
|
+
<button data-value="2">Item 2</button>
|
|
491
|
+
</k-dropdown>
|
|
492
|
+
`;
|
|
493
|
+
document.body.appendChild(container1);
|
|
494
|
+
document.body.appendChild(container2);
|
|
495
|
+
const dd1 = container1.querySelector('k-dropdown');
|
|
496
|
+
const dd2 = container2.querySelector('k-dropdown');
|
|
497
|
+
await dd1.updateComplete;
|
|
498
|
+
await dd2.updateComplete;
|
|
499
|
+
|
|
500
|
+
dd1.open();
|
|
501
|
+
await dd1.updateComplete;
|
|
502
|
+
|
|
503
|
+
if(!dd1.opened) {
|
|
504
|
+
cleanup(container1);
|
|
505
|
+
cleanup(container2);
|
|
506
|
+
return fail('Dropdown 1 should be open');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const trigger2 = dd2.querySelector('[slot="trigger"]');
|
|
510
|
+
trigger2.click();
|
|
511
|
+
await dd2.updateComplete;
|
|
512
|
+
|
|
513
|
+
if(dd1.opened) {
|
|
514
|
+
cleanup(container1);
|
|
515
|
+
cleanup(container2);
|
|
516
|
+
return fail('Dropdown 1 should be closed after clicking dd2 trigger');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
cleanup(container1);
|
|
520
|
+
cleanup(container2);
|
|
521
|
+
pass('Opening another dropdown closes the first');
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
/*
|
|
525
|
+
Open Direction Attribute
|
|
526
|
+
*/
|
|
527
|
+
'should accept various open-direction values': async ({pass, fail}) => {
|
|
528
|
+
const directions = ['down left', 'down right', 'up left', 'up right', 'left up', 'left down', 'right up', 'right down'];
|
|
529
|
+
for(const dir of directions) {
|
|
530
|
+
const { container, dropdown } = await createDropdown({ openDirection: dir });
|
|
531
|
+
if(dropdown.openDirection !== dir) {
|
|
532
|
+
cleanup(container);
|
|
533
|
+
return fail(`Expected openDirection to be '${dir}', got ${dropdown.openDirection}`);
|
|
534
|
+
}
|
|
535
|
+
cleanup(container);
|
|
536
|
+
}
|
|
537
|
+
pass('All open-direction values accepted');
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
/*
|
|
541
|
+
Disabled Items
|
|
542
|
+
*/
|
|
543
|
+
'disabled items should be skipped in keyboard navigation': async ({pass, fail}) => {
|
|
544
|
+
const { container, dropdown } = await createDropdown({ opened: true, withDisabled: true });
|
|
545
|
+
const items = dropdown.querySelectorAll('button:not([slot]):not([disabled])');
|
|
546
|
+
items[2].focus(); // item3
|
|
547
|
+
const event = new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true });
|
|
548
|
+
document.dispatchEvent(event);
|
|
549
|
+
// Should skip disabled and wrap to first
|
|
550
|
+
if(document.activeElement !== items[0]) {
|
|
551
|
+
cleanup(container);
|
|
552
|
+
return fail('Focus should skip disabled item and wrap');
|
|
553
|
+
}
|
|
554
|
+
cleanup(container);
|
|
555
|
+
pass('Disabled items skipped in navigation');
|
|
556
|
+
}
|
|
557
|
+
};
|
|
@@ -26,10 +26,16 @@ export default {
|
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const stylesheetLink = component.shadowRoot.querySelector('link[
|
|
29
|
+
const stylesheetLink = component.shadowRoot.querySelector('link[rel="stylesheet"]');
|
|
30
30
|
if(!stylesheetLink) {
|
|
31
31
|
document.body.removeChild(component);
|
|
32
|
-
fail('Shadow root should contain
|
|
32
|
+
fail('Shadow root should contain stylesheet link');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if(!stylesheetLink.href.includes('kempo')) {
|
|
37
|
+
document.body.removeChild(component);
|
|
38
|
+
fail('Stylesheet should reference kempo css');
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -22,16 +22,16 @@ export default {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const stylesheetLink = component.shadowRoot.querySelector('link[
|
|
25
|
+
const stylesheetLink = component.shadowRoot.querySelector('link[rel="stylesheet"]');
|
|
26
26
|
if(!stylesheetLink) {
|
|
27
27
|
document.body.removeChild(component);
|
|
28
|
-
fail('Shadow root should contain
|
|
28
|
+
fail('Shadow root should contain stylesheet link');
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
if(stylesheetLink.
|
|
32
|
+
if(!stylesheetLink.href.includes('kempo')) {
|
|
33
33
|
document.body.removeChild(component);
|
|
34
|
-
fail('
|
|
34
|
+
fail('Stylesheet should reference kempo css');
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -100,7 +100,7 @@ export default {
|
|
|
100
100
|
|
|
101
101
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
102
102
|
|
|
103
|
-
const stylesheetBefore = component.shadowRoot.querySelector('link[
|
|
103
|
+
const stylesheetBefore = component.shadowRoot.querySelector('link[rel="stylesheet"]');
|
|
104
104
|
if(!stylesheetBefore) {
|
|
105
105
|
document.body.removeChild(component);
|
|
106
106
|
fail('Stylesheet should exist before re-render');
|
|
@@ -110,7 +110,7 @@ export default {
|
|
|
110
110
|
component.requestUpdate();
|
|
111
111
|
await component.updateComplete;
|
|
112
112
|
|
|
113
|
-
const stylesheetAfter = component.shadowRoot.querySelector('link[
|
|
113
|
+
const stylesheetAfter = component.shadowRoot.querySelector('link[rel="stylesheet"]');
|
|
114
114
|
if(!stylesheetAfter) {
|
|
115
115
|
document.body.removeChild(component);
|
|
116
116
|
fail('Stylesheet should persist after re-render');
|