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.
- package/dist/classes.d.ts +47 -0
- package/dist/helpers.d.ts +5 -0
- package/dist/index.d.ts +53 -319
- package/dist/index.umd.d.ts +2 -0
- package/dist/react/index.d.ts +4 -319
- package/dist/react/index.js +1471 -1697
- package/dist/react/index.umd.js +2 -2
- package/dist/react/react.d.ts +17 -0
- package/dist/react/react.test.d.ts +1 -0
- package/dist/react/slim-select/react/index.d.ts +4 -0
- package/dist/render.d.ts +90 -0
- package/dist/select.d.ts +38 -0
- package/dist/settings.d.ts +40 -0
- package/dist/slimselect.cjs.js +2 -2
- package/dist/slimselect.cjs.js.map +1 -1
- package/dist/slimselect.css +1 -1
- package/dist/slimselect.css.map +1 -1
- package/dist/slimselect.es.js +1245 -1385
- package/dist/slimselect.es.js.map +1 -1
- package/dist/slimselect.iife.js +2 -2
- package/dist/slimselect.iife.js.map +1 -1
- package/dist/slimselect.js +2 -2
- package/dist/slimselect.js.map +1 -1
- package/dist/src/docs/main.d.ts +0 -0
- package/dist/src/docs/router.d.ts +2 -0
- package/dist/src/docs/store.d.ts +12 -0
- package/dist/store.d.ts +53 -0
- package/dist/vue/index.d.ts +4 -319
- package/dist/vue/index.js +1347 -1504
- package/dist/vue/index.umd.js +1 -1
- package/dist/vue/slim-select/vue/index.d.ts +4 -0
- package/package.json +30 -36
- package/src/slim-select/slimselect.scss +47 -5
- package/.nvmrc +0 -1
- package/.prettierignore +0 -1
- package/.prettierrc +0 -7
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -1
- package/cdn.html +0 -21
- package/docs.go +0 -12
- package/example.html +0 -25
- package/index.html +0 -76
- package/release.js +0 -191
- package/sitemap.js +0 -108
- package/src/slim-select/accessibility.test.ts +0 -782
- package/src/slim-select/classes.test.ts +0 -73
- package/src/slim-select/classes.ts +0 -117
- package/src/slim-select/helpers.test.ts +0 -162
- package/src/slim-select/helpers.ts +0 -61
- package/src/slim-select/index.test.ts +0 -1356
- package/src/slim-select/index.ts +0 -594
- package/src/slim-select/index.umd.ts +0 -8
- package/src/slim-select/react/index.tsx +0 -9
- package/src/slim-select/react/react.test.tsx +0 -498
- package/src/slim-select/react/react.tsx +0 -209
- package/src/slim-select/render.test.ts +0 -1927
- package/src/slim-select/render.ts +0 -1771
- package/src/slim-select/select.test.ts +0 -683
- package/src/slim-select/select.ts +0 -659
- package/src/slim-select/settings.test.ts +0 -285
- package/src/slim-select/settings.ts +0 -78
- package/src/slim-select/store.test.ts +0 -1007
- package/src/slim-select/store.ts +0 -433
- package/src/slim-select/vue/index.ts +0 -9
- package/src/slim-select/vue/vue.test.ts +0 -1091
- package/src/slim-select/vue/vue.vue +0 -224
- package/tsconfig.json +0 -26
- package/vite.config.docs.mts +0 -29
- package/vite.config.lib.es-cjs.ts +0 -40
- package/vite.config.lib.umd-iife.ts +0 -34
- package/vite.config.react.mts +0 -41
- package/vite.config.vue.mts +0 -40
- package/vitest.config.ts +0 -30
|
@@ -1,1091 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
-
import { mount, VueWrapper } from '@vue/test-utils'
|
|
3
|
-
import { nextTick, PropType } from 'vue'
|
|
4
|
-
import SlimSelect, { Option, Events } from '@/slim-select'
|
|
5
|
-
import SlimSelectVue from './vue.vue'
|
|
6
|
-
|
|
7
|
-
describe('SlimSelect Vue Component', () => {
|
|
8
|
-
let wrapper: VueWrapper<any>
|
|
9
|
-
let consoleInfoSpy: any
|
|
10
|
-
let consoleWarnSpy: any
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
// Mock console methods
|
|
14
|
-
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {})
|
|
15
|
-
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
if (wrapper) {
|
|
20
|
-
wrapper.unmount()
|
|
21
|
-
}
|
|
22
|
-
// Restore console methods
|
|
23
|
-
consoleInfoSpy.mockRestore()
|
|
24
|
-
consoleWarnSpy.mockRestore()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
describe('Basic Rendering', () => {
|
|
28
|
-
test('renders a select element', () => {
|
|
29
|
-
wrapper = mount(SlimSelectVue)
|
|
30
|
-
const select = wrapper.find('select')
|
|
31
|
-
expect(select.exists()).toBe(true)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('renders with single select by default', () => {
|
|
35
|
-
wrapper = mount(SlimSelectVue)
|
|
36
|
-
const select = wrapper.find('select')
|
|
37
|
-
expect(select.attributes('multiple')).toBeUndefined()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('renders with multiple attribute when multiple prop is true', () => {
|
|
41
|
-
wrapper = mount(SlimSelectVue, {
|
|
42
|
-
props: {
|
|
43
|
-
multiple: true
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
const select = wrapper.find('select')
|
|
47
|
-
expect(select.attributes('multiple')).toBeDefined()
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
describe('Slot Content', () => {
|
|
52
|
-
test('renders slot content', () => {
|
|
53
|
-
wrapper = mount(SlimSelectVue, {
|
|
54
|
-
slots: {
|
|
55
|
-
default: `
|
|
56
|
-
<option value="1">Option 1</option>
|
|
57
|
-
<option value="2">Option 2</option>
|
|
58
|
-
`
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const options = wrapper.findAll('option')
|
|
63
|
-
expect(options).toHaveLength(2)
|
|
64
|
-
expect(options[0].text()).toBe('Option 1')
|
|
65
|
-
expect(options[1].text()).toBe('Option 2')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('shows warning when both slot and data prop are provided', () => {
|
|
69
|
-
wrapper = mount(SlimSelectVue, {
|
|
70
|
-
props: {
|
|
71
|
-
data: [{ value: '1', text: 'Data Option' }]
|
|
72
|
-
},
|
|
73
|
-
slots: {
|
|
74
|
-
default: '<option value="2">Slot Option</option>'
|
|
75
|
-
}
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
79
|
-
expect.stringContaining('Both slot content and data prop are provided')
|
|
80
|
-
)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('reactive slot content WITHOUT :selected attribute works with v-model', async () => {
|
|
84
|
-
// Test that v-model auto-syncs without needing :selected
|
|
85
|
-
const TestComponent = {
|
|
86
|
-
components: { SlimSelectVue },
|
|
87
|
-
template: `
|
|
88
|
-
<SlimSelectVue v-model="selected" multiple>
|
|
89
|
-
<option v-for="opt in options" :key="opt.value" :value="opt.value">
|
|
90
|
-
{{ opt.text }}
|
|
91
|
-
</option>
|
|
92
|
-
</SlimSelectVue>
|
|
93
|
-
`,
|
|
94
|
-
data() {
|
|
95
|
-
return {
|
|
96
|
-
selected: ['v1'] as string[],
|
|
97
|
-
options: [
|
|
98
|
-
{ value: 'v1', text: 'Value 1' },
|
|
99
|
-
{ value: 'v2', text: 'Value 2' },
|
|
100
|
-
{ value: 'v3', text: 'Value 3' }
|
|
101
|
-
]
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const wrapper = mount(TestComponent)
|
|
107
|
-
await nextTick()
|
|
108
|
-
|
|
109
|
-
// Verify initial selection works without :selected
|
|
110
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
111
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
112
|
-
expect(slim.getSelected()).toEqual(['v1'])
|
|
113
|
-
|
|
114
|
-
// Parent component updates selected
|
|
115
|
-
;(wrapper.vm.selected as string[]) = ['v2', 'v3']
|
|
116
|
-
await nextTick()
|
|
117
|
-
|
|
118
|
-
// Verify SlimSelect reflects the change
|
|
119
|
-
expect(slim.getSelected()).toEqual(['v2', 'v3'])
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
describe('v-model binding', () => {
|
|
124
|
-
test('accepts initial modelValue for single select', async () => {
|
|
125
|
-
wrapper = mount(SlimSelectVue, {
|
|
126
|
-
props: {
|
|
127
|
-
modelValue: '2',
|
|
128
|
-
data: [
|
|
129
|
-
{ value: '1', text: 'Option 1' },
|
|
130
|
-
{ value: '2', text: 'Option 2' },
|
|
131
|
-
{ value: '3', text: 'Option 3' }
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
await nextTick()
|
|
137
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
138
|
-
const selected = slim.getSelected()
|
|
139
|
-
expect(selected[0]).toBe('2')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
test('accepts initial modelValue for multiple select', async () => {
|
|
143
|
-
wrapper = mount(SlimSelectVue, {
|
|
144
|
-
props: {
|
|
145
|
-
modelValue: ['2', '3'],
|
|
146
|
-
multiple: true,
|
|
147
|
-
data: [
|
|
148
|
-
{ value: '1', text: 'Option 1' },
|
|
149
|
-
{ value: '2', text: 'Option 2' },
|
|
150
|
-
{ value: '3', text: 'Option 3' }
|
|
151
|
-
]
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
await nextTick()
|
|
156
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
157
|
-
const selected = slim.getSelected()
|
|
158
|
-
expect(selected).toEqual(['2', '3'])
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
test('emits update:modelValue when selection changes', async () => {
|
|
162
|
-
wrapper = mount(SlimSelectVue, {
|
|
163
|
-
props: {
|
|
164
|
-
modelValue: '1',
|
|
165
|
-
data: [
|
|
166
|
-
{ value: '1', text: 'Option 1' },
|
|
167
|
-
{ value: '2', text: 'Option 2' }
|
|
168
|
-
]
|
|
169
|
-
}
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
await nextTick()
|
|
173
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
174
|
-
|
|
175
|
-
// Simulate selection change
|
|
176
|
-
slim.setSelected('2')
|
|
177
|
-
await nextTick()
|
|
178
|
-
|
|
179
|
-
const emitted = wrapper.emitted('update:modelValue')
|
|
180
|
-
expect(emitted).toBeTruthy()
|
|
181
|
-
expect(emitted?.[0]).toEqual(['2'])
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
test('updates selection when modelValue prop changes', async () => {
|
|
185
|
-
wrapper = mount(SlimSelectVue, {
|
|
186
|
-
props: {
|
|
187
|
-
modelValue: '1',
|
|
188
|
-
data: [
|
|
189
|
-
{ value: '1', text: 'Option 1' },
|
|
190
|
-
{ value: '2', text: 'Option 2' }
|
|
191
|
-
]
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
await nextTick()
|
|
196
|
-
|
|
197
|
-
// Update modelValue
|
|
198
|
-
await wrapper.setProps({ modelValue: '2' })
|
|
199
|
-
await nextTick()
|
|
200
|
-
|
|
201
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
202
|
-
const selected = slim.getSelected()
|
|
203
|
-
expect(selected[0]).toBe('2')
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
describe('Data Prop', () => {
|
|
208
|
-
test('initializes with data prop', async () => {
|
|
209
|
-
const testData = [
|
|
210
|
-
{ value: '1', text: 'Option 1' },
|
|
211
|
-
{ value: '2', text: 'Option 2' },
|
|
212
|
-
{ value: '3', text: 'Option 3' }
|
|
213
|
-
]
|
|
214
|
-
|
|
215
|
-
wrapper = mount(SlimSelectVue, {
|
|
216
|
-
props: {
|
|
217
|
-
data: testData
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
await nextTick()
|
|
222
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
223
|
-
const data = slim.getData()
|
|
224
|
-
|
|
225
|
-
expect(data).toHaveLength(3)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
test('updates data when data prop changes', async () => {
|
|
229
|
-
wrapper = mount(SlimSelectVue, {
|
|
230
|
-
props: {
|
|
231
|
-
data: [
|
|
232
|
-
{ value: '1', text: 'Option 1' },
|
|
233
|
-
{ value: '2', text: 'Option 2' }
|
|
234
|
-
]
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
await nextTick()
|
|
239
|
-
|
|
240
|
-
// Update data prop
|
|
241
|
-
await wrapper.setProps({
|
|
242
|
-
data: [
|
|
243
|
-
{ value: '3', text: 'Option 3' },
|
|
244
|
-
{ value: '4', text: 'Option 4' },
|
|
245
|
-
{ value: '5', text: 'Option 5' }
|
|
246
|
-
]
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
await nextTick()
|
|
250
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
251
|
-
const data = slim.getData()
|
|
252
|
-
|
|
253
|
-
expect(data).toHaveLength(3)
|
|
254
|
-
})
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
describe('Settings Prop', () => {
|
|
258
|
-
test('applies settings prop', async () => {
|
|
259
|
-
wrapper = mount(SlimSelectVue, {
|
|
260
|
-
props: {
|
|
261
|
-
data: [{ value: '1', text: 'Option 1' }],
|
|
262
|
-
settings: {
|
|
263
|
-
showSearch: false,
|
|
264
|
-
placeholderText: 'Custom Placeholder'
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
await nextTick()
|
|
270
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
271
|
-
|
|
272
|
-
expect(slim.settings.showSearch).toBe(false)
|
|
273
|
-
expect(slim.settings.placeholderText).toBe('Custom Placeholder')
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
describe('Events Prop', () => {
|
|
278
|
-
test('calls afterChange event from events prop', async () => {
|
|
279
|
-
const afterChangeMock = vi.fn()
|
|
280
|
-
|
|
281
|
-
wrapper = mount(SlimSelectVue, {
|
|
282
|
-
props: {
|
|
283
|
-
modelValue: '1',
|
|
284
|
-
data: [
|
|
285
|
-
{ value: '1', text: 'Option 1' },
|
|
286
|
-
{ value: '2', text: 'Option 2' }
|
|
287
|
-
],
|
|
288
|
-
events: {
|
|
289
|
-
afterChange: afterChangeMock
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
await nextTick()
|
|
295
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
296
|
-
|
|
297
|
-
// Trigger a change
|
|
298
|
-
slim.setSelected('2')
|
|
299
|
-
await nextTick()
|
|
300
|
-
|
|
301
|
-
expect(afterChangeMock).toHaveBeenCalled()
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
test('calls both custom afterChange and emits update:modelValue', async () => {
|
|
305
|
-
const afterChangeMock = vi.fn()
|
|
306
|
-
|
|
307
|
-
wrapper = mount(SlimSelectVue, {
|
|
308
|
-
props: {
|
|
309
|
-
modelValue: '1',
|
|
310
|
-
data: [
|
|
311
|
-
{ value: '1', text: 'Option 1' },
|
|
312
|
-
{ value: '2', text: 'Option 2' }
|
|
313
|
-
],
|
|
314
|
-
events: {
|
|
315
|
-
afterChange: afterChangeMock
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
await nextTick()
|
|
321
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
322
|
-
|
|
323
|
-
// Trigger a change
|
|
324
|
-
slim.setSelected('2')
|
|
325
|
-
await nextTick()
|
|
326
|
-
|
|
327
|
-
// Both should be called
|
|
328
|
-
expect(afterChangeMock).toHaveBeenCalled()
|
|
329
|
-
const emitted = wrapper.emitted('update:modelValue')
|
|
330
|
-
expect(emitted).toBeTruthy()
|
|
331
|
-
expect(emitted?.[0]).toEqual(['2'])
|
|
332
|
-
})
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
describe('Component Methods', () => {
|
|
336
|
-
test('getSlimSelect returns SlimSelect instance', async () => {
|
|
337
|
-
wrapper = mount(SlimSelectVue, {
|
|
338
|
-
props: {
|
|
339
|
-
data: [{ value: '1', text: 'Option 1' }]
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
await nextTick()
|
|
344
|
-
const slimInstance = (wrapper.vm as any).getSlimSelect()
|
|
345
|
-
|
|
346
|
-
expect(slimInstance).toBeInstanceOf(SlimSelect)
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
test('getCleanValue handles string values correctly', () => {
|
|
350
|
-
wrapper = mount(SlimSelectVue, {
|
|
351
|
-
props: {
|
|
352
|
-
multiple: false
|
|
353
|
-
}
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
const result = (wrapper.vm as any).getCleanValue('test')
|
|
357
|
-
expect(result).toBe('test')
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
test('getCleanValue converts string to array for multiple select', () => {
|
|
361
|
-
wrapper = mount(SlimSelectVue, {
|
|
362
|
-
props: {
|
|
363
|
-
multiple: true
|
|
364
|
-
}
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
const result = (wrapper.vm as any).getCleanValue('test')
|
|
368
|
-
expect(result).toEqual(['test'])
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
test('getCleanValue converts array to string for single select', () => {
|
|
372
|
-
wrapper = mount(SlimSelectVue, {
|
|
373
|
-
props: {
|
|
374
|
-
multiple: false
|
|
375
|
-
}
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
const result = (wrapper.vm as any).getCleanValue(['test1', 'test2'])
|
|
379
|
-
expect(result).toBe('test1')
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
test('getCleanValue returns empty array for undefined value in multiple select', () => {
|
|
383
|
-
wrapper = mount(SlimSelectVue, {
|
|
384
|
-
props: {
|
|
385
|
-
multiple: true
|
|
386
|
-
}
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
const result = (wrapper.vm as any).getCleanValue(undefined)
|
|
390
|
-
expect(result).toEqual([])
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
test('getCleanValue returns empty string for undefined value in single select', () => {
|
|
394
|
-
wrapper = mount(SlimSelectVue, {
|
|
395
|
-
props: {
|
|
396
|
-
multiple: false
|
|
397
|
-
}
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
const result = (wrapper.vm as any).getCleanValue(undefined)
|
|
401
|
-
expect(result).toBe('')
|
|
402
|
-
})
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
describe('Lifecycle', () => {
|
|
406
|
-
test('initializes SlimSelect on mount', async () => {
|
|
407
|
-
wrapper = mount(SlimSelectVue, {
|
|
408
|
-
props: {
|
|
409
|
-
data: [{ value: '1', text: 'Option 1' }]
|
|
410
|
-
}
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
await nextTick()
|
|
414
|
-
const slim = (wrapper.vm as any).slim
|
|
415
|
-
|
|
416
|
-
expect(slim).not.toBeNull()
|
|
417
|
-
expect(slim).toBeInstanceOf(SlimSelect)
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
test('destroys SlimSelect on unmount', async () => {
|
|
421
|
-
wrapper = mount(SlimSelectVue, {
|
|
422
|
-
props: {
|
|
423
|
-
data: [{ value: '1', text: 'Option 1' }]
|
|
424
|
-
}
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
await nextTick()
|
|
428
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
429
|
-
const destroySpy = vi.spyOn(slim, 'destroy')
|
|
430
|
-
|
|
431
|
-
wrapper.unmount()
|
|
432
|
-
|
|
433
|
-
expect(destroySpy).toHaveBeenCalled()
|
|
434
|
-
})
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
describe('Value Type Handling', () => {
|
|
438
|
-
test('handles empty string as modelValue for single select', async () => {
|
|
439
|
-
wrapper = mount(SlimSelectVue, {
|
|
440
|
-
props: {
|
|
441
|
-
modelValue: '',
|
|
442
|
-
data: [
|
|
443
|
-
{ value: '1', text: 'Option 1' },
|
|
444
|
-
{ value: '2', text: 'Option 2' }
|
|
445
|
-
]
|
|
446
|
-
}
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
await nextTick()
|
|
450
|
-
expect(wrapper.props('modelValue')).toBe('')
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
test('handles empty array as modelValue for multiple select', async () => {
|
|
454
|
-
wrapper = mount(SlimSelectVue, {
|
|
455
|
-
props: {
|
|
456
|
-
modelValue: [],
|
|
457
|
-
multiple: true,
|
|
458
|
-
data: [
|
|
459
|
-
{ value: '1', text: 'Option 1' },
|
|
460
|
-
{ value: '2', text: 'Option 2' }
|
|
461
|
-
]
|
|
462
|
-
}
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
await nextTick()
|
|
466
|
-
expect(wrapper.props('modelValue')).toEqual([])
|
|
467
|
-
})
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
describe('Reactivity Edge Cases', () => {
|
|
471
|
-
test('does not cause conflicts with v-once on slot', async () => {
|
|
472
|
-
// This test ensures v-once prevents reactivity issues
|
|
473
|
-
wrapper = mount(SlimSelectVue, {
|
|
474
|
-
slots: {
|
|
475
|
-
default: `
|
|
476
|
-
<option value="1">Option 1</option>
|
|
477
|
-
<option value="2">Option 2</option>
|
|
478
|
-
`
|
|
479
|
-
}
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
await nextTick()
|
|
483
|
-
|
|
484
|
-
// The slot should render only once
|
|
485
|
-
const initialOptions = wrapper.findAll('option')
|
|
486
|
-
expect(initialOptions).toHaveLength(2)
|
|
487
|
-
|
|
488
|
-
// SlimSelect should be initialized without errors
|
|
489
|
-
const slim = (wrapper.vm as any).slim
|
|
490
|
-
expect(slim).toBeInstanceOf(SlimSelect)
|
|
491
|
-
})
|
|
492
|
-
|
|
493
|
-
test('data prop changes are reactive', async () => {
|
|
494
|
-
const initialData = [
|
|
495
|
-
{ value: '1', text: 'Option 1' },
|
|
496
|
-
{ value: '2', text: 'Option 2' }
|
|
497
|
-
]
|
|
498
|
-
|
|
499
|
-
wrapper = mount(SlimSelectVue, {
|
|
500
|
-
props: {
|
|
501
|
-
data: initialData
|
|
502
|
-
}
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
await nextTick()
|
|
506
|
-
let slim = (wrapper.vm as any).slim as SlimSelect
|
|
507
|
-
let data = slim.getData()
|
|
508
|
-
expect(data).toHaveLength(2)
|
|
509
|
-
|
|
510
|
-
// Update data prop
|
|
511
|
-
const newData = [
|
|
512
|
-
{ value: '3', text: 'Option 3' },
|
|
513
|
-
{ value: '4', text: 'Option 4' },
|
|
514
|
-
{ value: '5', text: 'Option 5' }
|
|
515
|
-
]
|
|
516
|
-
|
|
517
|
-
await wrapper.setProps({ data: newData })
|
|
518
|
-
await nextTick()
|
|
519
|
-
|
|
520
|
-
slim = (wrapper.vm as any).slim as SlimSelect
|
|
521
|
-
data = slim.getData()
|
|
522
|
-
expect(data).toHaveLength(3)
|
|
523
|
-
})
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
describe('Complex Parent-Child Reactivity Scenario', () => {
|
|
527
|
-
test('handles computed props from parent with bidirectional v-model updates', async () => {
|
|
528
|
-
// Simulate a parent component with computed values based on modelValue
|
|
529
|
-
// Similar to the CustomFields use case
|
|
530
|
-
const parentModelValue = {
|
|
531
|
-
field1: ['option1', 'option2'],
|
|
532
|
-
field2: ['option3']
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const updateCallback = vi.fn()
|
|
536
|
-
|
|
537
|
-
// Create wrapper with data computed from parent's modelValue
|
|
538
|
-
wrapper = mount(SlimSelectVue, {
|
|
539
|
-
props: {
|
|
540
|
-
modelValue: parentModelValue.field1,
|
|
541
|
-
multiple: true,
|
|
542
|
-
data: [
|
|
543
|
-
{ value: 'option1', text: 'Option 1' },
|
|
544
|
-
{ value: 'option2', text: 'Option 2' },
|
|
545
|
-
{ value: 'option3', text: 'Option 3' },
|
|
546
|
-
{ value: 'option4', text: 'Option 4' }
|
|
547
|
-
],
|
|
548
|
-
events: {
|
|
549
|
-
afterChange: updateCallback
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
await nextTick()
|
|
555
|
-
|
|
556
|
-
// Verify initial selection from parent's computed value
|
|
557
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
558
|
-
let selected = slim.getSelected()
|
|
559
|
-
expect(selected).toEqual(['option1', 'option2'])
|
|
560
|
-
|
|
561
|
-
// User makes a change in SlimSelect
|
|
562
|
-
slim.setSelected(['option2', 'option3', 'option4'])
|
|
563
|
-
await nextTick()
|
|
564
|
-
|
|
565
|
-
// Verify the change callback was called
|
|
566
|
-
expect(updateCallback).toHaveBeenCalled()
|
|
567
|
-
|
|
568
|
-
// Verify v-model emitted the update
|
|
569
|
-
const emitted = wrapper.emitted('update:modelValue')
|
|
570
|
-
expect(emitted).toBeTruthy()
|
|
571
|
-
expect(emitted?.[emitted.length - 1]).toEqual([['option2', 'option3', 'option4']])
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
test('updates when parent changes computed data based on new modelValue', async () => {
|
|
575
|
-
// Start with initial values
|
|
576
|
-
const fieldValues = [
|
|
577
|
-
{ value: 'opt1', text: 'Option 1' },
|
|
578
|
-
{ value: 'opt2', text: 'Option 2' },
|
|
579
|
-
{ value: 'opt3', text: 'Option 3' }
|
|
580
|
-
]
|
|
581
|
-
|
|
582
|
-
wrapper = mount(SlimSelectVue, {
|
|
583
|
-
props: {
|
|
584
|
-
modelValue: ['opt1'],
|
|
585
|
-
multiple: true,
|
|
586
|
-
data: fieldValues.map((v) => ({
|
|
587
|
-
value: v.value,
|
|
588
|
-
text: v.text,
|
|
589
|
-
selected: ['opt1'].includes(v.value)
|
|
590
|
-
}))
|
|
591
|
-
}
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
await nextTick()
|
|
595
|
-
let slim = (wrapper.vm as any).slim as SlimSelect
|
|
596
|
-
let selected = slim.getSelected()
|
|
597
|
-
expect(selected).toEqual(['opt1'])
|
|
598
|
-
|
|
599
|
-
// Parent updates its modelValue (e.g., from API or another form field)
|
|
600
|
-
// This should update the computed values and SlimSelect
|
|
601
|
-
await wrapper.setProps({
|
|
602
|
-
modelValue: ['opt2', 'opt3'],
|
|
603
|
-
data: fieldValues.map((v) => ({
|
|
604
|
-
value: v.value,
|
|
605
|
-
text: v.text,
|
|
606
|
-
selected: ['opt2', 'opt3'].includes(v.value)
|
|
607
|
-
}))
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
await nextTick()
|
|
611
|
-
|
|
612
|
-
// Verify SlimSelect reflects the new selection
|
|
613
|
-
slim = (wrapper.vm as any).slim as SlimSelect
|
|
614
|
-
selected = slim.getSelected()
|
|
615
|
-
expect(selected).toEqual(['opt2', 'opt3'])
|
|
616
|
-
})
|
|
617
|
-
|
|
618
|
-
test('handles afterChange callback that triggers parent valueChange method', async () => {
|
|
619
|
-
// Simulates the pattern: user changes SlimSelect -> afterChange -> parent's valueChange() -> emit
|
|
620
|
-
let parentValues = {
|
|
621
|
-
customField1: ['value1']
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const valueChangeCallback = vi.fn(() => {
|
|
625
|
-
// Simulate parent's valueChange method that formats and emits
|
|
626
|
-
const formatted = { customField1: parentValues.customField1 }
|
|
627
|
-
return formatted
|
|
628
|
-
})
|
|
629
|
-
|
|
630
|
-
wrapper = mount(SlimSelectVue, {
|
|
631
|
-
props: {
|
|
632
|
-
modelValue: parentValues.customField1,
|
|
633
|
-
multiple: true,
|
|
634
|
-
data: [
|
|
635
|
-
{ value: 'value1', text: 'Value 1' },
|
|
636
|
-
{ value: 'value2', text: 'Value 2' },
|
|
637
|
-
{ value: 'value3', text: 'Value 3' }
|
|
638
|
-
],
|
|
639
|
-
events: {
|
|
640
|
-
afterChange: (newVal: Option[]) => {
|
|
641
|
-
// Update parent's local values
|
|
642
|
-
parentValues.customField1 = newVal.map((o: Option) => o.value)
|
|
643
|
-
// Call parent's valueChange method
|
|
644
|
-
valueChangeCallback()
|
|
645
|
-
}
|
|
646
|
-
} as Events
|
|
647
|
-
}
|
|
648
|
-
})
|
|
649
|
-
|
|
650
|
-
await nextTick()
|
|
651
|
-
|
|
652
|
-
// User changes selection
|
|
653
|
-
const slim = (wrapper.vm as any).slim as SlimSelect
|
|
654
|
-
slim.setSelected(['value2', 'value3'])
|
|
655
|
-
await nextTick()
|
|
656
|
-
|
|
657
|
-
// Verify the callback chain was triggered
|
|
658
|
-
expect(valueChangeCallback).toHaveBeenCalled()
|
|
659
|
-
expect(parentValues.customField1).toEqual(['value2', 'value3'])
|
|
660
|
-
|
|
661
|
-
// Verify v-model emission
|
|
662
|
-
const emitted = wrapper.emitted('update:modelValue')
|
|
663
|
-
expect(emitted).toBeTruthy()
|
|
664
|
-
expect(emitted?.[emitted.length - 1]).toEqual([['value2', 'value3']])
|
|
665
|
-
})
|
|
666
|
-
|
|
667
|
-
test('maintains reactivity through multiple prop updates from parent computed', async () => {
|
|
668
|
-
// Test that reactivity works through multiple cycles
|
|
669
|
-
wrapper = mount(SlimSelectVue, {
|
|
670
|
-
props: {
|
|
671
|
-
modelValue: ['a'],
|
|
672
|
-
multiple: true,
|
|
673
|
-
data: [
|
|
674
|
-
{ value: 'a', text: 'A' },
|
|
675
|
-
{ value: 'b', text: 'B' },
|
|
676
|
-
{ value: 'c', text: 'C' }
|
|
677
|
-
]
|
|
678
|
-
}
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
await nextTick()
|
|
682
|
-
let slim = (wrapper.vm as any).slim as SlimSelect
|
|
683
|
-
expect(slim.getSelected()).toEqual(['a'])
|
|
684
|
-
|
|
685
|
-
// First parent update
|
|
686
|
-
await wrapper.setProps({ modelValue: ['b'] })
|
|
687
|
-
await nextTick()
|
|
688
|
-
expect(slim.getSelected()).toEqual(['b'])
|
|
689
|
-
|
|
690
|
-
// Second parent update
|
|
691
|
-
await wrapper.setProps({ modelValue: ['a', 'c'] })
|
|
692
|
-
await nextTick()
|
|
693
|
-
expect(slim.getSelected()).toEqual(['a', 'c'])
|
|
694
|
-
|
|
695
|
-
// User makes a change
|
|
696
|
-
slim.setSelected(['b', 'c'])
|
|
697
|
-
await nextTick()
|
|
698
|
-
|
|
699
|
-
// Verify emission
|
|
700
|
-
const emitted = wrapper.emitted('update:modelValue')
|
|
701
|
-
expect(emitted?.[emitted.length - 1]).toEqual([['b', 'c']])
|
|
702
|
-
|
|
703
|
-
// Parent responds to the change
|
|
704
|
-
await wrapper.setProps({ modelValue: ['b', 'c'] })
|
|
705
|
-
await nextTick()
|
|
706
|
-
expect(slim.getSelected()).toEqual(['b', 'c'])
|
|
707
|
-
})
|
|
708
|
-
|
|
709
|
-
test('handles data prop updates from parent computed with selected state', async () => {
|
|
710
|
-
// Simulates when parent recomputes data array with updated selected states
|
|
711
|
-
const allOptions = [
|
|
712
|
-
{ value: 'v1', text: 'Value 1' },
|
|
713
|
-
{ value: 'v2', text: 'Value 2' },
|
|
714
|
-
{ value: 'v3', text: 'Value 3' }
|
|
715
|
-
]
|
|
716
|
-
|
|
717
|
-
const computeDataProp = (selectedValues: string[]) => {
|
|
718
|
-
return allOptions.map((opt) => ({
|
|
719
|
-
value: opt.value,
|
|
720
|
-
text: opt.text,
|
|
721
|
-
selected: selectedValues.includes(opt.value)
|
|
722
|
-
}))
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
wrapper = mount(SlimSelectVue, {
|
|
726
|
-
props: {
|
|
727
|
-
modelValue: ['v1'],
|
|
728
|
-
multiple: true,
|
|
729
|
-
data: computeDataProp(['v1'])
|
|
730
|
-
}
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
await nextTick()
|
|
734
|
-
let slim = (wrapper.vm as any).slim as SlimSelect
|
|
735
|
-
expect(slim.getSelected()).toEqual(['v1'])
|
|
736
|
-
|
|
737
|
-
// Parent's modelValue changes, triggering recompute of data prop
|
|
738
|
-
await wrapper.setProps({
|
|
739
|
-
modelValue: ['v2', 'v3'],
|
|
740
|
-
data: computeDataProp(['v2', 'v3'])
|
|
741
|
-
})
|
|
742
|
-
|
|
743
|
-
await nextTick()
|
|
744
|
-
slim = (wrapper.vm as any).slim as SlimSelect
|
|
745
|
-
expect(slim.getSelected()).toEqual(['v2', 'v3'])
|
|
746
|
-
|
|
747
|
-
// Verify data was updated
|
|
748
|
-
const data = slim.getData()
|
|
749
|
-
expect(data).toHaveLength(3)
|
|
750
|
-
})
|
|
751
|
-
|
|
752
|
-
test('CustomFields use case: parent-child pattern with reactive slot content', async () => {
|
|
753
|
-
// Recreate the actual working pattern: parent passes value to child via v-model
|
|
754
|
-
const ChildComponent = {
|
|
755
|
-
components: { SlimSelectVue },
|
|
756
|
-
template: `
|
|
757
|
-
<SlimSelectVue
|
|
758
|
-
v-model="value"
|
|
759
|
-
multiple
|
|
760
|
-
:events="{ afterChange: () => handleChange() }"
|
|
761
|
-
>
|
|
762
|
-
<option v-for="opt in fieldOptions" :key="opt.value" :value="opt.value">
|
|
763
|
-
{{ opt.name }}
|
|
764
|
-
</option>
|
|
765
|
-
</SlimSelectVue>
|
|
766
|
-
`,
|
|
767
|
-
props: {
|
|
768
|
-
modelValue: { type: Array as PropType<string[]>, required: true },
|
|
769
|
-
fieldOptions: { type: Array, required: true }
|
|
770
|
-
},
|
|
771
|
-
emits: ['update:modelValue'],
|
|
772
|
-
computed: {
|
|
773
|
-
value: {
|
|
774
|
-
get(this: any): string[] {
|
|
775
|
-
return this.modelValue || []
|
|
776
|
-
},
|
|
777
|
-
set(this: any, newValue: string[]) {
|
|
778
|
-
this.$emit('update:modelValue', newValue)
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
},
|
|
782
|
-
methods: {
|
|
783
|
-
handleChange() {
|
|
784
|
-
// Handle selection change
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const ParentComponent = {
|
|
790
|
-
components: { ChildComponent },
|
|
791
|
-
template: `
|
|
792
|
-
<ChildComponent
|
|
793
|
-
v-model="selectedValues"
|
|
794
|
-
:field-options="options"
|
|
795
|
-
/>
|
|
796
|
-
`,
|
|
797
|
-
data() {
|
|
798
|
-
return {
|
|
799
|
-
selectedValues: ['value1'] as string[],
|
|
800
|
-
options: [
|
|
801
|
-
{ value: 'value1', name: 'Value 1' },
|
|
802
|
-
{ value: 'value2', name: 'Value 2' },
|
|
803
|
-
{ value: 'value3', name: 'Value 3' }
|
|
804
|
-
]
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const wrapper = mount(ParentComponent)
|
|
810
|
-
await nextTick()
|
|
811
|
-
|
|
812
|
-
// Verify initial state
|
|
813
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
814
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
815
|
-
expect(slim.getSelected()).toEqual(['value1'])
|
|
816
|
-
|
|
817
|
-
// User changes selection via SlimSelect
|
|
818
|
-
slim.setSelected(['value2', 'value3'])
|
|
819
|
-
await nextTick()
|
|
820
|
-
|
|
821
|
-
// Verify parent data updated
|
|
822
|
-
expect((wrapper.vm as any).selectedValues).toEqual(['value2', 'value3'])
|
|
823
|
-
|
|
824
|
-
// Parent's data changes (from API or other form)
|
|
825
|
-
;((wrapper.vm as any).selectedValues as string[]) = ['value1', 'value3']
|
|
826
|
-
await nextTick()
|
|
827
|
-
|
|
828
|
-
// Verify SlimSelect reflects the new selection
|
|
829
|
-
expect(slim.getSelected()).toEqual(['value1', 'value3'])
|
|
830
|
-
})
|
|
831
|
-
})
|
|
832
|
-
|
|
833
|
-
describe('Empty v-model with slot options', () => {
|
|
834
|
-
test('should operate like a normal select with empty v-model and 3 options', async () => {
|
|
835
|
-
// Test scenario: SlimSelectVue with 3 options and v-model bound to empty string
|
|
836
|
-
// Should behave like a normal select - no option selected initially, can select options
|
|
837
|
-
const TestComponent = {
|
|
838
|
-
components: { SlimSelectVue },
|
|
839
|
-
template: `
|
|
840
|
-
<SlimSelectVue v-model="selected">
|
|
841
|
-
<option value="opt1">Option 1</option>
|
|
842
|
-
<option value="opt2">Option 2</option>
|
|
843
|
-
<option value="opt3">Option 3</option>
|
|
844
|
-
</SlimSelectVue>
|
|
845
|
-
`,
|
|
846
|
-
data() {
|
|
847
|
-
return {
|
|
848
|
-
selected: '' as string
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
wrapper = mount(TestComponent)
|
|
854
|
-
await nextTick()
|
|
855
|
-
|
|
856
|
-
// Get the SlimSelect instance
|
|
857
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
858
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
859
|
-
|
|
860
|
-
// Verify initial state: v-model is empty string
|
|
861
|
-
expect((wrapper.vm as any).selected).toBe('')
|
|
862
|
-
|
|
863
|
-
// Verify options exist in SlimSelect (may include placeholder if added)
|
|
864
|
-
const data = slim.getData()
|
|
865
|
-
// Filter out placeholder options to check actual options
|
|
866
|
-
const actualOptions = (data as Option[]).filter((opt: any) => !opt.placeholder)
|
|
867
|
-
expect(actualOptions.length).toBe(3)
|
|
868
|
-
expect(actualOptions[0].value).toBe('opt1')
|
|
869
|
-
expect(actualOptions[0].text).toBe('Option 1')
|
|
870
|
-
expect(actualOptions[1].value).toBe('opt2')
|
|
871
|
-
expect(actualOptions[1].text).toBe('Option 2')
|
|
872
|
-
expect(actualOptions[2].value).toBe('opt3')
|
|
873
|
-
expect(actualOptions[2].text).toBe('Option 3')
|
|
874
|
-
|
|
875
|
-
// The component should sync empty modelValue to SlimSelect in mounted hook
|
|
876
|
-
// The component's mounted hook compares modelValue with SlimSelect's selection
|
|
877
|
-
// and syncs if they don't match. Since modelValue is '', it should sync to empty.
|
|
878
|
-
// However, if the native select had a default selection (first option),
|
|
879
|
-
// SlimSelect might initially have that selected, but the component should clear it.
|
|
880
|
-
// For this test, we're verifying that SlimSelect operates like a normal select
|
|
881
|
-
// with empty v-model - meaning it can select options and work correctly.
|
|
882
|
-
|
|
883
|
-
// Test that we can select options using SlimSelect API
|
|
884
|
-
slim.setSelected('opt1')
|
|
885
|
-
await nextTick()
|
|
886
|
-
|
|
887
|
-
// Verify SlimSelect has the selection
|
|
888
|
-
expect(slim.getSelected()).toEqual(['opt1'])
|
|
889
|
-
|
|
890
|
-
// Test that we can select a different option
|
|
891
|
-
slim.setSelected('opt2')
|
|
892
|
-
await nextTick()
|
|
893
|
-
|
|
894
|
-
// Verify SlimSelect updated to new selection
|
|
895
|
-
expect(slim.getSelected()).toEqual(['opt2'])
|
|
896
|
-
|
|
897
|
-
// Test that we can update via v-model (parent updates modelValue)
|
|
898
|
-
// This tests the watch handler that syncs modelValue changes to SlimSelect
|
|
899
|
-
;(wrapper.vm as any).selected = 'opt3'
|
|
900
|
-
await nextTick()
|
|
901
|
-
|
|
902
|
-
// Verify SlimSelect reflects the v-model change (via watch handler)
|
|
903
|
-
expect(slim.getSelected()).toEqual(['opt3'])
|
|
904
|
-
|
|
905
|
-
// Clear selection (set back to empty via v-model)
|
|
906
|
-
// For single select, empty string should clear the selection
|
|
907
|
-
;(wrapper.vm as any).selected = ''
|
|
908
|
-
await nextTick()
|
|
909
|
-
|
|
910
|
-
// Verify SlimSelect is cleared (operates like normal select - can be empty)
|
|
911
|
-
// Note: setSelected('') might not clear if no option has value='',
|
|
912
|
-
// but the watch handler should handle it
|
|
913
|
-
const clearedSelection = slim.getSelected()
|
|
914
|
-
// If it's not empty, that's okay - the important thing is v-model is empty
|
|
915
|
-
// and SlimSelect can work with empty v-model
|
|
916
|
-
expect((wrapper.vm as any).selected).toBe('')
|
|
917
|
-
|
|
918
|
-
// The key test: verify that SlimSelect operates correctly with empty v-model
|
|
919
|
-
// We've verified it can select options and sync from v-model changes
|
|
920
|
-
// The ability to clear to empty might depend on implementation details
|
|
921
|
-
})
|
|
922
|
-
})
|
|
923
|
-
|
|
924
|
-
describe('Invalid v-model value (value not in options)', () => {
|
|
925
|
-
test('should show placeholder when v-model value does not exist in options on mount', async () => {
|
|
926
|
-
// Test scenario: SlimSelectVue with v-model set to a value that doesn't exist in options
|
|
927
|
-
// Should show placeholder text instead of selecting first option
|
|
928
|
-
const TestComponent = {
|
|
929
|
-
components: { SlimSelectVue },
|
|
930
|
-
template: `
|
|
931
|
-
<SlimSelectVue v-model="selected">
|
|
932
|
-
<option value="opt1">Option 1</option>
|
|
933
|
-
<option value="opt2">Option 2</option>
|
|
934
|
-
<option value="opt3">Option 3</option>
|
|
935
|
-
</SlimSelectVue>
|
|
936
|
-
`,
|
|
937
|
-
data() {
|
|
938
|
-
return {
|
|
939
|
-
selected: 'banana' as string // Value that doesn't exist in options
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
wrapper = mount(TestComponent)
|
|
945
|
-
await nextTick()
|
|
946
|
-
|
|
947
|
-
// Get the SlimSelect instance
|
|
948
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
949
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
950
|
-
|
|
951
|
-
// Verify v-model still has the invalid value
|
|
952
|
-
expect((wrapper.vm as any).selected).toBe('banana')
|
|
953
|
-
|
|
954
|
-
// Verify SlimSelect has a placeholder option
|
|
955
|
-
const data = slim.getData()
|
|
956
|
-
const hasPlaceholder = (data as Option[]).some((opt: any) => opt.placeholder)
|
|
957
|
-
expect(hasPlaceholder).toBe(true)
|
|
958
|
-
|
|
959
|
-
// Verify no valid option is selected (placeholder should be selected internally,
|
|
960
|
-
// but render should show placeholder text)
|
|
961
|
-
const selectedValues = slim.getSelected()
|
|
962
|
-
// If placeholder is selected, getSelected() might return empty or the placeholder value
|
|
963
|
-
// The key is that no valid option value is selected
|
|
964
|
-
const hasValidOptionSelected = selectedValues.some((val) => val !== '' && ['opt1', 'opt2', 'opt3'].includes(val))
|
|
965
|
-
expect(hasValidOptionSelected).toBe(false)
|
|
966
|
-
|
|
967
|
-
// The key test: v-model should still have the invalid value
|
|
968
|
-
// but SlimSelect should show placeholder, not the first option
|
|
969
|
-
expect((wrapper.vm as any).selected).toBe('banana')
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
test('should show placeholder when v-model value changes to invalid value', async () => {
|
|
973
|
-
// Test scenario: Start with valid value, then change to invalid value
|
|
974
|
-
const TestComponent = {
|
|
975
|
-
components: { SlimSelectVue },
|
|
976
|
-
template: `
|
|
977
|
-
<SlimSelectVue v-model="selected">
|
|
978
|
-
<option value="opt1">Option 1</option>
|
|
979
|
-
<option value="opt2">Option 2</option>
|
|
980
|
-
<option value="opt3">Option 3</option>
|
|
981
|
-
</SlimSelectVue>
|
|
982
|
-
`,
|
|
983
|
-
data() {
|
|
984
|
-
return {
|
|
985
|
-
selected: 'opt1' as string
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
wrapper = mount(TestComponent)
|
|
991
|
-
await nextTick()
|
|
992
|
-
|
|
993
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
994
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
995
|
-
|
|
996
|
-
// Verify initial valid selection
|
|
997
|
-
expect((wrapper.vm as any).selected).toBe('opt1')
|
|
998
|
-
expect(slim.getSelected()).toEqual(['opt1'])
|
|
999
|
-
|
|
1000
|
-
// Change to invalid value
|
|
1001
|
-
;(wrapper.vm as any).selected = 'invalid-value'
|
|
1002
|
-
await nextTick()
|
|
1003
|
-
|
|
1004
|
-
// Verify v-model has invalid value
|
|
1005
|
-
expect((wrapper.vm as any).selected).toBe('invalid-value')
|
|
1006
|
-
|
|
1007
|
-
// Verify placeholder exists
|
|
1008
|
-
const data = slim.getData()
|
|
1009
|
-
const hasPlaceholder = (data as Option[]).some((opt: any) => opt.placeholder)
|
|
1010
|
-
expect(hasPlaceholder).toBe(true)
|
|
1011
|
-
|
|
1012
|
-
// Verify no valid option is selected
|
|
1013
|
-
const selectedValues = slim.getSelected()
|
|
1014
|
-
const hasValidOptionSelected = selectedValues.some((val) => val !== '' && ['opt1', 'opt2', 'opt3'].includes(val))
|
|
1015
|
-
expect(hasValidOptionSelected).toBe(false)
|
|
1016
|
-
})
|
|
1017
|
-
|
|
1018
|
-
test('should show placeholder when v-model is empty string and no placeholder option exists', async () => {
|
|
1019
|
-
// Test scenario: Empty v-model, no placeholder in options initially
|
|
1020
|
-
const TestComponent = {
|
|
1021
|
-
components: { SlimSelectVue },
|
|
1022
|
-
template: `
|
|
1023
|
-
<SlimSelectVue v-model="selected">
|
|
1024
|
-
<option value="opt1">Option 1</option>
|
|
1025
|
-
<option value="opt2">Option 2</option>
|
|
1026
|
-
<option value="opt3">Option 3</option>
|
|
1027
|
-
</SlimSelectVue>
|
|
1028
|
-
`,
|
|
1029
|
-
data() {
|
|
1030
|
-
return {
|
|
1031
|
-
selected: '' as string
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
wrapper = mount(TestComponent)
|
|
1037
|
-
await nextTick()
|
|
1038
|
-
|
|
1039
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
1040
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
1041
|
-
|
|
1042
|
-
// Verify v-model is empty
|
|
1043
|
-
expect((wrapper.vm as any).selected).toBe('')
|
|
1044
|
-
|
|
1045
|
-
// Verify placeholder was added
|
|
1046
|
-
const data = slim.getData()
|
|
1047
|
-
const hasPlaceholder = (data as Option[]).some((opt: any) => opt.placeholder)
|
|
1048
|
-
expect(hasPlaceholder).toBe(true)
|
|
1049
|
-
|
|
1050
|
-
// Verify no valid option is selected
|
|
1051
|
-
const selectedValues = slim.getSelected()
|
|
1052
|
-
const hasValidOptionSelected = selectedValues.some((val) => val !== '' && ['opt1', 'opt2', 'opt3'].includes(val))
|
|
1053
|
-
expect(hasValidOptionSelected).toBe(false)
|
|
1054
|
-
})
|
|
1055
|
-
|
|
1056
|
-
test('should update v-model for multiple select when invalid values are provided', async () => {
|
|
1057
|
-
// Test scenario: Multiple select with v-model containing invalid values
|
|
1058
|
-
const TestComponent = {
|
|
1059
|
-
components: { SlimSelectVue },
|
|
1060
|
-
template: `
|
|
1061
|
-
<SlimSelectVue v-model="selected" multiple>
|
|
1062
|
-
<option value="opt1">Option 1</option>
|
|
1063
|
-
<option value="opt2">Option 2</option>
|
|
1064
|
-
<option value="opt3">Option 3</option>
|
|
1065
|
-
</SlimSelectVue>
|
|
1066
|
-
`,
|
|
1067
|
-
data() {
|
|
1068
|
-
return {
|
|
1069
|
-
selected: ['banana', 'apple'] as string[] // Values that don't exist in options
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
wrapper = mount(TestComponent)
|
|
1075
|
-
await nextTick()
|
|
1076
|
-
|
|
1077
|
-
const slimComponent = wrapper.findComponent(SlimSelectVue)
|
|
1078
|
-
const slim = (slimComponent.vm as any).slim as SlimSelect
|
|
1079
|
-
|
|
1080
|
-
// Verify SlimSelect has the invalid values selected (should work even if invalid)
|
|
1081
|
-
const selectedValues = slim.getSelected()
|
|
1082
|
-
|
|
1083
|
-
// When user manually selects a valid option, v-model should update
|
|
1084
|
-
slim.setSelected(['opt1', 'opt2'])
|
|
1085
|
-
await nextTick()
|
|
1086
|
-
|
|
1087
|
-
// Verify v-model has been updated to the valid values
|
|
1088
|
-
expect((wrapper.vm as any).selected).toEqual(['opt1', 'opt2'])
|
|
1089
|
-
})
|
|
1090
|
-
})
|
|
1091
|
-
})
|