niuma-ui 1.0.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 +29 -0
- package/LICENSE +202 -0
- package/NOTICE +25 -0
- package/README.en.md +99 -0
- package/README.md +161 -0
- package/package.json +106 -0
- package/src/__tests__/RsBadge.spec.ts +40 -0
- package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
- package/src/__tests__/RsButton.spec.ts +108 -0
- package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
- package/src/__tests__/RsCard.spec.ts +97 -0
- package/src/__tests__/RsCheckbox.spec.ts +36 -0
- package/src/__tests__/RsCodeEditor.spec.ts +111 -0
- package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
- package/src/__tests__/RsContainer.spec.ts +89 -0
- package/src/__tests__/RsContextMenu.spec.ts +118 -0
- package/src/__tests__/RsDatePicker.spec.ts +88 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
- package/src/__tests__/RsDialog.spec.ts +404 -0
- package/src/__tests__/RsDrawer.spec.ts +145 -0
- package/src/__tests__/RsDropdown.spec.ts +135 -0
- package/src/__tests__/RsEmpty.spec.ts +99 -0
- package/src/__tests__/RsForm.spec.ts +173 -0
- package/src/__tests__/RsIcon.spec.ts +244 -0
- package/src/__tests__/RsInput.spec.ts +269 -0
- package/src/__tests__/RsInputNumber.spec.ts +118 -0
- package/src/__tests__/RsLabel.spec.ts +106 -0
- package/src/__tests__/RsLink.spec.ts +49 -0
- package/src/__tests__/RsLoading.spec.ts +84 -0
- package/src/__tests__/RsMarkdown.spec.ts +143 -0
- package/src/__tests__/RsMenu.spec.ts +119 -0
- package/src/__tests__/RsNewComponents.spec.ts +17 -0
- package/src/__tests__/RsPagination.spec.ts +158 -0
- package/src/__tests__/RsPopover.spec.ts +126 -0
- package/src/__tests__/RsScrollbar.spec.ts +53 -0
- package/src/__tests__/RsSelect.spec.ts +291 -0
- package/src/__tests__/RsSidebar.spec.ts +119 -0
- package/src/__tests__/RsSplitPane.spec.ts +177 -0
- package/src/__tests__/RsStatCard.spec.ts +79 -0
- package/src/__tests__/RsSteps.spec.ts +125 -0
- package/src/__tests__/RsTable.perf.spec.ts +103 -0
- package/src/__tests__/RsTable.spec.ts +1135 -0
- package/src/__tests__/RsTabs.spec.ts +328 -0
- package/src/__tests__/RsTerminal.spec.ts +218 -0
- package/src/__tests__/RsTimePicker.spec.ts +77 -0
- package/src/__tests__/RsToaster.spec.ts +95 -0
- package/src/__tests__/RsToolbar.spec.ts +75 -0
- package/src/__tests__/RsTooltip.spec.ts +111 -0
- package/src/__tests__/RsTree.spec.ts +572 -0
- package/src/__tests__/RsUpload.spec.ts +160 -0
- package/src/__tests__/RsVirtualList.spec.ts +77 -0
- package/src/__tests__/date-picker-utils.spec.ts +70 -0
- package/src/__tests__/dialog-viewport.spec.ts +38 -0
- package/src/__tests__/iso-local-datetime.spec.ts +58 -0
- package/src/__tests__/rs-clipboard.spec.ts +142 -0
- package/src/__tests__/setup.ts +12 -0
- package/src/__tests__/split-pane-utils.spec.ts +136 -0
- package/src/__tests__/table-drag.spec.ts +35 -0
- package/src/__tests__/table-edit-utils.spec.ts +194 -0
- package/src/__tests__/table-utils.spec.ts +286 -0
- package/src/__tests__/theme.spec.ts +22 -0
- package/src/__tests__/tree-utils.spec.ts +234 -0
- package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
- package/src/components/RsAvatar.vue +115 -0
- package/src/components/RsBadge.vue +59 -0
- package/src/components/RsBreadcrumb.vue +65 -0
- package/src/components/RsBreadcrumbItems.vue +36 -0
- package/src/components/RsButton.vue +393 -0
- package/src/components/RsCalendarGrid.vue +284 -0
- package/src/components/RsCard.vue +237 -0
- package/src/components/RsCheckbox.vue +189 -0
- package/src/components/RsCodeBlock.vue +278 -0
- package/src/components/RsCodeEditor.vue +597 -0
- package/src/components/RsConfigProvider.vue +92 -0
- package/src/components/RsConfirmDialog.vue +178 -0
- package/src/components/RsContainer.vue +235 -0
- package/src/components/RsContextMenu.vue +238 -0
- package/src/components/RsContextMenuItems.vue +77 -0
- package/src/components/RsDatePicker.vue +756 -0
- package/src/components/RsDateTimePicker.vue +42 -0
- package/src/components/RsDialog.vue +486 -0
- package/src/components/RsDrawer.vue +175 -0
- package/src/components/RsDropdown.vue +252 -0
- package/src/components/RsDropdownItems.vue +90 -0
- package/src/components/RsEmpty.vue +107 -0
- package/src/components/RsForm.vue +131 -0
- package/src/components/RsIcon.vue +104 -0
- package/src/components/RsInput.vue +1101 -0
- package/src/components/RsInputNumber.vue +532 -0
- package/src/components/RsLabel.vue +54 -0
- package/src/components/RsLink.vue +106 -0
- package/src/components/RsLoading.vue +171 -0
- package/src/components/RsMarkdown.vue +329 -0
- package/src/components/RsMenu.vue +256 -0
- package/src/components/RsMenuItems.vue +125 -0
- package/src/components/RsMonacoEditor.vue +541 -0
- package/src/components/RsPagination.vue +184 -0
- package/src/components/RsPopover.vue +77 -0
- package/src/components/RsProseEditor.vue +160 -0
- package/src/components/RsScrollbar.vue +171 -0
- package/src/components/RsSelect.vue +1045 -0
- package/src/components/RsSidebar.vue +121 -0
- package/src/components/RsSidebarGroup.vue +32 -0
- package/src/components/RsSidebarItem.vue +72 -0
- package/src/components/RsSplitPane.vue +619 -0
- package/src/components/RsStatCard.vue +85 -0
- package/src/components/RsSteps.vue +138 -0
- package/src/components/RsTable.vue +2664 -0
- package/src/components/RsTableColgroup.vue +17 -0
- package/src/components/RsTableHeaderRow.vue +30 -0
- package/src/components/RsTabs.vue +951 -0
- package/src/components/RsTerminal.vue +697 -0
- package/src/components/RsTimePicker.vue +554 -0
- package/src/components/RsTimePickerColumns.vue +226 -0
- package/src/components/RsToaster.vue +396 -0
- package/src/components/RsToolbar.vue +135 -0
- package/src/components/RsTooltip.vue +142 -0
- package/src/components/RsTooltipProvider.vue +20 -0
- package/src/components/RsTree.vue +1248 -0
- package/src/components/RsUpload.vue +279 -0
- package/src/components/RsVirtualList.vue +217 -0
- package/src/components/avatar-utils.ts +19 -0
- package/src/components/breadcrumb-utils.ts +19 -0
- package/src/components/class-utils.ts +29 -0
- package/src/components/code-editor-utils.ts +106 -0
- package/src/components/code-mirror-completion.ts +40 -0
- package/src/components/code-mirror-diagnostics.ts +52 -0
- package/src/components/code-mirror-ghost.ts +158 -0
- package/src/components/code-mirror-inline-edit.ts +55 -0
- package/src/components/code-mirror-intel.ts +81 -0
- package/src/components/code-mirror-lang.ts +128 -0
- package/src/components/context-menu-utils.ts +20 -0
- package/src/components/date-picker-utils.ts +284 -0
- package/src/components/date-range-validation.ts +29 -0
- package/src/components/date-validation.ts +38 -0
- package/src/components/dialog-viewport.ts +48 -0
- package/src/components/dialog-window.ts +253 -0
- package/src/components/dropdown-utils.ts +31 -0
- package/src/components/form-utils.ts +77 -0
- package/src/components/input-number-utils.ts +128 -0
- package/src/components/input-rules.ts +84 -0
- package/src/components/loading-utils.ts +8 -0
- package/src/components/markdown-utils.ts +126 -0
- package/src/components/menu-utils.ts +48 -0
- package/src/components/overlay-utils.ts +47 -0
- package/src/components/pagination-utils.ts +38 -0
- package/src/components/popover-utils.ts +8 -0
- package/src/components/reka.ts +106 -0
- package/src/components/resolve-radius.ts +22 -0
- package/src/components/resolve-size.ts +29 -0
- package/src/components/scrollbar-utils.ts +10 -0
- package/src/components/select-utils.ts +72 -0
- package/src/components/split-pane-utils.ts +187 -0
- package/src/components/steps-utils.ts +22 -0
- package/src/components/table/RsTableBodyRow.vue +287 -0
- package/src/components/table/RsTableCell.vue +239 -0
- package/src/components/table/RsTableCellContent.ts +18 -0
- package/src/components/table/RsTableCellEditor.vue +351 -0
- package/src/components/table/RsTableEditGutter.vue +54 -0
- package/src/components/table/RsTableHeaderFilter.vue +107 -0
- package/src/components/table/RsTableRowStatus.vue +69 -0
- package/src/components/table/rs-table.css +1141 -0
- package/src/components/table/table-context-menu.ts +53 -0
- package/src/components/table/table-edit-utils.ts +496 -0
- package/src/components/table/table-keys.ts +12 -0
- package/src/components/table-drag.ts +215 -0
- package/src/components/table-utils.ts +950 -0
- package/src/components/tabs-utils.ts +107 -0
- package/src/components/terminal-utils.ts +301 -0
- package/src/components/terminal-wheel.ts +49 -0
- package/src/components/time-picker-utils.ts +167 -0
- package/src/components/time-range-validation.ts +23 -0
- package/src/components/time-validation.ts +20 -0
- package/src/components/tree-utils.ts +518 -0
- package/src/components/upload-utils.ts +55 -0
- package/src/components/virtual-list-utils.ts +17 -0
- package/src/composables/useClickOutside.ts +22 -0
- package/src/composables/useRsConfig.ts +91 -0
- package/src/composables/useRsI18n.ts +22 -0
- package/src/composables/useRsTabsNav.ts +187 -0
- package/src/composables/useRsToast.ts +48 -0
- package/src/composables/useTableCellTooltip.ts +239 -0
- package/src/composables/useTableEdit.ts +271 -0
- package/src/composables/useTableRowHighlight.ts +50 -0
- package/src/dev/vite-codemirror-deps.ts +20 -0
- package/src/icons/custom/api.ts +54 -0
- package/src/icons/custom/clickhouse.ts +52 -0
- package/src/icons/custom/dameng.ts +83 -0
- package/src/icons/custom/ftp.ts +61 -0
- package/src/icons/custom/kingbase.ts +52 -0
- package/src/icons/custom/mongodb.ts +50 -0
- package/src/icons/custom/mysql.ts +51 -0
- package/src/icons/custom/oracle.ts +52 -0
- package/src/icons/custom/redis.ts +51 -0
- package/src/icons/custom/sqlite.ts +50 -0
- package/src/icons/custom/sqlserver.ts +52 -0
- package/src/icons/custom/vastbase.ts +57 -0
- package/src/icons/lucide.ts +78 -0
- package/src/icons/registry.ts +35 -0
- package/src/index.ts +425 -0
- package/src/lib/iso-local-datetime.ts +152 -0
- package/src/lib/rs-dayjs.ts +56 -0
- package/src/locale/messages.ts +362 -0
- package/src/locale/types.ts +5 -0
- package/src/monaco/debug-decorations.css +20 -0
- package/src/monaco/debug-decorations.ts +70 -0
- package/src/monaco/index.ts +22 -0
- package/src/monaco/languages/index.ts +21 -0
- package/src/monaco/languages/mongodb-shell.ts +125 -0
- package/src/monaco/languages/types.ts +55 -0
- package/src/monaco/setup.ts +101 -0
- package/src/styles.css +1040 -0
- package/src/theme/apply.ts +12 -0
- package/src/theme/brand.example.css +37 -0
- package/src/theme/presets.ts +69 -0
- package/src/theme/types.ts +81 -0
- package/src/utils/rs-clipboard.ts +272 -0
- package/src/vite-env.d.ts +7 -0
- package/vite-plugins/monaco-zh-nls.ts +101 -0
- package/vite-plugins/silence-antlr-parse-console.ts +29 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { defineComponent, h, ref } from 'vue'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
4
|
+
import RsConfigProvider from '../components/RsConfigProvider.vue'
|
|
5
|
+
import RsDialog from '../components/RsDialog.vue'
|
|
6
|
+
|
|
7
|
+
async function flushAnimationFrames(count = 2): Promise<void> {
|
|
8
|
+
for (let i = 0; i < count; i += 1) {
|
|
9
|
+
await new Promise<void>((resolve) => {
|
|
10
|
+
requestAnimationFrame(() => resolve())
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('RsDialog', () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
document.body.innerHTML = ''
|
|
18
|
+
})
|
|
19
|
+
it('renders title, description, and body slot when open', async () => {
|
|
20
|
+
const wrapper = mount(RsDialog, {
|
|
21
|
+
props: {
|
|
22
|
+
open: true,
|
|
23
|
+
title: '编辑成员',
|
|
24
|
+
description: '修改角色与权限。',
|
|
25
|
+
deferBodyMount: false,
|
|
26
|
+
},
|
|
27
|
+
slots: { body: '<p class="slot-body">正文内容</p>' },
|
|
28
|
+
attachTo: document.body,
|
|
29
|
+
})
|
|
30
|
+
await flushPromises()
|
|
31
|
+
const content = document.body.querySelector('.rs-dialog__content')
|
|
32
|
+
expect(content?.textContent).toContain('编辑成员')
|
|
33
|
+
expect(content?.textContent).toContain('修改角色与权限。')
|
|
34
|
+
expect(document.body.querySelector('.slot-body')?.textContent).toBe('正文内容')
|
|
35
|
+
wrapper.unmount()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('defers body slot mount until after paint in window layout', async () => {
|
|
39
|
+
const wrapper = mount(RsDialog, {
|
|
40
|
+
props: { open: true, title: '延后挂载' },
|
|
41
|
+
slots: { body: '<p class="slot-body">正文内容</p>' },
|
|
42
|
+
attachTo: document.body,
|
|
43
|
+
})
|
|
44
|
+
expect(document.body.querySelector('.slot-body')).toBeNull()
|
|
45
|
+
|
|
46
|
+
await flushAnimationFrames()
|
|
47
|
+
await flushPromises()
|
|
48
|
+
expect(document.body.querySelector('.slot-body')?.textContent).toBe('正文内容')
|
|
49
|
+
wrapper.unmount()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('destroys deferred body content when closed', async () => {
|
|
53
|
+
const Host = defineComponent({
|
|
54
|
+
components: { RsDialog },
|
|
55
|
+
setup() {
|
|
56
|
+
const open = ref(true)
|
|
57
|
+
return { open }
|
|
58
|
+
},
|
|
59
|
+
template: '<RsDialog v-model:open="open" title="关闭销毁"><template #body><p class="slot-body">正文</p></template></RsDialog>',
|
|
60
|
+
})
|
|
61
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
62
|
+
await flushAnimationFrames()
|
|
63
|
+
await flushPromises()
|
|
64
|
+
expect(document.body.querySelector('.slot-body')).not.toBeNull()
|
|
65
|
+
|
|
66
|
+
await wrapper.findComponent(RsDialog).setValue(false, 'open')
|
|
67
|
+
await flushPromises()
|
|
68
|
+
expect(document.body.querySelector('.slot-body')).toBeNull()
|
|
69
|
+
wrapper.unmount()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('applies window layout with resize handles by default', async () => {
|
|
73
|
+
const wrapper = mount(RsDialog, {
|
|
74
|
+
props: { open: true, title: '测试', width: 'sm' },
|
|
75
|
+
attachTo: document.body,
|
|
76
|
+
})
|
|
77
|
+
await flushPromises()
|
|
78
|
+
const content = document.body.querySelector('.rs-dialog__content')
|
|
79
|
+
expect(content?.classList.contains('rs-dialog__content--window')).toBe(true)
|
|
80
|
+
expect(content?.classList.contains('rs-dialog__content--sm')).toBe(true)
|
|
81
|
+
expect(document.body.querySelectorAll('.rs-dialog__resize-handle').length).toBe(8)
|
|
82
|
+
wrapper.unmount()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('applies confirm layout when explicitly set', async () => {
|
|
86
|
+
const wrapper = mount(RsDialog, {
|
|
87
|
+
props: { open: true, title: '测试', width: 'sm', layout: 'confirm', resizable: false, fullscreenable: false },
|
|
88
|
+
attachTo: document.body,
|
|
89
|
+
})
|
|
90
|
+
await flushPromises()
|
|
91
|
+
const content = document.body.querySelector('.rs-dialog__content')
|
|
92
|
+
expect(content?.classList.contains('rs-dialog__content--confirm')).toBe(true)
|
|
93
|
+
expect(content?.classList.contains('rs-dialog__content--sm')).toBe(true)
|
|
94
|
+
wrapper.unmount()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('renders footer slot', async () => {
|
|
98
|
+
const wrapper = mount(RsDialog, {
|
|
99
|
+
props: { open: true, title: '保存' },
|
|
100
|
+
slots: { footer: '<button type="button" class="footer-save">保存</button>' },
|
|
101
|
+
attachTo: document.body,
|
|
102
|
+
})
|
|
103
|
+
await flushPromises()
|
|
104
|
+
expect(document.body.querySelector('.rs-dialog__footer .footer-save')?.textContent).toBe('保存')
|
|
105
|
+
wrapper.unmount()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('closes when header close button is clicked', async () => {
|
|
109
|
+
const Host = defineComponent({
|
|
110
|
+
components: { RsDialog },
|
|
111
|
+
setup() {
|
|
112
|
+
const open = ref(true)
|
|
113
|
+
return { open }
|
|
114
|
+
},
|
|
115
|
+
template: '<RsDialog v-model:open="open" title="关闭测试" :fullscreenable="false" />',
|
|
116
|
+
})
|
|
117
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
118
|
+
await flushPromises()
|
|
119
|
+
const closeBtn = document.body.querySelector('.rs-dialog__actions button') as HTMLElement
|
|
120
|
+
await closeBtn.click()
|
|
121
|
+
await flushPromises()
|
|
122
|
+
expect(wrapper.findComponent(RsDialog).props('open')).toBe(false)
|
|
123
|
+
wrapper.unmount()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('hides overlay by default', async () => {
|
|
127
|
+
const wrapper = mount(RsDialog, {
|
|
128
|
+
props: { open: true, title: '无遮罩' },
|
|
129
|
+
attachTo: document.body,
|
|
130
|
+
})
|
|
131
|
+
await flushPromises()
|
|
132
|
+
expect(document.body.querySelector('.rs-dialog__overlay')).toBeNull()
|
|
133
|
+
wrapper.unmount()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('shows overlay when showOverlay is true', async () => {
|
|
137
|
+
const wrapper = mount(RsDialog, {
|
|
138
|
+
props: { open: true, title: '有遮罩', showOverlay: true },
|
|
139
|
+
attachTo: document.body,
|
|
140
|
+
})
|
|
141
|
+
await flushPromises()
|
|
142
|
+
expect(document.body.querySelector('.rs-dialog__overlay')).not.toBeNull()
|
|
143
|
+
wrapper.unmount()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('hides close button when showClose is false', async () => {
|
|
147
|
+
const wrapper = mount(RsDialog, {
|
|
148
|
+
props: { open: true, title: '无关闭', showClose: false, fullscreenable: false },
|
|
149
|
+
attachTo: document.body,
|
|
150
|
+
})
|
|
151
|
+
await flushPromises()
|
|
152
|
+
expect(document.body.querySelectorAll('.rs-dialog__actions button').length).toBe(0)
|
|
153
|
+
wrapper.unmount()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('applies window layout class and inline size style', async () => {
|
|
157
|
+
const wrapper = mount(RsDialog, {
|
|
158
|
+
props: {
|
|
159
|
+
open: true,
|
|
160
|
+
title: '窗口',
|
|
161
|
+
layout: 'window',
|
|
162
|
+
draggable: true,
|
|
163
|
+
resizable: true,
|
|
164
|
+
},
|
|
165
|
+
attachTo: document.body,
|
|
166
|
+
})
|
|
167
|
+
await flushPromises()
|
|
168
|
+
const content = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
169
|
+
expect(content.classList.contains('rs-dialog__content--window')).toBe(true)
|
|
170
|
+
expect(content.style.width).not.toBe('')
|
|
171
|
+
expect(content.style.height).not.toBe('')
|
|
172
|
+
wrapper.unmount()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('renders resize handles in resizable window mode', async () => {
|
|
176
|
+
const wrapper = mount(RsDialog, {
|
|
177
|
+
props: {
|
|
178
|
+
open: true,
|
|
179
|
+
title: '可缩放',
|
|
180
|
+
layout: 'window',
|
|
181
|
+
resizable: true,
|
|
182
|
+
},
|
|
183
|
+
attachTo: document.body,
|
|
184
|
+
})
|
|
185
|
+
await flushPromises()
|
|
186
|
+
expect(document.body.querySelectorAll('.rs-dialog__resize-handle').length).toBe(8)
|
|
187
|
+
wrapper.unmount()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('shows fullscreen toggle only in window layout with fullscreenable', async () => {
|
|
191
|
+
const wrapper = mount(RsDialog, {
|
|
192
|
+
props: {
|
|
193
|
+
open: true,
|
|
194
|
+
title: '全屏',
|
|
195
|
+
layout: 'window',
|
|
196
|
+
fullscreenable: true,
|
|
197
|
+
},
|
|
198
|
+
attachTo: document.body,
|
|
199
|
+
})
|
|
200
|
+
await flushPromises()
|
|
201
|
+
const actionButtons = document.body.querySelectorAll('.rs-dialog__actions button')
|
|
202
|
+
expect(actionButtons.length).toBe(2)
|
|
203
|
+
wrapper.unmount()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('toggles fullscreen class and restores window bounds', async () => {
|
|
207
|
+
const wrapper = mount(RsDialog, {
|
|
208
|
+
props: {
|
|
209
|
+
open: true,
|
|
210
|
+
title: '全屏',
|
|
211
|
+
layout: 'window',
|
|
212
|
+
fullscreenable: true,
|
|
213
|
+
},
|
|
214
|
+
attachTo: document.body,
|
|
215
|
+
})
|
|
216
|
+
await flushPromises()
|
|
217
|
+
const content = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
218
|
+
const fullscreenBtn = document.body.querySelectorAll('.rs-dialog__actions button')[0] as HTMLElement
|
|
219
|
+
const beforeWidth = content.style.width
|
|
220
|
+
const beforeHeight = content.style.height
|
|
221
|
+
|
|
222
|
+
await fullscreenBtn.click()
|
|
223
|
+
await flushPromises()
|
|
224
|
+
expect(content.classList.contains('rs-dialog__content--fullscreen')).toBe(true)
|
|
225
|
+
expect(content.style.width).toContain('calc')
|
|
226
|
+
expect(content.classList.contains('rs-dialog__content--bounds-transition')).toBe(false)
|
|
227
|
+
|
|
228
|
+
await fullscreenBtn.click()
|
|
229
|
+
await flushPromises()
|
|
230
|
+
expect(content.classList.contains('rs-dialog__content--fullscreen')).toBe(false)
|
|
231
|
+
expect(content.style.width).toBe(beforeWidth)
|
|
232
|
+
expect(content.style.height).toBe(beforeHeight)
|
|
233
|
+
wrapper.unmount()
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('enables bounds transition when boundsTransition is true', async () => {
|
|
237
|
+
const wrapper = mount(RsDialog, {
|
|
238
|
+
props: {
|
|
239
|
+
open: true,
|
|
240
|
+
title: '全屏动画',
|
|
241
|
+
layout: 'window',
|
|
242
|
+
fullscreenable: true,
|
|
243
|
+
boundsTransition: true,
|
|
244
|
+
},
|
|
245
|
+
attachTo: document.body,
|
|
246
|
+
})
|
|
247
|
+
await flushPromises()
|
|
248
|
+
const content = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
249
|
+
const fullscreenBtn = document.body.querySelectorAll('.rs-dialog__actions button')[0] as HTMLElement
|
|
250
|
+
|
|
251
|
+
await fullscreenBtn.click()
|
|
252
|
+
await flushPromises()
|
|
253
|
+
expect(content.classList.contains('rs-dialog__content--bounds-transition')).toBe(true)
|
|
254
|
+
wrapper.unmount()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('moves window when draggable header is dragged', async () => {
|
|
258
|
+
const wrapper = mount(RsDialog, {
|
|
259
|
+
props: {
|
|
260
|
+
open: true,
|
|
261
|
+
title: '拖拽',
|
|
262
|
+
layout: 'window',
|
|
263
|
+
draggable: true,
|
|
264
|
+
resizable: true,
|
|
265
|
+
},
|
|
266
|
+
attachTo: document.body,
|
|
267
|
+
})
|
|
268
|
+
await flushPromises()
|
|
269
|
+
const content = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
270
|
+
const header = document.body.querySelector('.rs-dialog__header') as HTMLElement
|
|
271
|
+
expect(content.classList.contains('rs-dialog__content--draggable')).toBe(true)
|
|
272
|
+
const startLeft = content.style.left
|
|
273
|
+
const startTop = content.style.top
|
|
274
|
+
|
|
275
|
+
header.dispatchEvent(
|
|
276
|
+
new PointerEvent('pointerdown', { clientX: 120, clientY: 80, bubbles: true, cancelable: true }),
|
|
277
|
+
)
|
|
278
|
+
window.dispatchEvent(
|
|
279
|
+
new PointerEvent('pointermove', { clientX: 180, clientY: 120, bubbles: true, cancelable: true }),
|
|
280
|
+
)
|
|
281
|
+
window.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, cancelable: true }))
|
|
282
|
+
await flushPromises()
|
|
283
|
+
|
|
284
|
+
expect(content.style.left).not.toBe(startLeft)
|
|
285
|
+
expect(content.style.top).not.toBe(startTop)
|
|
286
|
+
wrapper.unmount()
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('does not move window when draggable is false', async () => {
|
|
290
|
+
const wrapper = mount(RsDialog, {
|
|
291
|
+
props: {
|
|
292
|
+
open: true,
|
|
293
|
+
title: '不可拖',
|
|
294
|
+
layout: 'window',
|
|
295
|
+
draggable: false,
|
|
296
|
+
},
|
|
297
|
+
attachTo: document.body,
|
|
298
|
+
})
|
|
299
|
+
await flushPromises()
|
|
300
|
+
const content = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
301
|
+
const header = document.body.querySelector('.rs-dialog__header') as HTMLElement
|
|
302
|
+
const startLeft = content.style.left
|
|
303
|
+
const startTop = content.style.top
|
|
304
|
+
|
|
305
|
+
header.dispatchEvent(
|
|
306
|
+
new PointerEvent('pointerdown', { clientX: 120, clientY: 80, bubbles: true, cancelable: true }),
|
|
307
|
+
)
|
|
308
|
+
window.dispatchEvent(
|
|
309
|
+
new PointerEvent('pointermove', { clientX: 180, clientY: 120, bubbles: true, cancelable: true }),
|
|
310
|
+
)
|
|
311
|
+
window.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, cancelable: true }))
|
|
312
|
+
await flushPromises()
|
|
313
|
+
|
|
314
|
+
expect(content.style.left).toBe(startLeft)
|
|
315
|
+
expect(content.style.top).toBe(startTop)
|
|
316
|
+
wrapper.unmount()
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('applies tone modifier class', async () => {
|
|
320
|
+
const wrapper = mount(RsDialog, {
|
|
321
|
+
props: { open: true, title: '警告', tone: 'warning' },
|
|
322
|
+
attachTo: document.body,
|
|
323
|
+
})
|
|
324
|
+
await flushPromises()
|
|
325
|
+
const content = document.body.querySelector('.rs-dialog__content')
|
|
326
|
+
expect(content?.classList.contains('rs-dialog__content--tone-warning')).toBe(true)
|
|
327
|
+
wrapper.unmount()
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('uses en-US close tooltip inside RsConfigProvider', async () => {
|
|
331
|
+
const Host = defineComponent({
|
|
332
|
+
components: { RsDialog, RsConfigProvider },
|
|
333
|
+
setup() {
|
|
334
|
+
return () =>
|
|
335
|
+
h(RsConfigProvider, { locale: 'en-US' }, {
|
|
336
|
+
default: () => h(RsDialog, { open: true, title: 'Dialog', fullscreenable: false }),
|
|
337
|
+
})
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
341
|
+
await flushPromises()
|
|
342
|
+
const closeBtn = document.body.querySelector('.rs-dialog__actions button') as HTMLElement
|
|
343
|
+
expect(closeBtn.querySelector('.rs-btn__tooltip')?.textContent).toContain('Close')
|
|
344
|
+
wrapper.unmount()
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('mounts portal into custom target via teleportTo', async () => {
|
|
348
|
+
const target = document.createElement('div')
|
|
349
|
+
target.id = 'rs-dialog-target'
|
|
350
|
+
document.body.appendChild(target)
|
|
351
|
+
|
|
352
|
+
const wrapper = mount(RsDialog, {
|
|
353
|
+
props: {
|
|
354
|
+
open: true,
|
|
355
|
+
title: '挂载目标',
|
|
356
|
+
teleportTo: '#rs-dialog-target',
|
|
357
|
+
},
|
|
358
|
+
attachTo: document.body,
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
await flushPromises()
|
|
362
|
+
expect(target.querySelector('.rs-dialog__content')).not.toBeNull()
|
|
363
|
+
wrapper.unmount()
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('passes modal=false to DialogRoot for non-modal usage', async () => {
|
|
367
|
+
const wrapper = mount(RsDialog, {
|
|
368
|
+
props: {
|
|
369
|
+
open: true,
|
|
370
|
+
title: '非模态',
|
|
371
|
+
modal: false,
|
|
372
|
+
},
|
|
373
|
+
attachTo: document.body,
|
|
374
|
+
})
|
|
375
|
+
await flushPromises()
|
|
376
|
+
const root = wrapper.findComponent({ name: 'DialogRoot' })
|
|
377
|
+
expect(root.props('modal')).toBe(false)
|
|
378
|
+
wrapper.unmount()
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('prevents dismiss on outside interact for non-modal when closeOnOverlayClick is false', async () => {
|
|
382
|
+
const wrapper = mount(RsDialog, {
|
|
383
|
+
props: {
|
|
384
|
+
open: true,
|
|
385
|
+
title: '非模态',
|
|
386
|
+
modal: false,
|
|
387
|
+
closeOnOverlayClick: false,
|
|
388
|
+
},
|
|
389
|
+
attachTo: document.body,
|
|
390
|
+
})
|
|
391
|
+
await flushPromises()
|
|
392
|
+
const content = wrapper.findComponent({ name: 'DialogContent' })
|
|
393
|
+
const vnodeProps = content.vm.$.vnode.props as Record<string, unknown> | null
|
|
394
|
+
const onOutside =
|
|
395
|
+
(vnodeProps?.onPointerDownOutside as ((e: Event) => void) | undefined) ??
|
|
396
|
+
(vnodeProps?.['onPointer-down-outside'] as ((e: Event) => void) | undefined)
|
|
397
|
+
expect(typeof onOutside).toBe('function')
|
|
398
|
+
const event = new Event('pointerdown', { cancelable: true })
|
|
399
|
+
onOutside!(event)
|
|
400
|
+
expect(event.defaultPrevented).toBe(true)
|
|
401
|
+
expect(wrapper.props('open')).toBe(true)
|
|
402
|
+
wrapper.unmount()
|
|
403
|
+
})
|
|
404
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { defineComponent, h, ref } from 'vue'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
4
|
+
import RsConfigProvider from '../components/RsConfigProvider.vue'
|
|
5
|
+
import RsDrawer from '../components/RsDrawer.vue'
|
|
6
|
+
|
|
7
|
+
describe('RsDrawer', () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
document.body.innerHTML = ''
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders title, description, and default slot when open', async () => {
|
|
13
|
+
const wrapper = mount(RsDrawer, {
|
|
14
|
+
props: {
|
|
15
|
+
open: true,
|
|
16
|
+
title: '筛选条件',
|
|
17
|
+
description: '按状态与时间过滤。',
|
|
18
|
+
},
|
|
19
|
+
slots: { default: '<p class="slot-body">正文内容</p>' },
|
|
20
|
+
attachTo: document.body,
|
|
21
|
+
})
|
|
22
|
+
await flushPromises()
|
|
23
|
+
const content = document.body.querySelector('.rs-drawer__content')
|
|
24
|
+
expect(content?.textContent).toContain('筛选条件')
|
|
25
|
+
expect(content?.textContent).toContain('按状态与时间过滤。')
|
|
26
|
+
expect(document.body.querySelector('.slot-body')?.textContent).toBe('正文内容')
|
|
27
|
+
wrapper.unmount()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('applies default right side and md size classes', async () => {
|
|
31
|
+
const wrapper = mount(RsDrawer, {
|
|
32
|
+
props: { open: true, title: '测试' },
|
|
33
|
+
attachTo: document.body,
|
|
34
|
+
})
|
|
35
|
+
await flushPromises()
|
|
36
|
+
const content = document.body.querySelector('.rs-drawer__content')
|
|
37
|
+
expect(content?.classList.contains('rs-drawer__content--right')).toBe(true)
|
|
38
|
+
expect(content?.classList.contains('rs-drawer__content--md')).toBe(true)
|
|
39
|
+
wrapper.unmount()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('applies custom side and size classes', async () => {
|
|
43
|
+
const wrapper = mount(RsDrawer, {
|
|
44
|
+
props: { open: true, title: '左侧', side: 'left', size: 'lg' },
|
|
45
|
+
attachTo: document.body,
|
|
46
|
+
})
|
|
47
|
+
await flushPromises()
|
|
48
|
+
const content = document.body.querySelector('.rs-drawer__content')
|
|
49
|
+
expect(content?.classList.contains('rs-drawer__content--left')).toBe(true)
|
|
50
|
+
expect(content?.classList.contains('rs-drawer__content--lg')).toBe(true)
|
|
51
|
+
wrapper.unmount()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('renders footer slot', async () => {
|
|
55
|
+
const wrapper = mount(RsDrawer, {
|
|
56
|
+
props: { open: true, title: '保存' },
|
|
57
|
+
slots: { footer: '<button type="button" class="footer-save">保存</button>' },
|
|
58
|
+
attachTo: document.body,
|
|
59
|
+
})
|
|
60
|
+
await flushPromises()
|
|
61
|
+
expect(document.body.querySelector('.rs-drawer__footer .footer-save')?.textContent).toBe('保存')
|
|
62
|
+
wrapper.unmount()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('renders custom header slot', async () => {
|
|
66
|
+
const wrapper = mount(RsDrawer, {
|
|
67
|
+
props: { open: true },
|
|
68
|
+
slots: {
|
|
69
|
+
header: '<div class="custom-header">自定义标题</div>',
|
|
70
|
+
default: '正文',
|
|
71
|
+
},
|
|
72
|
+
attachTo: document.body,
|
|
73
|
+
})
|
|
74
|
+
await flushPromises()
|
|
75
|
+
expect(document.body.querySelector('.custom-header')?.textContent).toBe('自定义标题')
|
|
76
|
+
expect(document.body.querySelector('.rs-drawer__title')).toBeNull()
|
|
77
|
+
wrapper.unmount()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('closes when header close button is clicked', async () => {
|
|
81
|
+
const Host = defineComponent({
|
|
82
|
+
components: { RsDrawer },
|
|
83
|
+
setup() {
|
|
84
|
+
const open = ref(true)
|
|
85
|
+
return { open }
|
|
86
|
+
},
|
|
87
|
+
template: '<RsDrawer v-model:open="open" title="关闭测试" />',
|
|
88
|
+
})
|
|
89
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
90
|
+
await flushPromises()
|
|
91
|
+
const closeBtn = document.body.querySelector('.rs-drawer__header button') as HTMLElement
|
|
92
|
+
await closeBtn.click()
|
|
93
|
+
await flushPromises()
|
|
94
|
+
expect(wrapper.findComponent(RsDrawer).props('open')).toBe(false)
|
|
95
|
+
wrapper.unmount()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('hides overlay when showOverlay is false', async () => {
|
|
99
|
+
const wrapper = mount(RsDrawer, {
|
|
100
|
+
props: { open: true, title: '无遮罩', showOverlay: false },
|
|
101
|
+
attachTo: document.body,
|
|
102
|
+
})
|
|
103
|
+
await flushPromises()
|
|
104
|
+
expect(document.body.querySelector('.rs-drawer__overlay')).toBeNull()
|
|
105
|
+
wrapper.unmount()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('hides close button when showClose is false', async () => {
|
|
109
|
+
const wrapper = mount(RsDrawer, {
|
|
110
|
+
props: { open: true, title: '无关闭', showClose: false },
|
|
111
|
+
attachTo: document.body,
|
|
112
|
+
})
|
|
113
|
+
await flushPromises()
|
|
114
|
+
expect(document.body.querySelectorAll('.rs-drawer__header button').length).toBe(0)
|
|
115
|
+
wrapper.unmount()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('omits header when no title, description, close, or header slot', async () => {
|
|
119
|
+
const wrapper = mount(RsDrawer, {
|
|
120
|
+
props: { open: true, showClose: false },
|
|
121
|
+
slots: { default: '仅正文' },
|
|
122
|
+
attachTo: document.body,
|
|
123
|
+
})
|
|
124
|
+
await flushPromises()
|
|
125
|
+
expect(document.body.querySelector('.rs-drawer__header')).toBeNull()
|
|
126
|
+
wrapper.unmount()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('uses en-US close tooltip inside RsConfigProvider', async () => {
|
|
130
|
+
const Host = defineComponent({
|
|
131
|
+
components: { RsDrawer, RsConfigProvider },
|
|
132
|
+
setup() {
|
|
133
|
+
return () =>
|
|
134
|
+
h(RsConfigProvider, { locale: 'en-US' }, {
|
|
135
|
+
default: () => h(RsDrawer, { open: true, title: 'Drawer' }),
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
140
|
+
await flushPromises()
|
|
141
|
+
const closeBtn = document.body.querySelector('.rs-drawer__header button') as HTMLElement
|
|
142
|
+
expect(closeBtn.querySelector('.rs-btn__tooltip')?.textContent).toContain('Close')
|
|
143
|
+
wrapper.unmount()
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
|
+
import RsDropdown from '../components/RsDropdown.vue'
|
|
4
|
+
|
|
5
|
+
describe('RsDropdown', () => {
|
|
6
|
+
const items = [
|
|
7
|
+
{ label: '对话', value: 'chat', icon: 'message-square' as const },
|
|
8
|
+
{ label: '编程', value: 'code', icon: 'folder' as const },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const groupedItems = [
|
|
12
|
+
{
|
|
13
|
+
label: '创作',
|
|
14
|
+
options: [
|
|
15
|
+
{ label: '对话', value: 'chat', icon: 'message-square' as const },
|
|
16
|
+
{ label: '编程', value: 'code', icon: 'folder' as const },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: '检索',
|
|
21
|
+
options: [{ label: '知识库', value: 'kb', icon: 'search' as const }],
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
it('opens menu on trigger click', async () => {
|
|
26
|
+
const wrapper = mount(RsDropdown, {
|
|
27
|
+
props: { items, modelValue: 'chat' },
|
|
28
|
+
attachTo: document.body,
|
|
29
|
+
})
|
|
30
|
+
expect(document.body.querySelector('.rs-dropdown__content')).toBeNull()
|
|
31
|
+
await wrapper.find('.rs-dropdown__trigger').trigger('click')
|
|
32
|
+
await flushPromises()
|
|
33
|
+
expect(document.body.querySelector('.rs-dropdown__content')).not.toBeNull()
|
|
34
|
+
wrapper.unmount()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('emits select when flat item chosen', async () => {
|
|
38
|
+
const wrapper = mount(RsDropdown, {
|
|
39
|
+
props: { items, modelValue: 'chat' },
|
|
40
|
+
attachTo: document.body,
|
|
41
|
+
})
|
|
42
|
+
await wrapper.find('.rs-dropdown__trigger').trigger('click')
|
|
43
|
+
await flushPromises()
|
|
44
|
+
const menuItems = document.body.querySelectorAll('.rs-dropdown__item')
|
|
45
|
+
;(menuItems[1] as HTMLElement).click()
|
|
46
|
+
await flushPromises()
|
|
47
|
+
expect(wrapper.emitted('update:modelValue')?.pop()).toEqual(['code'])
|
|
48
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['code'])
|
|
49
|
+
wrapper.unmount()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('renders grouped labels and nested items', async () => {
|
|
53
|
+
const wrapper = mount(RsDropdown, {
|
|
54
|
+
props: { items: groupedItems, modelValue: 'chat' },
|
|
55
|
+
attachTo: document.body,
|
|
56
|
+
})
|
|
57
|
+
await wrapper.find('.rs-dropdown__trigger').trigger('click')
|
|
58
|
+
await flushPromises()
|
|
59
|
+
const labels = document.body.querySelectorAll('.rs-dropdown__group-label')
|
|
60
|
+
expect(labels).toHaveLength(2)
|
|
61
|
+
expect(labels[0]?.textContent).toContain('创作')
|
|
62
|
+
expect(labels[1]?.textContent).toContain('检索')
|
|
63
|
+
expect(document.body.querySelectorAll('.rs-dropdown__item')).toHaveLength(3)
|
|
64
|
+
wrapper.unmount()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('emits select when grouped item chosen', async () => {
|
|
68
|
+
const wrapper = mount(RsDropdown, {
|
|
69
|
+
props: { items: groupedItems, modelValue: 'chat' },
|
|
70
|
+
attachTo: document.body,
|
|
71
|
+
})
|
|
72
|
+
await wrapper.find('.rs-dropdown__trigger').trigger('click')
|
|
73
|
+
await flushPromises()
|
|
74
|
+
const menuItems = document.body.querySelectorAll('.rs-dropdown__item')
|
|
75
|
+
;(menuItems[2] as HTMLElement).click()
|
|
76
|
+
await flushPromises()
|
|
77
|
+
expect(wrapper.emitted('update:modelValue')?.pop()).toEqual(['kb'])
|
|
78
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['kb'])
|
|
79
|
+
wrapper.unmount()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('shows selected label from grouped options', () => {
|
|
83
|
+
const wrapper = mount(RsDropdown, {
|
|
84
|
+
props: { items: groupedItems, modelValue: 'kb' },
|
|
85
|
+
})
|
|
86
|
+
expect(wrapper.find('.rs-dropdown__label').text()).toBe('知识库')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('shows placeholder when value not in items', () => {
|
|
90
|
+
const wrapper = mount(RsDropdown, {
|
|
91
|
+
props: { items: groupedItems, modelValue: '', placeholder: '选择模式' },
|
|
92
|
+
})
|
|
93
|
+
expect(wrapper.find('.rs-dropdown__label').text()).toBe('选择模式')
|
|
94
|
+
expect(wrapper.find('.rs-dropdown__label--placeholder').exists()).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('keeps trigger label when showSelected is false', async () => {
|
|
98
|
+
const wrapper = mount(RsDropdown, {
|
|
99
|
+
props: {
|
|
100
|
+
items,
|
|
101
|
+
modelValue: 'chat',
|
|
102
|
+
showSelected: false,
|
|
103
|
+
placeholder: '更多操作',
|
|
104
|
+
},
|
|
105
|
+
attachTo: document.body,
|
|
106
|
+
})
|
|
107
|
+
expect(wrapper.find('.rs-dropdown__label').text()).toBe('更多操作')
|
|
108
|
+
|
|
109
|
+
await wrapper.find('.rs-dropdown__trigger').trigger('click')
|
|
110
|
+
await flushPromises()
|
|
111
|
+
const menuItems = document.body.querySelectorAll('.rs-dropdown__item')
|
|
112
|
+
;(menuItems[1] as HTMLElement).click()
|
|
113
|
+
await flushPromises()
|
|
114
|
+
|
|
115
|
+
expect(wrapper.find('.rs-dropdown__label').text()).toBe('更多操作')
|
|
116
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['code'])
|
|
117
|
+
wrapper.unmount()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('does not mark checked state in action mode', async () => {
|
|
121
|
+
const wrapper = mount(RsDropdown, {
|
|
122
|
+
props: {
|
|
123
|
+
items,
|
|
124
|
+
modelValue: 'chat',
|
|
125
|
+
showSelected: false,
|
|
126
|
+
placeholder: '操作',
|
|
127
|
+
},
|
|
128
|
+
attachTo: document.body,
|
|
129
|
+
})
|
|
130
|
+
await wrapper.find('.rs-dropdown__trigger').trigger('click')
|
|
131
|
+
await flushPromises()
|
|
132
|
+
expect(document.body.querySelector('.rs-dropdown__item[data-state="checked"]')).toBeNull()
|
|
133
|
+
wrapper.unmount()
|
|
134
|
+
})
|
|
135
|
+
})
|