slim-select 2.13.1 → 3.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.
- package/README.md +184 -43
- package/dist/index.d.ts +304 -52
- package/dist/react/index.d.ts +304 -0
- package/dist/react/index.js +1549 -0
- package/dist/react/index.umd.js +22 -0
- package/dist/slimselect.cjs.js +1 -2030
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +1224 -2012
- package/dist/slimselect.umd.js +1 -2036
- package/dist/vue/index.d.ts +304 -0
- package/dist/vue/index.js +1341 -0
- package/dist/vue/index.umd.js +1 -0
- package/index.html +1 -0
- package/package.json +58 -36
- package/release.js +0 -7
- package/src/slim-select/accessibility.test.ts +782 -0
- package/src/slim-select/classes.test.ts +7 -6
- package/src/slim-select/classes.ts +8 -6
- package/src/slim-select/helpers.test.ts +10 -8
- package/src/slim-select/index.test.ts +509 -14
- package/src/slim-select/index.ts +58 -31
- package/src/slim-select/react/index.tsx +9 -0
- package/src/slim-select/react/react.tsx +112 -0
- package/src/slim-select/render.test.ts +479 -119
- package/src/slim-select/render.ts +213 -91
- package/src/slim-select/select.test.ts +28 -10
- package/src/slim-select/select.ts +35 -12
- package/src/slim-select/settings.test.ts +19 -8
- package/src/slim-select/settings.ts +2 -4
- package/src/slim-select/slimselect.scss +30 -15
- package/src/slim-select/store.test.ts +13 -16
- package/src/slim-select/store.ts +97 -86
- package/src/slim-select/vue/index.ts +9 -0
- package/src/slim-select/vue/vue.test.ts +832 -0
- package/src/slim-select/{vue.vue → vue/vue.vue} +45 -23
- package/tsconfig.json +6 -4
- package/{vite.config.mts → vite.config.docs.mts} +10 -0
- package/vite.config.lib.mts +42 -0
- package/vite.config.react.mts +40 -0
- package/vite.config.vue.mts +39 -0
- package/vitest.config.ts +29 -0
- package/dist/classes.d.ts +0 -43
- package/dist/helpers.d.ts +0 -5
- package/dist/render.d.ts +0 -81
- package/dist/select.d.ts +0 -30
- package/dist/settings.d.ts +0 -35
- package/dist/slimselect.global.js +0 -2033
- package/dist/slimselect.js +0 -2036
- package/dist/slimselect.min.js +0 -1
- package/dist/slimselect.umd.min.js +0 -1
- package/dist/store.d.ts +0 -84
- package/jest.config.js +0 -14
- package/src/slim-select/rollup.config.mjs +0 -127
- package/src/slim-select/tsconfig.json +0 -14
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict'
|
|
6
6
|
|
|
7
|
-
import { describe, expect, test } from '
|
|
8
|
-
import SlimSelect from '
|
|
9
|
-
import { OptionOptional } from './store'
|
|
10
|
-
import { Config } from './index'
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
8
|
+
import SlimSelect from '@/slim-select'
|
|
11
9
|
|
|
12
10
|
describe('SlimSelect Module', () => {
|
|
13
11
|
let slim: SlimSelect
|
|
@@ -20,9 +18,15 @@ describe('SlimSelect Module', () => {
|
|
|
20
18
|
})
|
|
21
19
|
})
|
|
22
20
|
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
if (slim) {
|
|
23
|
+
slim.destroy()
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
23
27
|
describe('constructor', () => {
|
|
24
28
|
test('missing select element throws error', () => {
|
|
25
|
-
const errorMock =
|
|
29
|
+
const errorMock = vi.fn()
|
|
26
30
|
const slim = new SlimSelect({ select: '#invalid', events: { error: errorMock } })
|
|
27
31
|
|
|
28
32
|
expect(slim.select).toBeUndefined()
|
|
@@ -35,7 +39,7 @@ describe('SlimSelect Module', () => {
|
|
|
35
39
|
test('invalid element throws error', () => {
|
|
36
40
|
document.body.innerHTML = '<div id="invalid"></div>'
|
|
37
41
|
|
|
38
|
-
const errorMock =
|
|
42
|
+
const errorMock = vi.fn()
|
|
39
43
|
const slim = new SlimSelect({ select: '#invalid', events: { error: errorMock } })
|
|
40
44
|
|
|
41
45
|
expect(slim.select).toBeUndefined()
|
|
@@ -82,8 +86,8 @@ describe('SlimSelect Module', () => {
|
|
|
82
86
|
test('enable', () => {
|
|
83
87
|
slim.settings.disabled = true
|
|
84
88
|
|
|
85
|
-
const selectEnableMock =
|
|
86
|
-
const renderEnableMock =
|
|
89
|
+
const selectEnableMock = vi.fn()
|
|
90
|
+
const renderEnableMock = vi.fn()
|
|
87
91
|
|
|
88
92
|
slim.select.enable = selectEnableMock
|
|
89
93
|
slim.render.enable = renderEnableMock
|
|
@@ -98,8 +102,8 @@ describe('SlimSelect Module', () => {
|
|
|
98
102
|
test('disable', () => {
|
|
99
103
|
slim.settings.disabled = false
|
|
100
104
|
|
|
101
|
-
const selectDisableMock =
|
|
102
|
-
const renderDisableMock =
|
|
105
|
+
const selectDisableMock = vi.fn()
|
|
106
|
+
const renderDisableMock = vi.fn()
|
|
103
107
|
|
|
104
108
|
slim.select.disable = selectDisableMock
|
|
105
109
|
slim.render.disable = renderDisableMock
|
|
@@ -119,7 +123,7 @@ describe('SlimSelect Module', () => {
|
|
|
119
123
|
<option id="3" value="3">Two</option>
|
|
120
124
|
</select>`
|
|
121
125
|
|
|
122
|
-
const options
|
|
126
|
+
const options = [
|
|
123
127
|
{
|
|
124
128
|
id: '1',
|
|
125
129
|
value: '1',
|
|
@@ -136,7 +140,7 @@ describe('SlimSelect Module', () => {
|
|
|
136
140
|
text: 'Three'
|
|
137
141
|
}
|
|
138
142
|
]
|
|
139
|
-
const config
|
|
143
|
+
const config = {
|
|
140
144
|
select: '#test',
|
|
141
145
|
settings: {
|
|
142
146
|
allowDeselect: true
|
|
@@ -157,7 +161,7 @@ describe('SlimSelect Module', () => {
|
|
|
157
161
|
<option id="3" value="3">Two</option>
|
|
158
162
|
</select>`
|
|
159
163
|
|
|
160
|
-
const options
|
|
164
|
+
const options = [
|
|
161
165
|
{
|
|
162
166
|
id: '1',
|
|
163
167
|
value: '1',
|
|
@@ -176,7 +180,7 @@ describe('SlimSelect Module', () => {
|
|
|
176
180
|
text: 'Three'
|
|
177
181
|
}
|
|
178
182
|
]
|
|
179
|
-
const config
|
|
183
|
+
const config = {
|
|
180
184
|
select: '#test',
|
|
181
185
|
settings: {
|
|
182
186
|
allowDeselect: true
|
|
@@ -189,4 +193,495 @@ describe('SlimSelect Module', () => {
|
|
|
189
193
|
expect(slimSelect.getSelected()).toHaveLength(2)
|
|
190
194
|
expect(slimSelect.render.main.deselect.main.classList).not.toContain(slimSelect.render.classes.hide)
|
|
191
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
|
+
expect(selectEl.style.clip).toContain('rect')
|
|
215
|
+
|
|
216
|
+
// Required attribute should still be present
|
|
217
|
+
expect(selectEl.hasAttribute('required')).toBe(true)
|
|
218
|
+
|
|
219
|
+
// Select should be able to receive focus programmatically
|
|
220
|
+
selectEl.focus()
|
|
221
|
+
expect(document.activeElement).toBe(selectEl)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('form validation works with required select', () => {
|
|
225
|
+
// Create a form with required select
|
|
226
|
+
document.body.innerHTML = `
|
|
227
|
+
<form id="test-form">
|
|
228
|
+
<select id="test" name="test-select" required>
|
|
229
|
+
<option value="">Select an option</option>
|
|
230
|
+
<option value="1">Option 1</option>
|
|
231
|
+
<option value="2">Option 2</option>
|
|
232
|
+
</select>
|
|
233
|
+
<button type="submit">Submit</button>
|
|
234
|
+
</form>
|
|
235
|
+
`
|
|
236
|
+
|
|
237
|
+
const slim = new SlimSelect({
|
|
238
|
+
select: '#test'
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const form = document.getElementById('test-form') as HTMLFormElement
|
|
242
|
+
const selectEl = document.getElementById('test') as HTMLSelectElement
|
|
243
|
+
|
|
244
|
+
// Form should be invalid when nothing is selected
|
|
245
|
+
expect(selectEl.required).toBe(true)
|
|
246
|
+
expect(selectEl.value).toBe('')
|
|
247
|
+
expect(form.checkValidity()).toBe(false)
|
|
248
|
+
|
|
249
|
+
// Select an option
|
|
250
|
+
slim.setSelected(['1'])
|
|
251
|
+
|
|
252
|
+
// Form should now be valid
|
|
253
|
+
expect(selectEl.value).toBe('1')
|
|
254
|
+
expect(form.checkValidity()).toBe(true)
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('keepOrder setting', () => {
|
|
259
|
+
test('keepOrder: false returns values in DOM order (default)', () => {
|
|
260
|
+
document.body.innerHTML = `
|
|
261
|
+
<select id="test" multiple>
|
|
262
|
+
<option value="apple">Apple</option>
|
|
263
|
+
<option value="banana">Banana</option>
|
|
264
|
+
<option value="cherry">Cherry</option>
|
|
265
|
+
</select>
|
|
266
|
+
`
|
|
267
|
+
|
|
268
|
+
const slim = new SlimSelect({
|
|
269
|
+
select: '#test',
|
|
270
|
+
settings: {
|
|
271
|
+
keepOrder: false // DOM order (default)
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// Select in reverse order: Cherry -> Apple -> Banana
|
|
276
|
+
slim.setSelected(['cherry', 'apple', 'banana'])
|
|
277
|
+
|
|
278
|
+
// Should return in DOM order (how they appear in HTML)
|
|
279
|
+
expect(slim.getSelected()).toEqual(['apple', 'banana', 'cherry'])
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test('keepOrder: true returns values in selection order', () => {
|
|
283
|
+
document.body.innerHTML = `
|
|
284
|
+
<select id="test" multiple>
|
|
285
|
+
<option value="apple">Apple</option>
|
|
286
|
+
<option value="banana">Banana</option>
|
|
287
|
+
<option value="cherry">Cherry</option>
|
|
288
|
+
</select>
|
|
289
|
+
`
|
|
290
|
+
|
|
291
|
+
const slim = new SlimSelect({
|
|
292
|
+
select: '#test',
|
|
293
|
+
settings: {
|
|
294
|
+
keepOrder: true // Selection order
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// Select in specific order: Cherry -> Apple -> Banana
|
|
299
|
+
slim.setSelected(['cherry', 'apple', 'banana'])
|
|
300
|
+
|
|
301
|
+
// Should return in the order they were selected (click order)
|
|
302
|
+
expect(slim.getSelected()).toEqual(['cherry', 'apple', 'banana'])
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test('keepOrder: true preserves click order in getSelected', () => {
|
|
306
|
+
document.body.innerHTML = `
|
|
307
|
+
<select id="test" multiple>
|
|
308
|
+
<option value="1">Option 1</option>
|
|
309
|
+
<option value="2">Option 2</option>
|
|
310
|
+
<option value="3">Option 3</option>
|
|
311
|
+
<option value="4">Option 4</option>
|
|
312
|
+
<option value="5">Option 5</option>
|
|
313
|
+
</select>
|
|
314
|
+
`
|
|
315
|
+
|
|
316
|
+
const slim = new SlimSelect({
|
|
317
|
+
select: '#test',
|
|
318
|
+
settings: {
|
|
319
|
+
keepOrder: true
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// Simulate user clicking options in random order
|
|
324
|
+
slim.setSelected(['5', '2', '4', '1'])
|
|
325
|
+
|
|
326
|
+
// Should maintain that exact order
|
|
327
|
+
expect(slim.getSelected()).toEqual(['5', '2', '4', '1'])
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
describe('maxValuesShown with deselection', () => {
|
|
332
|
+
test('can still deselect options via dropdown when maxValuesShown is exceeded', () => {
|
|
333
|
+
document.body.innerHTML = `
|
|
334
|
+
<select id="test" multiple>
|
|
335
|
+
<option value="1">Option 1</option>
|
|
336
|
+
<option value="2">Option 2</option>
|
|
337
|
+
<option value="3">Option 3</option>
|
|
338
|
+
<option value="4">Option 4</option>
|
|
339
|
+
<option value="5">Option 5</option>
|
|
340
|
+
</select>
|
|
341
|
+
`
|
|
342
|
+
|
|
343
|
+
const slim = new SlimSelect({
|
|
344
|
+
select: '#test',
|
|
345
|
+
settings: {
|
|
346
|
+
maxValuesShown: 2 // Show "X selected" when more than 2
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// Select 3 options (exceeds maxValuesShown)
|
|
351
|
+
slim.setSelected(['1', '2', '3'])
|
|
352
|
+
expect(slim.getSelected()).toEqual(['1', '2', '3'])
|
|
353
|
+
|
|
354
|
+
// Verify counter is shown (not individual values)
|
|
355
|
+
const counter = document.querySelector('.ss-max')
|
|
356
|
+
expect(counter?.textContent).toBe('3 selected')
|
|
357
|
+
|
|
358
|
+
// Open dropdown
|
|
359
|
+
slim.open()
|
|
360
|
+
|
|
361
|
+
// Find and click on a selected option to deselect it
|
|
362
|
+
const options = document.querySelectorAll('[role="option"]')
|
|
363
|
+
const selectedOption = Array.from(options).find(
|
|
364
|
+
(opt) => opt.getAttribute('aria-selected') === 'true' && opt.textContent?.includes('Option 1')
|
|
365
|
+
) as HTMLElement
|
|
366
|
+
|
|
367
|
+
expect(selectedOption).toBeTruthy()
|
|
368
|
+
|
|
369
|
+
// Click the selected option - should deselect it
|
|
370
|
+
selectedOption.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
371
|
+
|
|
372
|
+
// Should now only have 2 selected
|
|
373
|
+
expect(slim.getSelected()).toEqual(['2', '3'])
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
test('deselecting via dropdown updates maxValuesShown display', () => {
|
|
377
|
+
document.body.innerHTML = `
|
|
378
|
+
<select id="test" multiple>
|
|
379
|
+
<option value="1">Value 1</option>
|
|
380
|
+
<option value="2">Value 2</option>
|
|
381
|
+
<option value="3">Value 3</option>
|
|
382
|
+
</select>
|
|
383
|
+
`
|
|
384
|
+
|
|
385
|
+
const slim = new SlimSelect({
|
|
386
|
+
select: '#test',
|
|
387
|
+
settings: {
|
|
388
|
+
maxValuesShown: 2
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
// Select 3 options (shows "3 selected")
|
|
393
|
+
slim.setSelected(['1', '2', '3'])
|
|
394
|
+
|
|
395
|
+
// Open and deselect one
|
|
396
|
+
slim.open()
|
|
397
|
+
const options = document.querySelectorAll('[role="option"]')
|
|
398
|
+
const firstSelected = Array.from(options).find(
|
|
399
|
+
(opt) => opt.getAttribute('aria-selected') === 'true'
|
|
400
|
+
) as HTMLElement
|
|
401
|
+
firstSelected.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
402
|
+
|
|
403
|
+
// Now only 2 selected - should show individual values again (not counter)
|
|
404
|
+
const individualValues = document.querySelectorAll('.ss-value')
|
|
405
|
+
expect(individualValues.length).toBeGreaterThan(0)
|
|
406
|
+
|
|
407
|
+
const counter = document.querySelector('.ss-max')
|
|
408
|
+
expect(counter).toBeNull()
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('Search State Regression Tests', () => {
|
|
413
|
+
let slim: SlimSelect
|
|
414
|
+
let searchMock: ReturnType<typeof vi.fn>
|
|
415
|
+
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
document.body.innerHTML = '<select id="searchTest"></select>'
|
|
418
|
+
|
|
419
|
+
searchMock = vi.fn().mockImplementation((searchValue: string) => {
|
|
420
|
+
// Mock search results based on search value
|
|
421
|
+
if (searchValue.length >= 2) {
|
|
422
|
+
return [
|
|
423
|
+
{ value: 'null', text: 'Null' },
|
|
424
|
+
{ value: 'eins', text: 'Eins' },
|
|
425
|
+
{ value: 'zwei', text: 'Zwei' },
|
|
426
|
+
{ value: 'drei', text: 'Drei' },
|
|
427
|
+
{ value: 'vier', text: 'Vier' },
|
|
428
|
+
{ value: 'funf', text: 'Fünf' },
|
|
429
|
+
{ value: 'sechs', text: 'Sechs' },
|
|
430
|
+
{ value: 'sieben', text: 'Sieben' },
|
|
431
|
+
{ value: 'acht', text: 'Acht' },
|
|
432
|
+
{ value: 'neun', text: 'Neun' },
|
|
433
|
+
{ value: 'zehn', text: 'Zehn' }
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
return []
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
slim = new SlimSelect({
|
|
440
|
+
select: '#searchTest',
|
|
441
|
+
data: [
|
|
442
|
+
{ value: 'a', text: 'A' },
|
|
443
|
+
{ value: 'b', text: 'B' }
|
|
444
|
+
],
|
|
445
|
+
events: {
|
|
446
|
+
search: searchMock
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
afterEach(() => {
|
|
452
|
+
// Clean up to avoid timeout errors
|
|
453
|
+
if (slim) {
|
|
454
|
+
slim.destroy()
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
test('should maintain search results when reopening dropdown', () => {
|
|
459
|
+
// 1. Open dropdown - should show static options [A, B]
|
|
460
|
+
slim.open()
|
|
461
|
+
let options = document.querySelectorAll('.ss-option')
|
|
462
|
+
expect(options).toHaveLength(2)
|
|
463
|
+
expect(options[0].textContent).toBe('A')
|
|
464
|
+
expect(options[1].textContent).toBe('B')
|
|
465
|
+
slim.close()
|
|
466
|
+
|
|
467
|
+
// 2. Search - should update store data with search results
|
|
468
|
+
slim.search('te')
|
|
469
|
+
options = document.querySelectorAll('.ss-option')
|
|
470
|
+
// Should have search results (11) - static options are replaced
|
|
471
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
472
|
+
|
|
473
|
+
// 3. Close and reopen - search results persist in store
|
|
474
|
+
slim.close()
|
|
475
|
+
slim.open()
|
|
476
|
+
options = document.querySelectorAll('.ss-option')
|
|
477
|
+
// Store data should still contain search results
|
|
478
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
test('should clear search input when closing dropdown', async () => {
|
|
482
|
+
// Open dropdown first
|
|
483
|
+
slim.open()
|
|
484
|
+
|
|
485
|
+
// Search for something
|
|
486
|
+
slim.search('te')
|
|
487
|
+
expect(slim.render.content.search.input.value).toBe('te')
|
|
488
|
+
|
|
489
|
+
// Close dropdown - this clears the search input
|
|
490
|
+
slim.close()
|
|
491
|
+
|
|
492
|
+
// Wait for close to complete (timeoutDelay is 200ms by default)
|
|
493
|
+
await new Promise((resolve) => setTimeout(resolve, 300))
|
|
494
|
+
|
|
495
|
+
// Search input should be cleared
|
|
496
|
+
expect(slim.render.content.search.input.value).toBe('')
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
test('should persist search results in store across close/open cycles', () => {
|
|
500
|
+
// Search for something
|
|
501
|
+
slim.search('te')
|
|
502
|
+
|
|
503
|
+
// Search results are now in the store
|
|
504
|
+
let options = document.querySelectorAll('.ss-option')
|
|
505
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
506
|
+
|
|
507
|
+
// Close and reopen - store data persists
|
|
508
|
+
slim.close()
|
|
509
|
+
slim.open()
|
|
510
|
+
options = document.querySelectorAll('.ss-option')
|
|
511
|
+
expect(options.length).toBeGreaterThanOrEqual(11) // Store data maintained
|
|
512
|
+
|
|
513
|
+
slim.close()
|
|
514
|
+
slim.open()
|
|
515
|
+
options = document.querySelectorAll('.ss-option')
|
|
516
|
+
expect(options.length).toBeGreaterThanOrEqual(11) // Still maintained after multiple cycles
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
test('should handle selection from search results correctly', () => {
|
|
520
|
+
// Search for options
|
|
521
|
+
slim.search('te')
|
|
522
|
+
|
|
523
|
+
// Select first search result (should be from search results)
|
|
524
|
+
const options = document.querySelectorAll('.ss-option')
|
|
525
|
+
options[0].dispatchEvent(new MouseEvent('click'))
|
|
526
|
+
|
|
527
|
+
// Check that an option is selected
|
|
528
|
+
const selected = slim.getSelected()
|
|
529
|
+
expect(selected.length).toBe(1)
|
|
530
|
+
|
|
531
|
+
// Close and reopen
|
|
532
|
+
slim.close()
|
|
533
|
+
slim.open()
|
|
534
|
+
|
|
535
|
+
// Should still have search results in store
|
|
536
|
+
const reopenedOptions = document.querySelectorAll('.ss-option')
|
|
537
|
+
expect(reopenedOptions.length).toBeGreaterThanOrEqual(11)
|
|
538
|
+
|
|
539
|
+
// Selected value should be preserved
|
|
540
|
+
expect(slim.getSelected()).toEqual(selected)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test('should handle multiple selections from search results', () => {
|
|
544
|
+
// Configure for multi-select
|
|
545
|
+
slim = new SlimSelect({
|
|
546
|
+
select: '#searchTest',
|
|
547
|
+
data: [
|
|
548
|
+
{ value: 'a', text: 'A' },
|
|
549
|
+
{ value: 'b', text: 'B' }
|
|
550
|
+
],
|
|
551
|
+
settings: {
|
|
552
|
+
isMultiple: true
|
|
553
|
+
},
|
|
554
|
+
events: {
|
|
555
|
+
search: searchMock
|
|
556
|
+
}
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
// Search for options
|
|
560
|
+
slim.search('te')
|
|
561
|
+
|
|
562
|
+
// Select first two search results (Null, Eins) using Cmd+Click
|
|
563
|
+
const options = document.querySelectorAll('.ss-option')
|
|
564
|
+
options[0].dispatchEvent(new MouseEvent('click', { metaKey: true })) // Cmd+Click to keep dropdown open
|
|
565
|
+
options[1].dispatchEvent(new MouseEvent('click', { metaKey: true })) // Cmd+Click to keep dropdown open
|
|
566
|
+
|
|
567
|
+
// Note: Search results are temporary and not added to the store
|
|
568
|
+
// So the selection won't persist in the store, but the search state should be maintained
|
|
569
|
+
|
|
570
|
+
// Close and reopen
|
|
571
|
+
slim.close()
|
|
572
|
+
slim.open()
|
|
573
|
+
|
|
574
|
+
// Should still show search results (search state maintained)
|
|
575
|
+
const reopenedOptions = document.querySelectorAll('.ss-option')
|
|
576
|
+
// Multi-select may have additional options like deselect all, so we check for at least 11
|
|
577
|
+
expect(reopenedOptions.length).toBeGreaterThanOrEqual(11)
|
|
578
|
+
|
|
579
|
+
// Search results are maintained but selections from search results don't persist in store
|
|
580
|
+
// This is expected behavior - search results are temporary
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
test('should handle new search after reopening', () => {
|
|
584
|
+
// Initial search
|
|
585
|
+
slim.search('te')
|
|
586
|
+
let options = document.querySelectorAll('.ss-option')
|
|
587
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
588
|
+
|
|
589
|
+
// Close and reopen
|
|
590
|
+
slim.close()
|
|
591
|
+
slim.open()
|
|
592
|
+
|
|
593
|
+
// Should still have previous search results in store
|
|
594
|
+
options = document.querySelectorAll('.ss-option')
|
|
595
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
596
|
+
|
|
597
|
+
// Perform new search - this updates the store with new results
|
|
598
|
+
slim.search('ei')
|
|
599
|
+
options = document.querySelectorAll('.ss-option')
|
|
600
|
+
|
|
601
|
+
// Should show new search results
|
|
602
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
test('should handle search with no results correctly', () => {
|
|
606
|
+
// Search for something that returns no results
|
|
607
|
+
slim.search('xyz')
|
|
608
|
+
|
|
609
|
+
// Should show no results message (searchText)
|
|
610
|
+
const noResults = document.querySelector('.ss-search')
|
|
611
|
+
expect(noResults).toBeTruthy()
|
|
612
|
+
|
|
613
|
+
// Close and reopen
|
|
614
|
+
slim.close()
|
|
615
|
+
slim.open()
|
|
616
|
+
|
|
617
|
+
// Should still show no results (search state maintained)
|
|
618
|
+
const stillNoResults = document.querySelector('.ss-search')
|
|
619
|
+
expect(stillNoResults).toBeTruthy()
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
test('should handle async search results correctly', async () => {
|
|
623
|
+
// Mock async search
|
|
624
|
+
const asyncSearchMock = vi.fn().mockResolvedValue([
|
|
625
|
+
{ value: 'async1', text: 'Async Result 1' },
|
|
626
|
+
{ value: 'async2', text: 'Async Result 2' }
|
|
627
|
+
])
|
|
628
|
+
|
|
629
|
+
slim = new SlimSelect({
|
|
630
|
+
select: '#searchTest',
|
|
631
|
+
data: [
|
|
632
|
+
{ value: 'a', text: 'A' },
|
|
633
|
+
{ value: 'b', text: 'B' }
|
|
634
|
+
],
|
|
635
|
+
events: {
|
|
636
|
+
search: asyncSearchMock
|
|
637
|
+
}
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
// Trigger async search
|
|
641
|
+
slim.search('async')
|
|
642
|
+
|
|
643
|
+
// Wait for async results
|
|
644
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
645
|
+
|
|
646
|
+
// Check results are displayed (should include the async results)
|
|
647
|
+
let options = document.querySelectorAll('.ss-option')
|
|
648
|
+
expect(options.length).toBeGreaterThanOrEqual(2)
|
|
649
|
+
|
|
650
|
+
// Find the async results in the options
|
|
651
|
+
const asyncOptions = Array.from(options).filter((option) => option.textContent?.includes('Async Result'))
|
|
652
|
+
expect(asyncOptions).toHaveLength(2)
|
|
653
|
+
expect(asyncOptions[0].textContent).toBe('Async Result 1')
|
|
654
|
+
expect(asyncOptions[1].textContent).toBe('Async Result 2')
|
|
655
|
+
|
|
656
|
+
// Close and reopen
|
|
657
|
+
slim.close()
|
|
658
|
+
slim.open()
|
|
659
|
+
|
|
660
|
+
// Should still show async search results
|
|
661
|
+
options = document.querySelectorAll('.ss-option')
|
|
662
|
+
const reopenedAsyncOptions = Array.from(options).filter((option) => option.textContent?.includes('Async Result'))
|
|
663
|
+
expect(reopenedAsyncOptions).toHaveLength(2)
|
|
664
|
+
expect(reopenedAsyncOptions[0].textContent).toBe('Async Result 1')
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
test('should update store data with search results', () => {
|
|
668
|
+
// Test that search updates the store
|
|
669
|
+
slim.search('te')
|
|
670
|
+
expect(searchMock).toHaveBeenCalledWith('te', expect.any(Array))
|
|
671
|
+
|
|
672
|
+
let options = document.querySelectorAll('.ss-option')
|
|
673
|
+
expect(options.length).toBeGreaterThanOrEqual(11)
|
|
674
|
+
|
|
675
|
+
// Search results are now in the store
|
|
676
|
+
const storeData = slim.getData()
|
|
677
|
+
expect(storeData.length).toBeGreaterThanOrEqual(11)
|
|
678
|
+
|
|
679
|
+
// Clear search - this returns empty array from mock
|
|
680
|
+
slim.search('')
|
|
681
|
+
options = document.querySelectorAll('.ss-option')
|
|
682
|
+
|
|
683
|
+
// Empty search clears results
|
|
684
|
+
expect(options.length).toBeLessThanOrEqual(2)
|
|
685
|
+
})
|
|
686
|
+
})
|
|
192
687
|
})
|