@livestorm/ui-vue 1.1.1 → 1.1.2

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 (80) hide show
  1. package/.env +1 -0
  2. package/.eslintignore +4 -0
  3. package/.eslintrc.js +47 -0
  4. package/.pkg +11 -0
  5. package/babel.config.js +5 -0
  6. package/build/Dockerfile +31 -0
  7. package/ci/ci.yml +54 -0
  8. package/components-vite.js +29 -0
  9. package/components-webpack.js +29 -0
  10. package/components.d.ts +4 -0
  11. package/index.d.ts +3 -0
  12. package/index.js +24 -0
  13. package/package.json +1 -4
  14. package/plugins.js +17 -0
  15. package/postcss.config.js +1 -0
  16. package/src/components/BaseAutocomplete/BaseAutocomplete.spec.js +506 -0
  17. package/src/components/BaseAutocomplete/BaseAutocomplete.stories.js +184 -0
  18. package/src/components/BaseAutocomplete/BaseAutocomplete.vue +431 -0
  19. package/src/components/BaseBanner/BaseBanner.spec.js +32 -0
  20. package/src/components/BaseBanner/BaseBanner.stories.js +133 -0
  21. package/src/components/BaseBanner/BaseBanner.vue +229 -0
  22. package/src/components/BaseButton/BaseButton.spec.js +98 -0
  23. package/src/components/BaseButton/BaseButton.stories.js +291 -0
  24. package/src/components/BaseButton/BaseButton.vue +193 -0
  25. package/src/components/BaseButton/__snapshots__/BaseButton.spec.js.snap +37 -0
  26. package/src/components/BaseButton/__snapshots__/BaseButton.spec.js.snap.old +31 -0
  27. package/src/components/BaseCheckbox/BaseCheckbox.stories.js +82 -0
  28. package/src/components/BaseCheckbox/BaseCheckbox.vue +161 -0
  29. package/src/components/BaseDropdownMenu/BaseDropdownMenu.stories.js +84 -0
  30. package/src/components/BaseDropdownMenu/BaseDropdownMenu.vue +251 -0
  31. package/src/components/BaseDropdownMenu/BaseDropdownMenuButton.vue +26 -0
  32. package/src/components/BaseDropdownMenu/BaseDropdownMenuDivider.vue +10 -0
  33. package/src/components/BaseDropdownMenu/BaseDropdownMenuItem.vue +206 -0
  34. package/src/components/BaseDropdownMenu/BaseDropdownMenuSearch.vue +49 -0
  35. package/src/components/BaseDropdownMenu/BaseDropdownMenuTitle.vue +26 -0
  36. package/src/components/BaseModal/BaseModal.stories.js +143 -0
  37. package/src/components/BaseModal/BaseModal.vue +187 -0
  38. package/src/components/BaseModal/BaseModalContent.vue +164 -0
  39. package/src/components/BaseModal/BaseModalContentOld.vue +242 -0
  40. package/src/components/BaseRating/BaseRating.spec.js +46 -0
  41. package/src/components/BaseRating/BaseRating.stories.js +46 -0
  42. package/src/components/BaseRating/BaseRating.vue +119 -0
  43. package/src/components/BaseSelect/BaseSelect.stories.js +177 -0
  44. package/src/components/BaseSelect/BaseSelect.vue +443 -0
  45. package/src/components/BaseSelect/BaseSelectGroup.vue +34 -0
  46. package/src/components/BaseSelect/BaseSelectOption.vue +219 -0
  47. package/src/components/BaseSwitch/BaseSwitch.stories.js +44 -0
  48. package/src/components/BaseSwitch/BaseSwitch.vue +114 -0
  49. package/src/components/BaseTag/BaseTag.stories.js +123 -0
  50. package/src/components/BaseTag/BaseTag.vue +244 -0
  51. package/src/css/body.postcss +29 -0
  52. package/src/css/button.postcss +354 -0
  53. package/src/css/checkbox-radio.postcss +152 -0
  54. package/src/css/icon.postcss +9 -0
  55. package/src/css/loader.postcss +120 -0
  56. package/src/css/popper.postcss +143 -0
  57. package/src/css/tailwind-utilities.postcss +1 -0
  58. package/src/css/tailwind.postcss +2 -0
  59. package/src/css/transitions.postcss +66 -0
  60. package/src/mixins/DynamicAction.js +59 -0
  61. package/src/utils/breakpoints.js +8 -0
  62. package/src/utils/bus.js +3 -0
  63. package/src/utils/clipboard.js +38 -0
  64. package/src/utils/devices.js +134 -0
  65. package/src/utils/elements.js +25 -0
  66. package/src/utils/index.js +8 -0
  67. package/src/utils/responsive.js +61 -0
  68. package/src/utils/router.js +94 -0
  69. package/src/utils/slot.js +27 -0
  70. package/src/utils/stack-view.js +116 -0
  71. package/src/utils/uid.js +5 -0
  72. package/style-postcss.d.ts +3 -0
  73. package/style-postcss.js +2 -0
  74. package/style.js +12 -0
  75. package/tailwind.config.base.cjs +88 -0
  76. package/tailwind.config.js +9 -0
  77. package/tsconfig.check.json +13 -0
  78. package/tsconfig.json +66 -0
  79. package/vite.config.js +43 -0
  80. package/vitest.setup.js +26 -0
@@ -0,0 +1,506 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import BaseAutocomplete from './BaseAutocomplete.vue'
3
+
4
+ const ITEMS = 'Lorem ipsum dolor sit amet consectetur adipiscing elit Integer ne'.split(' ')
5
+ const ITEMS_OBJECT = ITEMS.map(item => {
6
+ return {
7
+ text: item,
8
+ value: `value-${item.toLowerCase()}`,
9
+ }
10
+ })
11
+ const Selectors = {
12
+ Tag: '[data-testid="base-tag"]',
13
+ Input: '[data-testid="base-autocomplete-input"]',
14
+ Item: '[data-testid="base-autocomplete-item"]',
15
+ ItemSelected: '.selected',
16
+ ItemActive: '.active',
17
+ Clear: '[data-testid="base-autocomplete-clear"]',
18
+ CreateItem: '[data-testid="base-autocomplete-create-item"]',
19
+ }
20
+
21
+ const DEFAULT_PROPS = {
22
+ multiple: false,
23
+ autofocus: true,
24
+ }
25
+
26
+ async function createWrapper ({ props, itemsObject, slots } = {}) {
27
+ const finaleProps = {
28
+ ...DEFAULT_PROPS,
29
+ items: itemsObject
30
+ ? ITEMS_OBJECT
31
+ : ITEMS,
32
+ ...(props || null),
33
+ 'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
34
+ }
35
+ const wrapper = mount(BaseAutocomplete, {
36
+ props: finaleProps,
37
+ slots: {
38
+ ...(slots || null),
39
+ },
40
+ })
41
+
42
+ if (finaleProps.autofocus) {
43
+ // Delay to ensure the popper content is present in the DOM
44
+ await new Promise(resolve => setTimeout(resolve, 150))
45
+ }
46
+
47
+ const popperContent = wrapper.getComponent({ name: 'VPopperContent' })
48
+
49
+ return { wrapper, popperContent }
50
+ }
51
+
52
+ describe('BaseAutocomplete', () => {
53
+ describe('Behaviors', () => {
54
+ describe('Common', () => {
55
+ describe('Open / close items popper', () => {
56
+ test('it should open the items popper when focus', async () => {
57
+ const { popperContent } = await createWrapper()
58
+ ITEMS.forEach(item => expect(popperContent.html()).toContain(item))
59
+ })
60
+
61
+ test('it should close the items popper when an item is clicked', async () => {
62
+ const { popperContent } = await createWrapper()
63
+ await popperContent.find(Selectors.Item).trigger('click')
64
+ await new Promise(resolve => setTimeout(resolve, 150))
65
+ expect(popperContent.classes()).toContain('v-popper__popper--hidden')
66
+ })
67
+ })
68
+
69
+ describe('Keyboard navigation', async () => {
70
+ describe('Items', async () => {
71
+ test('it should navigate down the items', async () => {
72
+ const { wrapper, popperContent } = await createWrapper()
73
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
74
+ expect(popperContent.findAll(Selectors.Item)[0].classes()).toContain(Selectors.ItemActive.slice(1))
75
+
76
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
77
+ expect(popperContent.findAll(Selectors.Item)[1].classes()).toContain(Selectors.ItemActive.slice(1))
78
+ })
79
+
80
+ test('it should navigate up the items', async () => {
81
+ const { wrapper, popperContent } = await createWrapper()
82
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
83
+ expect(popperContent.findAll(Selectors.Item)[0].classes()).toContain(Selectors.ItemActive.slice(1))
84
+
85
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
86
+ expect(popperContent.findAll(Selectors.Item)[1].classes()).toContain(Selectors.ItemActive.slice(1))
87
+
88
+ await wrapper.get(Selectors.Input).trigger('keydown.up')
89
+ expect(popperContent.findAll(Selectors.Item)[0].classes()).toContain(Selectors.ItemActive.slice(1))
90
+ })
91
+
92
+ test('it should navigate back to the first item after when the last one is bottomed', async () => {
93
+ const { wrapper, popperContent } = await createWrapper({ props: { items: ITEMS.slice(0, 2) } })
94
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
95
+ expect(popperContent.findAll(Selectors.Item)[0].classes()).toContain(Selectors.ItemActive.slice(1))
96
+
97
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
98
+ expect(popperContent.findAll(Selectors.Item)[1].classes()).toContain(Selectors.ItemActive.slice(1))
99
+
100
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
101
+ expect(popperContent.findAll(Selectors.Item)[0].classes()).toContain(Selectors.ItemActive.slice(1))
102
+ })
103
+
104
+ test('it should navigate back to the last item after the last one', async () => {
105
+ const itemsLength = 5
106
+ const { wrapper, popperContent } = await createWrapper({ props: { items: ITEMS.slice(0, itemsLength) } })
107
+ await wrapper.get(Selectors.Input).trigger('keydown.up')
108
+ expect(popperContent.findAll(Selectors.Item)[itemsLength - 1].classes()).toContain(Selectors.ItemActive.slice(1))
109
+ })
110
+
111
+ test('it should select the item when enters', async () => {
112
+ const { wrapper, popperContent } = await createWrapper({ props: { items: ITEMS.slice(0, 5) } })
113
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
114
+ await wrapper.get(Selectors.Input).trigger('keydown.down')
115
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
116
+ expect(wrapper.get(Selectors.Input).element.value).toBe(ITEMS[1])
117
+ })
118
+ })
119
+ describe('Tags', async () => {
120
+ test('it should navigate left the tags', async () => {
121
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: ITEMS_OBJECT.map(item => item.value) } })
122
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
123
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 1].classes()).toContain('dark-grey')
124
+
125
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
126
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 1].classes()).not.toContain('dark-grey')
127
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 2].classes()).toContain('dark-grey')
128
+ })
129
+
130
+ test('it should navigate right the tags', async () => {
131
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: ITEMS_OBJECT.map(item => item.value) } })
132
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
133
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
134
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 2].classes()).toContain('dark-grey')
135
+
136
+ await wrapper.get(Selectors.Input).trigger('keydown.right')
137
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 2].classes()).not.toContain('dark-grey')
138
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 1].classes()).toContain('dark-grey')
139
+ })
140
+
141
+ test('it should navigate back to the first tag after when the last one is bottomed', async () => {
142
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: ITEMS_OBJECT.map(item => item.value) } })
143
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
144
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 1].classes()).toContain('dark-grey')
145
+
146
+ await wrapper.get(Selectors.Input).trigger('keydown.right')
147
+ expect(wrapper.findAll(Selectors.Tag)[ITEMS_OBJECT.length - 1].classes()).not.toContain('dark-grey')
148
+ expect(wrapper.findAll(Selectors.Tag)[0].classes()).toContain('dark-grey')
149
+ })
150
+
151
+ test('it should navigate back to the last item after the last one', async () => {
152
+ const itemsLength = 2
153
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: ITEMS_OBJECT.slice(0, itemsLength).map(item => item.value) } })
154
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
155
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
156
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
157
+ expect(wrapper.findAll(Selectors.Tag)[itemsLength - 1].classes()).toContain('dark-grey')
158
+ })
159
+
160
+ test('it should remove the selected tag when deletes', async () => {
161
+ const itemsLength = 2
162
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: ITEMS_OBJECT.slice(0, itemsLength).map(item => item.value) } })
163
+ await wrapper.get(Selectors.Input).trigger('keydown.left')
164
+ expect(wrapper.findAll(Selectors.Tag)).toHaveLength(itemsLength)
165
+ expect(wrapper.html()).toContain(ITEMS_OBJECT[itemsLength - 1].text)
166
+
167
+ await wrapper.get(Selectors.Input).trigger('keydown.delete')
168
+ expect(wrapper.findAll(Selectors.Tag)).toHaveLength(itemsLength - 1)
169
+ expect(wrapper.html()).not.toContain(ITEMS_OBJECT[itemsLength - 1].text)
170
+ })
171
+
172
+ test('it should select the last tag when deletes without selected tag', async () => {
173
+ const itemsLength = 2
174
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: ITEMS_OBJECT.slice(0, itemsLength).map(item => item.value) } })
175
+ expect(wrapper.findAll(Selectors.Tag)).toHaveLength(itemsLength)
176
+
177
+ await wrapper.get(Selectors.Input).trigger('keydown.delete')
178
+ expect(wrapper.findAll(Selectors.Tag)).toHaveLength(itemsLength)
179
+ expect(wrapper.findAll(Selectors.Tag).pop().classes()).toContain('dark-grey')
180
+ })
181
+ })
182
+ })
183
+ })
184
+
185
+ describe('Single value behavior', () => {
186
+ describe('Set initial item', () => {
187
+ test('with item string', async () => {
188
+ const modelValue = ITEMS[0]
189
+ const { wrapper } = await createWrapper({ props: { autofocus: false, modelValue } })
190
+ expect(wrapper.get(Selectors.Input).element.value).toBe(modelValue)
191
+ })
192
+
193
+ test('with item object', async () => {
194
+ const modelValue = ITEMS_OBJECT[0]
195
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true, props: { items: ITEMS_OBJECT, modelValue } })
196
+ expect(popperContent.html()).toContain(ITEMS_OBJECT[0].text)
197
+ expect(wrapper.get(Selectors.Input).element.value).toBe(ITEMS_OBJECT[0].text)
198
+ })
199
+ })
200
+
201
+ describe('Select item', () => {
202
+ test('with item string', async () => {
203
+ const { wrapper, popperContent } = await createWrapper()
204
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
205
+ expect(wrapper.get(Selectors.Input).element.value).toBe(ITEMS[1])
206
+ })
207
+
208
+ test('with item object', async () => {
209
+ const modelValue = ITEMS_OBJECT[0].value
210
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true, props: { modelValue } })
211
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
212
+ expect(wrapper.get(Selectors.Input).element.value).toBe(ITEMS_OBJECT[1].text)
213
+ })
214
+ })
215
+
216
+ describe('Unselect item', () => {
217
+ test('with item string', async () => {
218
+ const { wrapper, popperContent } = await createWrapper({ props: { modelValue: ITEMS[1] } })
219
+ expect(wrapper.get(Selectors.Input).element.value).toBe(ITEMS[1])
220
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
221
+ expect(wrapper.get(Selectors.Input).element.value).toBe('')
222
+ })
223
+
224
+ test('with item object', async () => {
225
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true, props: { modelValue: ITEMS_OBJECT[1] } })
226
+ expect(wrapper.get(Selectors.Input).element.value).toBe(ITEMS_OBJECT[1].text)
227
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
228
+ expect(wrapper.get(Selectors.Input).element.value).toBe('')
229
+ })
230
+ })
231
+
232
+ describe('Filter items', () => {
233
+ test('with item string', async () => {
234
+ const { wrapper, popperContent } = await createWrapper()
235
+ await wrapper.get(Selectors.Input).setValue(ITEMS[2])
236
+ expect(popperContent.html()).toContain(ITEMS[2])
237
+ expect(popperContent.html()).not.toContain(ITEMS[1])
238
+ })
239
+
240
+ test('with item object', async () => {
241
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true })
242
+ await wrapper.get(Selectors.Input).setValue(ITEMS_OBJECT[2].text)
243
+ expect(popperContent.html()).toContain(ITEMS_OBJECT[2].text)
244
+ expect(popperContent.html()).not.toContain(ITEMS_OBJECT[1].text)
245
+ })
246
+ })
247
+
248
+ describe('Highlight selected items', () => {
249
+ describe('Highlight if false', () => {
250
+ test('with item string', async () => {
251
+ const modelValue = ITEMS[2]
252
+ const { popperContent } = await createWrapper({ props: { modelValue } })
253
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(1)
254
+ expect(popperContent.findAll(Selectors.Item)[2].text()).toContain(modelValue)
255
+ })
256
+
257
+ test('with item object', async () => {
258
+ const modelValue = ITEMS_OBJECT[2].value
259
+ const { popperContent } = await createWrapper({ itemsObject: true, props: { modelValue } })
260
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(1)
261
+ expect(popperContent.findAll(Selectors.Item)[2].text()).toContain(ITEMS_OBJECT[2].text)
262
+ })
263
+ })
264
+ })
265
+ })
266
+
267
+ describe('Multiple values behavior', () => {
268
+ describe('Set initial item', () => {
269
+ test('with item string', async () => {
270
+ const modelValue = [ITEMS[0]]
271
+ const { wrapper } = await createWrapper({ props: { multiple: true, modelValue, autofocus: false } })
272
+ expect(wrapper.get(Selectors.Input).element.value).toBe('')
273
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(1)
274
+ expect(wrapper.getComponent(Selectors.Tag).html()).toContain(ITEMS[0])
275
+ })
276
+
277
+ test('with item object', async () => {
278
+ const modelValue = [ITEMS_OBJECT[0]]
279
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue, autofocus: false } })
280
+ expect(wrapper.getComponent(Selectors.Tag).html()).toContain(ITEMS_OBJECT[0].text)
281
+ expect(wrapper.get(Selectors.Input).element.value).toBe('')
282
+ expect(wrapper.vm.selection).toStrictEqual(modelValue)
283
+ })
284
+ })
285
+
286
+ describe('Add item', () => {
287
+ test('with item string', async () => {
288
+ const { wrapper, popperContent } = await createWrapper({ props: { multiple: true } })
289
+ await popperContent.findAll(Selectors.Item)[0].trigger('click')
290
+ expect(wrapper.get(Selectors.Input).element.value).toBe('')
291
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(1)
292
+ expect(wrapper.getComponent(Selectors.Tag).html()).toContain(ITEMS[0])
293
+ expect(wrapper.emitted()['update:modelValue'][0][0]).toEqual([ITEMS[0]])
294
+ })
295
+
296
+ test('with item object', async () => {
297
+ const modelValue = [ITEMS_OBJECT[0]]
298
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue } })
299
+ await wrapper.get(Selectors.Input).trigger('focus')
300
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
301
+ expect(wrapper.findAllComponents(Selectors.Tag)[0].html()).toContain(ITEMS_OBJECT[0].text)
302
+ expect(wrapper.findAllComponents(Selectors.Tag)[1].html()).toContain(ITEMS_OBJECT[1].text)
303
+ expect(wrapper.emitted()['update:modelValue'][0][0]).toEqual([ITEMS_OBJECT[0].value, ITEMS_OBJECT[1].value])
304
+ })
305
+ })
306
+
307
+ describe('Unselect item', () => {
308
+ test('with item string', async () => {
309
+ const { wrapper, popperContent } = await createWrapper({ props: { multiple: true, modelValue: [ITEMS[1]] } })
310
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(1)
311
+
312
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
313
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(0)
314
+ })
315
+
316
+ test('with item object', async () => {
317
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue: [ITEMS_OBJECT[1].value] } })
318
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(1)
319
+
320
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
321
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(0)
322
+ })
323
+ })
324
+
325
+ describe('Remove a selected item', () => {
326
+ test('with item string', async () => {
327
+ const modelValue = [ITEMS[0]]
328
+ const { wrapper } = await createWrapper({ props: { multiple: true, modelValue, autofocus: false } })
329
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(1)
330
+ await wrapper.getComponent(Selectors.Tag).get('.icon-delete').trigger('click')
331
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(0)
332
+ expect(wrapper.emitted()['update:modelValue'][0][0]).toEqual([])
333
+ })
334
+
335
+ test('with item object', async () => {
336
+ const modelValue = [ITEMS_OBJECT[0].value, ITEMS_OBJECT[1].value]
337
+ const { wrapper } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue, autofocus: false } })
338
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(2)
339
+ await wrapper.getComponent(Selectors.Tag).get('.icon-delete').trigger('click')
340
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(1)
341
+ expect(wrapper.emitted()['update:modelValue'][0][0]).toEqual([ITEMS_OBJECT[1].value])
342
+ })
343
+ })
344
+
345
+ describe('Filter items', () => {
346
+ test('with item string', async () => {
347
+ const searchText = ITEMS[2]
348
+ const { wrapper, popperContent } = await createWrapper({ props: { multiple: true } })
349
+ await wrapper.get(Selectors.Input).setValue(searchText)
350
+ expect(popperContent.html()).toContain(searchText)
351
+ expect(popperContent.html()).not.toContain(ITEMS[0])
352
+ expect(popperContent.html()).not.toContain(ITEMS[1])
353
+ })
354
+
355
+ test('with item object', async () => {
356
+ const searchText = ITEMS_OBJECT[2].text
357
+ const { wrapper, popperContent } = await createWrapper({ itemsObject: true, props: { multiple: true } })
358
+ await wrapper.get(Selectors.Input).setValue(searchText)
359
+ expect(popperContent.html()).toContain(searchText)
360
+ expect(popperContent.html()).not.toContain(ITEMS_OBJECT[0].text)
361
+ expect(popperContent.html()).not.toContain(ITEMS_OBJECT[1].text)
362
+ })
363
+ })
364
+
365
+ describe('Highlight selected items', () => {
366
+ test('with item string', async () => {
367
+ const modelValue = [ITEMS[2]]
368
+ const { popperContent } = await createWrapper({ props: { multiple: true, modelValue } })
369
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(1)
370
+ expect(popperContent.findAll(Selectors.Item)[2].text()).toContain(ITEMS[2])
371
+ })
372
+
373
+ test('with item object', async () => {
374
+ const modelValue = [ITEMS_OBJECT[2].value]
375
+ const { popperContent } = await createWrapper({ itemsObject: true, props: { multiple: true, modelValue } })
376
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(1)
377
+ expect(popperContent.findAll(Selectors.Item)[2].text()).toContain(ITEMS_OBJECT[2].text)
378
+ })
379
+ })
380
+ })
381
+ })
382
+
383
+ describe('Props', () => {
384
+ describe('no-filter', () => {
385
+ test('it should not apply filtering when searching', async () => {
386
+ const { wrapper, popperContent } = await createWrapper({ props: { noFilter: true } })
387
+ await wrapper.get(Selectors.Input).setValue(ITEMS[2])
388
+ expect(popperContent.html()).toContain(ITEMS[2])
389
+ expect(popperContent.html()).toContain(ITEMS[1])
390
+ })
391
+ })
392
+
393
+ describe('hide-selected', () => {
394
+ describe('with single value', () => {
395
+ test('with item string', async () => {
396
+ const modelValue = ITEMS[2]
397
+ const { popperContent } = await createWrapper({ props: { hideSelected: true, modelValue } })
398
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(0)
399
+ expect(popperContent.findAll(Selectors.Item)[2].text()).not.toContain(modelValue)
400
+ })
401
+
402
+ test('with item object', async () => {
403
+ const modelValue = ITEMS_OBJECT[2].value
404
+ const { popperContent } = await createWrapper({ itemsObject: true, props: { hideSelected: true, modelValue } })
405
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(0)
406
+ expect(popperContent.findAll(Selectors.Item).length).toBe(ITEMS_OBJECT.length - 1)
407
+ })
408
+ })
409
+
410
+ describe('with multiple values', () => {
411
+ test('with item string', async () => {
412
+ const modelValue = [ITEMS[2]]
413
+ const { popperContent } = await createWrapper({ props: { hideSelected: true, multiple: true, modelValue } })
414
+
415
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(0)
416
+ expect(popperContent.findAll(Selectors.Item)[2].text()).not.toContain(ITEMS[2])
417
+ })
418
+
419
+ test('with item object', async () => {
420
+ const modelValue = [ITEMS_OBJECT[2].value]
421
+ const { popperContent } = await createWrapper({ itemsObject: true, props: { hideSelected: true, multiple: true, modelValue } })
422
+ expect(popperContent.findAll(`${Selectors.Item}${Selectors.ItemSelected}`).length).toBe(0)
423
+ expect(popperContent.findAll(Selectors.Item).length).toBe(ITEMS_OBJECT.length - 1)
424
+ })
425
+ })
426
+ })
427
+
428
+ describe('clearable', () => {
429
+ test('it should display a clear button', async () => {
430
+ const { wrapper, popperContent } = await createWrapper({ props: { clearable: true } })
431
+ expect(wrapper.find(Selectors.Clear).exists()).toBeFalsy()
432
+
433
+ await popperContent.find(Selectors.Item).trigger('click')
434
+ expect(wrapper.find(Selectors.Clear).exists()).toBeTruthy()
435
+ })
436
+
437
+ test('it should clear the input when the clear button is clicked', async () => {
438
+ const { wrapper } = await createWrapper({ props: { clearable: true, modelValue: ITEMS[0] } })
439
+ expect(wrapper.find(Selectors.Input).element.value).toBe(ITEMS[0])
440
+
441
+ await wrapper.find(Selectors.Clear).trigger('click')
442
+ expect(wrapper.find(Selectors.Input).element.value).toBe('')
443
+ })
444
+
445
+ test('it should clear the tags when the clear button is clicked', async () => {
446
+ const { wrapper } = await createWrapper({ props: { clearable: true, modelValue: ITEMS, multiple: true } })
447
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(ITEMS.length)
448
+
449
+ await wrapper.find(Selectors.Clear).trigger('click')
450
+ expect(wrapper.findAll(Selectors.Tag).length).toBe(0)
451
+ })
452
+ })
453
+
454
+ describe('creatable', () => {
455
+ test('it should display a button to add a item', async () => {
456
+ const search = 'added item'
457
+ const { wrapper, popperContent } = await createWrapper({ props: { multiple: true, creatable: true } })
458
+ expect(wrapper.find(Selectors.Clear).exists()).toBeFalsy()
459
+ expect(popperContent.find(Selectors.CreateItem).exists()).toBeFalsy()
460
+
461
+ await wrapper.get(Selectors.Input).setValue(search)
462
+ expect(popperContent.find(Selectors.CreateItem).exists()).toBeTruthy()
463
+ })
464
+
465
+ test('it should create the item from the search input', async () => {
466
+ const search = 'added item'
467
+ const { wrapper, popperContent } = await createWrapper({ props: { multiple: true, creatable: true } })
468
+
469
+ await wrapper.get(Selectors.Input).setValue(search)
470
+ await popperContent.find(Selectors.CreateItem).trigger('click')
471
+ expect(wrapper.findAll(Selectors.Tag).pop().html()).toContain(search)
472
+ })
473
+
474
+ test('it should not display the button if the search text is already in the items list', async () => {
475
+ const search = 'added item'
476
+ const modelValue = ITEMS
477
+ const { wrapper, popperContent } = await createWrapper({ props: { multiple: true, creatable: true, modelValue } })
478
+
479
+ await wrapper.get(Selectors.Input).setValue(search)
480
+ expect(popperContent.find(Selectors.CreateItem).exists()).toBeTruthy()
481
+
482
+ await wrapper.get(Selectors.Input).setValue(ITEMS[0])
483
+ expect(popperContent.find(Selectors.CreateItem).exists()).toBeFalsy()
484
+ })
485
+ })
486
+ })
487
+
488
+ describe('Events', () => {
489
+ test('update:search', async () => {
490
+ const modelValue = ITEMS[0]
491
+ const search = ITEMS[1]
492
+ const { wrapper, popperContent } = await createWrapper({ props: { modelValue } })
493
+ await popperContent.findAll(Selectors.Item)[1].trigger('click')
494
+
495
+ expect(wrapper.emitted()['update:search']).toBeUndefined()
496
+
497
+ await wrapper.get(Selectors.Input).setValue(search.substring(0, 1))
498
+ expect(wrapper.emitted()['update:search']).toHaveLength(1)
499
+ expect(wrapper.emitted()['update:search'][0]).toEqual([search.substring(0, 1)])
500
+
501
+ await wrapper.get(Selectors.Input).setValue(search.substring(0, 2))
502
+ expect(wrapper.emitted()['update:search']).toHaveLength(2)
503
+ expect(wrapper.emitted()['update:search'][1]).toEqual([search.substring(0, 2)])
504
+ })
505
+ })
506
+ })
@@ -0,0 +1,184 @@
1
+ import BaseAutocomplete from './BaseAutocomplete.vue'
2
+
3
+ const ITEMS = 'Lorem ipsum dolor sit amet consectetur adipiscing elit Integer ne'.split(' ')
4
+ const ITEMS_OBJECT = ITEMS.map(item => {
5
+ return {
6
+ text: item,
7
+ value: `value-${item.toLowerCase()}`,
8
+ avatar: item.substring(0, 2).toLowerCase(),
9
+ }
10
+ })
11
+
12
+ export default {
13
+ title: 'common/vue/components/BaseAutocomplete',
14
+ component: BaseAutocomplete,
15
+ argTypes: {
16
+ size: {
17
+ control: {
18
+ type: 'select',
19
+ },
20
+ options: [
21
+ 'small',
22
+ 'medium',
23
+ 'large',
24
+ ],
25
+ },
26
+ },
27
+ args: {
28
+ items: ITEMS,
29
+ },
30
+ }
31
+ const DefaultTemplate = ({ slots, ...args }) => {
32
+ return {
33
+ setup: () => ({ args }),
34
+ template: `
35
+ <BaseAutocomplete v-bind="args">${slots?.join(' ') || ''}</BaseAutocomplete>
36
+ `,
37
+ }
38
+ }
39
+
40
+ // default
41
+ export const Default = DefaultTemplate.bind({})
42
+
43
+ // props.placeholder
44
+ export const Placeholder = DefaultTemplate.bind()
45
+ Placeholder.args = {
46
+ placeholder: 'Search',
47
+ }
48
+
49
+ // props.size
50
+ export const Small = DefaultTemplate.bind({})
51
+ Small.args = {
52
+ size: 'small',
53
+ }
54
+ export const Medium = DefaultTemplate.bind({})
55
+ Medium.args = {
56
+ size: 'medium',
57
+ }
58
+ export const Large = DefaultTemplate.bind({})
59
+ Large.args = {
60
+ size: 'large',
61
+ }
62
+
63
+ // props.error
64
+ export const Error = DefaultTemplate.bind({})
65
+ Error.args = {
66
+ modelValue: ITEMS[1],
67
+ error: true,
68
+ }
69
+
70
+ // props.preprendIcon
71
+ export const PrependIcon = DefaultTemplate.bind({})
72
+ PrependIcon.args = {
73
+ prependIcon: 'i-ls-user',
74
+ }
75
+
76
+ // props.multiple
77
+ export const SingleValue = DefaultTemplate.bind({})
78
+ SingleValue.args = {
79
+ modelValue: ITEMS[1],
80
+ }
81
+
82
+ export const SingleValueObject = DefaultTemplate.bind({})
83
+ SingleValueObject.args = {
84
+ modelValue: ITEMS_OBJECT[1],
85
+ items: ITEMS_OBJECT,
86
+ }
87
+
88
+ export const MultipleValue = DefaultTemplate.bind({})
89
+ MultipleValue.args = {
90
+ modelValue: ITEMS.slice(0, 4),
91
+ multiple: true,
92
+ }
93
+
94
+ export const MultipleSmall = DefaultTemplate.bind({})
95
+ MultipleSmall.args = {
96
+ modelValue: ITEMS,
97
+ multiple: true,
98
+ size: 'small',
99
+ }
100
+
101
+ export const MultipleMedium = DefaultTemplate.bind({})
102
+ MultipleMedium.args = {
103
+ modelValue: [ITEMS_OBJECT[0].value],
104
+ multiple: true,
105
+ items: ITEMS_OBJECT,
106
+ }
107
+
108
+ export const MultipleLarge = DefaultTemplate.bind({})
109
+ MultipleLarge.args = {
110
+ modelValue: ITEMS,
111
+ multiple: true,
112
+ size: 'large',
113
+ }
114
+
115
+ // props.noFilter
116
+ export const NoFilter = DefaultTemplate.bind({})
117
+ NoFilter.args = {
118
+ modelValue: ITEMS[1],
119
+ noFilter: true,
120
+ }
121
+
122
+ // props.clearable
123
+ export const Clearable = DefaultTemplate.bind({})
124
+ Clearable.args = {
125
+ modelValue: ITEMS,
126
+ multiple: true,
127
+ clearable: true,
128
+ }
129
+
130
+ // props.hideSelected
131
+ export const HideSelected = DefaultTemplate.bind({})
132
+ HideSelected.args = {
133
+ modelValue: ITEMS.slice(0, ITEMS.length - 2),
134
+ multiple: true,
135
+ hideSelected: true,
136
+ }
137
+
138
+ // props.autofocus
139
+ export const Autofocus = DefaultTemplate.bind({})
140
+ Autofocus.args = {
141
+ modelValue: ITEMS.slice(0, ITEMS.length - 2),
142
+ multiple: true,
143
+ autofocus: true,
144
+ }
145
+
146
+ // props.hideNoData
147
+ export const HideNoData = DefaultTemplate.bind({})
148
+ HideNoData.args = {
149
+ modelValue: ITEMS.slice(0, ITEMS.length - 2),
150
+ multiple: true,
151
+ hideSelected: true,
152
+ hideNoData: true,
153
+ }
154
+
155
+ // props.noDataText
156
+ export const NoDataText = DefaultTemplate.bind({})
157
+ NoDataText.args = {
158
+ modelValue: ITEMS,
159
+ multiple: true,
160
+ hideSelected: true,
161
+ noDataText: 'No more items available',
162
+ autoFocus: true,
163
+ }
164
+ // props.creatable
165
+ export const Creatable = DefaultTemplate.bind({})
166
+ Creatable.args = {
167
+ modelValue: [ITEMS[1]],
168
+ hideSelected: true,
169
+ multiple: true,
170
+ autoFocus: true,
171
+ creatable: true,
172
+ }
173
+
174
+ // slot.tag
175
+ export const SlotTag = DefaultTemplate.bind({})
176
+ SlotTag.args = {
177
+ items: ITEMS_OBJECT,
178
+ modelValue: ITEMS_OBJECT.slice(0, 3).map(item => item.value),
179
+ hideSelected: true,
180
+ multiple: true,
181
+ slots: [
182
+ '<template #tag="{item, index}"><span class="inline-flex text-11-regular justify-center w-4 h-4 rounded-full bg-red-400 text-white">{{ item.avatar }}</span> {{ item.text }}</template>',
183
+ ],
184
+ }