@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.
- package/.env +1 -0
- package/.eslintignore +4 -0
- package/.eslintrc.js +47 -0
- package/.pkg +11 -0
- package/babel.config.js +5 -0
- package/build/Dockerfile +31 -0
- package/ci/ci.yml +54 -0
- package/components-vite.js +29 -0
- package/components-webpack.js +29 -0
- package/components.d.ts +4 -0
- package/index.d.ts +3 -0
- package/index.js +24 -0
- package/package.json +1 -4
- package/plugins.js +17 -0
- package/postcss.config.js +1 -0
- package/src/components/BaseAutocomplete/BaseAutocomplete.spec.js +506 -0
- package/src/components/BaseAutocomplete/BaseAutocomplete.stories.js +184 -0
- package/src/components/BaseAutocomplete/BaseAutocomplete.vue +431 -0
- package/src/components/BaseBanner/BaseBanner.spec.js +32 -0
- package/src/components/BaseBanner/BaseBanner.stories.js +133 -0
- package/src/components/BaseBanner/BaseBanner.vue +229 -0
- package/src/components/BaseButton/BaseButton.spec.js +98 -0
- package/src/components/BaseButton/BaseButton.stories.js +291 -0
- package/src/components/BaseButton/BaseButton.vue +193 -0
- package/src/components/BaseButton/__snapshots__/BaseButton.spec.js.snap +37 -0
- package/src/components/BaseButton/__snapshots__/BaseButton.spec.js.snap.old +31 -0
- package/src/components/BaseCheckbox/BaseCheckbox.stories.js +82 -0
- package/src/components/BaseCheckbox/BaseCheckbox.vue +161 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenu.stories.js +84 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenu.vue +251 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuButton.vue +26 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuDivider.vue +10 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuItem.vue +206 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuSearch.vue +49 -0
- package/src/components/BaseDropdownMenu/BaseDropdownMenuTitle.vue +26 -0
- package/src/components/BaseModal/BaseModal.stories.js +143 -0
- package/src/components/BaseModal/BaseModal.vue +187 -0
- package/src/components/BaseModal/BaseModalContent.vue +164 -0
- package/src/components/BaseModal/BaseModalContentOld.vue +242 -0
- package/src/components/BaseRating/BaseRating.spec.js +46 -0
- package/src/components/BaseRating/BaseRating.stories.js +46 -0
- package/src/components/BaseRating/BaseRating.vue +119 -0
- package/src/components/BaseSelect/BaseSelect.stories.js +177 -0
- package/src/components/BaseSelect/BaseSelect.vue +443 -0
- package/src/components/BaseSelect/BaseSelectGroup.vue +34 -0
- package/src/components/BaseSelect/BaseSelectOption.vue +219 -0
- package/src/components/BaseSwitch/BaseSwitch.stories.js +44 -0
- package/src/components/BaseSwitch/BaseSwitch.vue +114 -0
- package/src/components/BaseTag/BaseTag.stories.js +123 -0
- package/src/components/BaseTag/BaseTag.vue +244 -0
- package/src/css/body.postcss +29 -0
- package/src/css/button.postcss +354 -0
- package/src/css/checkbox-radio.postcss +152 -0
- package/src/css/icon.postcss +9 -0
- package/src/css/loader.postcss +120 -0
- package/src/css/popper.postcss +143 -0
- package/src/css/tailwind-utilities.postcss +1 -0
- package/src/css/tailwind.postcss +2 -0
- package/src/css/transitions.postcss +66 -0
- package/src/mixins/DynamicAction.js +59 -0
- package/src/utils/breakpoints.js +8 -0
- package/src/utils/bus.js +3 -0
- package/src/utils/clipboard.js +38 -0
- package/src/utils/devices.js +134 -0
- package/src/utils/elements.js +25 -0
- package/src/utils/index.js +8 -0
- package/src/utils/responsive.js +61 -0
- package/src/utils/router.js +94 -0
- package/src/utils/slot.js +27 -0
- package/src/utils/stack-view.js +116 -0
- package/src/utils/uid.js +5 -0
- package/style-postcss.d.ts +3 -0
- package/style-postcss.js +2 -0
- package/style.js +12 -0
- package/tailwind.config.base.cjs +88 -0
- package/tailwind.config.js +9 -0
- package/tsconfig.check.json +13 -0
- package/tsconfig.json +66 -0
- package/vite.config.js +43 -0
- 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
|
+
}
|