svelte-select-5 6.0.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 (123) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/.prettierignore +2 -0
  3. package/.prettierrc +17 -0
  4. package/CHANGELOG.md +571 -0
  5. package/LICENSE +9 -0
  6. package/MIGRATION_GUIDE.md +98 -0
  7. package/README.md +316 -0
  8. package/docs/generate_theming_variables_md.cjs +53 -0
  9. package/docs/theming_variables.md +113 -0
  10. package/jsconfig.json +3 -0
  11. package/package.json +77 -0
  12. package/rollup.config.js +33 -0
  13. package/src/app.html +29 -0
  14. package/src/global.d.ts +1 -0
  15. package/src/lib/ChevronIcon.svelte +22 -0
  16. package/src/lib/ClearIcon.svelte +22 -0
  17. package/src/lib/LoadingIcon.svelte +33 -0
  18. package/src/lib/Select.svelte +1345 -0
  19. package/src/lib/filter.js +38 -0
  20. package/src/lib/get-items.js +25 -0
  21. package/src/lib/index.js +1 -0
  22. package/src/lib/tailwind.css +130 -0
  23. package/src/post-prepare.cjs +6 -0
  24. package/src/remove-styles.cjs +22 -0
  25. package/src/routes/+layout.js +1 -0
  26. package/src/routes/+layout.svelte +263 -0
  27. package/src/routes/+page.js +5 -0
  28. package/src/routes/+page.svelte +0 -0
  29. package/src/routes/examples/+page.svelte +84 -0
  30. package/src/routes/examples/advanced/create-item/+page.svelte +36 -0
  31. package/src/routes/examples/advanced/create-item-multiple/+page.svelte +38 -0
  32. package/src/routes/examples/advanced/floating-ui/+page.svelte +22 -0
  33. package/src/routes/examples/advanced/form-action/+page.server.js +10 -0
  34. package/src/routes/examples/advanced/form-action/+page.svelte +20 -0
  35. package/src/routes/examples/advanced/limit-multi-value/+page.svelte +31 -0
  36. package/src/routes/examples/advanced/long-item/+page.svelte +38 -0
  37. package/src/routes/examples/advanced/multi-item-checkboxes/+page.svelte +49 -0
  38. package/src/routes/examples/advanced/style-props/+page.svelte +14 -0
  39. package/src/routes/examples/advanced/virtual-list/+page.svelte +95 -0
  40. package/src/routes/examples/events/blur/+page.svelte +19 -0
  41. package/src/routes/examples/events/change/+page.svelte +16 -0
  42. package/src/routes/examples/events/clear/+page.svelte +18 -0
  43. package/src/routes/examples/events/error/+page.svelte +17 -0
  44. package/src/routes/examples/events/filter/+page.svelte +16 -0
  45. package/src/routes/examples/events/focus/+page.svelte +16 -0
  46. package/src/routes/examples/events/hoverItem/+page.svelte +16 -0
  47. package/src/routes/examples/events/input/+page.svelte +16 -0
  48. package/src/routes/examples/events/loaded/+page.svelte +23 -0
  49. package/src/routes/examples/props/class/+page.svelte +17 -0
  50. package/src/routes/examples/props/clearFilterTextOnBlur/+page.svelte +13 -0
  51. package/src/routes/examples/props/clearable/+page.svelte +13 -0
  52. package/src/routes/examples/props/closeListOnChange/+page.svelte +12 -0
  53. package/src/routes/examples/props/container-styles/+page.svelte +11 -0
  54. package/src/routes/examples/props/debounce-wait/+page.svelte +19 -0
  55. package/src/routes/examples/props/disabled/+page.svelte +15 -0
  56. package/src/routes/examples/props/filter-text/+page.svelte +14 -0
  57. package/src/routes/examples/props/floating-config/+page.svelte +42 -0
  58. package/src/routes/examples/props/focused/+page.svelte +16 -0
  59. package/src/routes/examples/props/group-header-selectable/+page.svelte +18 -0
  60. package/src/routes/examples/props/hide-empty-state/+page.svelte +8 -0
  61. package/src/routes/examples/props/id/+page.svelte +15 -0
  62. package/src/routes/examples/props/input-attributes/+page.svelte +11 -0
  63. package/src/routes/examples/props/item-id/+page.svelte +15 -0
  64. package/src/routes/examples/props/items/+page.svelte +15 -0
  65. package/src/routes/examples/props/just-value/+page.svelte +16 -0
  66. package/src/routes/examples/props/label/+page.svelte +15 -0
  67. package/src/routes/examples/props/list-auto-width/+page.svelte +21 -0
  68. package/src/routes/examples/props/list-offset/+page.svelte +21 -0
  69. package/src/routes/examples/props/list-open/+page.svelte +31 -0
  70. package/src/routes/examples/props/loadOptions/+page.svelte +16 -0
  71. package/src/routes/examples/props/loading/+page.svelte +15 -0
  72. package/src/routes/examples/props/multiFullItemClearable/+page.svelte +12 -0
  73. package/src/routes/examples/props/multiple/+page.svelte +12 -0
  74. package/src/routes/examples/props/name/+page.svelte +13 -0
  75. package/src/routes/examples/props/placeholder/+page.svelte +14 -0
  76. package/src/routes/examples/props/placeholder-always-show/+page.svelte +11 -0
  77. package/src/routes/examples/props/required/+page.svelte +14 -0
  78. package/src/routes/examples/props/searchable/+page.svelte +15 -0
  79. package/src/routes/examples/props/show-chevron/+page.svelte +15 -0
  80. package/src/routes/examples/props/value/+page.svelte +19 -0
  81. package/src/routes/examples/slots/chevron-icon/+page.svelte +16 -0
  82. package/src/routes/examples/slots/clear-icon/+page.svelte +21 -0
  83. package/src/routes/examples/slots/empty/+page.svelte +18 -0
  84. package/src/routes/examples/slots/input-hidden/+page.server.js +10 -0
  85. package/src/routes/examples/slots/input-hidden/+page.svelte +22 -0
  86. package/src/routes/examples/slots/item/+page.svelte +15 -0
  87. package/src/routes/examples/slots/list/+page.svelte +49 -0
  88. package/src/routes/examples/slots/list-append/+page.svelte +16 -0
  89. package/src/routes/examples/slots/list-prepend/+page.svelte +16 -0
  90. package/src/routes/examples/slots/loading-icon/+page.svelte +29 -0
  91. package/src/routes/examples/slots/multi-clear-icon/+page.svelte +16 -0
  92. package/src/routes/examples/slots/prepend/+page.svelte +22 -0
  93. package/src/routes/examples/slots/required/+page.svelte +31 -0
  94. package/src/routes/examples/slots/selection/+page.svelte +25 -0
  95. package/static/nav-icon.svg +2 -0
  96. package/static/svelte-select.png +0 -0
  97. package/svelte-select.png +0 -0
  98. package/svelte.config.js +10 -0
  99. package/tailwind.config.cjs +4 -0
  100. package/test/public/favicon.ico +0 -0
  101. package/test/public/index.html +15 -0
  102. package/test/src/ChevronSlotTest.svelte +19 -0
  103. package/test/src/ClearIconSlotTest.svelte +12 -0
  104. package/test/src/CustomItem.svelte +78 -0
  105. package/test/src/GroupHeaderNotSelectable.svelte +17 -0
  106. package/test/src/HoverItemIndexTest.svelte +21 -0
  107. package/test/src/InputHiddenSlotTest.svelte +12 -0
  108. package/test/src/ItemHeightTest.svelte +7 -0
  109. package/test/src/ItemSlotTest.svelte +11 -0
  110. package/test/src/ListSlotTest.svelte +14 -0
  111. package/test/src/LoadOptionsGroup.svelte +21 -0
  112. package/test/src/MultiItemColor.svelte +7 -0
  113. package/test/src/OuterListTest.svelte +16 -0
  114. package/test/src/PrependSlotTest.svelte +12 -0
  115. package/test/src/Select/ParentContainer.svelte +11 -0
  116. package/test/src/SelectionSlotMultipleTest.svelte +12 -0
  117. package/test/src/SelectionSlotTest.svelte +12 -0
  118. package/test/src/TestClearIcon.svelte +1 -0
  119. package/test/src/TestIcon.svelte +15 -0
  120. package/test/src/env.js +1 -0
  121. package/test/src/test-utils.js +124 -0
  122. package/test/src/tests.js +3745 -0
  123. package/vite.config.js +9 -0
@@ -0,0 +1,3745 @@
1
+ import SelectComponent from '../../src/lib/Select.svelte';
2
+ import ParentContainer from './Select/ParentContainer.svelte'
3
+ import {assert, test} from 'tape-modern';
4
+ import SelectionSlotTest from './SelectionSlotTest.svelte';
5
+ import SelectionSlotMultipleTest from './SelectionSlotMultipleTest.svelte';
6
+ import ChevronSlotTest from './ChevronSlotTest.svelte';
7
+ import PrependSlotTest from './PrependSlotTest.svelte';
8
+ import ClearIconSlotTest from './ClearIconSlotTest.svelte';
9
+ import ListSlotTest from './ListSlotTest.svelte';
10
+ import InputHiddenSlotTest from './InputHiddenSlotTest.svelte';
11
+ import ItemSlotTest from './ItemSlotTest.svelte';
12
+ import OuterListTest from './OuterListTest.svelte';
13
+ import ItemHeightTest from './ItemHeightTest.svelte';
14
+ import MultiItemColor from './MultiItemColor.svelte';
15
+ import GroupHeaderNotSelectable from './GroupHeaderNotSelectable.svelte';
16
+ import HoverItemIndexTest from './HoverItemIndexTest.svelte';
17
+ import LoadOptionsGroup from './LoadOptionsGroup.svelte';
18
+ import { createTestComponent } from './test-utils.js';
19
+
20
+ // Wrapper for backward compatibility with Svelte 3 API
21
+ function Select(options) {
22
+ return createTestComponent(SelectComponent, options);
23
+ }
24
+
25
+ function querySelectorClick(selector) {
26
+ if (selector === '.svelte-select') {
27
+ const event = new PointerEvent('pointerup')
28
+ document.querySelector(selector).dispatchEvent(event);
29
+ } else {
30
+ document.querySelector(selector).click();
31
+ }
32
+ }
33
+
34
+ function handleKeyboard(key) {
35
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': key}));
36
+ return new Promise(f => setTimeout(f, 0));
37
+ }
38
+
39
+ async function handleSet(component, data) {
40
+ await component.$set(data);
41
+ return new Promise(f => setTimeout(f, 0));
42
+ }
43
+
44
+ function getPosts(filterText) {
45
+ filterText = filterText ? filterText.replace(' ','_') : '';
46
+
47
+ return new Promise((resolve, reject) => {
48
+ if (filterText.length < 2) return resolve([]);
49
+
50
+ const xhr = new XMLHttpRequest();
51
+ xhr.open('GET', `https://api.punkapi.com/v2/beers?beer_name=${filterText}`);
52
+ xhr.send();
53
+
54
+ xhr.onload = () => {
55
+ if (xhr.status >= 200 && xhr.status < 300) {
56
+ resolve(JSON.parse(xhr.response).sort((a, b) => {
57
+ if (a.name > b.name) return 1;
58
+ if (a.name < b.name) return -1;
59
+ }).map(i => { return { value: i.id, label:i.name}}));
60
+ } else {
61
+ reject()
62
+ }
63
+ };
64
+ });
65
+ }
66
+
67
+ function resolvePromise() {
68
+ return new Promise((resolve, reject) => {
69
+ resolve(['a', 'b', 'c']);
70
+ })
71
+ }
72
+
73
+ function rejectPromise() {
74
+ return new Promise((resolve, reject) => {
75
+ reject('error 123');
76
+ })
77
+ }
78
+
79
+ // setup
80
+ const target = document.createElement('main');
81
+ document.body.appendChild(target);
82
+
83
+ const testTarget = document.createElement("div");
84
+ testTarget.id = 'testTemplate';
85
+ document.body.appendChild(testTarget);
86
+
87
+ const extraTarget = document.createElement("div");
88
+ extraTarget.id = 'extra';
89
+ document.body.appendChild(extraTarget);
90
+
91
+ const items = [
92
+ {value: 'chocolate', label: 'Chocolate'},
93
+ {value: 'pizza', label: 'Pizza'},
94
+ {value: 'cake', label: 'Cake'},
95
+ {value: 'chips', label: 'Chips'},
96
+ {value: 'ice-cream', label: 'Ice Cream'},
97
+ ];
98
+
99
+ const itemsWithGroup = [
100
+ {value: 'chocolate', label: 'Chocolate', group: 'Sweet'},
101
+ {value: 'pizza', label: 'Pizza', group: 'Savory'},
102
+ {value: 'cake', label: 'Cake', group: 'Sweet'},
103
+ {value: 'chips', label: 'Chips', group: 'Savory'},
104
+ {value: 'ice-cream', label: 'Ice Cream', group: 'Sweet'}
105
+ ];
106
+
107
+ const itemsWithGroupAndSelectable = [
108
+ {value: 'chocolate', label: 'Chocolate', group: 'Sweet'},
109
+ {value: 'pizza', label: 'Pizza', group: 'Savory'},
110
+ {value: 'cake', label: 'Cake', group: 'Sweet', selectable: false},
111
+ {value: 'chips', label: 'Chips', group: 'Savory', selectable: false},
112
+ {value: 'ice-cream', label: 'Ice Cream', group: 'Sweet'}
113
+ ]
114
+
115
+ const itemsWithIndex = [
116
+ {value: 'chocolate', label: 'Chocolate', index: 0},
117
+ {value: 'pizza', label: 'Pizza', index: 1},
118
+ {value: 'cake', label: 'Cake', index: 2},
119
+ {value: 'chips', label: 'Chips', index: 3},
120
+ {value: 'ice-cream', label: 'Ice Cream', index: 4},
121
+ ];
122
+
123
+ const collection = [
124
+ {_id: 0, label: 'Chocolate'},
125
+ {_id: 1, label: 'Pizza'},
126
+ {_id: 2, label: 'Cake'},
127
+ {_id: 3, label: 'Chips'},
128
+ {_id: 4, label: 'Ice Cream'}
129
+ ];
130
+
131
+ const itemsWithSelectable = [
132
+ {value: 'notSelectable1', label: 'NotSelectable1', selectable: false},
133
+ {value: 'selectableDefault', label: 'SelectableDefault'},
134
+ {value: 'selectableTrue', label: 'SelectableTrue', selectable: true},
135
+ {value: 'notSelectable2', label: 'NotSelectable2', selectable: false}
136
+ ];
137
+
138
+ function itemsPromise() {
139
+ return new Promise(resolve => {
140
+ setTimeout(() => {
141
+ resolve(JSON.parse(JSON.stringify(items)));
142
+ })
143
+ })
144
+ }
145
+
146
+ function wait(ms) {
147
+ return new Promise(f => setTimeout(f, ms));
148
+ }
149
+
150
+ assert.arrayEqual = (a, b) => {
151
+ assert.ok(Array.isArray(a));
152
+ assert.ok(Array.isArray(b));
153
+ assert.equal(a.length, b.length);
154
+ assert.ok(a.every((val, i) => val === b[i]));
155
+ };
156
+
157
+ test('when focused true container adds focused class', async (t) => {
158
+ const select = new Select({
159
+ target,
160
+ props: {
161
+ focused: true
162
+ }
163
+ });
164
+
165
+ t.ok(target.querySelector('.focused'));
166
+
167
+ select.$destroy();
168
+ });
169
+
170
+ test('when focused changes to true input should focus', async (t) => {
171
+ const select = new Select({
172
+ target,
173
+ });
174
+
175
+ select.$set({focused: true});
176
+
177
+
178
+ const hasFocused = target.querySelector('.svelte-select input');
179
+ t.ok(hasFocused);
180
+ select.$destroy();
181
+ });
182
+
183
+ test('default empty list', async (t) => {
184
+ const select = new Select({
185
+ target,
186
+ props: {
187
+ listOpen: true
188
+ }
189
+ });
190
+
191
+ t.ok(document.querySelector('.empty'));
192
+
193
+ select.$destroy();
194
+ });
195
+
196
+ test('default list with five items', async (t) => {
197
+ const select = new Select({
198
+ target,
199
+ props: {
200
+ listOpen: true,
201
+ items: itemsWithIndex
202
+ }
203
+ });
204
+
205
+ t.ok(document.getElementsByClassName('list-item').length);
206
+
207
+ select.$destroy();
208
+ });
209
+
210
+ test('should highlight active list item', async (t) => {
211
+ const select = new Select({
212
+ target,
213
+ props: {
214
+ listOpen: true,
215
+ items: itemsWithIndex,
216
+ value: {value: 'pizza', label: 'Pizza', index: 1}
217
+ }
218
+ });
219
+
220
+ t.ok(document.querySelector('.list-item .active').innerHTML === 'Pizza');
221
+
222
+ select.$destroy();
223
+ });
224
+
225
+ test('list scrolls to active item', async (t) => {
226
+ const extras = [
227
+ {value: 'chicken', label: 'Chicken', index: 5},
228
+ {value: 'fried-chicken', label: 'Fried Chicken', index: 6},
229
+ {value: 'sunday-roast', label: 'Sunday Roast', index: 7},
230
+ ];
231
+
232
+ const select = new Select({
233
+ target,
234
+ props: {
235
+
236
+ items: itemsWithIndex.concat(extras),
237
+ value: {value: 'sunday-roast', label: 'Sunday Roast'},
238
+ }
239
+ });
240
+
241
+ select.listOpen = true;
242
+ let offsetBounding;
243
+ const container = document.querySelector('.svelte-select-list');
244
+ const focusedElemBounding = container.querySelector('.list-item .active');
245
+ if (focusedElemBounding) {
246
+ offsetBounding = container.getBoundingClientRect().bottom - focusedElemBounding.getBoundingClientRect().bottom;
247
+ }
248
+
249
+ t.equal(offsetBounding, 0);
250
+ select.$destroy();
251
+ });
252
+
253
+ test('list scrolls to hovered item when navigating with keys', async (t) => {
254
+ const extras = [
255
+ {value: 'chicken', label: 'Chicken', index: 5},
256
+ {value: 'fried-chicken', label: 'Fried Chicken', index: 6},
257
+ {value: 'sunday-roast', label: 'Sunday Roast', index: 7},
258
+ ];
259
+
260
+ const select = new Select({
261
+ target,
262
+ props: {
263
+ listOpen: true,
264
+ items: itemsWithIndex.concat(extras)
265
+ }
266
+ });
267
+
268
+ const container = document.querySelector('.svelte-select-list');
269
+
270
+
271
+ const totalListItems = container.querySelectorAll('.list-item').length;
272
+ let selectedItemsAreWithinBounds = true;
273
+ let loopCount = 1;
274
+
275
+ do {
276
+ await handleKeyboard('ArrowDown');
277
+
278
+ const hoveredItem = container.querySelector('.list-item .hover');
279
+ const isInViewport = container.getBoundingClientRect().bottom - hoveredItem.getBoundingClientRect().bottom >= 0;
280
+
281
+ selectedItemsAreWithinBounds = selectedItemsAreWithinBounds && isInViewport;
282
+
283
+ loopCount += 1;
284
+ } while (loopCount < totalListItems);
285
+
286
+
287
+ t.ok(selectedItemsAreWithinBounds);
288
+ select.$destroy();
289
+ });
290
+
291
+ test('hover item updates on keyUp or keyDown', async (t) => {
292
+ const select = new Select({
293
+ target,
294
+ props: {
295
+ listOpen: true,
296
+ items: items
297
+ }
298
+ });
299
+
300
+ await handleKeyboard('ArrowDown', document.querySelector('.svelte-select-list'));
301
+ const focusedElemBounding = document.querySelector('.list-item .hover');
302
+ t.equal(focusedElemBounding.innerHTML.trim(), `Pizza`);
303
+ select.$destroy();
304
+ });
305
+
306
+ test('on enter active item fires a select event', async (t) => {
307
+ const select = new Select({
308
+ target,
309
+ props: {
310
+ listOpen: true,
311
+ items: itemsWithIndex
312
+ }
313
+ });
314
+
315
+ let value = undefined;
316
+
317
+ select.$on('change', event => {
318
+ value = JSON.stringify(event.detail);
319
+ });
320
+
321
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
322
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
323
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
324
+ await wait(0);
325
+ t.equal(value, JSON.stringify({value: 'cake', label: 'Cake', index: 2}));
326
+ select.$destroy();
327
+ });
328
+
329
+ test('on tab active item fires a select event', async (t) => {
330
+ const select = new Select({
331
+ target,
332
+ props: {
333
+ listOpen: true,
334
+ items: itemsWithIndex
335
+ }
336
+ });
337
+
338
+ let value = undefined;
339
+ select.$on('change', event => {
340
+ value = JSON.stringify(event.detail);
341
+ });
342
+
343
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
344
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
345
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Tab'}));
346
+ await wait(0);
347
+ t.equal(value, JSON.stringify({value: 'cake', label: 'Cake', index: 2}));
348
+ select.$destroy();
349
+ });
350
+
351
+ test('on selected of current active item does not fire a select event', async (t) => {
352
+ const select = new Select({
353
+ target,
354
+ props: {
355
+ listOpen: true,
356
+ items: itemsWithIndex,
357
+ value: { value: 'chocolate', label: 'Chocolate', index: 0 }
358
+ }
359
+ });
360
+
361
+ let itemSelectedFired = false;
362
+
363
+ select.$on('change', () => {
364
+ itemSelectedFired = true;
365
+ });
366
+
367
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
368
+
369
+ t.equal(itemSelectedFired, false);
370
+ select.$destroy();
371
+ });
372
+
373
+ test('selected item\'s default view', async (t) => {
374
+ const select = new Select({
375
+ target,
376
+ props: {
377
+ value: {value: 'chips', label: 'Chips'},
378
+ }
379
+ });
380
+
381
+ t.ok(target.querySelector('.selected-item').innerHTML === 'Chips');
382
+ select.$destroy();
383
+ });
384
+
385
+ test('select view updates with value updates', async (t) => {
386
+ const select = new Select({
387
+ target
388
+ });
389
+
390
+ await handleSet(select, {value: {value: 'chips', label: 'Chips'}});
391
+ t.ok(target.querySelector('.selected-item').innerHTML === 'Chips');
392
+
393
+ select.$destroy();
394
+ });
395
+
396
+ test('clear wipes value and updates view', async (t) => {
397
+ const select = new Select({
398
+ target,
399
+ props: {
400
+ value: {value: 'chips', label: 'Chips'},
401
+ }
402
+ });
403
+
404
+ await wait(0);
405
+ await handleSet(select, {value: undefined});
406
+ t.ok(!target.querySelector('.selected-item'));
407
+
408
+ select.$destroy();
409
+ });
410
+
411
+ test('clicking on Select opens list', async (t) => {
412
+ const select = new Select({
413
+ target,
414
+ props: {
415
+ }
416
+ });
417
+
418
+ await querySelectorClick('.svelte-select');
419
+ const listContainer = document.querySelector('.svelte-select-list');
420
+ t.ok(listContainer);
421
+
422
+ select.$destroy();
423
+ });
424
+
425
+ test('Select opens list populated with items', async (t) => {
426
+ const select = new Select({
427
+ target,
428
+ props: {
429
+ items
430
+ }
431
+ });
432
+
433
+ await querySelectorClick('.svelte-select');
434
+ t.ok(document.querySelector('.list-item'));
435
+
436
+ select.$destroy();
437
+ });
438
+
439
+ test('list starts with first item in hover state', async (t) => {
440
+ const select = new Select({
441
+ target,
442
+ props: {
443
+ items
444
+ }
445
+ });
446
+
447
+ await querySelectorClick('.svelte-select');
448
+ t.ok(document.querySelector('.list-item .hover').innerHTML === 'Chocolate');
449
+
450
+ select.$destroy();
451
+ });
452
+
453
+ test('select item from list', async (t) => {
454
+ const select = new Select({
455
+ target,
456
+ props: {
457
+ items,
458
+ }
459
+ });
460
+
461
+ await querySelectorClick('.svelte-select');
462
+ await handleKeyboard('ArrowDown');
463
+ await handleKeyboard('ArrowDown');
464
+ await handleKeyboard('Enter');
465
+ t.ok(document.querySelector('.selected-item').innerHTML === 'Cake');
466
+
467
+ select.$destroy();
468
+ });
469
+
470
+ test('when placement is set to top list should be above the input', async (t) => {
471
+ const select = new Select({
472
+ target,
473
+ props: {
474
+ items,
475
+ listOpen: true,
476
+ floatingConfig: { placement: 'top-start' }
477
+ }
478
+ });
479
+
480
+ target.style.margin = '300px 0 0 0';
481
+ await wait(0);
482
+ const distanceOfListBottomFromViewportTop = document.querySelector('.svelte-select-list').getBoundingClientRect().bottom;
483
+ const distanceOfInputTopFromViewportTop = document.querySelector('.svelte-select').getBoundingClientRect().top;
484
+ t.ok(distanceOfListBottomFromViewportTop <= distanceOfInputTopFromViewportTop);
485
+ target.style.margin = '0';
486
+ select.$destroy();
487
+ });
488
+
489
+ test('when placement is set to bottom the list should be below the input', async (t) => {
490
+ const select = new Select({
491
+ target,
492
+ props: {
493
+ items,
494
+ listOpen: true,
495
+ floatingConfig: { placement: 'bottom-start' }
496
+ }
497
+ });
498
+
499
+ await wait(0);
500
+ const distanceOfListTopFromViewportTop = document.querySelector('.svelte-select-list').getBoundingClientRect().top;
501
+ const distanceOfInputBottomFromViewportTop = document.querySelector('.svelte-select').getBoundingClientRect().bottom;
502
+
503
+ t.ok(distanceOfListTopFromViewportTop >= distanceOfInputBottomFromViewportTop);
504
+
505
+ select.$destroy();
506
+ });
507
+
508
+ test('blur should close list and remove focus from select', async (t) => {
509
+ const div = document.createElement('div');
510
+ document.body.appendChild(div);
511
+
512
+ const select = new Select({
513
+ target,
514
+ props: {
515
+ items
516
+ }
517
+ });
518
+
519
+ select.$set({focused: true});
520
+ div.click();
521
+ div.remove();
522
+ t.ok(!document.querySelector('.svelte-select-list'));
523
+ t.ok(document.querySelector('.svelte-select input') !== document.activeElement);
524
+ select.$destroy();
525
+ });
526
+
527
+ test('blur should close list and remove focus from select but preserve filterText value', async (t) => {
528
+ const div = document.createElement('div');
529
+ document.body.appendChild(div);
530
+
531
+ const select = new Select({
532
+ target,
533
+ props: {
534
+ items,
535
+ clearFilterTextOnBlur: false
536
+ }
537
+ });
538
+
539
+ const selectInput = document.querySelector('.svelte-select input');
540
+
541
+ select.$set({focused: true});
542
+ select.$set({filterText: 'potato'});
543
+ div.click();
544
+ div.remove();
545
+ t.ok(!document.querySelector('.svelte-select-list'));
546
+ t.ok(selectInput !== document.activeElement);
547
+
548
+ await wait(0);
549
+ t.ok(selectInput.value === 'potato');
550
+ select.$destroy();
551
+ });
552
+
553
+ test('blur should close list and remove focus from select and clear filterText value', async (t) => {
554
+ const select = new Select({
555
+ target,
556
+ props: {
557
+ items
558
+ }
559
+ });
560
+
561
+ const selectInput = document.querySelector('.svelte-select input');
562
+
563
+ select.$set({listOpen: true});
564
+ select.$set({filterText: 'potato'});
565
+ await wait(0);
566
+ selectInput.blur();
567
+ await wait(0);
568
+ t.ok(selectInput.value === '');
569
+ select.$destroy();
570
+ });
571
+
572
+ test('selecting item should close list but keep focus on select', async (t) => {
573
+ const select = new Select({
574
+ target,
575
+ props: {
576
+ items
577
+ }
578
+ });
579
+
580
+ querySelectorClick('.svelte-select')
581
+ await wait(0);
582
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
583
+ await wait(0);
584
+ t.ok(!document.querySelector('.svelte-select-list'));
585
+ t.ok(document.querySelector('.svelte-select.focused'));
586
+ select.$destroy();
587
+ });
588
+
589
+ test('clicking Select with selected item should open list with item listed as active', async (t) => {
590
+ const select = new Select({
591
+ target,
592
+ props: {
593
+ items
594
+ }
595
+ });
596
+ querySelectorClick('.svelte-select');
597
+ await wait(0);
598
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
599
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
600
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
601
+ await wait(0);
602
+ querySelectorClick('.svelte-select');
603
+ await wait(0);
604
+ t.ok(document.querySelector('.list-item .active').innerHTML === 'Cake');
605
+ select.$destroy();
606
+ });
607
+
608
+ test('focus on Select input updates focus state', async (t) => {
609
+ const select = new Select({
610
+ target,
611
+ props: {
612
+ items
613
+ }
614
+ });
615
+
616
+ document.querySelector('.svelte-select input').focus();
617
+
618
+ t.ok(select.focused);
619
+ select.$destroy();
620
+ });
621
+
622
+ test('key up and down when Select focused opens list', async (t) => {
623
+ const select = new Select({
624
+ target,
625
+ props: {
626
+ items
627
+ }
628
+ });
629
+
630
+ const input = document.querySelector('.svelte-select input');
631
+ input.focus();
632
+ await wait(0);
633
+ t.ok(select.focused);
634
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
635
+ await wait(0);
636
+ t.ok(document.querySelector('.svelte-select-list'));
637
+
638
+ select.$destroy();
639
+ });
640
+
641
+ test('list should keep width of parent Select', async (t) => {
642
+ const select = new Select({
643
+ target,
644
+ props: {
645
+ items,
646
+ focused: true
647
+ }
648
+ });
649
+
650
+ const input = document.querySelector('.svelte-select input');
651
+ input.focus();
652
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
653
+ await wait(0);
654
+ const selectContainer = document.querySelector('.svelte-select');
655
+ const listContainer = document.querySelector('.svelte-select-list');
656
+ t.equal(selectContainer.offsetWidth, listContainer.offsetWidth);
657
+
658
+ select.$destroy();
659
+ });
660
+
661
+ test('Placeholder text should reappear when list is closed', async (t) => {
662
+ const div = document.createElement('div');
663
+ document.body.appendChild(div);
664
+
665
+ const select = new Select({
666
+ target,
667
+ props: {
668
+ items
669
+ }
670
+ });
671
+
672
+ querySelectorClick('.svelte-select');
673
+ div.click();
674
+ div.remove();
675
+ const selectInput = document.querySelector('.svelte-select input');
676
+ t.equal(selectInput.attributes.placeholder.value, 'Please select');
677
+
678
+ select.$destroy();
679
+ });
680
+
681
+ test('typing in Select filter will hide selected Item', async (t) => {
682
+ const select = new Select({
683
+ target,
684
+ props: {
685
+ items
686
+ }
687
+ });
688
+
689
+ querySelectorClick('.svelte-select');
690
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
691
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
692
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
693
+ select.$set({filterText: 'potato'});
694
+ t.ok(!document.querySelector('.svelte-select .value'));
695
+
696
+ select.$destroy();
697
+ });
698
+
699
+ test('clearing selected item closes list if open', async (t) => {
700
+ const select = new Select({
701
+ target,
702
+ props: {
703
+ items
704
+ }
705
+ });
706
+
707
+ querySelectorClick('.svelte-select');
708
+ await wait(0);
709
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
710
+ await wait(0);
711
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
712
+ await wait(0);
713
+ select.handleClear();
714
+ await wait(0);
715
+ t.ok(!document.querySelector('.svelte-select-list'));
716
+
717
+ select.$destroy();
718
+ });
719
+
720
+ test('closing list clears Select filter text', async (t) => {
721
+ const div = document.createElement('div');
722
+ document.body.appendChild(div);
723
+
724
+ const select = new Select({
725
+ target,
726
+ props: {
727
+ items
728
+ }
729
+ });
730
+
731
+ querySelectorClick('.svelte-select');
732
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
733
+ select.$set({filterText: 'potato'});
734
+ div.click();
735
+ div.remove();
736
+ const selectInput = document.querySelector('.svelte-select input');
737
+ t.equal(selectInput.attributes.placeholder.value, 'Please select');
738
+
739
+ select.$destroy();
740
+ });
741
+
742
+ test('closing list clears Select filter text', async (t) => {
743
+ const div = document.createElement('div');
744
+ document.body.appendChild(div);
745
+
746
+ const select = new Select({
747
+ target,
748
+ props: {
749
+ items
750
+ }
751
+ });
752
+
753
+ querySelectorClick('.svelte-select');
754
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
755
+ select.$set({filterText: 'potato'});
756
+ div.click();
757
+ div.remove();
758
+ const selectInput = document.querySelector('.svelte-select input');
759
+ t.equal(selectInput.attributes.placeholder.value, 'Please select');
760
+
761
+ select.$destroy();
762
+ });
763
+
764
+ test('closing list item clears Select filter text', async (t) => {
765
+ const div = document.createElement('div');
766
+ document.body.appendChild(div);
767
+
768
+ const select = new Select({
769
+ target,
770
+ props: {
771
+ items
772
+ }
773
+ });
774
+
775
+ querySelectorClick('.svelte-select');
776
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
777
+ select.$set({filterText: 'potato'});
778
+ div.click();
779
+ div.remove();
780
+ const selectInput = document.querySelector('.svelte-select input');
781
+ t.equal(selectInput.attributes.placeholder.value, 'Please select');
782
+
783
+ select.$destroy();
784
+ });
785
+
786
+ test('typing while Select is focused populates Select filter text', async (t) => {
787
+ const select = new Select({
788
+ target,
789
+ props: {
790
+ items
791
+ }
792
+ });
793
+
794
+ select.$set({focused: true});
795
+ document.querySelector('.svelte-select input').blur();
796
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 't'}));
797
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'e'}));
798
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 's'}));
799
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 't'}));
800
+ // KeyboardEvent not firing in svelte - not sure why, manual test seems to work
801
+
802
+ select.$destroy();
803
+ });
804
+
805
+ test('Select input placeholder wipes while item is selected', async (t) => {
806
+ const select = new Select({
807
+ target,
808
+ props: {
809
+ items,
810
+ value: {name: 'Item #2'},
811
+ }
812
+ });
813
+
814
+ const selectInput = document.querySelector('.svelte-select input');
815
+ t.equal(selectInput.attributes.placeholder.value, '');
816
+
817
+ select.$destroy();
818
+ });
819
+
820
+ test('Select listOpen state controls list', async (t) => {
821
+ const select = new Select({
822
+ target,
823
+ props: {
824
+ items,
825
+ listOpen: true
826
+ }
827
+ });
828
+
829
+ await wait(0);
830
+ t.ok(document.querySelector('.svelte-select-list'));
831
+ await handleSet(select, {listOpen: false})
832
+ t.ok(!document.querySelector('.svelte-select-list'));
833
+
834
+ select.$destroy();
835
+ });
836
+
837
+ test('clicking Select toggles list open state', async (t) => {
838
+ const select = new Select({
839
+ target,
840
+ props: {
841
+ items
842
+ }
843
+ });
844
+
845
+ t.ok(!document.querySelector('.svelte-select-list'));
846
+ await querySelectorClick('.svelte-select');
847
+ t.ok(document.querySelector('.svelte-select-list'));
848
+ await querySelectorClick('.svelte-select');
849
+ t.ok(!document.querySelector('.svelte-select-list'));
850
+ select.$destroy();
851
+ });
852
+
853
+ test('Select filter text filters list', async (t) => {
854
+ const select = new Select({
855
+ target,
856
+ props: {
857
+ items
858
+ }
859
+ });
860
+
861
+ t.ok(select.getFilteredItems().length === 5);
862
+ select.filterText = 'Ice';
863
+ t.ok(select.getFilteredItems().length === 1);
864
+
865
+ select.$destroy();
866
+ });
867
+
868
+ test('Select filter text filters list with itemFilter', async (t) => {
869
+ const select = new Select({
870
+ target,
871
+ props: {
872
+ items,
873
+ itemFilter: (label, filterText, option) => label === 'Ice Cream'
874
+ }
875
+ });
876
+
877
+ t.ok(select.getFilteredItems().length === 1);
878
+ select.filterText = 'cream ice';
879
+ t.ok(select.getFilteredItems().length === 1);
880
+
881
+ select.$destroy();
882
+ });
883
+
884
+ test('Typing in the Select filter opens list', async (t) => {
885
+ const select = new Select({
886
+ target,
887
+ props: {
888
+ items,
889
+ focused: true
890
+ }
891
+ });
892
+
893
+ await handleSet(select, {filterText: '5'})
894
+ t.ok(document.querySelector('.svelte-select-list'));
895
+ select.$destroy();
896
+ });
897
+
898
+ test('While filtering, the first item in list should receive hover class', async (t) => {
899
+ const select = new Select({
900
+ target,
901
+ props: {
902
+ items,
903
+ focused: true
904
+ }
905
+ });
906
+
907
+ await wait(0);
908
+ await handleSet(select, {filterText: 'I'})
909
+ t.ok(document.querySelector('.list-item .hover'));
910
+ select.$destroy();
911
+ });
912
+
913
+ test('Select container styles can be overridden', async (t) => {
914
+ const select = new Select({
915
+ target,
916
+ props: {
917
+ items,
918
+ value: {name: 'Item #2'},
919
+ containerStyles: `padding-left: 40px;`
920
+ }
921
+ });
922
+
923
+ t.equal(document.querySelector('.svelte-select').style.cssText, `padding-left: 40px;`);
924
+ select.$destroy();
925
+ });
926
+
927
+ test('Select can be disabled', async (t) => {
928
+ const select = new Select({
929
+ target,
930
+ props: {
931
+ items,
932
+ disabled: true,
933
+ }
934
+ });
935
+
936
+ t.ok(document.querySelector('.svelte-select.disabled'));
937
+
938
+ select.$destroy();
939
+ });
940
+
941
+ test('Select list closes when you click enter', async (t) => {
942
+ const select = new Select({
943
+ target,
944
+ props: {
945
+ items,
946
+ focused: true
947
+ }
948
+ });
949
+
950
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
951
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
952
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
953
+
954
+
955
+ select.$destroy();
956
+ });
957
+
958
+ test('tabbing should move between tabIndexes and others Selects', async (t) => {
959
+ const select = new Select({
960
+ target,
961
+ props: {
962
+ items,
963
+ focused: false
964
+ }
965
+ });
966
+
967
+ const other = new Select({
968
+ target: extraTarget,
969
+ props: {
970
+ items,
971
+ focused: false
972
+ }
973
+ });
974
+
975
+ // window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Tab'}));
976
+ // TAB not working from Puppeteer - not sure why.
977
+
978
+ select.$destroy();
979
+ other.$destroy();
980
+ });
981
+
982
+ test(`shouldn't be able to clear a disabled Select`, async (t) => {
983
+ const select = new Select({
984
+ target,
985
+ props: {
986
+ items,
987
+ disabled: true,
988
+ value: {name: 'Item #4'}
989
+ }
990
+ });
991
+
992
+
993
+ t.ok(!document.querySelector('.clear-select'));
994
+
995
+ select.$destroy();
996
+ });
997
+
998
+ test(`two way binding between Select and it's parent component`, async (t) => {
999
+ const parent = new ParentContainer({
1000
+ target,
1001
+ props: {
1002
+ items,
1003
+ value: {value: 'chips', label: 'Chips'},
1004
+ }
1005
+ });
1006
+
1007
+ t.equal(document.querySelector('.selected-item').innerHTML, document.querySelector('.result').innerHTML);
1008
+
1009
+ parent.$set({
1010
+ value: {value: 'ice-cream', label: 'Ice Cream'},
1011
+ });
1012
+
1013
+ t.equal(document.querySelector('.selected-item').innerHTML, document.querySelector('.result').innerHTML);
1014
+ querySelectorClick('.svelte-select');
1015
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
1016
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
1017
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
1018
+ t.equal(document.querySelector('.selected-item').innerHTML, document.querySelector('.result').innerHTML);
1019
+
1020
+ parent.$destroy();
1021
+ });
1022
+
1023
+ test(`show ellipsis for overflowing text in a list item`, async (t) => {
1024
+ const longest = 'super super super super super super super super super super super super super super super super super super super super super super super super super super super super loooooonnnng name';
1025
+
1026
+ target.style.width = '300px';
1027
+ target.style.position = 'relative';
1028
+
1029
+ const select = new Select({
1030
+ target,
1031
+ props: {
1032
+ listOpen: true,
1033
+ items: [
1034
+ {
1035
+ index: 0,
1036
+ label: longest
1037
+ },
1038
+ {
1039
+ index: 1,
1040
+ label: 'Not so loooooonnnng name'
1041
+ }
1042
+ ]
1043
+ }
1044
+ });
1045
+
1046
+ await wait(0);
1047
+ const first = document.querySelector('.list-item:first-child .item');
1048
+ const last = document.querySelector('.list-item:last-child .item');
1049
+
1050
+ t.ok(first.scrollWidth > first.clientWidth);
1051
+ t.ok(last.scrollWidth === last.clientWidth);
1052
+
1053
+ select.$destroy();
1054
+ target.style.width = '';
1055
+ });
1056
+
1057
+ test('focusing in an external textarea should close and blur it', async (t) => {
1058
+ const textarea = document.createElement('textarea');
1059
+ document.body.appendChild(textarea);
1060
+
1061
+ const select = new Select({
1062
+ target,
1063
+ props: {
1064
+ listOpen: true,
1065
+ items,
1066
+ }
1067
+ });
1068
+
1069
+ textarea.focus();
1070
+ await wait(0);
1071
+ t.ok(!select.listOpen);
1072
+ textarea.remove();
1073
+ select.$destroy();
1074
+ });
1075
+
1076
+ test('if only one item in list it should have hover state', async (t) => {
1077
+ const select = new Select({
1078
+ target,
1079
+ props: {
1080
+ listOpen: true,
1081
+ items: [{
1082
+ index: 0,
1083
+ name: 'test one'
1084
+ }]
1085
+ }
1086
+ });
1087
+
1088
+ t.ok(document.querySelector('.list-item .item').classList.contains('hover'));
1089
+
1090
+ select.$destroy();
1091
+ });
1092
+
1093
+ test(`hovered item in a filtered list shows hover state`, async (t) => {
1094
+ const select = new Select({
1095
+ target,
1096
+ props: {
1097
+ items
1098
+ }
1099
+ });
1100
+
1101
+ select.$set({filterText: 'i'});
1102
+
1103
+ // const lastItem = document.querySelector('.list-item:last-child');
1104
+ // hover item and check for hover state
1105
+
1106
+ t.ok(true);
1107
+
1108
+ select.$destroy();
1109
+ });
1110
+
1111
+ test(`data shouldn't be stripped from item - currently only saves name`, async (t) => {
1112
+ const select = new Select({
1113
+ target,
1114
+ props: {
1115
+ items
1116
+ }
1117
+ });
1118
+
1119
+ await querySelectorClick('.svelte-select');
1120
+ await querySelectorClick('.list-item');
1121
+ t.equal(JSON.stringify(select.value), JSON.stringify({value: 'chocolate', label: 'Chocolate'}));
1122
+
1123
+ select.$destroy();
1124
+ });
1125
+
1126
+ test('should not be able to clear when clearing is disabled', async (t) => {
1127
+ const select = new Select({
1128
+ target,
1129
+ props: {
1130
+ items,
1131
+ clearable: false
1132
+ }
1133
+ });
1134
+
1135
+ querySelectorClick('.svelte-select');
1136
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
1137
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
1138
+
1139
+ t.ok(!document.querySelector('.clear-select'));
1140
+
1141
+ select.$destroy();
1142
+ });
1143
+
1144
+ test('should not be able to search when searching is disabled', async (t) => {
1145
+ const select = new Select({
1146
+ target,
1147
+ props: {
1148
+ items,
1149
+ searchable: false
1150
+ }
1151
+ });
1152
+
1153
+ const selectInput = document.querySelector('.svelte-select input');
1154
+ t.ok(selectInput.attributes.readonly);
1155
+
1156
+ select.$destroy();
1157
+ });
1158
+
1159
+ test('placeholder should be prop value', async (t) => {
1160
+ const div = document.createElement('div');
1161
+ document.body.appendChild(div);
1162
+
1163
+ const placeholder = 'Test placeholder value';
1164
+
1165
+ const select = new Select({
1166
+ target,
1167
+ props: {
1168
+ items: itemsWithGroup,
1169
+ placeholder
1170
+ }
1171
+ });
1172
+
1173
+ const selectInput = document.querySelector('.svelte-select input');
1174
+ t.equal(selectInput.attributes.placeholder.value, placeholder);
1175
+
1176
+ select.$destroy();
1177
+ });
1178
+
1179
+ test('should display loading icon when loading is enabled', async (t) => {
1180
+ const div = document.createElement('div');
1181
+ document.body.appendChild(div);
1182
+
1183
+ const select = new Select({
1184
+ target,
1185
+ props: {
1186
+ items,
1187
+ loading: true
1188
+ }
1189
+ });
1190
+
1191
+ t.ok(document.querySelector('.loading'));
1192
+
1193
+ select.$destroy();
1194
+ });
1195
+
1196
+ test('inputStyles prop applies css to select input', async (t) => {
1197
+ const select = new Select({
1198
+ target,
1199
+ props: {
1200
+ items,
1201
+ value: {value: 'pizza', label: 'Pizza'},
1202
+ inputStyles: `padding-left: 40px;`
1203
+ }
1204
+ });
1205
+
1206
+ t.equal(document.querySelector('.svelte-select input').style.cssText, `padding-left: 40px;`);
1207
+ select.$destroy();
1208
+ });
1209
+
1210
+ test('items should be grouped by groupBy expression', async (t) => {
1211
+ const select = new Select({
1212
+ target,
1213
+ props: {
1214
+ listOpen: true,
1215
+ items: itemsWithGroup,
1216
+ groupBy
1217
+ }
1218
+ });
1219
+
1220
+ function groupBy(item) {
1221
+ return item.group;
1222
+ }
1223
+
1224
+ let title = document.querySelector('.list-group-title').innerHTML;
1225
+ t.ok(title === 'Sweet');
1226
+ let item = document.querySelector('.list-item .item.group-item').innerHTML;
1227
+ t.ok(item === 'Chocolate');
1228
+ select.$destroy();
1229
+ });
1230
+
1231
+
1232
+ test('clicking group header should not make a selected', async (t) => {
1233
+ const select = new Select({
1234
+ target,
1235
+ props: {
1236
+ listOpen: true,
1237
+ items: itemsWithGroup,
1238
+ groupBy: (item) => item.group
1239
+ }
1240
+ });
1241
+
1242
+ await wait(0);
1243
+ await querySelectorClick('.list-group-title');
1244
+
1245
+ t.ok(!select.value);
1246
+
1247
+ select.$destroy();
1248
+ });
1249
+
1250
+ test('clicking an item with selectable: false should not make a selected', async (t) => {
1251
+ const select = new Select({
1252
+ target,
1253
+ props: {
1254
+ listOpen: true,
1255
+ items: itemsWithSelectable
1256
+ }
1257
+ });
1258
+
1259
+ await wait(0);
1260
+ await querySelectorClick('.list-item:nth-child(1)');
1261
+ t.ok(!select.value);
1262
+ select.listOpen = true;
1263
+ await querySelectorClick('.list-item:nth-child(4)')
1264
+ t.ok(!select.value);
1265
+
1266
+ select.$destroy();
1267
+ });
1268
+
1269
+ test('clicking an item with selectable not specified should make a selected', async (t) => {
1270
+ const select = new Select({
1271
+ target,
1272
+ props: {
1273
+ listOpen: true,
1274
+ items: itemsWithSelectable
1275
+ }
1276
+ });
1277
+
1278
+ await wait(0);
1279
+ document.querySelector('.list-item:nth-child(2)').click();
1280
+ t.ok(select.value && select.value.value == 'selectableDefault');
1281
+
1282
+ select.$destroy();
1283
+ });
1284
+
1285
+ test('clicking an item with selectable: true should make a selected', async (t) => {
1286
+ const select = new Select({
1287
+ target,
1288
+ props: {
1289
+ listOpen: true,
1290
+ items: itemsWithSelectable
1291
+ }
1292
+ });
1293
+
1294
+ await wait(0);
1295
+ await querySelectorClick('.list-item:nth-child(3)')
1296
+ t.ok(select.value && select.value.value == 'selectableTrue');
1297
+ select.$destroy();
1298
+ });
1299
+
1300
+ test('when groupBy, no active item and keydown enter is fired then list should close without selecting item', async (t) => {
1301
+ const select = new Select({
1302
+ target,
1303
+ props: {
1304
+ listOpen: true,
1305
+ items: itemsWithGroup,
1306
+ groupBy: (item) => item.group
1307
+ }
1308
+ });
1309
+
1310
+ await wait(0);
1311
+ await querySelectorClick('.svelte-select');
1312
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
1313
+ t.ok(!select.value);
1314
+
1315
+ select.$destroy();
1316
+ });
1317
+
1318
+ test('when groupHeaderSelectable clicking group header should select createGroupHeaderItem(groupValue,item)', async (t) => {
1319
+ const select = new Select({
1320
+ target,
1321
+ props: {
1322
+ listOpen: true,
1323
+ items: itemsWithGroup,
1324
+ groupHeaderSelectable: true,
1325
+ groupBy,
1326
+ createGroupHeaderItem
1327
+ }
1328
+ });
1329
+
1330
+ function groupBy(item) {
1331
+ return item.group;
1332
+ }
1333
+
1334
+ function createGroupHeaderItem(groupValue, item) {
1335
+ return {
1336
+ label: `XXX ${groupValue} XXX ${item.label}`
1337
+ };
1338
+ }
1339
+
1340
+ await wait(0);
1341
+
1342
+ const groupHeaderItem = select.getFilteredItems()[0];
1343
+ const groupItem = select.getFilteredItems().find((item) => {
1344
+ return item.group === groupHeaderItem.id;
1345
+ });
1346
+
1347
+ await querySelectorClick('.list-item');
1348
+
1349
+ t.ok(select.value.groupHeader);
1350
+ t.equal(select.value.label, createGroupHeaderItem(groupBy(groupItem), groupItem).label);
1351
+
1352
+ select.$destroy();
1353
+ });
1354
+
1355
+ test('groups should be sorted by expression', async (t) => {
1356
+ const select = new Select({
1357
+ target,
1358
+ props: {
1359
+ listOpen: true,
1360
+ items: itemsWithGroup,
1361
+ groupBy: (item) => item.group,
1362
+ groupFilter: (groups) => groups.reverse()
1363
+ }
1364
+ });
1365
+
1366
+ await wait();
1367
+
1368
+ t.ok(document.querySelector('.list-group-title').textContent.trim() === 'Savory');
1369
+ t.ok(document.querySelector('.list-item .group-item').textContent.trim() === 'Pizza');
1370
+
1371
+ select.$destroy();
1372
+ });
1373
+
1374
+ test('when multiple is true show each item in value', async (t) => {
1375
+ const select = new Select({
1376
+ target,
1377
+ props: {
1378
+ multiple: true,
1379
+ items,
1380
+ value: [
1381
+ {value: 'pizza', label: 'Pizza'},
1382
+ {value: 'chips', label: 'Chips'},
1383
+ ],
1384
+ }
1385
+ });
1386
+
1387
+ const all = target.querySelectorAll('.multi-item span');
1388
+
1389
+ t.ok(all[0].innerHTML.startsWith('Pizza'));
1390
+ t.ok(all[1].innerHTML.startsWith('Chips'));
1391
+
1392
+ select.$destroy();
1393
+ });
1394
+
1395
+ test('when multiple is true and value is undefined show placeholder text', async (t) => {
1396
+ const select = new Select({
1397
+ target,
1398
+ props: {
1399
+ multiple: true,
1400
+ items,
1401
+ value: undefined
1402
+ }
1403
+ });
1404
+
1405
+ t.ok(!target.querySelector('.multi-item span'));
1406
+
1407
+ select.$destroy();
1408
+ });
1409
+
1410
+ test('when multiple is true clicking item in list will populate value', async (t) => {
1411
+ const select = new Select({
1412
+ target,
1413
+ props: {
1414
+ multiple: true,
1415
+ items,
1416
+ value: undefined
1417
+ }
1418
+ });
1419
+
1420
+ await querySelectorClick('.svelte-select');
1421
+ await querySelectorClick('.list-item');
1422
+
1423
+ t.equal(JSON.stringify(select.value), JSON.stringify([{value: 'chocolate', label: 'Chocolate'}]));
1424
+
1425
+ select.$destroy();
1426
+ });
1427
+
1428
+ test('when multiple is true items in value will not appear in list', async (t) => {
1429
+ const select = new Select({
1430
+ target,
1431
+ props: {
1432
+ multiple: true,
1433
+ items,
1434
+ value: [{value: 'chocolate', label: 'Chocolate'}]
1435
+ }
1436
+ });
1437
+
1438
+ await wait(0);
1439
+
1440
+ t.equal(JSON.stringify(select.getFilteredItems()), JSON.stringify([
1441
+ {value: 'pizza', label: 'Pizza'},
1442
+ {value: 'cake', label: 'Cake'},
1443
+ {value: 'chips', label: 'Chips'},
1444
+ {value: 'ice-cream', label: 'Ice Cream'}
1445
+ ]));
1446
+
1447
+ select.$destroy();
1448
+ });
1449
+
1450
+ test('when multiple is true both value and filterText filters list', async (t) => {
1451
+ const select = new Select({
1452
+ target,
1453
+ props: {
1454
+ listOpen: true,
1455
+ multiple: true,
1456
+ items,
1457
+ value: [{value: 'chocolate', label: 'Chocolate'}]
1458
+ }
1459
+ });
1460
+
1461
+ select.filterText = 'Pizza',
1462
+
1463
+ t.equal(JSON.stringify(select.getFilteredItems()), JSON.stringify([
1464
+ {value: 'pizza', label: 'Pizza'}
1465
+ ]));
1466
+
1467
+ select.$destroy();
1468
+ });
1469
+
1470
+ test('when multiple is true clicking X on a selected item will remove it from value', async (t) => {
1471
+ const select = new Select({
1472
+ target,
1473
+ props: {
1474
+ multiple: true,
1475
+ items,
1476
+ value: [{value: 'chocolate', label: 'Chocolate'}, {value: 'pizza', label: 'Pizza'}]
1477
+ }
1478
+ });
1479
+
1480
+ const event = new PointerEvent('pointerup')
1481
+ document.querySelector('.multi-item-clear').dispatchEvent(event);
1482
+ t.equal(JSON.stringify(select.value), JSON.stringify([{value: 'pizza', label: 'Pizza'}]));
1483
+
1484
+ select.$destroy();
1485
+ });
1486
+
1487
+ test('when multiple is true and all selected items have been removed then placeholder should show and clear all should hide', async (t) => {
1488
+ const select = new Select({
1489
+ target,
1490
+ props: {
1491
+ multiple: true,
1492
+ items,
1493
+ value: [{value: 'chocolate', label: 'Chocolate'}]
1494
+ }
1495
+ });
1496
+
1497
+ document.querySelector('.multi-item-clear').click();
1498
+
1499
+ select.$destroy();
1500
+ });
1501
+
1502
+ test('when multiple is true and items are selected then clear all should wipe all selected items', async (t) => {
1503
+ const select = new Select({
1504
+ target,
1505
+ props: {
1506
+ multiple: true,
1507
+ items,
1508
+ value: [{value: 'chocolate', label: 'Chocolate'}, {value: 'pizza', label: 'Pizza'}]
1509
+ }
1510
+ });
1511
+
1512
+ document.querySelector('.clear-select').click();
1513
+ t.equal(select.value, undefined);
1514
+
1515
+ select.$destroy();
1516
+ });
1517
+
1518
+ test('when multiple and groupBy is active then items should be selectable', async (t) => {
1519
+ const select = new Select({
1520
+ target,
1521
+ props: {
1522
+ multiple: true,
1523
+ items: itemsWithGroup,
1524
+ groupBy: (item) => item.group
1525
+ }
1526
+ });
1527
+
1528
+ target.style.maxWidth = '400px';
1529
+ await querySelectorClick('.svelte-select');
1530
+ await querySelectorClick('.list-item .group-item');
1531
+ t.equal(JSON.stringify(select.value), JSON.stringify([{"groupItem":true,"value":"chocolate","label":"Chocolate","group":"Sweet"}]));
1532
+
1533
+ select.$destroy();
1534
+ });
1535
+
1536
+ test('when multiple and selected items reach edge of container then Select height should increase and selected items should wrap to new line', async (t) => {
1537
+ const select = new Select({
1538
+ target,
1539
+ props: {
1540
+ multiple: true,
1541
+ items
1542
+ }
1543
+ });
1544
+
1545
+ target.style.maxWidth = '200px';
1546
+ t.ok(document.querySelector('.svelte-select').scrollHeight === 40);
1547
+ await handleSet(select, {value: [{value: 'chocolate', label: 'Chocolate'}, {value: 'pizza', label: 'Pizza'}]});
1548
+ t.ok(document.querySelector('.svelte-select').scrollHeight > 42);
1549
+ select.$destroy();
1550
+ });
1551
+
1552
+ test('when multiple and value is populated then navigating with LeftArrow updates activeValue', async (t) => {
1553
+ const select = new Select({
1554
+ target,
1555
+ props: {
1556
+ multiple: true,
1557
+ items,
1558
+ value: [{value: 'chocolate', label: 'Chocolate'}, {value: 'pizza', label: 'Pizza'}, {value: 'chips', label: 'Chips'},],
1559
+ focused: true
1560
+ }
1561
+ });
1562
+
1563
+ target.style.maxWidth = '100%';
1564
+
1565
+ const input = document.querySelector('.svelte-select input');
1566
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowLeft'}));
1567
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowLeft'}));
1568
+
1569
+ t.ok(select.$capture_state().activeValue === 1);
1570
+
1571
+ select.$destroy();
1572
+ });
1573
+
1574
+ test('when multiple and value is populated then navigating with ArrowRight updates activeValue', async (t) => {
1575
+ const select = new Select({
1576
+ target,
1577
+ props: {
1578
+ multiple: true,
1579
+ items,
1580
+ value: [{value: 'chocolate', label: 'Chocolate'}, {value: 'pizza', label: 'Pizza'}, {value: 'chips', label: 'Chips'},],
1581
+ focused: true
1582
+ }
1583
+ });
1584
+
1585
+ const input = document.querySelector('.svelte-select input');
1586
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowLeft'}));
1587
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowLeft'}));
1588
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowLeft'}));
1589
+ input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowRight'}));
1590
+ t.ok(select.$capture_state().activeValue === 1);
1591
+
1592
+ select.$destroy();
1593
+ });
1594
+
1595
+ test('when multiple and value has items and list opens then first item in list should be active', async (t) => {
1596
+ const select = new Select({
1597
+ target,
1598
+ props: {
1599
+ multiple: true,
1600
+ items,
1601
+ }
1602
+ });
1603
+
1604
+ await querySelectorClick('.svelte-select');
1605
+ await querySelectorClick('.list-item');
1606
+ await wait(0);
1607
+ await handleKeyboard('ArrowDown');
1608
+ t.ok(document.querySelector('.list-item .hover'));
1609
+ select.$destroy();
1610
+ });
1611
+
1612
+ test('when multiple, disabled, and value has items then items should be locked', async (t) => {
1613
+ const select = new Select({
1614
+ target,
1615
+ props: {
1616
+ multiple: true,
1617
+ items,
1618
+ disabled: true,
1619
+ value: [{value: 'chocolate', label: 'Chocolate'}],
1620
+ }
1621
+ });
1622
+
1623
+ t.ok(document.querySelector('.multi-item.disabled'));
1624
+
1625
+ select.$destroy();
1626
+ });
1627
+
1628
+ test('when multiple is true show each item in value if simple arrays are used', async (t) => {
1629
+ const select = new Select({
1630
+ target,
1631
+ props: {
1632
+ multiple: true,
1633
+ items: ['pizza', 'chips', 'chocolate'],
1634
+ value: ['pizza', 'chocolate']
1635
+ }
1636
+ });
1637
+
1638
+ const all = target.querySelectorAll('.multi-item span');
1639
+ t.ok(all[0].innerHTML.startsWith('pizza'));
1640
+ t.ok(all[1].innerHTML.startsWith('chocolate'));
1641
+
1642
+ select.$destroy();
1643
+ });
1644
+
1645
+
1646
+ test('when label is set you can pass a string and see the right label', async (t) => {
1647
+ const select = new Select({
1648
+ target,
1649
+ props: {
1650
+ items: [{id: 0, name: 'ONE'}, {id: 1, name: 'TWO'}],
1651
+ value: {id: 0, name: 'ONE'},
1652
+ label: 'name',
1653
+ }
1654
+ });
1655
+
1656
+ t.ok(document.querySelector('.selected-item').innerHTML === 'ONE');
1657
+
1658
+ select.$destroy();
1659
+ });
1660
+
1661
+
1662
+ test('when getValue method is set should use that key to update value', async (t) => {
1663
+ const select = new Select({
1664
+ target,
1665
+ props: {
1666
+ items: [{id: 0, label: 'ONE'}, {id: 1, label: 'TWO'}],
1667
+ value: {id: 0, label: 'ONE'},
1668
+ itemId: 'id'
1669
+ }
1670
+ });
1671
+
1672
+ t.ok(select.value.id === 0);
1673
+ await querySelectorClick('.svelte-select');
1674
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
1675
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
1676
+ t.ok(select.value.id === 1);
1677
+
1678
+ select.$destroy();
1679
+ });
1680
+
1681
+ test('when loadOptions method is supplied and filterText has length then items should populate via promise resolve', async (t) => {
1682
+ const select = new Select({
1683
+ target,
1684
+ props: {
1685
+ label: 'name',
1686
+ loadOptions: getPosts,
1687
+ itemId: 'id',
1688
+ }
1689
+ });
1690
+
1691
+ await wait(0);
1692
+ select.$set({filterText: 'Juniper'});
1693
+ await wait(0);
1694
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
1695
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
1696
+
1697
+ select.$destroy();
1698
+ });
1699
+
1700
+
1701
+ test('when label method is supplied and value are no items then display result of label', async (t) => {
1702
+ const select = new Select({
1703
+ target,
1704
+ props: {
1705
+ label: 'notLabel',
1706
+ value: {notLabel: 'This is not a label', value: 'not important'},
1707
+ }
1708
+ });
1709
+
1710
+
1711
+ t.ok(document.querySelector('.selected-item').innerHTML === 'This is not a label');
1712
+
1713
+ select.$destroy();
1714
+ });
1715
+
1716
+ test('when label and items is supplied then display result of label for each option', async (t) => {
1717
+ const select = new Select({
1718
+ target,
1719
+ props: {
1720
+ label: 'notLabel',
1721
+ listOpen: true,
1722
+ items: [{notLabel: 'This is not a label', value: 'not important #1'}, {notLabel: 'This is not also not a label', value: 'not important #2'}],
1723
+ }
1724
+ });
1725
+
1726
+ t.ok(document.querySelector('.item')?.innerHTML === 'This is not a label');
1727
+
1728
+ select.$destroy();
1729
+ });
1730
+
1731
+ test('when label method and items is supplied then display result of label for each option', async (t) => {
1732
+ const select = new Select({
1733
+ target,
1734
+ props: {
1735
+ label: 'notLabel',
1736
+ listOpen: true,
1737
+ items: [{notLabel: 'This is not a label', value: 'not important #1'}, {notLabel: 'This is not also not a label', value: 'not important #2'}],
1738
+ }
1739
+ });
1740
+
1741
+ t.ok(document.querySelector('.item').innerHTML === 'This is not a label');
1742
+
1743
+ select.$destroy();
1744
+ });
1745
+
1746
+ test('when loadOptions method is supplied, multiple is true and filterText has length then items should populate via promise resolve', async (t) => {
1747
+ const select = new Select({
1748
+ target,
1749
+ props: {
1750
+ loadOptions: getPosts,
1751
+ itemId: 'id',
1752
+ multiple: true
1753
+ }
1754
+ });
1755
+
1756
+ await wait(0);
1757
+ await handleSet(select, {filterText: 'Juniper'});
1758
+ await wait(600);
1759
+ await handleKeyboard('ArrowDown');
1760
+ await handleKeyboard('Enter');
1761
+ t.ok(document.querySelector('.multi-item span').innerHTML.startsWith('Juniper Wheat Beer'));
1762
+ select.$destroy();
1763
+ });
1764
+
1765
+ test('when selection slot render slot content', async (t) => {
1766
+ const select = new SelectionSlotTest({
1767
+ target
1768
+ });
1769
+
1770
+ t.ok(document.querySelector('.selected-item').innerHTML === 'Slot: one');
1771
+
1772
+ select.$destroy();
1773
+ });
1774
+
1775
+ test('when multiple and selection slot render slot content', async (t) => {
1776
+ const select = new SelectionSlotMultipleTest({
1777
+ target
1778
+ });
1779
+
1780
+ const items = document.querySelectorAll('.multi-item span');
1781
+
1782
+ t.ok(items[0].innerHTML.startsWith('Index: 0 Slot: one'));
1783
+ t.ok(items[1].innerHTML.startsWith('Index: 1 Slot: two'));
1784
+
1785
+ select.$destroy();
1786
+ });
1787
+
1788
+ test('when hideEmptyState true then do not show "no items" div ', async (t) => {
1789
+ const select = new Select({
1790
+ target,
1791
+ props: {
1792
+ items,
1793
+ listOpen: true,
1794
+ filterText: 'x',
1795
+ hideEmptyState: true
1796
+ }
1797
+ });
1798
+
1799
+ await wait(0);
1800
+
1801
+ t.ok(!document.querySelector('.empty'));
1802
+
1803
+ select.$destroy();
1804
+ });
1805
+
1806
+ test('when value is selected then change event should fire', async (t) => {
1807
+ const select = new Select({
1808
+ target,
1809
+ props: {
1810
+ listOpen: true,
1811
+ items,
1812
+ }
1813
+ });
1814
+
1815
+ let selectEvent = undefined;
1816
+
1817
+ select.$on('change', event => {
1818
+ selectEvent = event;
1819
+ });
1820
+
1821
+ await handleKeyboard('ArrowDown');
1822
+ await handleKeyboard('Enter');
1823
+
1824
+ t.ok(selectEvent);
1825
+
1826
+ select.$destroy();
1827
+ });
1828
+
1829
+ test('when value is cleared the clear event is fired', async (t) => {
1830
+ const select = new Select({
1831
+ target,
1832
+ props: {
1833
+ items,
1834
+ value: items[0],
1835
+ }
1836
+ });
1837
+
1838
+ let clearEvent = false;
1839
+ select.$on('clear', () => {
1840
+ clearEvent = true;
1841
+ });
1842
+
1843
+ document.querySelector('.clear-select').click();
1844
+
1845
+ t.ok(clearEvent);
1846
+
1847
+ select.$destroy();
1848
+ });
1849
+
1850
+ test('when multi item is cleared the clear event is fired with removed item', async (t) => {
1851
+ const itemToRemove = items[0];
1852
+
1853
+ const select = new Select({
1854
+ target,
1855
+ props: {
1856
+ multiple: true,
1857
+ items,
1858
+ value: [itemToRemove]
1859
+ }
1860
+ });
1861
+
1862
+ let removedItem;
1863
+
1864
+ select.$on('clear', (event) => {
1865
+ removedItem = event.detail;
1866
+ });
1867
+
1868
+ const event = new PointerEvent('pointerup')
1869
+ document.querySelector('.multi-item-clear').dispatchEvent(event);
1870
+ t.equal(JSON.stringify(removedItem), JSON.stringify(itemToRemove));
1871
+
1872
+ select.$destroy();
1873
+ });
1874
+
1875
+ test('when single item is cleared the clear event is fired with removed item', async (t) => {
1876
+ const itemToRemove = items[0];
1877
+
1878
+ const select = new Select({
1879
+ target,
1880
+ props: {
1881
+ items,
1882
+ value: itemToRemove
1883
+ }
1884
+ });
1885
+
1886
+ let removedItem;
1887
+
1888
+ select.$on('clear', (event) => {
1889
+ removedItem = event.detail;
1890
+ });
1891
+
1892
+ document.querySelector('.clear-select').click();
1893
+ t.equal(JSON.stringify(removedItem), JSON.stringify(itemToRemove));
1894
+
1895
+ select.$destroy();
1896
+ });
1897
+
1898
+ test('when items in list filter or update then first item in list should highlight', async (t) => {
1899
+ const select = new Select({
1900
+ target,
1901
+ props: {
1902
+ items,
1903
+ focused: true
1904
+ }
1905
+ });
1906
+
1907
+
1908
+ await handleKeyboard('ArrowDown');
1909
+ t.ok(document.querySelector('.svelte-select-list .hover').innerHTML === 'Chocolate');
1910
+ await handleSet(select, {filterText: 'chi'});
1911
+ t.ok(document.querySelector('.hover').innerHTML === 'Chips');
1912
+
1913
+ select.$destroy();
1914
+ });
1915
+
1916
+ test('when item is selected or state changes then check value[itemId] has changed before firing "input" event', async (t) => {
1917
+ const select = new Select({
1918
+ target,
1919
+ props: {
1920
+ items,
1921
+ value: {value: 'cake', label: 'Cake'}
1922
+ }
1923
+ });
1924
+
1925
+ let item = undefined;
1926
+ select.$on('input', () => {
1927
+ item = true;
1928
+ });
1929
+ await handleSet(select, {value: {value: 'cake', label: 'Cake'}});
1930
+ t.ok(!item)
1931
+
1932
+ select.$destroy();
1933
+ });
1934
+
1935
+ test('when multiple and item is selected or state changes then check value[itemId] has changed before firing "input" event', async (t) => {
1936
+ const select = new Select({
1937
+ target,
1938
+ props: {
1939
+ multiple: true,
1940
+ items,
1941
+ value: [
1942
+ {value: 'pizza', label: 'Pizza'},
1943
+ {value: 'chips', label: 'Chips'},
1944
+ ],
1945
+ }
1946
+ });
1947
+
1948
+ let item = undefined;
1949
+
1950
+ select.$on('input', () => {
1951
+ item = true;
1952
+ });
1953
+
1954
+ await handleSet(select, {value: [{value: 'pizza', label: 'Pizza'},{value: 'chips', label: 'Chips'}]});
1955
+ t.ok(!item);
1956
+ item = false;
1957
+ await handleSet(select, {value: [{value: 'pizza', label: 'Pizza'}]});
1958
+
1959
+ t.ok(item);
1960
+ select.$destroy();
1961
+ });
1962
+
1963
+ test('when focused turns to false then check Select is no longer in focus', async (t) => {
1964
+ const select = new Select({
1965
+ target,
1966
+ props: {
1967
+ focused: true,
1968
+ items,
1969
+ }
1970
+ });
1971
+
1972
+ const selectSecond = new Select({
1973
+ target: extraTarget,
1974
+ props: {
1975
+ focused: false,
1976
+ items,
1977
+ }
1978
+ });
1979
+
1980
+ select.$on('input', () => {
1981
+ setTimeout(() => {
1982
+ select.$set({
1983
+ focused: false,
1984
+ })
1985
+ }, 0)
1986
+
1987
+ selectSecond.$set({
1988
+ focused: true
1989
+ })
1990
+ });
1991
+
1992
+ await handleSet(select, {value: {value: 'pizza', label: 'Pizza'}});
1993
+
1994
+
1995
+ await wait(0);
1996
+
1997
+ t.ok(selectSecond.focused);
1998
+ t.ok(!select.focused);
1999
+
2000
+ selectSecond.$destroy();
2001
+ select.$destroy();
2002
+ });
2003
+
2004
+ test('when items is just an array of strings then render list', async (t) => {
2005
+ const items = ['one', 'two', 'three'];
2006
+
2007
+ const select = new Select({
2008
+ target,
2009
+ props: {
2010
+ items,
2011
+ listOpen: true
2012
+ }
2013
+ });
2014
+
2015
+ await wait(0);
2016
+ t.ok(document.querySelector('.item').innerHTML === 'one');
2017
+
2018
+ select.$destroy();
2019
+ });
2020
+
2021
+ test('when items are just strings then value should render', async (t) => {
2022
+ const items = ['one', 'two', 'three'];
2023
+
2024
+ const select = new Select({
2025
+ target,
2026
+ props: {
2027
+ items,
2028
+ value: {value: 'one', label: 'one', index: 0}
2029
+ }
2030
+ });
2031
+
2032
+ t.ok(document.querySelector('.selected-item').innerHTML === 'one');
2033
+ select.$destroy();
2034
+ });
2035
+
2036
+ test('when multiple and value has items then check each item is unique', async (t) => {
2037
+ const select = new Select({
2038
+ target,
2039
+ props: {
2040
+ multiple: true,
2041
+ items,
2042
+ value: [
2043
+ {value: 'pizza', label: 'Pizza'},
2044
+ {value: 'pizza', label: 'Pizza'},
2045
+ {value: 'cake', label: 'Cake'},
2046
+ ],
2047
+ }
2048
+ });
2049
+
2050
+ t.ok(select.value.length === 2);
2051
+
2052
+ select.$destroy();
2053
+ });
2054
+
2055
+ test('when multiple and textFilter has length then enter should select item', async (t) => {
2056
+ const select = new Select({
2057
+ target,
2058
+ props: {
2059
+ multiple: true,
2060
+ items,
2061
+ focused: true,
2062
+ filterText: 'p',
2063
+ listOpen: true
2064
+ }
2065
+ });
2066
+
2067
+ await wait(0);
2068
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
2069
+ t.ok(select.value[0].value === 'pizza');
2070
+
2071
+ select.$destroy();
2072
+ });
2073
+
2074
+ test('when multiple and textFilter has length and no items in list then enter should do nothing', async (t) => {
2075
+ const select = new Select({
2076
+ target,
2077
+ props: {
2078
+ multiple: true,
2079
+ items,
2080
+ focused: true,
2081
+ filterText: 'zc',
2082
+ listOpen: true
2083
+ }
2084
+ });
2085
+
2086
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
2087
+ t.ok(!select.value);
2088
+
2089
+ select.$destroy();
2090
+ });
2091
+
2092
+ test('When multiple and no selected item then delete should do nothing', async (t) => {
2093
+ const select = new Select({
2094
+ target,
2095
+ props: {
2096
+ multiple: true,
2097
+ items,
2098
+ focused: true,
2099
+ listOpen: true
2100
+ }
2101
+ });
2102
+
2103
+ await wait(0);
2104
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Backspace'}));
2105
+ t.ok(select.listOpen === true);
2106
+
2107
+ select.$destroy();
2108
+ });
2109
+
2110
+ test('When list is open, filterText applied and Enter/Tab key pressed should select and show highlighted value', async (t) => {
2111
+ const select = new Select({
2112
+ target,
2113
+ props: {
2114
+ listOpen: true,
2115
+ focused: true,
2116
+ filterText: 'A5',
2117
+ items: ['A5', 'test string', 'something else']
2118
+ }
2119
+ });
2120
+
2121
+ await wait(0);
2122
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
2123
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
2124
+ t.equal(select.value.value, 'A5');
2125
+ await wait(0);
2126
+ t.ok(target.querySelector('.selected-item').innerHTML === 'A5');
2127
+
2128
+ select.$destroy();
2129
+ });
2130
+
2131
+
2132
+ test('When inputAttributes is supplied each attribute is placed on the Select input field', async (t) => {
2133
+ const select = new Select({
2134
+ target,
2135
+ props: {
2136
+ items,
2137
+ inputAttributes: {
2138
+ id: 'testId',
2139
+ autocomplete: 'custom-value'
2140
+ }
2141
+ }
2142
+ });
2143
+
2144
+ const el = document.getElementById('testId');
2145
+
2146
+ t.equal(el.id, 'testId');
2147
+ t.equal(el.getAttribute('autocomplete'), 'custom-value');
2148
+
2149
+ select.$destroy();
2150
+ });
2151
+
2152
+ test('when items and value supplied as just strings then value should render correctly', async (t) => {
2153
+ const select = new Select({
2154
+ target,
2155
+ props: {
2156
+ items: ['Pizza', 'Chocolate', 'Crisps'],
2157
+ value: 'Pizza'
2158
+ }
2159
+ });
2160
+
2161
+ t.equal(document.querySelector('.selected-item').innerHTML, 'Pizza');
2162
+
2163
+ select.$destroy();
2164
+ });
2165
+
2166
+ test('when multiple with items and value supplied as just strings then value should render correctly', async (t) => {
2167
+ const select = new Select({
2168
+ target,
2169
+ props: {
2170
+ multiple: true,
2171
+ items: ['Pizza', 'Chocolate', 'Crisps'],
2172
+ value: ['Pizza']
2173
+ }
2174
+ });
2175
+
2176
+ t.ok(document.querySelector('.multi-item span').innerHTML.startsWith('Pizza'));
2177
+
2178
+ select.$destroy();
2179
+ });
2180
+
2181
+ test('when multiple, groupBy and value are supplied then list should be filtered', async (t) => {
2182
+ let _items = [
2183
+ { id: 1, name: "Foo", group: "first" },
2184
+ { id: 2, name: "Bar", group: "second" },
2185
+ { id: 3, name: "Baz", group: "second" },
2186
+ { id: 4, name: "Qux", group: "first" },
2187
+ { id: 5, name: "Bah", group: "first" },
2188
+ ];
2189
+
2190
+ const select = new Select({
2191
+ target,
2192
+ props: {
2193
+ multiple: true,
2194
+ items: _items,
2195
+ groupBy: (item) => item.group,
2196
+ itemId: 'id',
2197
+ label: 'name',
2198
+ value: [{ id: 2, name: "Bar", group: "second" }],
2199
+ listOpen: true
2200
+ }
2201
+ });
2202
+
2203
+ t.ok(!select.getFilteredItems().find(item => item.name === 'Bar'));
2204
+
2205
+ select.$destroy();
2206
+ });
2207
+
2208
+ test('When items are collection and value a string then lookup item using itemId and update value to match', async (t) => {
2209
+ const select = new Select({
2210
+ target,
2211
+ props: {
2212
+ items,
2213
+ value: 'cake'
2214
+ }
2215
+ });
2216
+
2217
+ await wait(0);
2218
+ t.ok(select.value.value === 'cake');
2219
+ select.$set({ value: 'pizza' });
2220
+ await wait(0);
2221
+ t.ok(select.value.value === 'pizza');
2222
+ select.$destroy();
2223
+ });
2224
+
2225
+ test('When listAutoWidth is set to false list container should have style of width:auto', async (t) => {
2226
+ const select = new Select({
2227
+ target,
2228
+ props: {
2229
+ items,
2230
+ listAutoWidth: false,
2231
+ listOpen: true
2232
+ }
2233
+ });
2234
+
2235
+ await wait(0);
2236
+ const listWidth = document.querySelectorAll('.svelte-select-list')[0].style.width;
2237
+ t.ok(listWidth === 'auto');
2238
+ select.$destroy();
2239
+ });
2240
+
2241
+
2242
+ test('When item is already active and is selected from list then close list', async (t) => {
2243
+ const select = new Select({
2244
+ target,
2245
+ props: {
2246
+ items,
2247
+ listOpen: true,
2248
+ value: 'pizza'
2249
+ }
2250
+ });
2251
+
2252
+ await wait(0);
2253
+ await querySelectorClick('.svelte-select-list > .list-item > .item.active');
2254
+ await wait(0);
2255
+ t.ok(select.value.value === 'pizza');
2256
+ select.$destroy();
2257
+ });
2258
+
2259
+
2260
+ test('When prepend named slot is supplied then render content', async (t) => {
2261
+ const select = new PrependSlotTest({
2262
+ target,
2263
+ });
2264
+
2265
+ t.ok(document.querySelector('.before').innerHTML === 'Before it all');
2266
+
2267
+ select.$destroy();
2268
+ });
2269
+
2270
+ test('When showChevron prop is true only show chevron when there is no value on Select', async (t) => {
2271
+ const select = new Select({
2272
+ target,
2273
+ props: {
2274
+ items,
2275
+ value: {value: 'chocolate', label: 'Chocolate'},
2276
+ showChevron: true
2277
+ }
2278
+ });
2279
+
2280
+ t.ok(document.querySelectorAll('.indicator').length === 0);
2281
+
2282
+ select.$destroy();
2283
+ });
2284
+
2285
+ test('When showChevron prop is true and no value show chevron on Select', async (t) => {
2286
+ const select = new Select({
2287
+ target,
2288
+ props: {
2289
+ items,
2290
+ showChevron: true
2291
+ }
2292
+ });
2293
+
2294
+ t.ok(document.querySelectorAll('.chevron')[0]);
2295
+
2296
+ select.$destroy();
2297
+ });
2298
+
2299
+ test('When showChevron and clearable is true always show chevron on Select', async (t) => {
2300
+ const select = new Select({
2301
+ target,
2302
+ props: {
2303
+ items,
2304
+ value: {value: 'chocolate', label: 'Chocolate'},
2305
+ showChevron: true,
2306
+ clearable: false
2307
+ }
2308
+ });
2309
+
2310
+ t.ok(document.querySelectorAll('.chevron')[0]);
2311
+
2312
+ select.$destroy();
2313
+ });
2314
+
2315
+ test('When items and loadOptions then listOpen should be false', async (t) => {
2316
+ const select = new Select({
2317
+ target,
2318
+ props: {
2319
+ loadOptions: resolvePromise,
2320
+ }
2321
+ });
2322
+
2323
+ t.ok(select.listOpen === false);
2324
+
2325
+ select.$destroy();
2326
+ });
2327
+
2328
+ test('Select container classes can be injected', async (t) => {
2329
+ const select = new Select({
2330
+ target,
2331
+ props: {
2332
+ items,
2333
+ value: {value: 'cake', label: 'Cake'},
2334
+ class: 'svelte-select testclass',
2335
+ },
2336
+ });
2337
+
2338
+ t.ok(
2339
+ document.querySelector('.svelte-select').classList.contains('testclass')
2340
+ );
2341
+ select.$destroy();
2342
+ });
2343
+
2344
+ test('When loadOptions promise is resolved then dispatch loaded', async (t) => {
2345
+ const select = new Select({
2346
+ target,
2347
+ props: {
2348
+ loadOptions: resolvePromise,
2349
+ },
2350
+ });
2351
+
2352
+ let loadedEventData = undefined;
2353
+ const loadedOff = select.$on('loaded', event => {
2354
+ loadedEventData = event;
2355
+ });
2356
+ let errorEventData = undefined;
2357
+ const errorOff = select.$on('error', event => {
2358
+ errorEventData = event;
2359
+ })
2360
+
2361
+ await wait(0);
2362
+ select.$set({listOpen: true});
2363
+ await wait(0);
2364
+ select.$set({filterText: 'test'});
2365
+ await wait(500);
2366
+
2367
+ t.equal(loadedEventData.detail.items[0].value, 'a');
2368
+ t.equal(errorEventData, undefined);
2369
+
2370
+ loadedOff();
2371
+ errorOff();
2372
+ select.$destroy();
2373
+ });
2374
+
2375
+ test('When loadOptions promise is rejected then dispatch error', async (t) => {
2376
+ const select = new Select({
2377
+ target,
2378
+ props: {
2379
+ loadOptions: rejectPromise,
2380
+ },
2381
+ });
2382
+
2383
+ let loadedEventData = undefined;
2384
+ const loadedOff = select.$on('loaded', event => {
2385
+ loadedEventData = event;
2386
+ });
2387
+ let errorEventData = undefined;
2388
+ const errorOff = select.$on('error', event => {
2389
+ errorEventData = event;
2390
+ });
2391
+
2392
+ await wait(0);
2393
+ select.$set({listOpen: true});
2394
+ await wait(0);
2395
+ select.$set({filterText: 'test'});
2396
+ await wait(500);
2397
+ t.equal(loadedEventData, undefined);
2398
+ t.equal(errorEventData.detail.type, 'loadOptions');
2399
+ t.equal(errorEventData.detail.details, 'error 123');
2400
+
2401
+ loadedOff();
2402
+ errorOff();
2403
+ select.$destroy();
2404
+ });
2405
+
2406
+ test('When items change then value should also update', async (t) => {
2407
+ const select = new Select({
2408
+ target,
2409
+ props: {
2410
+ items,
2411
+ value: {value: 'chips', label: 'Chips'},
2412
+ },
2413
+ });
2414
+
2415
+ await wait(0);
2416
+
2417
+ select.$set({items: [
2418
+ {value: 'chocolate', label: 'Chocolate'},
2419
+ {value: 'pizza', label: 'Pizza'},
2420
+ {value: 'cake', label: 'Cake'},
2421
+ {value: 'chips', label: 'Loaded Fries'},
2422
+ {value: 'ice-cream', label: 'Ice Cream'},
2423
+ ]});
2424
+
2425
+ await wait(0);
2426
+
2427
+ t.ok(select.value.label === 'Loaded Fries');
2428
+ t.ok(target.querySelector('.selected-item').innerHTML === 'Loaded Fries');
2429
+
2430
+ select.$destroy();
2431
+
2432
+ await wait(0);
2433
+
2434
+ const multiSelect = new Select({
2435
+ target,
2436
+ props: {
2437
+ multiple: true,
2438
+ items,
2439
+ value: [{value: 'chips', label: 'Chips'}, {value: 'pizza', label: 'Pizza'}],
2440
+ },
2441
+ });
2442
+
2443
+ await wait(0);
2444
+
2445
+ multiSelect.$set({items: [
2446
+ {value: 'chocolate', label: 'Chocolate'},
2447
+ {value: 'pizza', label: 'Cheese Pizza'},
2448
+ {value: 'cake', label: 'Cake'},
2449
+ {value: 'chips', label: 'Loaded Fries'},
2450
+ {value: 'ice-cream', label: 'Ice Cream'},
2451
+ ]});
2452
+
2453
+ await wait(0);
2454
+
2455
+ t.ok(multiSelect.value[0].label === 'Loaded Fries');
2456
+ t.ok(multiSelect.value[1].label === 'Cheese Pizza');
2457
+
2458
+ multiSelect.$destroy();
2459
+ });
2460
+
2461
+ test('When items change then value should also update but only if found in items', async (t) => {
2462
+ const select = new Select({
2463
+ target,
2464
+ props: {
2465
+ items,
2466
+ value: {value: 'chips', label: 'Chips'},
2467
+ },
2468
+ });
2469
+
2470
+ await wait(0);
2471
+
2472
+ select.$set({items: [
2473
+ {value: 'chocolate', label: 'Chocolate'},
2474
+ {value: 'pizza', label: 'Pizza'},
2475
+ {value: 'cake', label: 'Cake'},
2476
+ {value: 'loaded-fries', label: 'Loaded Fries'},
2477
+ {value: 'ice-cream', label: 'Ice Cream'},
2478
+ ]});
2479
+
2480
+ await wait(0);
2481
+
2482
+ t.ok(select.value.label === 'Chips');
2483
+ t.ok(target.querySelector('.selected-item').innerHTML === 'Chips');
2484
+
2485
+ select.$destroy();
2486
+ });
2487
+
2488
+ test('When multiple and multiFullItemClearable then clicking anywhere on the item will remove item', async (t) => {
2489
+ const multiSelect = new Select({
2490
+ target,
2491
+ props: {
2492
+ multiple: true,
2493
+ items,
2494
+ multiFullItemClearable: true,
2495
+ value: [{value: 'chips', label: 'Chips'}, {value: 'pizza', label: 'Pizza'}],
2496
+ },
2497
+ });
2498
+
2499
+ await wait(0);
2500
+ await querySelectorClick('.multi-item span');
2501
+ await wait(0);
2502
+ t.ok(multiSelect.value[0].label === 'Pizza');
2503
+
2504
+ multiSelect.$destroy();
2505
+ });
2506
+
2507
+ test('When multiple and filterText then items should filter out already selected items', async (t) => {
2508
+ const multiSelect = new Select({
2509
+ target,
2510
+ props: {
2511
+ multiple: true,
2512
+ items,
2513
+ value: [{value: 'chips', label: 'Chips'}, {value: 'pizza', label: 'Pizza'}],
2514
+ },
2515
+ });
2516
+
2517
+ t.ok(multiSelect.getFilteredItems().length === 3);
2518
+
2519
+ multiSelect.$destroy();
2520
+ });
2521
+
2522
+ test('when loadOptions and items is supplied then list should close on blur', async (t) => {
2523
+ const div = document.createElement('div');
2524
+ document.body.appendChild(div);
2525
+ let items=[{value:1, label:1}, {value:2, label:2}];
2526
+ let loadOptions = async(filterText) => {
2527
+ const res = await fetch(`https://api.punkapi.com/v2/beers?beer_name=${filterText}`)
2528
+ const data = await res.json();
2529
+ return data.map((beer)=> ({value: beer.id, label: beer.name}));
2530
+ }
2531
+
2532
+ const select = new Select({
2533
+ target,
2534
+ props: {
2535
+ items,
2536
+ loadOptions,
2537
+ }
2538
+ });
2539
+
2540
+ select.$set({focused: true});
2541
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
2542
+ await wait(0);
2543
+ select.$set(({ filterText: 's'}))
2544
+ await wait(600);
2545
+ div.click();
2546
+ div.remove();
2547
+
2548
+ select.$destroy();
2549
+ });
2550
+
2551
+ async function getCancelledRes() {
2552
+ Promise.resolve({cancelled: true});
2553
+ }
2554
+
2555
+ test('when loadOptions response returns cancelled true then dont end loading state', async (t) => {
2556
+ const select = new Select({
2557
+ target,
2558
+ props: {
2559
+ loadOptions: getCancelledRes,
2560
+ }
2561
+ });
2562
+
2563
+ select.$set({filterText: 'Juniper'});
2564
+ await wait(0);
2565
+
2566
+
2567
+ select.$destroy();
2568
+ });
2569
+
2570
+ test('when ClearIcon replace clear icon', async (t) => {
2571
+ const select = new ClearIconSlotTest({
2572
+ target,
2573
+ });
2574
+
2575
+ t.ok(target.querySelector('.clear-select div').innerHTML === 'x');
2576
+
2577
+ select.$destroy();
2578
+ });
2579
+
2580
+ test('losing focus of Select should close list', async (t) => {
2581
+ const select = new Select({
2582
+ target,
2583
+ props: {
2584
+ items,
2585
+ listOpen: true
2586
+ }
2587
+ });
2588
+
2589
+ t.ok(select.listOpen);
2590
+ document.querySelector('.svelte-select input').blur();
2591
+ await wait();
2592
+ t.ok(!select.listOpen);
2593
+ select.$destroy();
2594
+ });
2595
+
2596
+ test('clicking on an external textarea should close and blur it', async (t) => {
2597
+ const textarea = document.createElement('textarea');
2598
+ document.body.appendChild(textarea);
2599
+ const select = new Select({
2600
+ target,
2601
+ props: {
2602
+ listOpen: true,
2603
+ items,
2604
+ }
2605
+ });
2606
+
2607
+ t.ok(select.listOpen);
2608
+ document.querySelector('textarea').focus();
2609
+ t.ok(!select.listOpen);
2610
+
2611
+ textarea.remove();
2612
+ select.$destroy();
2613
+ });
2614
+
2615
+ test('when switching between multiple true/false ensure Select continues working', async (t) => {
2616
+ const select = new Select({
2617
+ target,
2618
+ props: {
2619
+ items,
2620
+ listOpen: true,
2621
+ value: {value: 'chips', label: 'Chips'}
2622
+ }
2623
+ });
2624
+
2625
+ select.multiple = true;
2626
+ select.loadOptions = itemsPromise;
2627
+
2628
+ t.ok(JSON.stringify(select.value) === JSON.stringify([{value: 'chips', label: 'Chips'}]));
2629
+ t.ok(Array.isArray(select.value));
2630
+
2631
+ select.multiple = false;
2632
+ select.loadOptions = null;
2633
+ select.items = [...items];
2634
+
2635
+ t.ok(!select.value);
2636
+
2637
+ select.$destroy();
2638
+ });
2639
+
2640
+ test('when searchable is false then input should be readonly', async (t) => {
2641
+ const select = new Select({
2642
+ target,
2643
+ props: {
2644
+ items,
2645
+ searchable: false
2646
+ }
2647
+ });
2648
+
2649
+ let elem = target.querySelector('.svelte-select input');
2650
+ t.ok(elem.hasAttribute('readonly'));
2651
+
2652
+ select.$destroy();
2653
+ });
2654
+
2655
+
2656
+ test('when esc key pressed should close list', async (t) => {
2657
+ const select = new Select({
2658
+ target,
2659
+ props: {
2660
+ items,
2661
+ listOpen: true
2662
+ }
2663
+ });
2664
+
2665
+ await wait(0);
2666
+ t.ok(select.listOpen === true);
2667
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Escape'}));
2668
+ t.ok(select.listOpen === false);
2669
+
2670
+ select.$destroy();
2671
+ });
2672
+
2673
+
2674
+ test('when multiple and placeholderAlwaysShow then always show placeholder text', async (t) => {
2675
+ const select = new Select({
2676
+ target,
2677
+ props: {
2678
+ items,
2679
+ value: [{value: 'chocolate', label: 'Chocolate'},
2680
+ {value: 'pizza', label: 'Pizza'},],
2681
+ multiple: true,
2682
+ placeholderAlwaysShow: true,
2683
+ placeholder: 'foo bar'
2684
+ }
2685
+ });
2686
+
2687
+ await wait(0);
2688
+ let elem = target.querySelector('.svelte-select input[type="text"]');
2689
+ t.ok(elem.placeholder === 'foo bar');
2690
+
2691
+ select.$destroy();
2692
+ });
2693
+
2694
+
2695
+ test('when loadOptions and value then items should show on promise resolve',async (t) => {
2696
+ const loadOptionsFn = async () => {
2697
+ return Promise.resolve([
2698
+ {value: 'chocolate', label: 'Chocolate'},
2699
+ {value: 'ice-cream', label: 'Ice-cream'},
2700
+ {value: 'pizza', label: 'pizza'},
2701
+ ]);
2702
+ }
2703
+
2704
+ const select = new Select({
2705
+ target,
2706
+ props: {
2707
+ value: {
2708
+ value: 'chocolate', label: 'Chocolate'
2709
+ },
2710
+ listOpen: true,
2711
+ filterText: 'a',
2712
+ loadOptions: loadOptionsFn
2713
+ }
2714
+ });
2715
+
2716
+ await wait(300);
2717
+ t.ok(select.getFilteredItems().length === 3);
2718
+
2719
+ select.$destroy();
2720
+ });
2721
+
2722
+ test('when loadOptions, multiple and value then filterText should remain on promise resolve',async (t) => {
2723
+ const loadOptionsFn = async () => {
2724
+ return Promise.resolve([
2725
+ {value: 'chocolate', label: 'Chocolate'},
2726
+ {value: 'ice-cream', label: 'Ice-cream'},
2727
+ {value: 'pizza', label: 'pizza'},
2728
+ ]);
2729
+ }
2730
+
2731
+ const select = new Select({
2732
+ target,
2733
+ props: {
2734
+ multiple: true,
2735
+ value: {
2736
+ value: 'chocolate', label: 'Chocolate'
2737
+ },
2738
+ listOpen: true,
2739
+ filterText: 'test',
2740
+ loadOptions: loadOptionsFn
2741
+ }
2742
+ });
2743
+
2744
+ await wait(300);
2745
+ t.ok(select.filterText === 'test');
2746
+
2747
+ select.$destroy();
2748
+ });
2749
+
2750
+ test('When listOffset is set list position offset changes', async (t) => {
2751
+ const select = new Select({
2752
+ target,
2753
+ props: {
2754
+ items,
2755
+ listOffset: 0,
2756
+ listOpen: true
2757
+ },
2758
+ });
2759
+
2760
+ await wait(0);
2761
+ let elem = document.querySelector('.svelte-select-list');
2762
+ t.ok(elem.style.top === '41px');
2763
+
2764
+ select.$destroy();
2765
+ });
2766
+
2767
+ test('When items are updated post onMount ensure filtering still works', async (t) => {
2768
+ const select = new Select({
2769
+ target,
2770
+ props: {
2771
+ items: null
2772
+ },
2773
+ });
2774
+
2775
+ await wait(0);
2776
+
2777
+ select.items = ['One', 'Two', 'Three'].map(item => ({ value: item, label: item }));
2778
+ select.filterText = 'Two';
2779
+ select.listOpen = true;
2780
+
2781
+ t.ok(select.getFilteredItems().length === 1);
2782
+ t.ok(select.getFilteredItems()[0].value === 'Two');
2783
+
2784
+ select.$destroy();
2785
+ });
2786
+
2787
+ test('When grouped items are updated post onMount ensure filtering still works', async (t) => {
2788
+ const select = new Select({
2789
+ target,
2790
+ props: {
2791
+ groupBy: item => item.group
2792
+ },
2793
+ });
2794
+
2795
+ await wait(0);
2796
+
2797
+ select.items = ['One', 'Two', 'Three'].map(item => ({ value: item, label: item, group: item.includes('T') ? '2nd Group' : '1st Group' }));
2798
+ select.filterText = 'Tw';
2799
+ select.listOpen = true;
2800
+
2801
+ t.ok(select.getFilteredItems().length === 2);
2802
+ t.ok(select.getFilteredItems()[0].label === '2nd Group');
2803
+ t.ok(select.getFilteredItems()[1].label === 'Two');
2804
+
2805
+
2806
+ select.$destroy();
2807
+ });
2808
+
2809
+
2810
+ test('When groupBy and value selected ensure filtering still works', async (t) => {
2811
+ const select = new Select({
2812
+ target,
2813
+ props: {
2814
+ items: itemsWithGroup,
2815
+ groupBy: (item) => item.group,
2816
+ listOpen: true
2817
+ },
2818
+ });
2819
+
2820
+ select.filterText = 'Cake';
2821
+ document.querySelector('.list-item .item.group-item').click();
2822
+ await wait(0);
2823
+ t.ok(select.getFilteredItems().length === 7);
2824
+
2825
+ select.$destroy();
2826
+ });
2827
+
2828
+ test('When value selected and filterText then ensure selecting the active value still clears filterText', async (t) => {
2829
+ const select = new Select({
2830
+ target,
2831
+ props: {
2832
+ items,
2833
+ },
2834
+ });
2835
+
2836
+ select.filterText = 'Cake';
2837
+ document.querySelector('.list-item .item').click();
2838
+ await wait(0);
2839
+ select.listOpen = true;
2840
+ select.filterText = 'Cake';
2841
+ document.querySelector('.list-item .item').click();
2842
+
2843
+ t.ok(select.filterText.length === 0);
2844
+
2845
+ select.$destroy();
2846
+ });
2847
+
2848
+ test('When multiple on:input events should fire on each item removal (including the last item)', async (t) => {
2849
+ const select = new Select({
2850
+ target,
2851
+ props: {
2852
+ items,
2853
+ multiple: true,
2854
+ value: ['Cake', 'Chips']
2855
+ },
2856
+ });
2857
+
2858
+ let events = [];
2859
+
2860
+ select.$on('input', (e) => {
2861
+ events.push('event fired');
2862
+ });
2863
+
2864
+ const event = new PointerEvent('pointerup')
2865
+ document.querySelector('.multi-item-clear').dispatchEvent(event);
2866
+ await wait(0);
2867
+ document.querySelector('.multi-item-clear').dispatchEvent(event);
2868
+ await wait(0);
2869
+ t.ok(events.length === 2);
2870
+
2871
+ select.$destroy();
2872
+ });
2873
+
2874
+ test('When inputAttributes.name supplied, add to hidden input', async (t) => {
2875
+ const select = new Select({
2876
+ target,
2877
+ props: {
2878
+ name: 'Foods',
2879
+ items: items,
2880
+ showChevron: true,
2881
+ },
2882
+ });
2883
+
2884
+ let hidden = document.querySelector('input[type="hidden"]').name;
2885
+ t.equal(hidden, 'Foods');
2886
+
2887
+ select.$destroy();
2888
+ });
2889
+
2890
+ test('When no value then hidden field should also have no value', async (t) => {
2891
+ const select = new Select({
2892
+ target,
2893
+ props: {
2894
+ inputAttributes: { name: 'Foods' },
2895
+ items: items,
2896
+ },
2897
+ });
2898
+
2899
+ let hidden = document.querySelector('input[type="hidden"]').value;
2900
+ t.ok(!hidden);
2901
+
2902
+ select.$destroy();
2903
+ });
2904
+
2905
+ test('When value then hidden field should have value', async (t) => {
2906
+ const select = new Select({
2907
+ target,
2908
+ props: {
2909
+ items: items,
2910
+ value: {value: 'cake', label: 'Cake'},
2911
+ },
2912
+ });
2913
+
2914
+ let hidden = document.querySelector('input[type="hidden"]').value;
2915
+ t.equal(JSON.parse(hidden).value, 'cake');
2916
+
2917
+ select.$destroy();
2918
+ });
2919
+
2920
+ test('When multiple and no value then hidden field should no value', async (t) => {
2921
+ const select = new Select({
2922
+ target,
2923
+ props: {
2924
+ multiple: true,
2925
+ items: items,
2926
+ },
2927
+ });
2928
+
2929
+ let hidden = document.querySelector('input[type="hidden"]').value;
2930
+ t.ok(!hidden);
2931
+
2932
+ select.$destroy();
2933
+ });
2934
+
2935
+ test('When multiple and value then hidden fields should list value items', async (t) => {
2936
+ const select = new Select({
2937
+ target,
2938
+ props: {
2939
+ multiple: true,
2940
+ items: items,
2941
+ value: [{value: 'cake', label: 'Cake'}, {value: 'pizza', label: 'Pizza'},]
2942
+ },
2943
+ });
2944
+
2945
+ let hidden = JSON.parse(document.querySelector('input[type="hidden"]').value);
2946
+ t.equal(hidden[0].value, 'cake');
2947
+ t.equal(hidden[1].value, 'pizza');
2948
+
2949
+ select.$destroy();
2950
+ });
2951
+
2952
+
2953
+ test('When listOpen then aria-context describes highlighted item', async (t) => {
2954
+ const select = new Select({
2955
+ target,
2956
+ props: {
2957
+ items: items,
2958
+ listOpen: true
2959
+ },
2960
+ });
2961
+
2962
+ let aria = document.querySelector('#aria-context');
2963
+ t.ok(aria.innerHTML.includes('Chocolate'));
2964
+ await handleKeyboard('ArrowDown');
2965
+ t.ok(aria.innerHTML.includes('Pizza'));
2966
+
2967
+ select.$destroy();
2968
+ });
2969
+
2970
+ test('When listOpen and value then aria-selection describes value', async (t) => {
2971
+ const select = new Select({
2972
+ target,
2973
+ props: {
2974
+ items: items,
2975
+ value: {value: 'cake', label: 'Cake'},
2976
+ focused: true
2977
+ },
2978
+ });
2979
+
2980
+ let aria = document.querySelector('#aria-selection');
2981
+ t.ok(aria.innerHTML.includes('Cake'));
2982
+
2983
+ select.$destroy();
2984
+ });
2985
+
2986
+ test('When listOpen, value and multiple then aria-selection describes value', async (t) => {
2987
+ const select = new Select({
2988
+ target,
2989
+ props: {
2990
+ multiple: true,
2991
+ items: items,
2992
+ value: [{value: 'cake', label: 'Cake'}, {value: 'pizza', label: 'Pizza'},],
2993
+ focused: true
2994
+ },
2995
+ });
2996
+
2997
+ let aria = document.querySelector('#aria-selection');
2998
+ t.ok(aria.innerHTML.includes('Cake'));
2999
+ t.ok(aria.innerHTML.includes('Pizza'));
3000
+
3001
+ select.$destroy();
3002
+ });
3003
+
3004
+ test('When ariaValues and value supplied, then aria-selection uses default updated', async (t) => {
3005
+ const select = new Select({
3006
+ target,
3007
+ props: {
3008
+ items: items,
3009
+ value: {value: 'pizza', label: 'Pizza'},
3010
+ focused: true,
3011
+ ariaValues: (val) => `Yummy ${val} in my tummy!`
3012
+ },
3013
+ });
3014
+
3015
+ let aria = document.querySelector('#aria-selection');
3016
+ t.equal(aria.innerHTML, 'Yummy Pizza in my tummy!');
3017
+
3018
+ select.$destroy();
3019
+ });
3020
+
3021
+ test('When ariaListOpen, listOpen, then aria-context uses default updated', async (t) => {
3022
+ const select = new Select({
3023
+ target,
3024
+ props: {
3025
+ items: items,
3026
+ listOpen: true,
3027
+ ariaListOpen: (label, count) => `label: ${label}, count: ${count}`
3028
+ },
3029
+ });
3030
+
3031
+ await wait(0);
3032
+ let aria = document.querySelector('#aria-context');
3033
+ t.equal(aria.innerHTML, 'label: Chocolate, count: 5');
3034
+
3035
+ select.$destroy();
3036
+ });
3037
+
3038
+ test('When ariaFocused, focused value supplied, then aria-context uses default updated', async (t) => {
3039
+ const select = new Select({
3040
+ target,
3041
+ props: {
3042
+ items: items,
3043
+ focused: true,
3044
+ listOpen: false,
3045
+ ariaFocused: () => `nothing to see here.`
3046
+ },
3047
+ });
3048
+
3049
+ let aria = document.querySelector('#aria-context');
3050
+ t.equal(aria.innerHTML, 'nothing to see here.');
3051
+ select.$destroy();
3052
+ });
3053
+
3054
+
3055
+ test('When id supplied then add to input', async (t) => {
3056
+ const select = new Select({
3057
+ target,
3058
+ props: {
3059
+ id: 'foods',
3060
+ items: items,
3061
+ },
3062
+ });
3063
+
3064
+ let aria = document.querySelector('input[type="text"]');
3065
+ t.equal(aria.id, 'foods');
3066
+
3067
+ select.$destroy();
3068
+ });
3069
+
3070
+
3071
+ test('allows the user to select an item by clicking with a focusable ancestor', async (t) => {
3072
+ const ancestor = document.createElement("div");
3073
+ ancestor.setAttribute("tabindex", "-1");
3074
+ target.appendChild(ancestor);
3075
+
3076
+ const select = new Select({
3077
+ target: ancestor,
3078
+ props: {
3079
+ items,
3080
+ },
3081
+ });
3082
+
3083
+ await querySelectorClick('.svelte-select');
3084
+ await querySelectorClick('.list-item');
3085
+ t.equal(select.value.label, 'Chocolate');
3086
+
3087
+ select.$destroy();
3088
+ });
3089
+
3090
+
3091
+ test('when listOpen true on page load then list should show onMount', async (t) => {
3092
+ const select = new Select({
3093
+ target,
3094
+ props: {
3095
+ items,
3096
+ listOpen: true,
3097
+ },
3098
+ });
3099
+
3100
+ let list = document.querySelector('.svelte-select-list');
3101
+
3102
+ t.ok(list);
3103
+
3104
+ select.$destroy();
3105
+ });
3106
+
3107
+ test('when listOpen true on page load then list should show onMount', async (t) => {
3108
+ const select = new Select({
3109
+ target,
3110
+ props: {
3111
+ items,
3112
+ listOpen: true,
3113
+ },
3114
+ });
3115
+
3116
+ let list = document.querySelector('.svelte-select-list');
3117
+
3118
+ t.ok(list);
3119
+
3120
+ select.$destroy();
3121
+ });
3122
+
3123
+ test('when value is set check from item and show correct label', async (t) => {
3124
+ const select = new Select({
3125
+ target,
3126
+ props: {
3127
+ items,
3128
+ listOpen: true,
3129
+ }
3130
+ });
3131
+
3132
+ select.value = 'cake';
3133
+ t.equal(select.value.label, 'Cake');
3134
+ select.$destroy();
3135
+ });
3136
+
3137
+ test('when component focuses fire on:focus event', async (t) => {
3138
+ const select = new Select({
3139
+ target,
3140
+ props: {
3141
+ items
3142
+ }
3143
+ });
3144
+
3145
+ let f = false;
3146
+ select.$on('focus', () => {
3147
+ f = true;
3148
+ });
3149
+
3150
+ let ele = document.querySelector('.svelte-select input');
3151
+ ele.focus();
3152
+
3153
+ t.ok(f);
3154
+
3155
+ select.$destroy();
3156
+ });
3157
+
3158
+
3159
+ test('when component blurs fire on:blur event', async (t) => {
3160
+ const select = new Select({
3161
+ target,
3162
+ props: {
3163
+ items,
3164
+ focused: true
3165
+ }
3166
+ });
3167
+
3168
+ let b = false;
3169
+ select.$on('blur', () => {
3170
+ b = true;
3171
+ });
3172
+
3173
+ let ele = document.querySelector('.svelte-select input');
3174
+ ele.blur();
3175
+
3176
+ t.ok(b);
3177
+
3178
+ select.$destroy();
3179
+ });
3180
+
3181
+ test('when loadOptions and groupBy then group headers should appear', async (t) => {
3182
+ const select = new Select({
3183
+ target,
3184
+ props: {
3185
+ debounceWait: 1,
3186
+ groupBy,
3187
+ loadOptions: async function () {
3188
+ return itemsWithGroup;
3189
+ }
3190
+ }
3191
+ });
3192
+
3193
+ function groupBy(item) {
3194
+ return item.group;
3195
+ }
3196
+
3197
+ select.$set({filterText: 'potato'});
3198
+ await wait(50);
3199
+ const header = document.querySelector('.svelte-select-list .list-group-title');
3200
+ t.ok(header.innerHTML === 'Sweet');
3201
+
3202
+ select.$destroy();
3203
+ });
3204
+
3205
+ test('when user selects an item then change event fires', async (t) => {
3206
+ const select = new Select({
3207
+ target,
3208
+ props: {
3209
+ listOpen: true,
3210
+ items
3211
+ }
3212
+ });
3213
+
3214
+ let value = undefined;
3215
+
3216
+ select.$on('change', event => {
3217
+ value = JSON.stringify(event.detail);
3218
+ });
3219
+
3220
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3221
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3222
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3223
+ await wait(0);
3224
+ t.equal(value, JSON.stringify({value: 'cake', label: 'Cake'}));
3225
+
3226
+ select.$destroy();
3227
+ });
3228
+
3229
+ test('when item selected programmatically a change event should NOT fire', async (t) => {
3230
+ const select = new Select({
3231
+ target,
3232
+ props: {
3233
+ listOpen: true,
3234
+ items
3235
+ }
3236
+ });
3237
+
3238
+ let value = undefined;
3239
+ select.$set({ value: {value: 'cake', label: 'Cake'}});
3240
+
3241
+ select.$on('change', event => {
3242
+ value = event.detail;
3243
+ });
3244
+
3245
+ await wait(0);
3246
+ t.ok(value === undefined);
3247
+
3248
+ select.$destroy();
3249
+ });
3250
+
3251
+
3252
+ test('when value is cleared then justValue should be null', async (t) => {
3253
+ const select = new Select({
3254
+ target,
3255
+ props: {
3256
+ listOpen: true,
3257
+ items,
3258
+ value: {value: 'cake', label: 'Cake'}
3259
+ }
3260
+ });
3261
+
3262
+ select.handleClear();
3263
+ await wait(0);
3264
+ t.ok(!select.justValue);
3265
+
3266
+ select.$destroy();
3267
+ });
3268
+
3269
+ test('when items are grouped and filter text results in no items then list renders correct message', async (t) => {
3270
+ const select = new Select({
3271
+ target,
3272
+ props: {
3273
+ listOpen: true,
3274
+ items: itemsWithGroup,
3275
+ groupBy
3276
+ }
3277
+ });
3278
+
3279
+ function groupBy(item) {
3280
+ return item.group;
3281
+ }
3282
+
3283
+ let title = document.querySelector('.list-group-title').innerHTML;
3284
+ t.ok(title === 'Sweet');
3285
+ let item = document.querySelector('.list-item .item.group-item').innerHTML;
3286
+ t.ok(item === 'Chocolate');
3287
+ select.filterText = 'foo';
3288
+ let empty = document.querySelector('.svelte-select-list .empty');
3289
+ t.ok(empty);
3290
+ select.$destroy();
3291
+ });
3292
+
3293
+ test('when named slot chevron show content', async (t) => {
3294
+ const select = new ChevronSlotTest({
3295
+ target,
3296
+ });
3297
+
3298
+ t.ok(document.querySelector('.chevron div').innerHTML === '⬆️');
3299
+
3300
+ select.$destroy();
3301
+ });
3302
+
3303
+ test('when named slot list show content', async (t) => {
3304
+ const select = new ListSlotTest({
3305
+ target,
3306
+ });
3307
+
3308
+ t.ok(document.querySelector('.svelte-select-list').innerHTML.trim() === 'onetwo');
3309
+
3310
+ select.$destroy();
3311
+ });
3312
+
3313
+ test('when named slot input-hidden', async (t) => {
3314
+ const select = new InputHiddenSlotTest({
3315
+ target,
3316
+ });
3317
+
3318
+ t.ok(document.querySelector('input[type="hidden"][name="test"]').value.trim() === 'one');
3319
+
3320
+ select.$destroy();
3321
+ });
3322
+
3323
+ test('when named slot item show content', async (t) => {
3324
+ const select = new ItemSlotTest({
3325
+ target,
3326
+ });
3327
+
3328
+ t.ok(document.querySelector('.svelte-select-list .item').innerHTML === '* one *');
3329
+
3330
+ select.$destroy();
3331
+ });
3332
+
3333
+
3334
+ test('when named slots list-prepend and list-append show content', async (t) => {
3335
+ const select = new OuterListTest({
3336
+ target,
3337
+ });
3338
+
3339
+ t.ok(document.querySelector('.svelte-select-list').innerHTML.startsWith('prepend'));
3340
+ t.ok(document.querySelector('.svelte-select-list').innerHTML.endsWith('append'));
3341
+
3342
+ select.$destroy();
3343
+ });
3344
+
3345
+ test('when itemId and justValue then return correct value', async (t) => {
3346
+ const select = new Select({
3347
+ target,
3348
+ props: {
3349
+ items: collection,
3350
+ value: {_id: 2, label: 'Cake'},
3351
+ itemId: '_id'
3352
+ }
3353
+ });
3354
+
3355
+ t.ok(select.justValue === 2);
3356
+ select.$destroy();
3357
+ });
3358
+
3359
+ test('when --item-height css variable supplied then item height should match new height', async (t) => {
3360
+ const select = new ItemHeightTest({
3361
+ target
3362
+ });
3363
+
3364
+ t.ok(document.querySelector('.item').offsetHeight === 50);
3365
+
3366
+ select.$destroy();
3367
+ });
3368
+
3369
+ test('when --multi-item-color css variable supplied then CSS should apply', async (t) => {
3370
+ const select = new MultiItemColor({
3371
+ target
3372
+ });
3373
+
3374
+ t.ok(getComputedStyle(document.querySelector('.multi-item')).getPropertyValue('color') === 'rgb(255, 0, 0)');
3375
+
3376
+ select.$destroy();
3377
+ });
3378
+
3379
+
3380
+ test('when groupHeaderSelectable false and groupBy true then group headers should never have active/hover states', async (t) => {
3381
+ const select = new GroupHeaderNotSelectable({
3382
+ target
3383
+ });
3384
+
3385
+ await querySelectorClick('.svelte-select');
3386
+
3387
+ let item = document.querySelector('.item.hover.group-item');
3388
+
3389
+ t.ok(item.innerHTML === 'Chocolate');
3390
+
3391
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3392
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3393
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3394
+
3395
+ await wait(0);
3396
+ item = document.querySelector('.item.hover.group-item');
3397
+ t.ok(item.innerHTML === 'Chips');
3398
+
3399
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowUp'}));
3400
+
3401
+ await wait(0);
3402
+ item = document.querySelector('.item.hover.group-item');
3403
+ t.ok(item.innerHTML === 'Pizza');
3404
+
3405
+ select.$set({filterText: 'Ice'});
3406
+
3407
+ await wait(0);
3408
+ item = document.querySelector('.item.hover.group-item');
3409
+ t.ok(item.innerHTML === 'Ice Cream');
3410
+
3411
+ select.$set({filterText: ''});
3412
+
3413
+ await wait(0);
3414
+ item = document.querySelector('.item.hover.group-item');
3415
+ t.ok(item.innerHTML === 'Chocolate');
3416
+
3417
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowUp'}));
3418
+
3419
+ await wait(0);
3420
+ item = document.querySelector('.item.hover.group-item');
3421
+ t.ok(item.innerHTML === 'Chips');
3422
+
3423
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3424
+
3425
+ await wait(0);
3426
+ item = document.querySelector('.item.hover.group-item');
3427
+ t.ok(item.innerHTML === 'Chocolate');
3428
+
3429
+ select.$destroy();
3430
+ });
3431
+
3432
+
3433
+ test('when hasError then show error styles', async (t) => {
3434
+ const select = new Select({
3435
+ target,
3436
+ props: {
3437
+ hasError: true
3438
+ }
3439
+ });
3440
+
3441
+ t.ok(document.querySelector('.svelte-select.error'));
3442
+ select.$set({hasError: false});
3443
+ await wait(0);
3444
+ t.ok(!document.querySelector('.svelte-select.error'));
3445
+
3446
+ select.$destroy();
3447
+ });
3448
+
3449
+
3450
+ test('when items filter then event on:filter fires', async (t) => {
3451
+ const select = new Select({
3452
+ target,
3453
+ props: {
3454
+ items,
3455
+ listOpen: true
3456
+ }
3457
+ });
3458
+
3459
+ let event = undefined;
3460
+
3461
+ select.$on('filter', (e) => {
3462
+ event = e.detail
3463
+ });
3464
+
3465
+ select.$set({filterText: 'ch'});
3466
+ await wait(0);
3467
+ t.ok(event && event.length === 2);
3468
+
3469
+ select.$destroy();
3470
+ });
3471
+
3472
+
3473
+ test('clicking tab on item with selectable false should not select item', async (t) => {
3474
+ const select = new Select({
3475
+ target,
3476
+ props: {
3477
+ listOpen: true,
3478
+ items: itemsWithSelectable,
3479
+ filterText: '2'
3480
+ }
3481
+ });
3482
+
3483
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Tab'}));
3484
+ await wait(0);
3485
+ t.ok(!select.value)
3486
+ select.$destroy();
3487
+ });
3488
+
3489
+
3490
+ test('when multiple and clicking enter an item with selectable false should not be selected', async (t) => {
3491
+ const select = new Select({
3492
+ target,
3493
+ props: {
3494
+ listOpen: true,
3495
+ items: itemsWithSelectable,
3496
+ filterText: '2',
3497
+ multiple: true
3498
+ }
3499
+ });
3500
+
3501
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3502
+ await wait(0);
3503
+ t.ok(!select.value)
3504
+ select.$destroy();
3505
+ });
3506
+
3507
+ test('when list has one item that is not selectable then clicking up/down keys should reset hover index', async (t) => {
3508
+ const select = new Select({
3509
+ target,
3510
+ props: {
3511
+ listOpen: true,
3512
+ items: [
3513
+ { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
3514
+ { value: 'pizza', label: 'Pizza', group: 'Savory' },
3515
+ { value: 'cake', label: 'Cake', group: 'Sweet', selectable: false },
3516
+ { value: 'chips', label: 'Chips', group: 'Savory' },
3517
+ { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' },
3518
+ ],
3519
+ filterText: 'Ca',
3520
+ },
3521
+ });
3522
+
3523
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3524
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3525
+ t.ok(!select.value);
3526
+ select.$set({filterText: 'pi'});
3527
+ await wait(0);
3528
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3529
+ t.ok(select.value.label === 'Pizza');
3530
+
3531
+ select.$destroy();
3532
+ });
3533
+
3534
+ test('when list has no items that are selectable then clicking up/down keys should reset hover index', async (t) => {
3535
+ const select = new Select({
3536
+ target,
3537
+ props: {
3538
+ listOpen: true,
3539
+ items: itemsWithSelectable,
3540
+ filterText: 'not'
3541
+ }
3542
+ });
3543
+
3544
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3545
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3546
+ t.ok(!select.value);
3547
+ select.$set({filterText: 'se'});
3548
+ await wait(0);
3549
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'ArrowDown'}));
3550
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3551
+ await wait(0);
3552
+ t.ok(select.value.label === 'SelectableDefault')
3553
+ select.$destroy();
3554
+ });
3555
+
3556
+ test('when listOpen and value then hoverItemIndex should be the active value', async (t) => {
3557
+ const select = new Select({
3558
+ target,
3559
+ props: {
3560
+ listOpen: true,
3561
+ items: items,
3562
+ value: {value: 'cake', label: 'Cake'},
3563
+ }
3564
+ });
3565
+
3566
+ t.ok(select.hoverItemIndex === 2);
3567
+
3568
+ select.$destroy();
3569
+ });
3570
+
3571
+ test('when listOpen and multiple then hoverItemIndex should be 0', async (t) => {
3572
+ const select = new Select({
3573
+ target,
3574
+ props: {
3575
+ listOpen: true,
3576
+ items: items,
3577
+ multiple: true
3578
+ }
3579
+ });
3580
+
3581
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3582
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3583
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3584
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3585
+
3586
+ await wait(0);
3587
+ await querySelectorClick('.svelte-select');
3588
+ t.ok(select.hoverItemIndex === 0);
3589
+
3590
+ select.$destroy();
3591
+ });
3592
+
3593
+ test('when listOpen and value and groupBy then hoverItemIndex should be the active value', async (t) => {
3594
+ const select = new Select({
3595
+ target,
3596
+ props: {
3597
+ listOpen: true,
3598
+ items: itemsWithGroupAndSelectable,
3599
+ value: {value: 'chocolate', label: 'Chocolate', group: 'Sweet'},
3600
+ groupBy: (i) => i.group,
3601
+ groupHeaderSelectable: true
3602
+ }
3603
+ });
3604
+
3605
+ t.ok(select.hoverItemIndex === 1);
3606
+
3607
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3608
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3609
+
3610
+ t.ok(select.hoverItemIndex === 4);
3611
+
3612
+ select.$destroy();
3613
+ });
3614
+
3615
+ test('when groupBy, itemId and label then list should render correctly', async (t) => {
3616
+ const select = new Select({
3617
+ target,
3618
+ props: {
3619
+ listOpen: true,
3620
+ items: [
3621
+ {id: 1, name: 'name 1', group: 'group 1'},
3622
+ {id: 2, name: 'name 2', group: 'group 1'},
3623
+ {id: 3, name: 'name 3', group: 'group 2'},
3624
+ {id: 4, name: 'name 4', group: 'group 1'},
3625
+ {id: 5, name: 'name 5', group: 'group 3'},
3626
+ ],
3627
+ itemId: 'id',
3628
+ label: 'name',
3629
+ groupBy: (i) => i.group,
3630
+ }
3631
+ });
3632
+
3633
+ let titles = document.querySelectorAll('.list-group-title');
3634
+ let items = document.querySelectorAll('.item.group-item');
3635
+
3636
+ t.ok(titles[1].innerHTML === 'group 2');
3637
+ t.ok(items[3].innerHTML === 'name 3');
3638
+
3639
+ select.$destroy();
3640
+ });
3641
+
3642
+ test('when listOpen and value and groupBy then hoverItemIndex should be the active value', async (t) => {
3643
+ const select = new Select({
3644
+ target,
3645
+ props: {
3646
+ listOpen: true,
3647
+ items: [
3648
+ {id: 1, name: 'name 1', group: 'group 1'},
3649
+ {id: 2, name: 'name 2', group: 'group 1'},
3650
+ {id: 3, name: 'name 3', group: 'group 2'},
3651
+ {id: 4, name: 'name 4', group: 'group 1'},
3652
+ {id: 5, name: 'name 5', group: 'group 3'},
3653
+ ],
3654
+ itemId: 'id',
3655
+ label: 'name',
3656
+ groupBy: (i) => i.group,
3657
+ }
3658
+ });
3659
+
3660
+ t.ok(select.hoverItemIndex === 1);
3661
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3662
+ t.ok(select.hoverItemIndex === 2);
3663
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3664
+ await wait(0);
3665
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3666
+ t.ok(select.hoverItemIndex === 2);
3667
+
3668
+ select.$destroy();
3669
+ });
3670
+
3671
+
3672
+ test('when closeListOnChange is false and item selected then list should remain open', async (t) => {
3673
+ const select = new Select({
3674
+ target,
3675
+ props: {
3676
+ items,
3677
+ closeListOnChange: false
3678
+ }
3679
+ });
3680
+
3681
+ await querySelectorClick('.svelte-select');
3682
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3683
+ t.ok(select.value.value === 'chocolate');
3684
+ t.ok(select.listOpen);
3685
+
3686
+ await querySelectorClick('.svelte-select');
3687
+ t.ok(!select.listOpen);
3688
+
3689
+ await querySelectorClick('.svelte-select');
3690
+ await querySelectorClick('.list-item:nth-child(3)');
3691
+ t.ok(select.value.value === 'cake');
3692
+ t.ok(select.listOpen);
3693
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3694
+ t.ok(!select.listOpen);
3695
+
3696
+ select.$destroy();
3697
+ });
3698
+
3699
+
3700
+
3701
+ test('when listOpen and value and groupBy then hoverItemIndex should be the active value', async (t) => {
3702
+ const select = new HoverItemIndexTest({
3703
+ target,
3704
+ });
3705
+
3706
+ await querySelectorClick('.svelte-select');
3707
+ t.ok(select.hoverItemIndex === 1);
3708
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3709
+ window.dispatchEvent(new KeyboardEvent('keydown', {'key': 'Enter'}));
3710
+ await wait(0);
3711
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3712
+ t.ok(select.hoverItemIndex === 2);
3713
+ select.$destroy();
3714
+ });
3715
+
3716
+
3717
+ test('when loadOptions and groupBy then titles should not duplicate after filterText clears', async (t) => {
3718
+ const select = new LoadOptionsGroup({
3719
+ target,
3720
+ });
3721
+
3722
+ select.$set({filterText: 'cre'});
3723
+ await wait(500);
3724
+ t.ok(document.querySelectorAll('.list-group-title').length === 1);
3725
+ select.$set({filterText: 'cr'});
3726
+ await wait(500);
3727
+ t.ok(document.querySelectorAll('.list-group-title').length === 1);
3728
+
3729
+ select.$destroy();
3730
+ });
3731
+
3732
+ test('when loadOptions and value then it should set initial value', async (t) => {
3733
+ const select = new LoadOptionsGroup({
3734
+ target,
3735
+ props: {
3736
+ value: 'cake'
3737
+ }
3738
+ });
3739
+
3740
+ t.ok(document.querySelector('.value-container .selected-item').innerHTML === 'cake');
3741
+ await wait(500);
3742
+ t.ok(document.querySelector('.value-container .selected-item').innerHTML === 'Cake');
3743
+
3744
+ select.$destroy();
3745
+ });