slim-select 3.4.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/classes.d.ts +47 -0
  2. package/dist/helpers.d.ts +5 -0
  3. package/dist/index.d.ts +53 -319
  4. package/dist/index.umd.d.ts +2 -0
  5. package/dist/react/index.d.ts +4 -319
  6. package/dist/react/index.js +1471 -1697
  7. package/dist/react/index.umd.js +2 -2
  8. package/dist/react/react.d.ts +17 -0
  9. package/dist/react/react.test.d.ts +1 -0
  10. package/dist/react/slim-select/react/index.d.ts +4 -0
  11. package/dist/render.d.ts +90 -0
  12. package/dist/select.d.ts +38 -0
  13. package/dist/settings.d.ts +40 -0
  14. package/dist/slimselect.cjs.js +2 -2
  15. package/dist/slimselect.cjs.js.map +1 -1
  16. package/dist/slimselect.css +1 -1
  17. package/dist/slimselect.css.map +1 -1
  18. package/dist/slimselect.es.js +1245 -1385
  19. package/dist/slimselect.es.js.map +1 -1
  20. package/dist/slimselect.iife.js +2 -2
  21. package/dist/slimselect.iife.js.map +1 -1
  22. package/dist/slimselect.js +2 -2
  23. package/dist/slimselect.js.map +1 -1
  24. package/dist/src/docs/main.d.ts +0 -0
  25. package/dist/src/docs/router.d.ts +2 -0
  26. package/dist/src/docs/store.d.ts +12 -0
  27. package/dist/store.d.ts +53 -0
  28. package/dist/vue/index.d.ts +4 -319
  29. package/dist/vue/index.js +1347 -1504
  30. package/dist/vue/index.umd.js +1 -1
  31. package/dist/vue/slim-select/vue/index.d.ts +4 -0
  32. package/package.json +30 -36
  33. package/src/slim-select/slimselect.scss +47 -5
  34. package/.nvmrc +0 -1
  35. package/.prettierignore +0 -1
  36. package/.prettierrc +0 -7
  37. package/CODE_OF_CONDUCT.md +0 -46
  38. package/CONTRIBUTING.md +0 -1
  39. package/cdn.html +0 -21
  40. package/docs.go +0 -12
  41. package/example.html +0 -25
  42. package/index.html +0 -76
  43. package/release.js +0 -191
  44. package/sitemap.js +0 -108
  45. package/src/slim-select/accessibility.test.ts +0 -782
  46. package/src/slim-select/classes.test.ts +0 -73
  47. package/src/slim-select/classes.ts +0 -117
  48. package/src/slim-select/helpers.test.ts +0 -162
  49. package/src/slim-select/helpers.ts +0 -61
  50. package/src/slim-select/index.test.ts +0 -1356
  51. package/src/slim-select/index.ts +0 -594
  52. package/src/slim-select/index.umd.ts +0 -8
  53. package/src/slim-select/react/index.tsx +0 -9
  54. package/src/slim-select/react/react.test.tsx +0 -498
  55. package/src/slim-select/react/react.tsx +0 -209
  56. package/src/slim-select/render.test.ts +0 -1927
  57. package/src/slim-select/render.ts +0 -1771
  58. package/src/slim-select/select.test.ts +0 -683
  59. package/src/slim-select/select.ts +0 -659
  60. package/src/slim-select/settings.test.ts +0 -285
  61. package/src/slim-select/settings.ts +0 -78
  62. package/src/slim-select/store.test.ts +0 -1007
  63. package/src/slim-select/store.ts +0 -433
  64. package/src/slim-select/vue/index.ts +0 -9
  65. package/src/slim-select/vue/vue.test.ts +0 -1091
  66. package/src/slim-select/vue/vue.vue +0 -224
  67. package/tsconfig.json +0 -26
  68. package/vite.config.docs.mts +0 -29
  69. package/vite.config.lib.es-cjs.ts +0 -40
  70. package/vite.config.lib.umd-iife.ts +0 -34
  71. package/vite.config.react.mts +0 -41
  72. package/vite.config.vue.mts +0 -40
  73. package/vitest.config.ts +0 -30
@@ -1,1356 +0,0 @@
1
- /**
2
- * @jest-environment jsdom
3
- */
4
-
5
- 'use strict'
6
-
7
- import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
8
- import SlimSelect from '@/slim-select'
9
-
10
- describe('SlimSelect Module', () => {
11
- let slim: SlimSelect
12
-
13
- beforeEach(() => {
14
- document.body.innerHTML = '<select id="test"></select>'
15
-
16
- slim = new SlimSelect({
17
- select: '#test'
18
- })
19
- })
20
-
21
- afterEach(() => {
22
- if (slim) {
23
- slim.destroy()
24
- }
25
- })
26
-
27
- describe('constructor', () => {
28
- test('missing select element throws error', () => {
29
- const errorMock = vi.fn()
30
- const slim = new SlimSelect({ select: '#invalid', events: { error: errorMock } })
31
-
32
- expect(slim.select).toBeUndefined()
33
- expect(slim.store).toBeUndefined()
34
- expect(slim.render).toBeUndefined()
35
- expect(errorMock).toHaveBeenCalled()
36
- expect(errorMock.mock.calls[0][0].message).toBe('Could not find select element')
37
- })
38
-
39
- test('invalid element throws error', () => {
40
- document.body.innerHTML = '<div id="invalid"></div>'
41
-
42
- const errorMock = vi.fn()
43
- const slim = new SlimSelect({ select: '#invalid', events: { error: errorMock } })
44
-
45
- expect(slim.select).toBeUndefined()
46
- expect(slim.store).toBeUndefined()
47
- expect(slim.render).toBeUndefined()
48
- expect(errorMock).toHaveBeenCalled()
49
- expect(errorMock.mock.calls[0][0].message).toBe('Element isnt of type select')
50
- })
51
-
52
- test('valid minimal constructor with query string', () => {
53
- document.body.innerHTML = '<select id="test"></select>'
54
-
55
- const slimSelect = new SlimSelect({
56
- select: '#test'
57
- })
58
-
59
- expect(slimSelect).toBeInstanceOf(SlimSelect)
60
- })
61
-
62
- test('valid minimal constructor with HTML element', () => {
63
- document.body.innerHTML = '<select id="test"></select>'
64
-
65
- const slimSelect = new SlimSelect({
66
- select: document.getElementById('test') as Element
67
- })
68
-
69
- expect(slimSelect).toBeInstanceOf(SlimSelect)
70
- })
71
-
72
- test('disabled gets applied correctly', () => {
73
- document.body.innerHTML = '<select id="test"></select>'
74
-
75
- const slim = new SlimSelect({
76
- select: document.getElementById('test') as Element,
77
- settings: {
78
- disabled: true
79
- }
80
- })
81
-
82
- expect(slim.settings.disabled).toBe(true)
83
- })
84
- })
85
-
86
- test('enable', () => {
87
- slim.settings.disabled = true
88
-
89
- const selectEnableMock = vi.fn()
90
- const renderEnableMock = vi.fn()
91
-
92
- slim.select.enable = selectEnableMock
93
- slim.render.enable = renderEnableMock
94
-
95
- slim.enable()
96
-
97
- expect(slim.settings.disabled).toBe(false)
98
- expect(selectEnableMock).toHaveBeenCalled()
99
- expect(renderEnableMock).toHaveBeenCalled()
100
- })
101
-
102
- test('disable', () => {
103
- slim.settings.disabled = false
104
-
105
- const selectDisableMock = vi.fn()
106
- const renderDisableMock = vi.fn()
107
-
108
- slim.select.disable = selectDisableMock
109
- slim.render.disable = renderDisableMock
110
-
111
- slim.disable()
112
-
113
- expect(slim.settings.disabled).toBe(true)
114
- expect(selectDisableMock).toHaveBeenCalled()
115
- expect(renderDisableMock).toHaveBeenCalled()
116
- })
117
-
118
- test('multiple - do not render deselect all with no selected options', () => {
119
- document.body.innerHTML = `<select id="test" multiple>
120
- <option data-placeholder="true"></option>
121
- <option id="1" value="1">One</option>
122
- <option id="2" value="2">Two</option>
123
- <option id="3" value="3">Two</option>
124
- </select>`
125
-
126
- const options = [
127
- {
128
- id: '1',
129
- value: '1',
130
- text: 'One'
131
- },
132
- {
133
- id: '2',
134
- value: '2',
135
- text: 'Two'
136
- },
137
- {
138
- id: '3',
139
- value: '3',
140
- text: 'Three'
141
- }
142
- ]
143
- const config = {
144
- select: '#test',
145
- settings: {
146
- allowDeselect: true
147
- },
148
- data: options
149
- }
150
-
151
- let slimSelect = new SlimSelect(config)
152
- expect(slimSelect.store.getSelectType()).toEqual('multiple')
153
- expect(slimSelect.getSelected()).toHaveLength(0)
154
- expect(slimSelect.render.main.deselect.main.classList).toContain(slimSelect.render.classes.hide)
155
- })
156
- test('multiple - render deselect all option with selected options', () => {
157
- document.body.innerHTML = `<select id="test" multiple>
158
- <option data-placeholder="true"></option>
159
- <option id="1" value="1">One</option>
160
- <option id="2" value="2">Two</option>
161
- <option id="3" value="3">Two</option>
162
- </select>`
163
-
164
- const options = [
165
- {
166
- id: '1',
167
- value: '1',
168
- text: 'One',
169
- selected: true
170
- },
171
- {
172
- id: '2',
173
- value: '2',
174
- text: 'Two',
175
- selected: true
176
- },
177
- {
178
- id: '3',
179
- value: '3',
180
- text: 'Three'
181
- }
182
- ]
183
- const config = {
184
- select: '#test',
185
- settings: {
186
- allowDeselect: true
187
- },
188
- data: options
189
- }
190
-
191
- let slimSelect = new SlimSelect(config)
192
- expect(slimSelect.store.getSelectType()).toEqual('multiple')
193
- expect(slimSelect.getSelected()).toHaveLength(2)
194
- expect(slimSelect.render.main.deselect.main.classList).not.toContain(slimSelect.render.classes.hide)
195
- })
196
-
197
- describe('required attribute support', () => {
198
- test('select with required attribute remains focusable for validation', () => {
199
- document.body.innerHTML = '<select id="test" required></select>'
200
-
201
- const slim = new SlimSelect({
202
- select: '#test'
203
- })
204
-
205
- const selectEl = document.getElementById('test') as HTMLSelectElement
206
-
207
- // Select should be hidden but still focusable (1px for validation popup)
208
- expect(selectEl.style.position).toBe('absolute')
209
- expect(selectEl.style.width).toBe('1px')
210
- expect(selectEl.style.height).toBe('1px')
211
- expect(selectEl.style.opacity).toBe('0')
212
- expect(selectEl.style.margin).toBe('0px')
213
- expect(selectEl.style.padding).toBe('0px')
214
- // clip property is deprecated and may not be accessible in all test environments
215
- // The code sets it, but some browsers/test environments may ignore or clear it
216
- // Check if clip is set OR if the other hiding properties are sufficient
217
- const clipValue = selectEl.style.clip
218
- if (clipValue) {
219
- expect(clipValue).toContain('rect')
220
- }
221
- // If clip is empty, the other hiding properties (position, width, height, opacity) are sufficient
222
-
223
- // Required attribute should still be present
224
- expect(selectEl.hasAttribute('required')).toBe(true)
225
-
226
- // Select should be able to receive focus programmatically
227
- selectEl.focus()
228
- expect(document.activeElement).toBe(selectEl)
229
- })
230
-
231
- test('form validation works with required select', () => {
232
- // Create a form with required select
233
- document.body.innerHTML = `
234
- <form id="test-form">
235
- <select id="test" name="test-select" required>
236
- <option value="">Select an option</option>
237
- <option value="1">Option 1</option>
238
- <option value="2">Option 2</option>
239
- </select>
240
- <button type="submit">Submit</button>
241
- </form>
242
- `
243
-
244
- const slim = new SlimSelect({
245
- select: '#test'
246
- })
247
-
248
- const form = document.getElementById('test-form') as HTMLFormElement
249
- const selectEl = document.getElementById('test') as HTMLSelectElement
250
-
251
- // Form should be invalid when nothing is selected
252
- expect(selectEl.required).toBe(true)
253
- expect(selectEl.value).toBe('')
254
- expect(form.checkValidity()).toBe(false)
255
-
256
- // Select an option
257
- slim.setSelected(['1'])
258
-
259
- // Form should now be valid
260
- expect(selectEl.value).toBe('1')
261
- expect(form.checkValidity()).toBe(true)
262
- })
263
- })
264
-
265
- describe('keepOrder setting', () => {
266
- test('keepOrder: false returns values in DOM order (default)', () => {
267
- document.body.innerHTML = `
268
- <select id="test" multiple>
269
- <option value="apple">Apple</option>
270
- <option value="banana">Banana</option>
271
- <option value="cherry">Cherry</option>
272
- </select>
273
- `
274
-
275
- const slim = new SlimSelect({
276
- select: '#test',
277
- settings: {
278
- keepOrder: false // DOM order (default)
279
- }
280
- })
281
-
282
- // Select in reverse order: Cherry -> Apple -> Banana
283
- slim.setSelected(['cherry', 'apple', 'banana'])
284
-
285
- // Should return in DOM order (how they appear in HTML)
286
- expect(slim.getSelected()).toEqual(['apple', 'banana', 'cherry'])
287
- })
288
-
289
- test('keepOrder: true returns values in selection order', () => {
290
- document.body.innerHTML = `
291
- <select id="test" multiple>
292
- <option value="apple">Apple</option>
293
- <option value="banana">Banana</option>
294
- <option value="cherry">Cherry</option>
295
- </select>
296
- `
297
-
298
- const slim = new SlimSelect({
299
- select: '#test',
300
- settings: {
301
- keepOrder: true // Selection order
302
- }
303
- })
304
-
305
- // Select in specific order: Cherry -> Apple -> Banana
306
- slim.setSelected(['cherry', 'apple', 'banana'])
307
-
308
- // Should return in the order they were selected (click order)
309
- expect(slim.getSelected()).toEqual(['cherry', 'apple', 'banana'])
310
- })
311
-
312
- test('keepOrder: true preserves click order in getSelected', () => {
313
- document.body.innerHTML = `
314
- <select id="test" multiple>
315
- <option value="1">Option 1</option>
316
- <option value="2">Option 2</option>
317
- <option value="3">Option 3</option>
318
- <option value="4">Option 4</option>
319
- <option value="5">Option 5</option>
320
- </select>
321
- `
322
-
323
- const slim = new SlimSelect({
324
- select: '#test',
325
- settings: {
326
- keepOrder: true
327
- }
328
- })
329
-
330
- // Simulate user clicking options in random order
331
- slim.setSelected(['5', '2', '4', '1'])
332
-
333
- // Should maintain that exact order
334
- expect(slim.getSelected()).toEqual(['5', '2', '4', '1'])
335
- })
336
- })
337
-
338
- describe('maxValuesShown with deselection', () => {
339
- test('can still deselect options via dropdown when maxValuesShown is exceeded', () => {
340
- document.body.innerHTML = `
341
- <select id="test" multiple>
342
- <option value="1">Option 1</option>
343
- <option value="2">Option 2</option>
344
- <option value="3">Option 3</option>
345
- <option value="4">Option 4</option>
346
- <option value="5">Option 5</option>
347
- </select>
348
- `
349
-
350
- const slim = new SlimSelect({
351
- select: '#test',
352
- settings: {
353
- maxValuesShown: 2 // Show "X selected" when more than 2
354
- }
355
- })
356
-
357
- // Select 3 options (exceeds maxValuesShown)
358
- slim.setSelected(['1', '2', '3'])
359
- expect(slim.getSelected()).toEqual(['1', '2', '3'])
360
-
361
- // Verify counter is shown (not individual values)
362
- const counter = document.querySelector('.ss-max')
363
- expect(counter?.textContent).toBe('3 selected')
364
-
365
- // Open dropdown
366
- slim.open()
367
-
368
- // Find and click on a selected option to deselect it
369
- const options = document.querySelectorAll('[role="option"]')
370
- const selectedOption = Array.from(options).find(
371
- (opt) => opt.getAttribute('aria-selected') === 'true' && opt.textContent?.includes('Option 1')
372
- ) as HTMLElement
373
-
374
- expect(selectedOption).toBeTruthy()
375
-
376
- // Click the selected option - should deselect it
377
- selectedOption.dispatchEvent(new MouseEvent('click', { bubbles: true }))
378
-
379
- // Should now only have 2 selected
380
- expect(slim.getSelected()).toEqual(['2', '3'])
381
- })
382
-
383
- test('deselecting via dropdown updates maxValuesShown display', () => {
384
- document.body.innerHTML = `
385
- <select id="test" multiple>
386
- <option value="1">Value 1</option>
387
- <option value="2">Value 2</option>
388
- <option value="3">Value 3</option>
389
- </select>
390
- `
391
-
392
- const slim = new SlimSelect({
393
- select: '#test',
394
- settings: {
395
- maxValuesShown: 2
396
- }
397
- })
398
-
399
- // Select 3 options (shows "3 selected")
400
- slim.setSelected(['1', '2', '3'])
401
-
402
- // Open and deselect one
403
- slim.open()
404
- const options = document.querySelectorAll('[role="option"]')
405
- const firstSelected = Array.from(options).find(
406
- (opt) => opt.getAttribute('aria-selected') === 'true'
407
- ) as HTMLElement
408
- firstSelected.dispatchEvent(new MouseEvent('click', { bubbles: true }))
409
-
410
- // Now only 2 selected - should show individual values again (not counter)
411
- const individualValues = document.querySelectorAll('.ss-value')
412
- expect(individualValues.length).toBeGreaterThan(0)
413
-
414
- const counter = document.querySelector('.ss-max')
415
- expect(counter).toBeNull()
416
- })
417
- })
418
-
419
- describe('Search State Regression Tests', () => {
420
- let slim: SlimSelect
421
- let searchMock: (searchValue: string, currentData: any[]) => any[]
422
-
423
- beforeEach(() => {
424
- document.body.innerHTML = '<select id="searchTest"></select>'
425
-
426
- searchMock = vi.fn().mockImplementation((searchValue: string, currentData: any[]) => {
427
- // Mock search results based on search value
428
- if (searchValue.length >= 2) {
429
- return [
430
- { value: 'null', text: 'Null' },
431
- { value: 'eins', text: 'Eins' },
432
- { value: 'zwei', text: 'Zwei' },
433
- { value: 'drei', text: 'Drei' },
434
- { value: 'vier', text: 'Vier' },
435
- { value: 'funf', text: 'Fünf' },
436
- { value: 'sechs', text: 'Sechs' },
437
- { value: 'sieben', text: 'Sieben' },
438
- { value: 'acht', text: 'Acht' },
439
- { value: 'neun', text: 'Neun' },
440
- { value: 'zehn', text: 'Zehn' }
441
- ]
442
- }
443
- return []
444
- }) as typeof searchMock
445
-
446
- slim = new SlimSelect({
447
- select: '#searchTest',
448
- data: [
449
- { value: 'a', text: 'A' },
450
- { value: 'b', text: 'B' }
451
- ],
452
- events: {
453
- search: searchMock
454
- }
455
- })
456
- })
457
-
458
- afterEach(() => {
459
- // Clean up to avoid timeout errors
460
- if (slim) {
461
- slim.destroy()
462
- }
463
- })
464
-
465
- test('should maintain search results when reopening dropdown', () => {
466
- // 1. Open dropdown - should show static options [A, B]
467
- slim.open()
468
- let options = document.querySelectorAll('.ss-option')
469
- expect(options).toHaveLength(2)
470
- expect(options[0].textContent).toBe('A')
471
- expect(options[1].textContent).toBe('B')
472
- slim.close()
473
-
474
- // 2. Search - should update store data with search results
475
- slim.search('te')
476
- options = document.querySelectorAll('.ss-option')
477
- // Should have search results (11) - static options are replaced
478
- expect(options.length).toBeGreaterThanOrEqual(11)
479
-
480
- // 3. Close and reopen - search results persist in store
481
- slim.close()
482
- slim.open()
483
- options = document.querySelectorAll('.ss-option')
484
- // Store data should still contain search results
485
- expect(options.length).toBeGreaterThanOrEqual(11)
486
- })
487
-
488
- test('should clear search input when closing dropdown', async () => {
489
- // Open dropdown first
490
- slim.open()
491
-
492
- // Search for something
493
- slim.search('te')
494
- expect(slim.render.content.search.input.value).toBe('te')
495
-
496
- // Close dropdown - this clears the search input
497
- slim.close()
498
-
499
- // Wait for close to complete (timeoutDelay is 200ms by default)
500
- await new Promise((resolve) => setTimeout(resolve, 300))
501
-
502
- // Search input should be cleared
503
- expect(slim.render.content.search.input.value).toBe('')
504
- })
505
-
506
- test('should keep search input when keepSearch is true', async () => {
507
- // Create new instance with keepSearch enabled
508
- const select = document.createElement('select')
509
- select.innerHTML = `
510
- <option value="a">A</option>
511
- <option value="b">B</option>
512
- <option value="c">C</option>
513
- `
514
- document.body.appendChild(select)
515
-
516
- const slimWithKeepSearch = new SlimSelect({
517
- select: select,
518
- settings: {
519
- keepSearch: true
520
- }
521
- })
522
-
523
- // Open dropdown
524
- slimWithKeepSearch.open()
525
-
526
- // Search for something
527
- slimWithKeepSearch.search('test')
528
- expect(slimWithKeepSearch.render.content.search.input.value).toBe('test')
529
-
530
- // Close dropdown
531
- slimWithKeepSearch.close()
532
-
533
- // Search input should NOT be cleared when keepSearch is true
534
- expect(slimWithKeepSearch.render.content.search.input.value).toBe('test')
535
-
536
- // Cleanup
537
- slimWithKeepSearch.destroy()
538
- document.body.removeChild(select)
539
- })
540
-
541
- test('should persist search results in store across close/open cycles', () => {
542
- // Search for something
543
- slim.search('te')
544
-
545
- // Search results are now in the store
546
- let options = document.querySelectorAll('.ss-option')
547
- expect(options.length).toBeGreaterThanOrEqual(11)
548
-
549
- // Close and reopen - store data persists
550
- slim.close()
551
- slim.open()
552
- options = document.querySelectorAll('.ss-option')
553
- expect(options.length).toBeGreaterThanOrEqual(11) // Store data maintained
554
-
555
- slim.close()
556
- slim.open()
557
- options = document.querySelectorAll('.ss-option')
558
- expect(options.length).toBeGreaterThanOrEqual(11) // Still maintained after multiple cycles
559
- })
560
-
561
- test('should handle selection from search results correctly', () => {
562
- // Search for options
563
- slim.search('te')
564
-
565
- // Select first search result (should be from search results)
566
- const options = document.querySelectorAll('.ss-option')
567
- options[0].dispatchEvent(new MouseEvent('click'))
568
-
569
- // Check that an option is selected
570
- const selected = slim.getSelected()
571
- expect(selected.length).toBe(1)
572
-
573
- // Close and reopen
574
- slim.close()
575
- slim.open()
576
-
577
- // Should still have search results in store
578
- const reopenedOptions = document.querySelectorAll('.ss-option')
579
- expect(reopenedOptions.length).toBeGreaterThanOrEqual(11)
580
-
581
- // Selected value should be preserved
582
- expect(slim.getSelected()).toEqual(selected)
583
- })
584
-
585
- test('should handle multiple selections from search results', () => {
586
- // Configure for multi-select
587
- slim = new SlimSelect({
588
- select: '#searchTest',
589
- data: [
590
- { value: 'a', text: 'A' },
591
- { value: 'b', text: 'B' }
592
- ],
593
- settings: {
594
- isMultiple: true
595
- },
596
- events: {
597
- search: searchMock
598
- }
599
- })
600
-
601
- // Search for options
602
- slim.search('te')
603
-
604
- // Select first two search results (Null, Eins) using Cmd+Click
605
- const options = document.querySelectorAll('.ss-option')
606
- options[0].dispatchEvent(new MouseEvent('click', { metaKey: true })) // Cmd+Click to keep dropdown open
607
- options[1].dispatchEvent(new MouseEvent('click', { metaKey: true })) // Cmd+Click to keep dropdown open
608
-
609
- // Note: Search results are temporary and not added to the store
610
- // So the selection won't persist in the store, but the search state should be maintained
611
-
612
- // Close and reopen
613
- slim.close()
614
- slim.open()
615
-
616
- // Should still show search results (search state maintained)
617
- const reopenedOptions = document.querySelectorAll('.ss-option')
618
- // Multi-select may have additional options like deselect all, so we check for at least 11
619
- expect(reopenedOptions.length).toBeGreaterThanOrEqual(11)
620
-
621
- // Search results are maintained but selections from search results don't persist in store
622
- // This is expected behavior - search results are temporary
623
- })
624
-
625
- test('should handle new search after reopening', () => {
626
- // Initial search
627
- slim.search('te')
628
- let options = document.querySelectorAll('.ss-option')
629
- expect(options.length).toBeGreaterThanOrEqual(11)
630
-
631
- // Close and reopen
632
- slim.close()
633
- slim.open()
634
-
635
- // Should still have previous search results in store
636
- options = document.querySelectorAll('.ss-option')
637
- expect(options.length).toBeGreaterThanOrEqual(11)
638
-
639
- // Perform new search - this updates the store with new results
640
- slim.search('ei')
641
- options = document.querySelectorAll('.ss-option')
642
-
643
- // Should show new search results
644
- expect(options.length).toBeGreaterThanOrEqual(11)
645
- })
646
-
647
- test('should handle search with no results correctly', () => {
648
- // Search for something that returns no results
649
- slim.search('xyz')
650
-
651
- // Should show no results message (searchText)
652
- const noResults = document.querySelector('.ss-search')
653
- expect(noResults).toBeTruthy()
654
-
655
- // Close and reopen
656
- slim.close()
657
- slim.open()
658
-
659
- // Should still show no results (search state maintained)
660
- const stillNoResults = document.querySelector('.ss-search')
661
- expect(stillNoResults).toBeTruthy()
662
- })
663
-
664
- test('should handle async search results correctly', async () => {
665
- // Mock async search
666
- const asyncSearchMock = vi.fn().mockResolvedValue([
667
- { value: 'async1', text: 'Async Result 1' },
668
- { value: 'async2', text: 'Async Result 2' }
669
- ])
670
-
671
- slim = new SlimSelect({
672
- select: '#searchTest',
673
- data: [
674
- { value: 'a', text: 'A' },
675
- { value: 'b', text: 'B' }
676
- ],
677
- events: {
678
- search: asyncSearchMock
679
- }
680
- })
681
-
682
- // Trigger async search
683
- slim.search('async')
684
-
685
- // Wait for async results
686
- await new Promise((resolve) => setTimeout(resolve, 100))
687
-
688
- // Check results are displayed (should include the async results)
689
- let options = document.querySelectorAll('.ss-option')
690
- expect(options.length).toBeGreaterThanOrEqual(2)
691
-
692
- // Find the async results in the options
693
- const asyncOptions = Array.from(options).filter((option) => option.textContent?.includes('Async Result'))
694
- expect(asyncOptions).toHaveLength(2)
695
- expect(asyncOptions[0].textContent).toBe('Async Result 1')
696
- expect(asyncOptions[1].textContent).toBe('Async Result 2')
697
-
698
- // Close and reopen
699
- slim.close()
700
- slim.open()
701
-
702
- // Should still show async search results
703
- options = document.querySelectorAll('.ss-option')
704
- const reopenedAsyncOptions = Array.from(options).filter((option) => option.textContent?.includes('Async Result'))
705
- expect(reopenedAsyncOptions).toHaveLength(2)
706
- expect(reopenedAsyncOptions[0].textContent).toBe('Async Result 1')
707
- })
708
-
709
- test('issue #671: first search result is selectable when allowDeselect and no prior selection (single select)', async () => {
710
- document.body.innerHTML = '<select id="searchFirstResult"></select>'
711
-
712
- const asyncSearchMock = vi.fn().mockResolvedValue([
713
- { value: 'first', text: 'First Result' },
714
- { value: 'second', text: 'Second Result' }
715
- ])
716
-
717
- const slimFirst = new SlimSelect({
718
- select: '#searchFirstResult',
719
- data: [],
720
- settings: { allowDeselect: true },
721
- events: { search: asyncSearchMock }
722
- })
723
-
724
- slimFirst.open()
725
- slimFirst.search('xy')
726
- await new Promise((r) => setTimeout(r, 100))
727
-
728
- const options = document.querySelectorAll('.ss-option')
729
- expect(options.length).toBeGreaterThanOrEqual(2)
730
-
731
- const firstOption = Array.from(options).find((el) => el.textContent?.trim() === 'First Result')
732
- expect(firstOption).toBeTruthy()
733
- expect(firstOption?.getAttribute('aria-selected')).toBe('false')
734
-
735
- firstOption!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
736
- expect(slimFirst.getSelected()).toEqual(['first'])
737
-
738
- slimFirst.destroy()
739
- })
740
-
741
- test('should update store data with search results', () => {
742
- // Test that search updates the store
743
- slim.search('te')
744
- expect(searchMock).toHaveBeenCalledWith('te', expect.any(Array))
745
-
746
- let options = document.querySelectorAll('.ss-option')
747
- expect(options.length).toBeGreaterThanOrEqual(11)
748
-
749
- // Search results are now in the store
750
- const storeData = slim.getData()
751
- expect(storeData.length).toBeGreaterThanOrEqual(11)
752
-
753
- // Clear search - this now returns all current data in store
754
- slim.search('')
755
- options = document.querySelectorAll('.ss-option')
756
-
757
- // Empty search now shows all current data in store (search results)
758
- expect(options.length).toBeGreaterThanOrEqual(11)
759
- })
760
- })
761
-
762
- describe('label click support', () => {
763
- test('clicking label with for attribute opens SlimSelect', async () => {
764
- document.body.innerHTML = `
765
- <label for="label-test-select">Select a Country</label>
766
- <select id="label-test-select">
767
- <option value="usa">United States</option>
768
- <option value="uk">United Kingdom</option>
769
- <option value="canada">Canada</option>
770
- </select>
771
- `
772
-
773
- const slim = new SlimSelect({
774
- select: '#label-test-select'
775
- })
776
-
777
- // Initially closed
778
- expect(slim.settings.isOpen).toBe(false)
779
-
780
- // Click the label
781
- const label = document.querySelector('label[for="label-test-select"]') as HTMLLabelElement
782
- label.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
783
-
784
- // Wait for setTimeout in label handler
785
- await new Promise((r) => setTimeout(r, 10))
786
-
787
- // SlimSelect should be open
788
- expect(slim.settings.isOpen).toBe(true)
789
-
790
- slim.destroy()
791
- })
792
-
793
- test('clicking wrapped label opens SlimSelect', async () => {
794
- document.body.innerHTML = `
795
- <label>
796
- Select a Country
797
- <select id="wrapped-label-select">
798
- <option value="usa">United States</option>
799
- <option value="uk">United Kingdom</option>
800
- </select>
801
- </label>
802
- `
803
-
804
- const slim = new SlimSelect({
805
- select: '#wrapped-label-select'
806
- })
807
-
808
- // Initially closed
809
- expect(slim.settings.isOpen).toBe(false)
810
-
811
- // Click the label
812
- const label = document.querySelector('label') as HTMLLabelElement
813
- label.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
814
-
815
- // Wait for setTimeout in label handler
816
- await new Promise((r) => setTimeout(r, 10))
817
-
818
- // SlimSelect should be open
819
- expect(slim.settings.isOpen).toBe(true)
820
-
821
- slim.destroy()
822
- })
823
-
824
- test('clicking label does not open SlimSelect when disabled', async () => {
825
- document.body.innerHTML = `
826
- <label for="disabled-label-select">Select a Country</label>
827
- <select id="disabled-label-select">
828
- <option value="usa">United States</option>
829
- </select>
830
- `
831
-
832
- const slim = new SlimSelect({
833
- select: '#disabled-label-select',
834
- settings: {
835
- disabled: true
836
- }
837
- })
838
-
839
- // Initially closed
840
- expect(slim.settings.isOpen).toBe(false)
841
-
842
- // Click the label
843
- const label = document.querySelector('label[for="disabled-label-select"]') as HTMLLabelElement
844
- label.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
845
-
846
- // Wait for setTimeout in label handler
847
- await new Promise((r) => setTimeout(r, 10))
848
-
849
- // SlimSelect should remain closed when disabled
850
- expect(slim.settings.isOpen).toBe(false)
851
-
852
- slim.destroy()
853
- })
854
-
855
- test('SlimSelect automatically assigns id to select if missing for label association', () => {
856
- document.body.innerHTML = `
857
- <label for="auto-id-select">Select a Country</label>
858
- <select>
859
- <option value="usa">United States</option>
860
- </select>
861
- `
862
-
863
- // Note: This won't actually work because the label's 'for' points to 'auto-id-select'
864
- // but we're selecting the select without an id. This test verifies that SlimSelect
865
- // assigns an id. The actual id will be generated, so the label won't match.
866
- // Let's test the id assignment instead.
867
- document.body.innerHTML = '<select><option value="usa">United States</option></select>'
868
-
869
- const selectElement = document.querySelector('select') as HTMLSelectElement
870
- const slim = new SlimSelect({
871
- select: selectElement
872
- })
873
-
874
- // SlimSelect should have assigned an id to the select
875
- expect(selectElement.id).toBeTruthy()
876
- expect(selectElement.id).toBe(slim.settings.id)
877
-
878
- slim.destroy()
879
- })
880
-
881
- test('label handlers are cleaned up on destroy', async () => {
882
- document.body.innerHTML = `
883
- <label for="cleanup-test-select">Select a Country</label>
884
- <select id="cleanup-test-select">
885
- <option value="usa">United States</option>
886
- </select>
887
- `
888
-
889
- const slim = new SlimSelect({
890
- select: '#cleanup-test-select'
891
- })
892
-
893
- // Verify label handler works
894
- const label = document.querySelector('label[for="cleanup-test-select"]') as HTMLLabelElement
895
- label.dispatchEvent(new MouseEvent('click', { bubbles: true }))
896
- await new Promise((r) => setTimeout(r, 10))
897
- expect(slim.settings.isOpen).toBe(true)
898
-
899
- // Close and destroy
900
- slim.close()
901
- slim.destroy()
902
-
903
- // After destroy, label click should not open SlimSelect
904
- // (though SlimSelect is destroyed, so we can't check isOpen)
905
- // But we verify no errors are thrown
906
- expect(() => {
907
- label.dispatchEvent(new MouseEvent('click', { bubbles: true }))
908
- }).not.toThrow()
909
- })
910
-
911
- test('clicking main div closes SlimSelect when wrapped in label', async () => {
912
- document.body.innerHTML = `
913
- <label>
914
- Select a Country
915
- <select id="wrapped-label-close-test">
916
- <option value="usa">United States</option>
917
- <option value="uk">United Kingdom</option>
918
- </select>
919
- </label>
920
- `
921
-
922
- const slim = new SlimSelect({
923
- select: '#wrapped-label-close-test',
924
- settings: {
925
- allowDeselect: true
926
- }
927
- })
928
-
929
- // Initially closed
930
- expect(slim.settings.isOpen).toBe(false)
931
-
932
- // Click the label to open
933
- const label = document.querySelector('label') as HTMLLabelElement
934
- label.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
935
- await new Promise((r) => setTimeout(r, 300)) // after animation
936
- expect(slim.settings.isOpen).toBe(true)
937
-
938
- // Now click the main div to close
939
- const mainDiv = document.querySelector(`.ss-main[data-id="${slim.settings.id}"]`) as HTMLElement
940
- expect(mainDiv).toBeTruthy()
941
-
942
- mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
943
- await new Promise((r) => setTimeout(r, 300)) // after animation
944
-
945
- // SlimSelect should be closed
946
- expect(slim.settings.isOpen).toBe(false)
947
-
948
- slim.destroy()
949
- })
950
-
951
- test('deselect button works when wrapped in label', async () => {
952
- document.body.innerHTML = `
953
- <label>
954
- Select a Country
955
- <select id="wrapped-label-deselect-test">
956
- <option value="usa">United States</option>
957
- <option value="uk">United Kingdom</option>
958
- </select>
959
- </label>
960
- `
961
-
962
- const slim = new SlimSelect({
963
- select: '#wrapped-label-deselect-test',
964
- settings: {
965
- allowDeselect: true
966
- }
967
- })
968
-
969
- // Select an option
970
- slim.setSelected('usa')
971
- await new Promise((r) => setTimeout(r, 10))
972
- expect(slim.getSelected()).toEqual(['usa'])
973
-
974
- // Click the deselect button
975
- const deselectButton = document.querySelector(
976
- `.ss-main[data-id="${slim.settings.id}"] .ss-deselect`
977
- ) as HTMLElement
978
- expect(deselectButton).toBeTruthy()
979
-
980
- deselectButton.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
981
- await new Promise((r) => setTimeout(r, 10))
982
-
983
- // Should be deselected (returns array with empty string for single select)
984
- expect(slim.getSelected()).toEqual([''])
985
-
986
- slim.destroy()
987
- })
988
-
989
- test('clicking main div toggles when wrapped in label', async () => {
990
- document.body.innerHTML = `
991
- <label>
992
- Select a Country
993
- <select id="wrapped-label-toggle-test">
994
- <option value="usa">United States</option>
995
- <option value="uk">United Kingdom</option>
996
- </select>
997
- </label>
998
- `
999
-
1000
- const slim = new SlimSelect({
1001
- select: '#wrapped-label-toggle-test'
1002
- })
1003
-
1004
- // Initially closed
1005
- expect(slim.settings.isOpen).toBe(false)
1006
-
1007
- // Click main div to open
1008
- const mainDiv = document.querySelector(`.ss-main[data-id="${slim.settings.id}"]`) as HTMLElement
1009
- mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
1010
- await new Promise((r) => setTimeout(r, 10))
1011
- expect(slim.settings.isOpen).toBe(true)
1012
-
1013
- // Click main div again to close
1014
- mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
1015
- await new Promise((r) => setTimeout(r, 10))
1016
- expect(slim.settings.isOpen).toBe(false)
1017
-
1018
- // Click main div again to open
1019
- mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
1020
- await new Promise((r) => setTimeout(r, 10))
1021
- expect(slim.settings.isOpen).toBe(true)
1022
-
1023
- slim.destroy()
1024
- })
1025
-
1026
- test('clicking label on second select closes first select when both have labels', async () => {
1027
- document.body.innerHTML = `
1028
- <label>
1029
- First Select
1030
- <select id="first-select">
1031
- <option value="1">Option 1</option>
1032
- <option value="2">Option 2</option>
1033
- </select>
1034
- </label>
1035
- <label>
1036
- Second Select
1037
- <select id="second-select">
1038
- <option value="a">Option A</option>
1039
- <option value="b">Option B</option>
1040
- </select>
1041
- </label>
1042
- `
1043
-
1044
- const slim1 = new SlimSelect({
1045
- select: '#first-select'
1046
- })
1047
-
1048
- const slim2 = new SlimSelect({
1049
- select: '#second-select'
1050
- })
1051
-
1052
- // Both should be closed initially
1053
- expect(slim1.settings.isOpen).toBe(false)
1054
- expect(slim2.settings.isOpen).toBe(false)
1055
-
1056
- // Click first label to open first select
1057
- const label1 = document.querySelector('label:first-of-type') as HTMLLabelElement
1058
- label1.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
1059
- await new Promise((r) => setTimeout(r, 300))
1060
- expect(slim1.settings.isOpen).toBe(true)
1061
- expect(slim2.settings.isOpen).toBe(false)
1062
-
1063
- // Click second label - should close first and open second
1064
- const label2 = document.querySelector('label:last-of-type') as HTMLLabelElement
1065
- label2.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
1066
- await new Promise((r) => setTimeout(r, 300))
1067
- expect(slim1.settings.isOpen).toBe(false)
1068
- expect(slim2.settings.isOpen).toBe(true)
1069
-
1070
- // Click first label again - should close second and open first
1071
- label1.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
1072
- await new Promise((r) => setTimeout(r, 300))
1073
- expect(slim1.settings.isOpen).toBe(true)
1074
- expect(slim2.settings.isOpen).toBe(false)
1075
-
1076
- slim1.destroy()
1077
- slim2.destroy()
1078
- })
1079
- })
1080
-
1081
- describe('option changes race condition scenarios', () => {
1082
- test('options should persist when added to select and value is set immediately', async () => {
1083
- // Reproduce the issue: empty select, init SlimSelect, add options, set value
1084
- document.body.innerHTML = '<select id="test-select"></select>'
1085
-
1086
- const selectElement = document.getElementById('test-select') as HTMLSelectElement
1087
- const slim = new SlimSelect({
1088
- select: selectElement
1089
- })
1090
-
1091
- // Add option directly to the select element (not through SlimSelect API)
1092
- const newOption = document.createElement('option')
1093
- newOption.value = '1'
1094
- newOption.textContent = 'one'
1095
- selectElement.appendChild(newOption)
1096
-
1097
- // Set the value immediately
1098
- selectElement.value = '1'
1099
-
1100
- // Wait for mutation observer and any async operations
1101
- await new Promise((r) => setTimeout(r, 100))
1102
-
1103
- // Verify the option still exists
1104
- expect(selectElement.options.length).toBe(1)
1105
- expect(selectElement.value).toBe('1')
1106
- expect(selectElement.options[0].value).toBe('1')
1107
- expect(selectElement.options[0].textContent).toBe('one')
1108
-
1109
- // Verify SlimSelect also has the option
1110
- const data = slim.getData()
1111
- expect(data.length).toBe(1)
1112
- expect((data[0] as any).value).toBe('1')
1113
- expect((data[0] as any).text).toBe('one')
1114
-
1115
- slim.destroy()
1116
- })
1117
-
1118
- test('rapid option and value changes should preserve final state', async () => {
1119
- // Test multiple rapid mutations to ensure queue mechanism works correctly
1120
- document.body.innerHTML = '<select id="test-select"></select>'
1121
-
1122
- const selectElement = document.getElementById('test-select') as HTMLSelectElement
1123
- const slim = new SlimSelect({
1124
- select: selectElement
1125
- })
1126
-
1127
- // Add first option
1128
- const option1 = document.createElement('option')
1129
- option1.value = '1'
1130
- option1.textContent = 'one'
1131
- selectElement.appendChild(option1)
1132
- selectElement.value = '1'
1133
-
1134
- // Immediately add second option
1135
- const option2 = document.createElement('option')
1136
- option2.value = '2'
1137
- option2.textContent = 'two'
1138
- selectElement.appendChild(option2)
1139
- selectElement.value = '2'
1140
-
1141
- // Immediately add third option
1142
- const option3 = document.createElement('option')
1143
- option3.value = '3'
1144
- option3.textContent = 'three'
1145
- selectElement.appendChild(option3)
1146
- selectElement.value = '3'
1147
-
1148
- // Wait for all mutations to process
1149
- await new Promise((r) => setTimeout(r, 150))
1150
-
1151
- // Verify all options exist and final value is correct
1152
- expect(selectElement.options.length).toBe(3)
1153
- expect(selectElement.value).toBe('3')
1154
- expect(selectElement.options[0].value).toBe('1')
1155
- expect(selectElement.options[0].textContent).toBe('one')
1156
- expect(selectElement.options[1].value).toBe('2')
1157
- expect(selectElement.options[1].textContent).toBe('two')
1158
- expect(selectElement.options[2].value).toBe('3')
1159
- expect(selectElement.options[2].textContent).toBe('three')
1160
-
1161
- // Verify SlimSelect also has all options
1162
- const data = slim.getData()
1163
- expect(data.length).toBe(3)
1164
- expect((data[0] as any).value).toBe('1')
1165
- expect((data[0] as any).text).toBe('one')
1166
- expect((data[1] as any).value).toBe('2')
1167
- expect((data[1] as any).text).toBe('two')
1168
- expect((data[2] as any).value).toBe('3')
1169
- expect((data[2] as any).text).toBe('three')
1170
-
1171
- // Verify selected value
1172
- const selected = slim.getSelected()
1173
- expect(selected).toEqual(['3'])
1174
-
1175
- slim.destroy()
1176
- })
1177
-
1178
- test('should handle removing all options and adding new ones', async () => {
1179
- // Test scenario: start with options, remove all, then add new ones
1180
- // This tests that the queue mechanism handles the removal and addition correctly
1181
- document.body.innerHTML = `
1182
- <select id="test-select">
1183
- <option value="old1">Old One</option>
1184
- <option value="old2">Old Two</option>
1185
- <option value="old3">Old Three</option>
1186
- </select>
1187
- `
1188
-
1189
- const selectElement = document.getElementById('test-select') as HTMLSelectElement
1190
- const slim = new SlimSelect({
1191
- select: selectElement
1192
- })
1193
-
1194
- // Verify initial state
1195
- expect(selectElement.options.length).toBe(3)
1196
- expect(slim.getData().length).toBe(3)
1197
-
1198
- // Remove all options (this will trigger mutation observer with empty data, which we skip)
1199
- selectElement.innerHTML = ''
1200
-
1201
- // Wait for mutation observer to process the removal
1202
- await new Promise((r) => setTimeout(r, 100))
1203
-
1204
- // Verify options are removed from DOM
1205
- expect(selectElement.options.length).toBe(0)
1206
-
1207
- // Add completely new set of options one by one
1208
- const newOption1 = document.createElement('option')
1209
- newOption1.value = 'new1'
1210
- newOption1.textContent = 'New One'
1211
- selectElement.appendChild(newOption1)
1212
-
1213
- const newOption2 = document.createElement('option')
1214
- newOption2.value = 'new2'
1215
- newOption2.textContent = 'New Two'
1216
- selectElement.appendChild(newOption2)
1217
-
1218
- const newOption3 = document.createElement('option')
1219
- newOption3.value = 'new3'
1220
- newOption3.textContent = 'New Three'
1221
- selectElement.appendChild(newOption3)
1222
-
1223
- // Wait for all mutations to process
1224
- await new Promise((r) => setTimeout(r, 200))
1225
-
1226
- // The key test: verify new options persist in DOM (not deleted by race condition)
1227
- expect(selectElement.options.length).toBe(3)
1228
- expect(selectElement.options[0].value).toBe('new1')
1229
- expect(selectElement.options[0].textContent).toBe('New One')
1230
- expect(selectElement.options[1].value).toBe('new2')
1231
- expect(selectElement.options[1].textContent).toBe('New Two')
1232
- expect(selectElement.options[2].value).toBe('new3')
1233
- expect(selectElement.options[2].textContent).toBe('New Three')
1234
-
1235
- slim.destroy()
1236
- })
1237
-
1238
- test('should handle rapidly removing all options', async () => {
1239
- // Test scenario: start with options, rapidly remove them all
1240
- document.body.innerHTML = `
1241
- <select id="test-select">
1242
- <option value="opt1">Option 1</option>
1243
- <option value="opt2">Option 2</option>
1244
- <option value="opt3">Option 3</option>
1245
- <option value="opt4">Option 4</option>
1246
- </select>
1247
- `
1248
-
1249
- const selectElement = document.getElementById('test-select') as HTMLSelectElement
1250
- const slim = new SlimSelect({
1251
- select: selectElement
1252
- })
1253
-
1254
- // Set an initial value
1255
- selectElement.value = 'opt2'
1256
-
1257
- // Verify initial state
1258
- expect(selectElement.options.length).toBe(4)
1259
- expect(selectElement.value).toBe('opt2')
1260
- expect(slim.getData().length).toBe(4)
1261
-
1262
- // Rapidly remove all options
1263
- selectElement.removeChild(selectElement.options[0])
1264
- selectElement.removeChild(selectElement.options[0])
1265
- selectElement.removeChild(selectElement.options[0])
1266
- selectElement.removeChild(selectElement.options[0])
1267
-
1268
- // Wait for all mutations to process
1269
- await new Promise((r) => setTimeout(r, 200))
1270
-
1271
- // Verify all options are removed
1272
- expect(selectElement.options.length).toBe(0)
1273
- expect(selectElement.value).toBe('')
1274
-
1275
- // Verify SlimSelect also has no options
1276
- const data = slim.getData()
1277
- expect(data.length).toBe(0)
1278
-
1279
- // Verify selected value is empty
1280
- const selected = slim.getSelected()
1281
- expect(selected).toEqual([])
1282
-
1283
- slim.destroy()
1284
- })
1285
-
1286
- test('should handle option change after first render', async () => {
1287
- // Test scenario: start with 2 options
1288
- document.body.innerHTML = `
1289
- <select id="test-select">
1290
- <option value="opt1">Option 1</option>
1291
- <option value="opt2">Option 2</option>
1292
- </select>
1293
- `
1294
-
1295
- const selectElement = document.getElementById('test-select') as HTMLSelectElement
1296
- const slim = new SlimSelect({
1297
- select: selectElement
1298
- })
1299
-
1300
- // Set an initial value
1301
- selectElement.value = 'opt2'
1302
-
1303
- // Verify initial state
1304
- expect(selectElement.options.length).toBe(2)
1305
- expect(selectElement.value).toBe('opt2')
1306
- expect(slim.getData().length).toBe(2)
1307
-
1308
- // Wait for all mutations to process
1309
- await new Promise((r) => setTimeout(r, 200))
1310
-
1311
- // Rebuild options
1312
- document.getElementById('test-select')!.innerHTML = `
1313
- <option value="opt1">Option 1 updated</option>
1314
- <option value="opt2" selected>Option 2 updated</option>
1315
- `
1316
-
1317
- // Wait for all mutations to process
1318
- await new Promise((r) => setTimeout(r, 200))
1319
-
1320
- // Verify options are still there
1321
- expect(selectElement.options.length).toBe(2)
1322
- expect(selectElement.options[0].textContent).toBe('Option 1 updated')
1323
- expect(selectElement.options[1].textContent).toBe('Option 2 updated')
1324
- expect(selectElement.value).toBe('opt2')
1325
-
1326
- // Verify SlimSelect also has the options
1327
- const data = slim.getData()
1328
- expect(data.length).toBe(2)
1329
-
1330
- // Verify selected value is empty
1331
- const selected = slim.getSelected()
1332
- expect(selected).toEqual(['opt2'])
1333
-
1334
- slim.destroy()
1335
- })
1336
- })
1337
-
1338
- describe('cssClasses with space-separated strings', () => {
1339
- test('space-separated cssClasses are applied as individual classes', () => {
1340
- document.body.innerHTML = '<select id="test"><option>Test</option></select>'
1341
-
1342
- const slim = new SlimSelect({
1343
- select: '#test',
1344
- cssClasses: {
1345
- main: 'class1 class2'
1346
- }
1347
- })
1348
-
1349
- expect(slim.render.main.main.classList.contains('ss-main')).toBe(true)
1350
- expect(slim.render.main.main.classList.contains('class1')).toBe(true)
1351
- expect(slim.render.main.main.classList.contains('class2')).toBe(true)
1352
-
1353
- slim.destroy()
1354
- })
1355
- })
1356
- })