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
|
@@ -131,7 +131,13 @@ describe('RsPagination', () => {
|
|
|
131
131
|
|
|
132
132
|
it('jumps to page when confirm is clicked', async () => {
|
|
133
133
|
const wrapper = mount(RsPagination, {
|
|
134
|
-
props: {
|
|
134
|
+
props: {
|
|
135
|
+
total: 100,
|
|
136
|
+
page: 1,
|
|
137
|
+
pageSize: 10,
|
|
138
|
+
showQuickJumper: true,
|
|
139
|
+
showJumpConfirm: true,
|
|
140
|
+
},
|
|
135
141
|
})
|
|
136
142
|
const input = wrapper.find('.rs-pagination__jumper-input input')
|
|
137
143
|
await input.setValue('7')
|
|
@@ -143,9 +149,68 @@ describe('RsPagination', () => {
|
|
|
143
149
|
expect(wrapper.emitted('update:page')?.pop()).toEqual([7])
|
|
144
150
|
})
|
|
145
151
|
|
|
152
|
+
it('hides jump confirm button by default', () => {
|
|
153
|
+
const wrapper = mount(RsPagination, {
|
|
154
|
+
props: {
|
|
155
|
+
total: 100,
|
|
156
|
+
page: 1,
|
|
157
|
+
pageSize: 10,
|
|
158
|
+
showQuickJumper: true,
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
const jumperBtn = wrapper.findAll('button').find((btn) => btn.text() === '确定')
|
|
162
|
+
expect(jumperBtn).toBeUndefined()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('hides jump confirm button when showJumpConfirm is false', () => {
|
|
166
|
+
const wrapper = mount(RsPagination, {
|
|
167
|
+
props: {
|
|
168
|
+
total: 100,
|
|
169
|
+
page: 1,
|
|
170
|
+
pageSize: 10,
|
|
171
|
+
showQuickJumper: true,
|
|
172
|
+
showJumpConfirm: false,
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
const jumperBtn = wrapper.findAll('button').find((btn) => btn.text() === '确定')
|
|
176
|
+
expect(jumperBtn).toBeUndefined()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('jumps to page on enter when confirm is hidden', async () => {
|
|
180
|
+
const wrapper = mount(RsPagination, {
|
|
181
|
+
props: {
|
|
182
|
+
total: 100,
|
|
183
|
+
page: 1,
|
|
184
|
+
pageSize: 10,
|
|
185
|
+
showQuickJumper: true,
|
|
186
|
+
showJumpConfirm: false,
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
const input = wrapper.find('.rs-pagination__jumper-input input')
|
|
190
|
+
await input.setValue('4')
|
|
191
|
+
// RsInput 对 Enter 发 pressEnter;需带 key 才能命中 onKeydown
|
|
192
|
+
await input.trigger('keydown', { key: 'Enter' })
|
|
193
|
+
expect(wrapper.emitted('update:page')?.pop()).toEqual([4])
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('applies size class for sm / md / lg', () => {
|
|
197
|
+
for (const size of ['sm', 'md', 'lg'] as const) {
|
|
198
|
+
const wrapper = mount(RsPagination, {
|
|
199
|
+
props: { total: 40, page: 1, pageSize: 10, size },
|
|
200
|
+
})
|
|
201
|
+
expect(wrapper.find('nav').classes()).toContain(`rs-pagination--${size}`)
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
146
205
|
it('clamps jump target to valid page range', async () => {
|
|
147
206
|
const wrapper = mount(RsPagination, {
|
|
148
|
-
props: {
|
|
207
|
+
props: {
|
|
208
|
+
total: 100,
|
|
209
|
+
page: 1,
|
|
210
|
+
pageSize: 10,
|
|
211
|
+
showQuickJumper: true,
|
|
212
|
+
showJumpConfirm: true,
|
|
213
|
+
},
|
|
149
214
|
})
|
|
150
215
|
const input = wrapper.find('.rs-pagination__jumper-input input')
|
|
151
216
|
await input.setValue('99')
|
|
@@ -4,7 +4,18 @@ import { defineComponent, h, ref } from 'vue'
|
|
|
4
4
|
import RsConfigProvider from '../components/RsConfigProvider.vue'
|
|
5
5
|
import RsForm from '../components/RsForm.vue'
|
|
6
6
|
import RsSelect from '../components/RsSelect.vue'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
filterSelectOptions,
|
|
9
|
+
fromComboboxValue,
|
|
10
|
+
normalizeSelectOptions,
|
|
11
|
+
optionDisplayLabel,
|
|
12
|
+
packSelectModel,
|
|
13
|
+
restoreSelectValue,
|
|
14
|
+
sortSelectOptions,
|
|
15
|
+
splitByTokenSeparators,
|
|
16
|
+
toComboboxValue,
|
|
17
|
+
RS_SELECT_EMPTY_VALUE,
|
|
18
|
+
} from '../components/select-utils'
|
|
8
19
|
|
|
9
20
|
describe('RsSelect', () => {
|
|
10
21
|
const options = [
|
|
@@ -35,6 +46,59 @@ describe('RsSelect', () => {
|
|
|
35
46
|
expect(wrapper.find('.rs-select__placeholder').text()).toBe('请选择')
|
|
36
47
|
})
|
|
37
48
|
|
|
49
|
+
it('keeps numeric option values on v-model including 0', async () => {
|
|
50
|
+
const numeric = [
|
|
51
|
+
{ label: '静态配置', value: 0 },
|
|
52
|
+
{ label: '服务发现', value: 1 },
|
|
53
|
+
]
|
|
54
|
+
const wrapper = mount(RsSelect, {
|
|
55
|
+
props: { options: numeric, modelValue: 0 },
|
|
56
|
+
})
|
|
57
|
+
expect(wrapper.find('.rs-select__single-label').text()).toBe('静态配置')
|
|
58
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
59
|
+
await root.vm.$emit('update:modelValue', '1')
|
|
60
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([1])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('collapses overflow tags with maxTagCount', () => {
|
|
64
|
+
const wrapper = mount(RsSelect, {
|
|
65
|
+
props: {
|
|
66
|
+
options,
|
|
67
|
+
modelValue: ['gpt-4o', 'claude'],
|
|
68
|
+
multiple: true,
|
|
69
|
+
maxTagCount: 1,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
expect(wrapper.findAll('.rs-select__tag')).toHaveLength(2)
|
|
73
|
+
expect(wrapper.find('.rs-select__tag--rest').text()).toBe('+1')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('renders prefix and option slots', async () => {
|
|
77
|
+
const wrapper = mount(RsSelect, {
|
|
78
|
+
props: { options, modelValue: '' },
|
|
79
|
+
slots: {
|
|
80
|
+
prefix: () => h('span', { class: 'pref' }, 'P'),
|
|
81
|
+
option: ({ option }: { option: { label: string } }) =>
|
|
82
|
+
h('span', { class: 'opt' }, option.label),
|
|
83
|
+
},
|
|
84
|
+
attachTo: document.body,
|
|
85
|
+
})
|
|
86
|
+
expect(wrapper.find('.pref').text()).toBe('P')
|
|
87
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
88
|
+
await flushPromises()
|
|
89
|
+
expect(document.body.querySelector('.opt')?.textContent).toContain('GPT-4o')
|
|
90
|
+
wrapper.unmount()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('marks invalid on the trigger instead of the combobox root', () => {
|
|
94
|
+
const wrapper = mount(RsSelect, {
|
|
95
|
+
props: { options, modelValue: '', invalid: true },
|
|
96
|
+
})
|
|
97
|
+
expect(wrapper.find('.rs-select__trigger').classes()).toContain('rs-select__trigger--invalid')
|
|
98
|
+
expect(wrapper.find('.rs-select__trigger').attributes('aria-invalid')).toBe('true')
|
|
99
|
+
expect(wrapper.find('.rs-select').attributes('aria-invalid')).toBeUndefined()
|
|
100
|
+
})
|
|
101
|
+
|
|
38
102
|
it('uses en-US default placeholder inside RsConfigProvider', () => {
|
|
39
103
|
const wrapper = mount(RsConfigProvider, {
|
|
40
104
|
props: { locale: 'en-US' },
|
|
@@ -91,6 +155,35 @@ describe('RsSelect', () => {
|
|
|
91
155
|
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([''])
|
|
92
156
|
})
|
|
93
157
|
|
|
158
|
+
it('maps empty option value through sentinel without crashing', async () => {
|
|
159
|
+
const withEmpty = [
|
|
160
|
+
{ label: '空串项', value: '' },
|
|
161
|
+
{ label: 'Claude', value: 'claude' },
|
|
162
|
+
]
|
|
163
|
+
const wrapper = mount(RsSelect, {
|
|
164
|
+
props: { options: withEmpty, modelValue: '', clearable: true },
|
|
165
|
+
attachTo: document.body,
|
|
166
|
+
})
|
|
167
|
+
expect(wrapper.find('.rs-select__placeholder').exists()).toBe(true)
|
|
168
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
169
|
+
await flushPromises()
|
|
170
|
+
expect(document.body.textContent).toContain('空串项')
|
|
171
|
+
wrapper.unmount()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('selecting empty sentinel emits empty string', async () => {
|
|
175
|
+
const withEmpty = [
|
|
176
|
+
{ label: '空串项', value: '' },
|
|
177
|
+
{ label: 'Claude', value: 'claude' },
|
|
178
|
+
]
|
|
179
|
+
const wrapper = mount(RsSelect, {
|
|
180
|
+
props: { options: withEmpty, modelValue: 'claude', clearable: true },
|
|
181
|
+
})
|
|
182
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
183
|
+
await root.vm.$emit('update:modelValue', '__rs_select_empty__')
|
|
184
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([''])
|
|
185
|
+
})
|
|
186
|
+
|
|
94
187
|
it('supports multiple mode tags', () => {
|
|
95
188
|
const wrapper = mount(RsSelect, {
|
|
96
189
|
props: {
|
|
@@ -180,7 +273,7 @@ describe('RsSelect', () => {
|
|
|
180
273
|
},
|
|
181
274
|
template: `
|
|
182
275
|
<RsForm ref="formRef">
|
|
183
|
-
<RsSelect v-model="role" :options="roleOptions" required />
|
|
276
|
+
<RsSelect v-model="role" name="role" :options="roleOptions" required />
|
|
184
277
|
</RsForm>
|
|
185
278
|
`,
|
|
186
279
|
})
|
|
@@ -260,12 +353,382 @@ describe('RsSelect', () => {
|
|
|
260
353
|
expect(matched!.classList.contains('rs-select__content--match-trigger')).toBe(true)
|
|
261
354
|
wrapper.unmount()
|
|
262
355
|
})
|
|
356
|
+
|
|
357
|
+
it('packs labelInValue including numeric 0', async () => {
|
|
358
|
+
const numeric = [
|
|
359
|
+
{ label: '静态配置', value: 0 },
|
|
360
|
+
{ label: '服务发现', value: 1 },
|
|
361
|
+
]
|
|
362
|
+
const wrapper = mount(RsSelect, {
|
|
363
|
+
props: { options: numeric, modelValue: '', labelInValue: true },
|
|
364
|
+
})
|
|
365
|
+
expect(wrapper.find('.rs-select__placeholder').exists()).toBe(true)
|
|
366
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
367
|
+
await root.vm.$emit('update:modelValue', '0')
|
|
368
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([
|
|
369
|
+
{ label: '静态配置', value: 0 },
|
|
370
|
+
])
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('echoes labelInValue snapshot for number 0', () => {
|
|
374
|
+
const numeric = [
|
|
375
|
+
{ label: '静态配置', value: 0 },
|
|
376
|
+
{ label: '服务发现', value: 1 },
|
|
377
|
+
]
|
|
378
|
+
const wrapper = mount(RsSelect, {
|
|
379
|
+
props: {
|
|
380
|
+
options: numeric,
|
|
381
|
+
modelValue: { label: '静态配置', value: 0 },
|
|
382
|
+
labelInValue: true,
|
|
383
|
+
},
|
|
384
|
+
})
|
|
385
|
+
expect(wrapper.find('.rs-select__single-label').text()).toBe('静态配置')
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
it('packs labelInValue arrays in multiple mode', async () => {
|
|
389
|
+
const wrapper = mount(RsSelect, {
|
|
390
|
+
props: { options, modelValue: [], multiple: true, labelInValue: true },
|
|
391
|
+
})
|
|
392
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
393
|
+
await root.vm.$emit('update:modelValue', ['gpt-4o'])
|
|
394
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([
|
|
395
|
+
[{ label: 'GPT-4o', value: 'gpt-4o' }],
|
|
396
|
+
])
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('emits select, deselect and clear', async () => {
|
|
400
|
+
const wrapper = mount(RsSelect, {
|
|
401
|
+
props: { options, modelValue: '', clearable: true },
|
|
402
|
+
})
|
|
403
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
404
|
+
await root.vm.$emit('update:modelValue', 'claude')
|
|
405
|
+
expect(wrapper.emitted('select')?.at(-1)?.[0]).toBe('claude')
|
|
406
|
+
|
|
407
|
+
await wrapper.setProps({ modelValue: 'claude' })
|
|
408
|
+
await root.vm.$emit('update:modelValue', 'gpt-4o')
|
|
409
|
+
expect(wrapper.emitted('deselect')?.at(-1)?.[0]).toBe('claude')
|
|
410
|
+
expect(wrapper.emitted('select')?.at(-1)?.[0]).toBe('gpt-4o')
|
|
411
|
+
|
|
412
|
+
await wrapper.setProps({ modelValue: 'gpt-4o' })
|
|
413
|
+
await wrapper.find('.rs-select__clear').trigger('click')
|
|
414
|
+
expect(wrapper.emitted('clear')).toHaveLength(1)
|
|
415
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([''])
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('truncates tag text with maxTagTextLength', () => {
|
|
419
|
+
const wrapper = mount(RsSelect, {
|
|
420
|
+
props: {
|
|
421
|
+
options: [{ label: 'TypeScript', value: 'ts' }],
|
|
422
|
+
modelValue: ['ts'],
|
|
423
|
+
multiple: true,
|
|
424
|
+
maxTagTextLength: 4,
|
|
425
|
+
},
|
|
426
|
+
})
|
|
427
|
+
expect(wrapper.find('.rs-select__tag-label').text()).toBe('Type…')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('applies warning status on the trigger', () => {
|
|
431
|
+
const wrapper = mount(RsSelect, {
|
|
432
|
+
props: { options, modelValue: '', status: 'warning' },
|
|
433
|
+
})
|
|
434
|
+
expect(wrapper.find('.rs-select__trigger').classes()).toContain('rs-select__trigger--warning')
|
|
435
|
+
expect(wrapper.find('.rs-select__trigger').classes()).not.toContain(
|
|
436
|
+
'rs-select__trigger--invalid',
|
|
437
|
+
)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('treats status error as invalid', () => {
|
|
441
|
+
const wrapper = mount(RsSelect, {
|
|
442
|
+
props: { options, modelValue: '', status: 'error' },
|
|
443
|
+
})
|
|
444
|
+
expect(wrapper.find('.rs-select__trigger').classes()).toContain('rs-select__trigger--invalid')
|
|
445
|
+
expect(wrapper.find('.rs-select__trigger').classes()).not.toContain(
|
|
446
|
+
'rs-select__trigger--warning',
|
|
447
|
+
)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it('hides arrow when showArrow is false', () => {
|
|
451
|
+
const wrapper = mount(RsSelect, {
|
|
452
|
+
props: { options, modelValue: '', showArrow: false },
|
|
453
|
+
})
|
|
454
|
+
expect(wrapper.find('.rs-select__suffix').exists()).toBe(false)
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('sorts options with filterSort', async () => {
|
|
458
|
+
const unsorted = [
|
|
459
|
+
{ label: 'Zulu', value: 'z' },
|
|
460
|
+
{ label: 'Alpha', value: 'a' },
|
|
461
|
+
]
|
|
462
|
+
const wrapper = mount(RsSelect, {
|
|
463
|
+
props: {
|
|
464
|
+
options: unsorted,
|
|
465
|
+
modelValue: '',
|
|
466
|
+
filterSort: (a, b) => String(a.label).localeCompare(String(b.label)),
|
|
467
|
+
},
|
|
468
|
+
attachTo: document.body,
|
|
469
|
+
})
|
|
470
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
471
|
+
await flushPromises()
|
|
472
|
+
const items = [...document.body.querySelectorAll('.rs-select__item-label')].map(
|
|
473
|
+
(el) => el.textContent?.trim(),
|
|
474
|
+
)
|
|
475
|
+
expect(items[0]).toBe('Alpha')
|
|
476
|
+
expect(items[1]).toBe('Zulu')
|
|
477
|
+
wrapper.unmount()
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it('renders header and footer slots', async () => {
|
|
481
|
+
const wrapper = mount(RsSelect, {
|
|
482
|
+
props: { options, modelValue: '' },
|
|
483
|
+
slots: {
|
|
484
|
+
header: () => h('div', { class: 'sel-head' }, 'HEAD'),
|
|
485
|
+
footer: () => h('div', { class: 'sel-foot' }, 'FOOT'),
|
|
486
|
+
},
|
|
487
|
+
attachTo: document.body,
|
|
488
|
+
})
|
|
489
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
490
|
+
await flushPromises()
|
|
491
|
+
expect(document.body.querySelector('.sel-head')?.textContent).toBe('HEAD')
|
|
492
|
+
expect(document.body.querySelector('.sel-foot')?.textContent).toBe('FOOT')
|
|
493
|
+
wrapper.unmount()
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('applies popupClassName and listHeight on the panel', async () => {
|
|
497
|
+
const wrapper = mount(RsSelect, {
|
|
498
|
+
props: {
|
|
499
|
+
options,
|
|
500
|
+
modelValue: '',
|
|
501
|
+
popupClassName: 'extra-popup',
|
|
502
|
+
listHeight: 120,
|
|
503
|
+
},
|
|
504
|
+
attachTo: document.body,
|
|
505
|
+
})
|
|
506
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
507
|
+
await flushPromises()
|
|
508
|
+
const content = document.body.querySelector('.rs-select__content') as HTMLElement | null
|
|
509
|
+
expect(content?.classList.contains('extra-popup')).toBe(true)
|
|
510
|
+
expect(content?.style.getPropertyValue('--rs-select-list-height')).toBe('120px')
|
|
511
|
+
wrapper.unmount()
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('commits tokens when typing a separator', async () => {
|
|
515
|
+
const wrapper = mount(RsSelect, {
|
|
516
|
+
props: {
|
|
517
|
+
options,
|
|
518
|
+
modelValue: [],
|
|
519
|
+
multiple: true,
|
|
520
|
+
searchable: true,
|
|
521
|
+
tokenSeparators: [','],
|
|
522
|
+
},
|
|
523
|
+
attachTo: document.body,
|
|
524
|
+
})
|
|
525
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
526
|
+
await flushPromises()
|
|
527
|
+
const input = document.querySelector('.rs-select__search') as HTMLInputElement | null
|
|
528
|
+
expect(input).toBeTruthy()
|
|
529
|
+
input!.value = 'gpt-4o,'
|
|
530
|
+
input!.dispatchEvent(new Event('input', { bubbles: true }))
|
|
531
|
+
await flushPromises()
|
|
532
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([['gpt-4o']])
|
|
533
|
+
wrapper.unmount()
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
it('maps fieldNames including numeric 0', async () => {
|
|
537
|
+
const raw = [
|
|
538
|
+
{ name: '静态配置', id: 0 },
|
|
539
|
+
{ name: '服务发现', id: 1 },
|
|
540
|
+
]
|
|
541
|
+
const wrapper = mount(RsSelect, {
|
|
542
|
+
props: {
|
|
543
|
+
options: raw,
|
|
544
|
+
modelValue: 0,
|
|
545
|
+
fieldNames: { label: 'name', value: 'id' },
|
|
546
|
+
},
|
|
547
|
+
})
|
|
548
|
+
expect(wrapper.find('.rs-select__single-label').text()).toBe('静态配置')
|
|
549
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
550
|
+
await root.vm.$emit('update:modelValue', '1')
|
|
551
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([1])
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
it('shows optionLabelProp on the trigger', () => {
|
|
555
|
+
const wrapper = mount(RsSelect, {
|
|
556
|
+
props: { options, modelValue: 'claude', optionLabelProp: 'value' },
|
|
557
|
+
})
|
|
558
|
+
expect(wrapper.find('.rs-select__single-label').text()).toBe('claude')
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
it('binds searchValue and emits updates', async () => {
|
|
562
|
+
const wrapper = mount(RsSelect, {
|
|
563
|
+
props: { options, modelValue: '', searchable: true, searchValue: 'cla' },
|
|
564
|
+
attachTo: document.body,
|
|
565
|
+
})
|
|
566
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
567
|
+
await flushPromises()
|
|
568
|
+
const input = document.querySelector('.rs-select__search') as HTMLInputElement | null
|
|
569
|
+
expect(input?.value).toBe('cla')
|
|
570
|
+
input!.value = 'gpt'
|
|
571
|
+
input!.dispatchEvent(new Event('input', { bubbles: true }))
|
|
572
|
+
await flushPromises()
|
|
573
|
+
expect(wrapper.emitted('update:searchValue')?.at(-1)).toEqual(['gpt'])
|
|
574
|
+
wrapper.unmount()
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
it('renders dropdownRender instead of the default panel body', async () => {
|
|
578
|
+
const wrapper = mount(RsSelect, {
|
|
579
|
+
props: { options, modelValue: '' },
|
|
580
|
+
slots: {
|
|
581
|
+
dropdownRender: () => h('div', { class: 'custom-dd' }, 'CUSTOM'),
|
|
582
|
+
},
|
|
583
|
+
attachTo: document.body,
|
|
584
|
+
})
|
|
585
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
586
|
+
await flushPromises()
|
|
587
|
+
expect(document.body.querySelector('.custom-dd')?.textContent).toBe('CUSTOM')
|
|
588
|
+
expect(document.body.querySelector('.rs-select__item')).toBeNull()
|
|
589
|
+
wrapper.unmount()
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('mounts the panel into getPopupContainer', async () => {
|
|
593
|
+
const host = document.createElement('div')
|
|
594
|
+
host.id = 'rs-select-portal-host'
|
|
595
|
+
document.body.appendChild(host)
|
|
596
|
+
const wrapper = mount(RsSelect, {
|
|
597
|
+
props: {
|
|
598
|
+
options,
|
|
599
|
+
modelValue: '',
|
|
600
|
+
getPopupContainer: () => host,
|
|
601
|
+
},
|
|
602
|
+
attachTo: document.body,
|
|
603
|
+
})
|
|
604
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
605
|
+
await flushPromises()
|
|
606
|
+
expect(host.querySelector('.rs-select__content')).toBeTruthy()
|
|
607
|
+
wrapper.unmount()
|
|
608
|
+
host.remove()
|
|
609
|
+
})
|
|
263
610
|
})
|
|
264
611
|
|
|
265
612
|
describe('select-utils', () => {
|
|
266
613
|
const contains = (text: string, search: string) =>
|
|
267
614
|
text.toLowerCase().includes(search.toLowerCase())
|
|
268
615
|
|
|
616
|
+
it('maps empty string to combobox sentinel and back', () => {
|
|
617
|
+
expect(toComboboxValue('')).toBe(RS_SELECT_EMPTY_VALUE)
|
|
618
|
+
expect(toComboboxValue('claude')).toBe('claude')
|
|
619
|
+
expect(toComboboxValue(0)).toBe('0')
|
|
620
|
+
expect(fromComboboxValue(RS_SELECT_EMPTY_VALUE)).toBe('')
|
|
621
|
+
expect(fromComboboxValue('claude')).toBe('claude')
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
it('restoreSelectValue keeps option number type', () => {
|
|
625
|
+
const numeric = [
|
|
626
|
+
{ label: '静态配置', value: 0 },
|
|
627
|
+
{ label: '服务发现', value: 1 },
|
|
628
|
+
]
|
|
629
|
+
expect(restoreSelectValue('0', numeric)).toBe(0)
|
|
630
|
+
expect(restoreSelectValue('1', numeric)).toBe(1)
|
|
631
|
+
expect(restoreSelectValue('custom', numeric)).toBe('custom')
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
it('filterSelectOptions honors filterOption and optionFilterProp', () => {
|
|
635
|
+
const numeric = [
|
|
636
|
+
{ label: '静态配置', value: 0 },
|
|
637
|
+
{ label: '服务发现', value: 1 },
|
|
638
|
+
]
|
|
639
|
+
const byValue = filterSelectOptions(numeric, '0', contains, true, 'value')
|
|
640
|
+
expect(byValue).toHaveLength(1)
|
|
641
|
+
expect(byValue[0]).toMatchObject({ value: 0 })
|
|
642
|
+
|
|
643
|
+
const custom = filterSelectOptions(numeric, '发现', contains, (q, opt) =>
|
|
644
|
+
String(opt.label).includes(q),
|
|
645
|
+
)
|
|
646
|
+
expect(custom).toHaveLength(1)
|
|
647
|
+
expect(custom[0]).toMatchObject({ value: 1 })
|
|
648
|
+
|
|
649
|
+
const unfiltered = filterSelectOptions(numeric, 'zzz', contains, false)
|
|
650
|
+
expect(unfiltered).toHaveLength(2)
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
it('packSelectModel keeps number 0 and labelInValue', () => {
|
|
654
|
+
const numeric = [
|
|
655
|
+
{ label: '静态配置', value: 0 },
|
|
656
|
+
{ label: '服务发现', value: 1 },
|
|
657
|
+
]
|
|
658
|
+
expect(packSelectModel([0], numeric, false, false)).toBe(0)
|
|
659
|
+
expect(packSelectModel([0], numeric, false, true)).toEqual({
|
|
660
|
+
label: '静态配置',
|
|
661
|
+
value: 0,
|
|
662
|
+
})
|
|
663
|
+
expect(packSelectModel([0, 1], numeric, true, true)).toEqual([
|
|
664
|
+
{ label: '静态配置', value: 0 },
|
|
665
|
+
{ label: '服务发现', value: 1 },
|
|
666
|
+
])
|
|
667
|
+
expect(packSelectModel([], numeric, false, true)).toBe('')
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
it('sortSelectOptions sorts flat and grouped lists', () => {
|
|
671
|
+
const flat = [
|
|
672
|
+
{ label: 'Zulu', value: 'z' },
|
|
673
|
+
{ label: 'Alpha', value: 'a' },
|
|
674
|
+
]
|
|
675
|
+
const sorted = sortSelectOptions(flat, (a, b) => String(a.label).localeCompare(String(b.label)))
|
|
676
|
+
expect(sorted.map((item) => ('value' in item ? item.value : ''))).toEqual(['a', 'z'])
|
|
677
|
+
|
|
678
|
+
const grouped = [
|
|
679
|
+
{
|
|
680
|
+
label: '后端',
|
|
681
|
+
options: [
|
|
682
|
+
{ label: 'Go', value: 'go' },
|
|
683
|
+
{ label: 'Ada', value: 'ada' },
|
|
684
|
+
],
|
|
685
|
+
},
|
|
686
|
+
]
|
|
687
|
+
const groupedSorted = sortSelectOptions(grouped, (a, b) =>
|
|
688
|
+
String(a.label).localeCompare(String(b.label)),
|
|
689
|
+
)
|
|
690
|
+
expect(groupedSorted[0]).toMatchObject({ label: '后端' })
|
|
691
|
+
if ('options' in groupedSorted[0]) {
|
|
692
|
+
expect(groupedSorted[0].options.map((opt) => opt.value)).toEqual(['ada', 'go'])
|
|
693
|
+
}
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
it('splitByTokenSeparators splits on the first matching separator', () => {
|
|
697
|
+
expect(splitByTokenSeparators('a,b,c', [','])).toEqual(['a', 'b', 'c'])
|
|
698
|
+
expect(splitByTokenSeparators('ab', [','])).toBeNull()
|
|
699
|
+
expect(splitByTokenSeparators('a;b', [',', ';'])).toEqual(['a', 'b'])
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
it('normalizeSelectOptions maps fieldNames and keeps number 0', () => {
|
|
703
|
+
const raw = [
|
|
704
|
+
{ name: '静态配置', id: 0 },
|
|
705
|
+
{
|
|
706
|
+
name: '后端',
|
|
707
|
+
children: [
|
|
708
|
+
{ name: 'Go', id: 'go' },
|
|
709
|
+
{ name: 'Ada', id: 'ada' },
|
|
710
|
+
],
|
|
711
|
+
},
|
|
712
|
+
]
|
|
713
|
+
const normalized = normalizeSelectOptions(raw, {
|
|
714
|
+
label: 'name',
|
|
715
|
+
value: 'id',
|
|
716
|
+
options: 'children',
|
|
717
|
+
})
|
|
718
|
+
expect(normalized[0]).toMatchObject({ label: '静态配置', value: 0 })
|
|
719
|
+
expect(normalized[1]).toMatchObject({ label: '后端' })
|
|
720
|
+
if ('options' in normalized[1]) {
|
|
721
|
+
expect(normalized[1].options.map((opt) => opt.value)).toEqual(['go', 'ada'])
|
|
722
|
+
}
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
it('optionDisplayLabel reads optionLabelProp', () => {
|
|
726
|
+
const option = { label: 'Claude', value: 'claude', code: 'CL' }
|
|
727
|
+
expect(optionDisplayLabel(option)).toBe('Claude')
|
|
728
|
+
expect(optionDisplayLabel(option, 'value')).toBe('claude')
|
|
729
|
+
expect(optionDisplayLabel(option, 'code')).toBe('CL')
|
|
730
|
+
})
|
|
731
|
+
|
|
269
732
|
it('filterSelectOptions filters flat and grouped options', () => {
|
|
270
733
|
const grouped = [
|
|
271
734
|
{
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { defineComponent, nextTick } from 'vue'
|
|
4
|
+
import RsSwitch from '../components/RsSwitch.vue'
|
|
5
|
+
|
|
6
|
+
describe('RsSwitch', () => {
|
|
7
|
+
it('toggles v-model on click', async () => {
|
|
8
|
+
const Host = defineComponent({
|
|
9
|
+
components: { RsSwitch },
|
|
10
|
+
data: () => ({ on: false }),
|
|
11
|
+
template: '<RsSwitch v-model="on" />',
|
|
12
|
+
})
|
|
13
|
+
const wrapper = mount(Host)
|
|
14
|
+
|
|
15
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
16
|
+
await nextTick()
|
|
17
|
+
expect(wrapper.find('.rs-switch--checked').exists()).toBe(true)
|
|
18
|
+
expect((wrapper.vm as { on: boolean }).on).toBe(true)
|
|
19
|
+
|
|
20
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
21
|
+
await nextTick()
|
|
22
|
+
expect(wrapper.find('.rs-switch--checked').exists()).toBe(false)
|
|
23
|
+
expect((wrapper.vm as { on: boolean }).on).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('emits change when toggled', async () => {
|
|
27
|
+
const wrapper = mount(RsSwitch, {
|
|
28
|
+
props: { modelValue: false },
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
32
|
+
expect(wrapper.emitted('change')?.[0]).toEqual([true])
|
|
33
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([true])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('does not toggle when disabled', async () => {
|
|
37
|
+
const wrapper = mount(RsSwitch, {
|
|
38
|
+
props: { modelValue: false, disabled: true },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
42
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('writes custom checkedValue / uncheckedValue to v-model', async () => {
|
|
46
|
+
const Host = defineComponent({
|
|
47
|
+
components: { RsSwitch },
|
|
48
|
+
data: () => ({ flag: 'N' }),
|
|
49
|
+
template: '<RsSwitch v-model="flag" checked-value="Y" unchecked-value="N" />',
|
|
50
|
+
})
|
|
51
|
+
const wrapper = mount(Host)
|
|
52
|
+
|
|
53
|
+
expect(wrapper.find('.rs-switch--checked').exists()).toBe(false)
|
|
54
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
55
|
+
await nextTick()
|
|
56
|
+
expect((wrapper.vm as { flag: string }).flag).toBe('Y')
|
|
57
|
+
expect(wrapper.find('.rs-switch--checked').exists()).toBe(true)
|
|
58
|
+
|
|
59
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
60
|
+
await nextTick()
|
|
61
|
+
expect((wrapper.vm as { flag: string }).flag).toBe('N')
|
|
62
|
+
expect(wrapper.find('.rs-switch--checked').exists()).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('treats numeric 1 / 0 as checked and unchecked', async () => {
|
|
66
|
+
const wrapper = mount(RsSwitch, {
|
|
67
|
+
props: { modelValue: 0, checkedValue: 1, uncheckedValue: 0 },
|
|
68
|
+
})
|
|
69
|
+
expect(wrapper.find('.rs-switch--checked').exists()).toBe(false)
|
|
70
|
+
await wrapper.find('.rs-switch__root').trigger('click')
|
|
71
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([1])
|
|
72
|
+
expect(wrapper.emitted('change')?.[0]).toEqual([1])
|
|
73
|
+
})
|
|
74
|
+
})
|