niuma-ui 1.0.1 → 1.1.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/CHANGELOG.md +65 -1
- package/package.json +10 -2
- package/src/__tests__/RsButton.spec.ts +47 -0
- package/src/__tests__/RsCard.spec.ts +160 -1
- package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
- package/src/__tests__/RsContextMenu.spec.ts +12 -0
- package/src/__tests__/RsDatePicker.spec.ts +21 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
- package/src/__tests__/RsDescriptions.spec.ts +50 -0
- package/src/__tests__/RsDialog.spec.ts +178 -0
- package/src/__tests__/RsDrawer.spec.ts +96 -3
- package/src/__tests__/RsDynamicTags.spec.ts +52 -0
- package/src/__tests__/RsForm.spec.ts +250 -6
- package/src/__tests__/RsFormItem.spec.ts +478 -0
- package/src/__tests__/RsFormList.spec.ts +109 -0
- package/src/__tests__/RsInput.spec.ts +108 -1
- package/src/__tests__/RsInputNumber.spec.ts +33 -0
- package/src/__tests__/RsMenu.spec.ts +406 -14
- package/src/__tests__/RsPagination.spec.ts +67 -2
- package/src/__tests__/RsSelect.spec.ts +465 -2
- package/src/__tests__/RsSwitch.spec.ts +74 -0
- package/src/__tests__/RsTable.a11y.spec.ts +83 -0
- package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
- package/src/__tests__/RsTable.perf.spec.ts +23 -4
- package/src/__tests__/RsTable.spec.ts +63 -8
- package/src/__tests__/RsTable.ssr.spec.ts +71 -0
- package/src/__tests__/RsTable.summary.spec.ts +45 -0
- package/src/__tests__/RsTabs.spec.ts +102 -4
- package/src/__tests__/RsTimePicker.spec.ts +20 -0
- package/src/__tests__/RsTooltip.spec.ts +43 -0
- package/src/__tests__/RsUpload.spec.ts +62 -6
- package/src/__tests__/createRsDialog.spec.ts +145 -0
- package/src/__tests__/css-token.spec.ts +51 -0
- package/src/__tests__/date-picker-utils.spec.ts +49 -0
- package/src/__tests__/dialog-utils.spec.ts +43 -0
- package/src/__tests__/form-path.spec.ts +46 -0
- package/src/__tests__/form-rules.spec.ts +139 -0
- package/src/__tests__/perf-budgets.ts +32 -0
- package/src/__tests__/rs-table-api.spec.ts +206 -0
- package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
- package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
- package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
- package/src/__tests__/rs-table-isolation.spec.ts +158 -0
- package/src/__tests__/split-pane-utils.spec.ts +21 -0
- package/src/__tests__/table-chart-utils.spec.ts +88 -0
- package/src/__tests__/table-summary-utils.spec.ts +62 -0
- package/src/__tests__/table-utils.spec.ts +124 -0
- package/src/__tests__/theme.spec.ts +27 -0
- package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
- package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
- package/src/components/RsAlert.vue +212 -0
- package/src/components/RsAvatar.vue +1 -1
- package/src/components/RsBadge.vue +1 -1
- package/src/components/RsBreadcrumb.vue +1 -1
- package/src/components/RsButton.vue +147 -23
- package/src/components/RsCalendarGrid.vue +3 -3
- package/src/components/RsCard.vue +204 -20
- package/src/components/RsCodeBlock.vue +4 -4
- package/src/components/RsCodeEditor.vue +8 -8
- package/src/components/RsConfirmDialog.vue +217 -30
- package/src/components/RsContextMenu.vue +57 -17
- package/src/components/RsDatePicker.vue +316 -39
- package/src/components/RsDateTimePicker.vue +27 -2
- package/src/components/RsDescriptions.vue +186 -0
- package/src/components/RsDescriptionsItem.vue +32 -0
- package/src/components/RsDialog.vue +374 -52
- package/src/components/RsDivider.vue +89 -0
- package/src/components/RsDrawer.vue +559 -34
- package/src/components/RsDropdown.vue +34 -3
- package/src/components/RsDynamicTags.vue +531 -0
- package/src/components/RsEmpty.vue +1 -1
- package/src/components/RsForm.vue +164 -8
- package/src/components/RsFormItem.vue +423 -0
- package/src/components/RsFormItemControl.vue +21 -0
- package/src/components/RsFormList.vue +117 -0
- package/src/components/RsInput.vue +491 -100
- package/src/components/RsInputNumber.vue +77 -27
- package/src/components/RsLabel.vue +20 -5
- package/src/components/RsLink.vue +1 -1
- package/src/components/RsLoadingBar.vue +151 -0
- package/src/components/RsMarkdown.vue +5 -5
- package/src/components/RsMenu.vue +74 -16
- package/src/components/RsMenuItems.vue +147 -9
- package/src/components/RsMonacoEditor.vue +9 -3
- package/src/components/RsPagination.vue +93 -14
- package/src/components/RsProseEditor.vue +3 -3
- package/src/components/RsRadio.vue +81 -0
- package/src/components/RsRadioItem.vue +156 -0
- package/src/components/RsSelect.vue +372 -642
- package/src/components/RsSidebarGroup.vue +1 -1
- package/src/components/RsSplitPane.vue +90 -1
- package/src/components/RsStatCard.vue +2 -2
- package/src/components/RsSteps.vue +2 -2
- package/src/components/RsSwitch.vue +192 -0
- package/src/components/RsTable.vue +653 -2555
- package/src/components/RsTabs.vue +422 -90
- package/src/components/RsTag.vue +191 -0
- package/src/components/RsTerminal.vue +58 -19
- package/src/components/RsTimePicker.vue +72 -9
- package/src/components/RsTimePickerColumns.vue +137 -78
- package/src/components/RsToaster.vue +64 -24
- package/src/components/RsTooltip.vue +10 -1
- package/src/components/RsTooltipProvider.vue +11 -1
- package/src/components/RsTree.vue +45 -27
- package/src/components/RsUpload.vue +147 -14
- package/src/components/RsVNodeHost.vue +20 -0
- package/src/components/button-utils.ts +39 -0
- package/src/components/context-menu-utils.ts +6 -0
- package/src/components/date-picker-utils.ts +195 -4
- package/src/components/descriptions-utils.ts +24 -0
- package/src/components/dialog-utils.ts +182 -0
- package/src/components/dialog-window.ts +74 -9
- package/src/components/drawer-utils.ts +73 -0
- package/src/components/form-item-control.ts +55 -0
- package/src/components/form-path.ts +92 -0
- package/src/components/form-rules.ts +311 -0
- package/src/components/form-utils.ts +145 -3
- package/src/components/menu-utils.ts +11 -0
- package/src/components/overlay-utils.ts +3 -0
- package/src/components/radio-utils.ts +12 -0
- package/src/components/reka.ts +5 -0
- package/src/components/select-utils.ts +285 -6
- package/src/components/split-pane-utils.ts +65 -12
- package/src/components/table/RsTableBody.vue +217 -0
- package/src/components/table/RsTableBodyRow.vue +90 -1
- package/src/components/table/RsTableColGroup.vue +41 -0
- package/src/components/table/RsTableHeader.vue +158 -0
- package/src/components/table/RsTableHeaderFilter.vue +1 -1
- package/src/components/table/RsTableSummaryRow.vue +52 -0
- package/src/components/table/rs-table-api.ts +99 -0
- package/src/components/table/rs-table-compat-matrix.ts +71 -0
- package/src/components/table/rs-table-echarts-adapter.ts +81 -0
- package/src/components/table/rs-table-feature-host.ts +178 -0
- package/src/components/table/rs-table-grid-nav.ts +122 -0
- package/src/components/table/rs-table-header-types.ts +47 -0
- package/src/components/table/rs-table-module-registry.ts +72 -0
- package/src/components/table/rs-table-props.ts +274 -0
- package/src/components/table/rs-table-view-context.ts +258 -0
- package/src/components/table/rs-table.css +85 -7
- package/src/components/table/table-chart-utils.ts +165 -0
- package/src/components/table/table-features.ts +284 -0
- package/src/components/table/table-summary-utils.ts +194 -0
- package/src/components/table-utils.ts +452 -2
- package/src/components/tabs-utils.ts +142 -2
- package/src/components/time-picker-utils.ts +76 -3
- package/src/components/upload-utils.ts +122 -0
- package/src/components/use-rs-select.ts +436 -0
- package/src/components/virtual-list-utils.ts +15 -2
- package/src/composables/assembleRsTableApi.ts +79 -0
- package/src/composables/bindRsTableViewContext.ts +269 -0
- package/src/composables/createRsDialog.ts +357 -0
- package/src/composables/createRsTableEditEmitBridge.ts +38 -0
- package/src/composables/useRsConfig.ts +10 -3
- package/src/composables/useRsI18n.ts +16 -5
- package/src/composables/useRsLoadingBar.ts +36 -0
- package/src/composables/useRsTable.ts +134 -0
- package/src/composables/useRsTableA11y.ts +52 -0
- package/src/composables/useRsTableChartBridge.ts +79 -0
- package/src/composables/useRsTableColumnLayout.ts +320 -0
- package/src/composables/useRsTableColumnResize.ts +244 -0
- package/src/composables/useRsTableColumnVirtual.ts +180 -0
- package/src/composables/useRsTableColumns.ts +200 -0
- package/src/composables/useRsTableContextMenu.ts +257 -0
- package/src/composables/useRsTableCore.ts +256 -0
- package/src/composables/useRsTableEditActions.ts +606 -0
- package/src/composables/useRsTableEditLayer.ts +251 -0
- package/src/composables/useRsTableEngine.ts +357 -0
- package/src/composables/useRsTableGridKeyboard.ts +123 -0
- package/src/composables/useRsTableHeadless.ts +137 -0
- package/src/composables/useRsTableInteraction.ts +433 -0
- package/src/composables/useRsTableScrollHost.ts +75 -0
- package/src/composables/useRsTableScrollLayout.ts +255 -0
- package/src/composables/useRsTableSelectionSource.ts +89 -0
- package/src/composables/useRsTableShell.ts +416 -0
- package/src/composables/useRsTableShellChrome.ts +170 -0
- package/src/composables/useRsTableSummary.ts +113 -0
- package/src/composables/useRsTableViewProvide.ts +263 -0
- package/src/composables/useRsTableVirtual.ts +154 -0
- package/src/composables/useRsTabsNav.ts +4 -0
- package/src/composables/useTableEdit.ts +1 -1
- package/src/index.ts +378 -10
- package/src/locale/interpolate.ts +32 -0
- package/src/locale/messages.ts +40 -0
- package/src/styles.css +365 -56
- package/src/theme/brand.example.css +24 -2
- package/src/theme/css-token.ts +107 -0
- package/src/theme/presets.ts +4 -0
- package/src/theme/types.ts +68 -0
- package/src/components/RsTableColgroup.vue +0 -17
- package/src/components/RsTableHeaderRow.vue +0 -30
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
3
|
import { h } from 'vue'
|
|
4
4
|
import RsConfigProvider from '../components/RsConfigProvider.vue'
|
|
5
5
|
import RsInput from '../components/RsInput.vue'
|
|
@@ -35,6 +35,16 @@ describe('RsInput', () => {
|
|
|
35
35
|
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['world'])
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
+
it('defaults autocomplete to off (new-password for password)', () => {
|
|
39
|
+
const text = mount(RsInput, { props: { modelValue: '' } })
|
|
40
|
+
expect(text.find('input').attributes('autocomplete')).toBe('off')
|
|
41
|
+
text.unmount()
|
|
42
|
+
|
|
43
|
+
const password = mount(RsInput, { props: { modelValue: '', type: 'password' } })
|
|
44
|
+
expect(password.find('input').attributes('autocomplete')).toBe('new-password')
|
|
45
|
+
password.unmount()
|
|
46
|
+
})
|
|
47
|
+
|
|
38
48
|
it('marks invalid state on group', () => {
|
|
39
49
|
const wrapper = mount(RsInput, { props: { invalid: true } })
|
|
40
50
|
expect(wrapper.find('.rs-input-group').classes()).toContain('rs-input-group--invalid')
|
|
@@ -154,6 +164,7 @@ describe('RsInput', () => {
|
|
|
154
164
|
props: { modelValue: 'bad-email', rule: 'email', id: 'e1' },
|
|
155
165
|
})
|
|
156
166
|
await wrapper.find('input').trigger('blur')
|
|
167
|
+
await flushPromises()
|
|
157
168
|
expect(wrapper.find('.rs-input-group').classes()).toContain('rs-input-group--invalid')
|
|
158
169
|
expect(wrapper.find('.rs-input-field__error').text()).toContain('邮箱')
|
|
159
170
|
expect(wrapper.emitted('validate')?.[0]?.[0]).toEqual({
|
|
@@ -167,6 +178,7 @@ describe('RsInput', () => {
|
|
|
167
178
|
props: { modelValue: 'user@example.com', rule: 'email' },
|
|
168
179
|
})
|
|
169
180
|
await wrapper.find('input').trigger('blur')
|
|
181
|
+
await flushPromises()
|
|
170
182
|
expect(wrapper.find('.rs-input-group').classes()).not.toContain('rs-input-group--invalid')
|
|
171
183
|
expect(wrapper.emitted('validate')?.[0]?.[0]).toEqual({ valid: true })
|
|
172
184
|
})
|
|
@@ -179,6 +191,7 @@ describe('RsInput', () => {
|
|
|
179
191
|
},
|
|
180
192
|
})
|
|
181
193
|
await wrapper.find('input').trigger('blur')
|
|
194
|
+
await flushPromises()
|
|
182
195
|
expect(wrapper.find('.rs-input-field__error').text()).toBe('至少 3 个字符')
|
|
183
196
|
})
|
|
184
197
|
|
|
@@ -196,6 +209,7 @@ describe('RsInput', () => {
|
|
|
196
209
|
},
|
|
197
210
|
})
|
|
198
211
|
await wrapper.find('input').trigger('blur')
|
|
212
|
+
await flushPromises()
|
|
199
213
|
expect(wrapper.find('.rs-input-field__error').text()).toContain('email')
|
|
200
214
|
})
|
|
201
215
|
|
|
@@ -266,4 +280,97 @@ describe('RsInput', () => {
|
|
|
266
280
|
})
|
|
267
281
|
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('256')
|
|
268
282
|
})
|
|
283
|
+
|
|
284
|
+
it('places clear before custom suffix inside the affix', () => {
|
|
285
|
+
const wrapper = mount(RsInput, {
|
|
286
|
+
props: { modelValue: 'hub', clearable: true },
|
|
287
|
+
slots: { suffix: () => '选' },
|
|
288
|
+
})
|
|
289
|
+
const affix = wrapper.find('.rs-input-group__affix--suffix')
|
|
290
|
+
const children = [...affix.element.children] as HTMLElement[]
|
|
291
|
+
expect(children.map((el) => el.className)).toEqual([
|
|
292
|
+
'rs-input-group__action',
|
|
293
|
+
'rs-input-group__custom-suffix',
|
|
294
|
+
])
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('renders addonAfterIcon as an owned addon button and emits addonAfterClick', async () => {
|
|
298
|
+
const wrapper = mount(RsInput, {
|
|
299
|
+
props: {
|
|
300
|
+
modelValue: 'hub',
|
|
301
|
+
clearable: true,
|
|
302
|
+
addonAfterIcon: 'ellipsis',
|
|
303
|
+
addonAfterIconLabel: '选择',
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
expect(wrapper.find('.rs-input-shell--combined').exists()).toBe(true)
|
|
307
|
+
expect(wrapper.find('.rs-input-addon--icon').exists()).toBe(true)
|
|
308
|
+
const button = wrapper.find('button.rs-input-addon__button')
|
|
309
|
+
expect(button.attributes('aria-label')).toBe('选择')
|
|
310
|
+
await button.trigger('click')
|
|
311
|
+
expect(wrapper.emitted('addonAfterClick')).toHaveLength(1)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('prefers addonAfter content over addonAfterIcon', () => {
|
|
315
|
+
const wrapper = mount(RsInput, {
|
|
316
|
+
props: {
|
|
317
|
+
modelValue: '',
|
|
318
|
+
addonAfter: '.com',
|
|
319
|
+
addonAfterIcon: 'ellipsis',
|
|
320
|
+
},
|
|
321
|
+
})
|
|
322
|
+
expect(wrapper.find('.rs-input-addon--after').text()).toBe('.com')
|
|
323
|
+
expect(wrapper.find('button.rs-input-addon__button').exists()).toBe(false)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('renders addonBefore and addonAfter as a combined shell', () => {
|
|
327
|
+
const wrapper = mount(RsInput, {
|
|
328
|
+
props: { modelValue: '', addonBefore: 'https://', addonAfter: '.com' },
|
|
329
|
+
})
|
|
330
|
+
expect(wrapper.find('.rs-input-shell--combined').exists()).toBe(true)
|
|
331
|
+
expect(wrapper.find('.rs-input-addon--before').text()).toBe('https://')
|
|
332
|
+
expect(wrapper.find('.rs-input-addon--after').text()).toBe('.com')
|
|
333
|
+
expect(wrapper.find('.rs-input-group--combined-before').exists()).toBe(true)
|
|
334
|
+
expect(wrapper.find('.rs-input-group--combined-after').exists()).toBe(true)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('renders addonAfter slot as a combined shell', () => {
|
|
338
|
+
const wrapper = mount(RsInput, {
|
|
339
|
+
props: { modelValue: '' },
|
|
340
|
+
slots: { addonAfter: () => h('span', { class: 'slot-addon' }, '选') },
|
|
341
|
+
})
|
|
342
|
+
expect(wrapper.find('.rs-input-addon--after .slot-addon').text()).toBe('选')
|
|
343
|
+
expect(wrapper.find('.rs-input-group--combined-after').exists()).toBe(true)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('does not emit pressEnter while IME is composing', async () => {
|
|
347
|
+
const wrapper = mount(RsInput, { props: { modelValue: '' } })
|
|
348
|
+
const input = wrapper.find('input')
|
|
349
|
+
await input.trigger('compositionstart')
|
|
350
|
+
await input.trigger('keydown', { key: 'Enter' })
|
|
351
|
+
expect(wrapper.emitted('pressEnter')).toBeFalsy()
|
|
352
|
+
await input.trigger('compositionend')
|
|
353
|
+
await input.trigger('keydown', { key: 'Enter' })
|
|
354
|
+
expect(wrapper.emitted('pressEnter')).toHaveLength(1)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('does not emit validate while IME is composing', async () => {
|
|
358
|
+
const wrapper = mount(RsInput, {
|
|
359
|
+
props: {
|
|
360
|
+
modelValue: '',
|
|
361
|
+
required: true,
|
|
362
|
+
validateTrigger: 'input',
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
const input = wrapper.find('input')
|
|
366
|
+
await input.trigger('blur')
|
|
367
|
+
await input.trigger('compositionstart')
|
|
368
|
+
const before = wrapper.emitted('validate')?.length ?? 0
|
|
369
|
+
await input.trigger('input')
|
|
370
|
+
await flushPromises()
|
|
371
|
+
expect(wrapper.emitted('validate')?.length ?? 0).toBe(before)
|
|
372
|
+
await input.trigger('compositionend')
|
|
373
|
+
await flushPromises()
|
|
374
|
+
expect((wrapper.emitted('validate')?.length ?? 0)).toBeGreaterThan(before)
|
|
375
|
+
})
|
|
269
376
|
})
|
|
@@ -108,6 +108,39 @@ describe('RsInputNumber', () => {
|
|
|
108
108
|
expect(state.value).toBe(5)
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
+
it('ignores wheel when changeOnWheel is off', async () => {
|
|
112
|
+
const { wrapper, state } = mountNumber({ modelValue: 5, step: 1 })
|
|
113
|
+
const input = wrapper.find('input')
|
|
114
|
+
await input.trigger('focus')
|
|
115
|
+
await input.trigger('wheel', { deltaY: -100 })
|
|
116
|
+
expect(state.value).toBe(5)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('steps on wheel when changeOnWheel is on and focused', async () => {
|
|
120
|
+
const { wrapper, state } = mountNumber({
|
|
121
|
+
modelValue: 5,
|
|
122
|
+
step: 1,
|
|
123
|
+
changeOnWheel: true,
|
|
124
|
+
})
|
|
125
|
+
const input = wrapper.find('input')
|
|
126
|
+
await input.trigger('focus')
|
|
127
|
+
await input.trigger('wheel', { deltaY: -100 })
|
|
128
|
+
expect(state.value).toBe(6)
|
|
129
|
+
await input.trigger('wheel', { deltaY: 100 })
|
|
130
|
+
expect(state.value).toBe(5)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('ignores wheel when changeOnWheel is on but not focused', async () => {
|
|
134
|
+
const { wrapper, state } = mountNumber({
|
|
135
|
+
modelValue: 5,
|
|
136
|
+
step: 1,
|
|
137
|
+
changeOnWheel: true,
|
|
138
|
+
})
|
|
139
|
+
const input = wrapper.find('input')
|
|
140
|
+
await input.trigger('wheel', { deltaY: -100 })
|
|
141
|
+
expect(state.value).toBe(5)
|
|
142
|
+
})
|
|
143
|
+
|
|
111
144
|
it('clears to null on empty blur', async () => {
|
|
112
145
|
const { wrapper, state } = mountNumber({ modelValue: 3 })
|
|
113
146
|
const input = wrapper.find('input')
|
|
@@ -1,42 +1,72 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import { h } from 'vue'
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
|
+
import { h, nextTick } from 'vue'
|
|
4
4
|
import RsMenu from '../components/RsMenu.vue'
|
|
5
5
|
import RsConfigProvider from '../components/RsConfigProvider.vue'
|
|
6
|
-
import type { RsMenuItemGroup } from '../components/menu-utils'
|
|
6
|
+
import type { RsMenuItem, RsMenuItemGroup } from '../components/menu-utils'
|
|
7
7
|
|
|
8
8
|
describe('RsMenu', () => {
|
|
9
|
-
const flatItems = [
|
|
10
|
-
{ key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard'
|
|
11
|
-
{ key: 'chat', label: '对话', icon: 'message-square'
|
|
12
|
-
{ key: 'settings', label: '设置', icon: 'settings'
|
|
9
|
+
const flatItems: RsMenuItem[] = [
|
|
10
|
+
{ key: 'dashboard', label: '仪表盘', icon: 'layout-dashboard' },
|
|
11
|
+
{ key: 'chat', label: '对话', icon: 'message-square' },
|
|
12
|
+
{ key: 'settings', label: '设置', icon: 'settings' },
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
const groupedItems: RsMenuItemGroup[] = [
|
|
16
16
|
{
|
|
17
17
|
label: '工作区',
|
|
18
18
|
children: [
|
|
19
|
-
{ key: 'projects', label: '项目', icon: 'folder'
|
|
20
|
-
{ key: 'docs', label: '文档', icon: 'search'
|
|
19
|
+
{ key: 'projects', label: '项目', icon: 'folder' },
|
|
20
|
+
{ key: 'docs', label: '文档', icon: 'search' },
|
|
21
21
|
],
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
label: '系统',
|
|
25
|
-
children: [{ key: 'settings', label: '设置', icon: 'settings'
|
|
25
|
+
children: [{ key: 'settings', label: '设置', icon: 'settings' }],
|
|
26
26
|
},
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
-
const nestedItems = [
|
|
29
|
+
const nestedItems: RsMenuItem[] = [
|
|
30
30
|
{
|
|
31
31
|
key: 'workspace',
|
|
32
32
|
label: '工作区',
|
|
33
|
-
icon: 'folder'
|
|
33
|
+
icon: 'folder',
|
|
34
34
|
children: [
|
|
35
35
|
{ key: 'projects', label: '项目' },
|
|
36
36
|
{ key: 'archived', label: '归档', disabled: true },
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
|
-
{ key: 'settings', label: '设置', icon: 'settings'
|
|
39
|
+
{ key: 'settings', label: '设置', icon: 'settings' },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const deepNestedItems: RsMenuItem[] = [
|
|
43
|
+
{
|
|
44
|
+
key: 'workspace',
|
|
45
|
+
label: '工作区',
|
|
46
|
+
children: [
|
|
47
|
+
{
|
|
48
|
+
key: 'team',
|
|
49
|
+
label: '团队',
|
|
50
|
+
children: [
|
|
51
|
+
{ key: 'members', label: '成员' },
|
|
52
|
+
{ key: 'permissions', label: '权限' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
const groupedNestedItems: RsMenuItemGroup[] = [
|
|
60
|
+
{
|
|
61
|
+
label: '产品',
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
key: 'product-line',
|
|
65
|
+
label: '产品线',
|
|
66
|
+
children: [{ key: 'saas-billing', label: '计费' }],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
40
70
|
]
|
|
41
71
|
|
|
42
72
|
it('renders nav with menu aria-label', () => {
|
|
@@ -60,6 +90,82 @@ describe('RsMenu', () => {
|
|
|
60
90
|
expect(wrapper.find('nav.rs-menu').attributes('aria-label')).toBe('Navigation menu')
|
|
61
91
|
})
|
|
62
92
|
|
|
93
|
+
it('applies vertical mode class by default', () => {
|
|
94
|
+
const wrapper = mount(RsMenu, {
|
|
95
|
+
props: { items: flatItems, modelValue: 'dashboard' },
|
|
96
|
+
})
|
|
97
|
+
expect(wrapper.classes()).toContain('rs-menu--vertical')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('applies horizontal mode class', () => {
|
|
101
|
+
const wrapper = mount(RsMenu, {
|
|
102
|
+
props: { items: flatItems, modelValue: 'dashboard', mode: 'horizontal' },
|
|
103
|
+
})
|
|
104
|
+
expect(wrapper.classes()).toContain('rs-menu--horizontal')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('marks active leaf with aria-current and active class', () => {
|
|
108
|
+
const wrapper = mount(RsMenu, {
|
|
109
|
+
props: { items: flatItems, modelValue: 'chat' },
|
|
110
|
+
})
|
|
111
|
+
const active = wrapper.find('.rs-menu__item--active')
|
|
112
|
+
expect(active.exists()).toBe(true)
|
|
113
|
+
expect(active.attributes('aria-current')).toBe('page')
|
|
114
|
+
expect(active.text()).toContain('对话')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('does not highlight parent by default when nested leaf is active', () => {
|
|
118
|
+
const wrapper = mount(RsMenu, {
|
|
119
|
+
props: {
|
|
120
|
+
items: nestedItems,
|
|
121
|
+
modelValue: 'projects',
|
|
122
|
+
openKeys: ['workspace'],
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
const actives = wrapper.findAll('.rs-menu__item--active')
|
|
126
|
+
expect(actives).toHaveLength(1)
|
|
127
|
+
expect(actives[0]!.text()).toContain('项目')
|
|
128
|
+
expect(actives[0]!.attributes('aria-current')).toBe('page')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('highlights ancestor submenu triggers when highlightParent is true', () => {
|
|
132
|
+
const wrapper = mount(RsMenu, {
|
|
133
|
+
props: {
|
|
134
|
+
items: nestedItems,
|
|
135
|
+
modelValue: 'projects',
|
|
136
|
+
openKeys: ['workspace'],
|
|
137
|
+
highlightParent: true,
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
const leaf = wrapper.findAll('.rs-menu__item--active').find((n) => n.text().includes('项目'))
|
|
141
|
+
expect(leaf).toBeDefined()
|
|
142
|
+
expect(leaf?.attributes('aria-current')).toBe('page')
|
|
143
|
+
const parent = wrapper
|
|
144
|
+
.findAll('.rs-menu__item--active-parent')
|
|
145
|
+
.find((n) => n.text().includes('工作区'))
|
|
146
|
+
expect(parent).toBeDefined()
|
|
147
|
+
expect(parent?.attributes('aria-current')).toBeUndefined()
|
|
148
|
+
expect(parent!.classes()).not.toContain('rs-menu__item--active')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('highlights collapsed parent icon when highlightParent is true', () => {
|
|
152
|
+
const wrapper = mount(RsMenu, {
|
|
153
|
+
props: {
|
|
154
|
+
items: nestedItems,
|
|
155
|
+
modelValue: 'projects',
|
|
156
|
+
openKeys: [],
|
|
157
|
+
collapsed: true,
|
|
158
|
+
highlightParent: true,
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
const parent = wrapper
|
|
162
|
+
.findAll('.rs-menu__item--submenu-trigger')
|
|
163
|
+
.find((btn) => btn.attributes('aria-label') === '工作区')
|
|
164
|
+
expect(parent).toBeDefined()
|
|
165
|
+
expect(parent!.classes()).toContain('rs-menu__item--active-parent')
|
|
166
|
+
expect(parent!.classes()).not.toContain('rs-menu__item--active')
|
|
167
|
+
})
|
|
168
|
+
|
|
63
169
|
it('emits select and updates model when item clicked', async () => {
|
|
64
170
|
const wrapper = mount(RsMenu, {
|
|
65
171
|
props: { items: flatItems, modelValue: 'dashboard' },
|
|
@@ -78,6 +184,10 @@ describe('RsMenu', () => {
|
|
|
78
184
|
props: { items: groupedItems, modelValue: 'projects' },
|
|
79
185
|
})
|
|
80
186
|
expect(wrapper.find('.rs-menu__group-label').text()).toBe('工作区')
|
|
187
|
+
expect(wrapper.findAll('.rs-menu__group-label').map((n) => n.text())).toEqual([
|
|
188
|
+
'工作区',
|
|
189
|
+
'系统',
|
|
190
|
+
])
|
|
81
191
|
})
|
|
82
192
|
|
|
83
193
|
it('auto-expands parent submenu for active nested item', () => {
|
|
@@ -91,6 +201,76 @@ describe('RsMenu', () => {
|
|
|
91
201
|
expect(wrapper.emitted('update:openKeys')?.pop()?.[0]).toContain('workspace')
|
|
92
202
|
})
|
|
93
203
|
|
|
204
|
+
it('auto-expands all ancestor keys for deeply nested active item', () => {
|
|
205
|
+
const wrapper = mount(RsMenu, {
|
|
206
|
+
props: {
|
|
207
|
+
items: deepNestedItems,
|
|
208
|
+
modelValue: 'permissions',
|
|
209
|
+
openKeys: [],
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
const keys = wrapper.emitted('update:openKeys')?.pop()?.[0] as string[]
|
|
213
|
+
expect(keys).toEqual(expect.arrayContaining(['workspace', 'team']))
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('auto-expands ancestors inside grouped nested items', () => {
|
|
217
|
+
const wrapper = mount(RsMenu, {
|
|
218
|
+
props: {
|
|
219
|
+
items: groupedNestedItems,
|
|
220
|
+
modelValue: 'saas-billing',
|
|
221
|
+
openKeys: [],
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
expect(wrapper.emitted('update:openKeys')?.pop()?.[0]).toContain('product-line')
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('toggles openKeys when submenu trigger is clicked', async () => {
|
|
228
|
+
const wrapper = mount(RsMenu, {
|
|
229
|
+
props: {
|
|
230
|
+
items: nestedItems,
|
|
231
|
+
modelValue: 'settings',
|
|
232
|
+
openKeys: [],
|
|
233
|
+
},
|
|
234
|
+
})
|
|
235
|
+
const trigger = wrapper.find('.rs-menu__item--submenu-trigger')
|
|
236
|
+
expect(trigger.exists()).toBe(true)
|
|
237
|
+
await trigger.trigger('click')
|
|
238
|
+
await nextTick()
|
|
239
|
+
expect(wrapper.emitted('update:openKeys')?.pop()?.[0]).toContain('workspace')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('closes submenu when open trigger is clicked again', async () => {
|
|
243
|
+
const wrapper = mount(RsMenu, {
|
|
244
|
+
props: {
|
|
245
|
+
items: nestedItems,
|
|
246
|
+
modelValue: 'settings',
|
|
247
|
+
openKeys: ['workspace'],
|
|
248
|
+
},
|
|
249
|
+
})
|
|
250
|
+
const trigger = wrapper.find('.rs-menu__item--submenu-trigger')
|
|
251
|
+
await trigger.trigger('click')
|
|
252
|
+
await nextTick()
|
|
253
|
+
const keys = wrapper.emitted('update:openKeys')?.pop()?.[0] as string[]
|
|
254
|
+
expect(keys).not.toContain('workspace')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('selects nested leaf under open submenu', async () => {
|
|
258
|
+
const wrapper = mount(RsMenu, {
|
|
259
|
+
props: {
|
|
260
|
+
items: nestedItems,
|
|
261
|
+
modelValue: 'settings',
|
|
262
|
+
openKeys: ['workspace'],
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
const projects = wrapper
|
|
266
|
+
.findAll('.rs-menu__item')
|
|
267
|
+
.find((btn) => btn.text().includes('项目'))
|
|
268
|
+
expect(projects).toBeDefined()
|
|
269
|
+
await projects!.trigger('click')
|
|
270
|
+
expect(wrapper.emitted('update:modelValue')?.pop()).toEqual(['projects'])
|
|
271
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['projects'])
|
|
272
|
+
})
|
|
273
|
+
|
|
94
274
|
it('does not emit select for disabled item', async () => {
|
|
95
275
|
const wrapper = mount(RsMenu, {
|
|
96
276
|
props: {
|
|
@@ -105,7 +285,76 @@ describe('RsMenu', () => {
|
|
|
105
285
|
expect(wrapper.emitted('select')).toBeUndefined()
|
|
106
286
|
})
|
|
107
287
|
|
|
108
|
-
it('
|
|
288
|
+
it('renders nested submenu as popup list in horizontal mode', () => {
|
|
289
|
+
const wrapper = mount(RsMenu, {
|
|
290
|
+
props: {
|
|
291
|
+
items: nestedItems,
|
|
292
|
+
modelValue: 'projects',
|
|
293
|
+
openKeys: ['workspace'],
|
|
294
|
+
mode: 'horizontal',
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
expect(wrapper.classes()).toContain('rs-menu--horizontal')
|
|
298
|
+
expect(wrapper.find('.rs-menu__list--nested').exists()).toBe(true)
|
|
299
|
+
expect(wrapper.find('.rs-menu__entry--submenu').exists()).toBe(true)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('applies collapsed class and hides labels', () => {
|
|
303
|
+
const wrapper = mount(RsMenu, {
|
|
304
|
+
props: {
|
|
305
|
+
items: flatItems,
|
|
306
|
+
modelValue: 'dashboard',
|
|
307
|
+
collapsed: true,
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
expect(wrapper.classes()).toContain('rs-menu--collapsed')
|
|
311
|
+
expect(wrapper.find('.rs-menu__label').exists()).toBe(false)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('hides group labels when collapsed', () => {
|
|
315
|
+
const wrapper = mount(RsMenu, {
|
|
316
|
+
props: {
|
|
317
|
+
items: groupedItems,
|
|
318
|
+
modelValue: 'projects',
|
|
319
|
+
collapsed: true,
|
|
320
|
+
},
|
|
321
|
+
})
|
|
322
|
+
expect(wrapper.find('.rs-menu__group-label').exists()).toBe(false)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('sets title and aria-label on items when collapsed', () => {
|
|
326
|
+
const wrapper = mount(RsMenu, {
|
|
327
|
+
props: {
|
|
328
|
+
items: flatItems,
|
|
329
|
+
modelValue: 'dashboard',
|
|
330
|
+
collapsed: true,
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
const settings = wrapper
|
|
334
|
+
.findAll('.rs-menu__item')
|
|
335
|
+
.find((btn) => btn.attributes('title') === '设置')
|
|
336
|
+
expect(settings).toBeDefined()
|
|
337
|
+
expect(settings!.attributes('aria-label')).toBe('设置')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('still selects leaf items when collapsed', async () => {
|
|
341
|
+
const wrapper = mount(RsMenu, {
|
|
342
|
+
props: {
|
|
343
|
+
items: flatItems,
|
|
344
|
+
modelValue: 'dashboard',
|
|
345
|
+
collapsed: true,
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
const chat = wrapper
|
|
349
|
+
.findAll('.rs-menu__item')
|
|
350
|
+
.find((btn) => btn.attributes('title') === '对话')
|
|
351
|
+
expect(chat).toBeDefined()
|
|
352
|
+
await chat!.trigger('click')
|
|
353
|
+
expect(wrapper.emitted('update:modelValue')?.pop()).toEqual(['chat'])
|
|
354
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['chat'])
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('does not render inline nested submenu list when collapsed', () => {
|
|
109
358
|
const wrapper = mount(RsMenu, {
|
|
110
359
|
props: {
|
|
111
360
|
items: nestedItems,
|
|
@@ -113,7 +362,150 @@ describe('RsMenu', () => {
|
|
|
113
362
|
openKeys: ['workspace'],
|
|
114
363
|
collapsed: true,
|
|
115
364
|
},
|
|
365
|
+
attachTo: document.body,
|
|
116
366
|
})
|
|
117
367
|
expect(wrapper.find('.rs-menu__list--nested').exists()).toBe(false)
|
|
368
|
+
expect(wrapper.find('.rs-menu__item--submenu-trigger').exists()).toBe(true)
|
|
369
|
+
expect(document.body.querySelector('.rs-menu__flyout')).toBeNull()
|
|
370
|
+
wrapper.unmount()
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('shows flyout submenu on hover when collapsed vertically', async () => {
|
|
374
|
+
const wrapper = mount(RsMenu, {
|
|
375
|
+
props: {
|
|
376
|
+
items: nestedItems,
|
|
377
|
+
modelValue: 'settings',
|
|
378
|
+
openKeys: [],
|
|
379
|
+
collapsed: true,
|
|
380
|
+
},
|
|
381
|
+
attachTo: document.body,
|
|
382
|
+
})
|
|
383
|
+
const parent = wrapper
|
|
384
|
+
.findAll('.rs-menu__item')
|
|
385
|
+
.find((btn) => btn.attributes('aria-label') === '工作区')
|
|
386
|
+
expect(parent).toBeDefined()
|
|
387
|
+
|
|
388
|
+
await parent!.trigger('mouseenter')
|
|
389
|
+
await nextTick()
|
|
390
|
+
await flushPromises()
|
|
391
|
+
|
|
392
|
+
const flyout = document.body.querySelector('.rs-menu__flyout')
|
|
393
|
+
expect(flyout).not.toBeNull()
|
|
394
|
+
expect(flyout?.textContent).toContain('工作区')
|
|
395
|
+
expect(flyout?.textContent).toContain('项目')
|
|
396
|
+
expect(wrapper.emitted('update:openKeys')).toBeUndefined()
|
|
397
|
+
// 折叠态父级点击不选中父 key,仅用于打开浮层
|
|
398
|
+
await parent!.trigger('click')
|
|
399
|
+
await nextTick()
|
|
400
|
+
expect(wrapper.emitted('select')).toBeUndefined()
|
|
401
|
+
wrapper.unmount()
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('selects nested leaf from collapsed flyout', async () => {
|
|
405
|
+
const wrapper = mount(RsMenu, {
|
|
406
|
+
props: {
|
|
407
|
+
items: nestedItems,
|
|
408
|
+
modelValue: 'settings',
|
|
409
|
+
openKeys: [],
|
|
410
|
+
collapsed: true,
|
|
411
|
+
},
|
|
412
|
+
attachTo: document.body,
|
|
413
|
+
})
|
|
414
|
+
const parent = wrapper
|
|
415
|
+
.findAll('.rs-menu__item')
|
|
416
|
+
.find((btn) => btn.attributes('aria-label') === '工作区')
|
|
417
|
+
expect(parent).toBeDefined()
|
|
418
|
+
await parent!.trigger('mouseenter')
|
|
419
|
+
await nextTick()
|
|
420
|
+
await flushPromises()
|
|
421
|
+
|
|
422
|
+
const projects = Array.from(
|
|
423
|
+
document.body.querySelectorAll('.rs-menu__flyout .rs-menu__item'),
|
|
424
|
+
).find((el) => el.textContent?.includes('项目')) as HTMLButtonElement | undefined
|
|
425
|
+
expect(projects).toBeDefined()
|
|
426
|
+
projects!.click()
|
|
427
|
+
await nextTick()
|
|
428
|
+
await flushPromises()
|
|
429
|
+
|
|
430
|
+
expect(wrapper.emitted('update:modelValue')?.pop()).toEqual(['projects'])
|
|
431
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['projects'])
|
|
432
|
+
expect(document.body.querySelector('.rs-menu__flyout')).toBeNull()
|
|
433
|
+
wrapper.unmount()
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it('renders icons on collapsed triggers and flyout children', async () => {
|
|
437
|
+
const iconNestedItems: RsMenuItem[] = [
|
|
438
|
+
{
|
|
439
|
+
key: 'workspace',
|
|
440
|
+
label: '工作区',
|
|
441
|
+
icon: 'folder',
|
|
442
|
+
children: [
|
|
443
|
+
{ key: 'projects', label: '项目', icon: 'box' },
|
|
444
|
+
{ key: 'docs', label: '文档', icon: 'file-text' },
|
|
445
|
+
],
|
|
446
|
+
},
|
|
447
|
+
{ key: 'settings', label: '设置', icon: 'settings' },
|
|
448
|
+
]
|
|
449
|
+
const wrapper = mount(RsMenu, {
|
|
450
|
+
props: {
|
|
451
|
+
items: iconNestedItems,
|
|
452
|
+
modelValue: 'settings',
|
|
453
|
+
openKeys: [],
|
|
454
|
+
collapsed: true,
|
|
455
|
+
},
|
|
456
|
+
attachTo: document.body,
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
const triggers = wrapper.findAll('.rs-menu__item')
|
|
460
|
+
expect(triggers.every((btn) => btn.find('.rs-menu__icon').exists())).toBe(true)
|
|
461
|
+
|
|
462
|
+
const parent = triggers.find((btn) => btn.attributes('aria-label') === '工作区')
|
|
463
|
+
expect(parent).toBeDefined()
|
|
464
|
+
await parent!.trigger('mouseenter')
|
|
465
|
+
await nextTick()
|
|
466
|
+
await flushPromises()
|
|
467
|
+
|
|
468
|
+
const flyout = document.body.querySelector('.rs-menu__flyout')
|
|
469
|
+
expect(flyout).not.toBeNull()
|
|
470
|
+
expect(flyout!.querySelectorAll('.rs-menu__icon').length).toBeGreaterThanOrEqual(2)
|
|
471
|
+
expect(flyout!.textContent).toContain('项目')
|
|
472
|
+
expect(flyout!.textContent).toContain('文档')
|
|
473
|
+
wrapper.unmount()
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('keeps horizontal submenu expandable even when collapsed prop is true', async () => {
|
|
477
|
+
const wrapper = mount(RsMenu, {
|
|
478
|
+
props: {
|
|
479
|
+
items: nestedItems,
|
|
480
|
+
modelValue: 'settings',
|
|
481
|
+
openKeys: [],
|
|
482
|
+
mode: 'horizontal',
|
|
483
|
+
collapsed: true,
|
|
484
|
+
},
|
|
485
|
+
})
|
|
486
|
+
// 水平模式忽略 collapsed:仍显示文案,不挂 rs-menu--collapsed
|
|
487
|
+
expect(wrapper.classes()).not.toContain('rs-menu--collapsed')
|
|
488
|
+
expect(wrapper.text()).toContain('工作区')
|
|
489
|
+
const trigger = wrapper.find('.rs-menu__item--submenu-trigger')
|
|
490
|
+
expect(trigger.exists()).toBe(true)
|
|
491
|
+
await trigger.trigger('click')
|
|
492
|
+
await nextTick()
|
|
493
|
+
expect(wrapper.emitted('update:openKeys')?.pop()?.[0]).toContain('workspace')
|
|
494
|
+
// 子菜单内容可见,而非空气泡
|
|
495
|
+
expect(wrapper.find('.rs-menu__list--nested').exists()).toBe(true)
|
|
496
|
+
expect(wrapper.text()).toContain('项目')
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it('does not hide horizontal labels when collapsed is true', () => {
|
|
500
|
+
const wrapper = mount(RsMenu, {
|
|
501
|
+
props: {
|
|
502
|
+
items: nestedItems,
|
|
503
|
+
modelValue: 'settings',
|
|
504
|
+
mode: 'horizontal',
|
|
505
|
+
collapsed: true,
|
|
506
|
+
},
|
|
507
|
+
})
|
|
508
|
+
expect(wrapper.find('.rs-menu__label').exists()).toBe(true)
|
|
509
|
+
expect(wrapper.find('.rs-menu__submenu-arrow').exists()).toBe(true)
|
|
118
510
|
})
|
|
119
511
|
})
|