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,158 @@
|
|
|
1
|
+
import { defineComponent, h, ref } from 'vue'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
4
|
+
import RsConfigProvider from '../components/RsConfigProvider.vue'
|
|
5
|
+
import RsConfirmDialog from '../components/RsConfirmDialog.vue'
|
|
6
|
+
|
|
7
|
+
describe('RsConfirmDialog', () => {
|
|
8
|
+
it('renders custom title and description when open', async () => {
|
|
9
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
10
|
+
props: {
|
|
11
|
+
open: true,
|
|
12
|
+
title: '删除项目?',
|
|
13
|
+
description: '此操作不可撤销。',
|
|
14
|
+
},
|
|
15
|
+
attachTo: document.body,
|
|
16
|
+
})
|
|
17
|
+
await flushPromises()
|
|
18
|
+
const content = document.body.querySelector('.rs-confirm-dialog__content')
|
|
19
|
+
expect(content?.textContent).toContain('删除项目?')
|
|
20
|
+
expect(content?.textContent).toContain('此操作不可撤销。')
|
|
21
|
+
wrapper.unmount()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('falls back to zh-CN i18n defaults', async () => {
|
|
25
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
26
|
+
props: { open: true },
|
|
27
|
+
attachTo: document.body,
|
|
28
|
+
})
|
|
29
|
+
await flushPromises()
|
|
30
|
+
const content = document.body.querySelector('.rs-confirm-dialog__content')
|
|
31
|
+
expect(content?.textContent).toContain('确认操作?')
|
|
32
|
+
expect(content?.textContent).toContain('此操作可能无法撤销')
|
|
33
|
+
wrapper.unmount()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('uses en-US defaults inside RsConfigProvider', async () => {
|
|
37
|
+
const Host = defineComponent({
|
|
38
|
+
components: { RsConfirmDialog, RsConfigProvider },
|
|
39
|
+
setup() {
|
|
40
|
+
return () =>
|
|
41
|
+
h(RsConfigProvider, { locale: 'en-US' }, {
|
|
42
|
+
default: () => h(RsConfirmDialog, { open: true }),
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
47
|
+
await flushPromises()
|
|
48
|
+
const content = document.body.querySelector('.rs-confirm-dialog__content')
|
|
49
|
+
expect(content?.textContent).toContain('Confirm action?')
|
|
50
|
+
expect(content?.textContent).toContain('This action may not be reversible')
|
|
51
|
+
wrapper.unmount()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('applies tone class to icon', async () => {
|
|
55
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
56
|
+
props: { open: true, tone: 'warning' },
|
|
57
|
+
attachTo: document.body,
|
|
58
|
+
})
|
|
59
|
+
await flushPromises()
|
|
60
|
+
const icon = document.body.querySelector('.rs-confirm-dialog__icon')
|
|
61
|
+
expect(icon?.classList.contains('rs-confirm-dialog__icon--warning')).toBe(true)
|
|
62
|
+
wrapper.unmount()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('hides overlay by default', async () => {
|
|
66
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
67
|
+
props: { open: true, title: '无遮罩' },
|
|
68
|
+
attachTo: document.body,
|
|
69
|
+
})
|
|
70
|
+
await flushPromises()
|
|
71
|
+
expect(document.body.querySelector('.rs-confirm-dialog__overlay')).toBeNull()
|
|
72
|
+
wrapper.unmount()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('shows overlay when showOverlay is true', async () => {
|
|
76
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
77
|
+
props: { open: true, title: '有遮罩', showOverlay: true },
|
|
78
|
+
attachTo: document.body,
|
|
79
|
+
})
|
|
80
|
+
await flushPromises()
|
|
81
|
+
expect(document.body.querySelector('.rs-confirm-dialog__overlay')).not.toBeNull()
|
|
82
|
+
wrapper.unmount()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('enables pointer events on content when open (reka-ui 2.9.10)', async () => {
|
|
86
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
87
|
+
props: { open: true, title: '测试' },
|
|
88
|
+
attachTo: document.body,
|
|
89
|
+
})
|
|
90
|
+
await flushPromises()
|
|
91
|
+
const content = document.body.querySelector('.rs-confirm-dialog__content') as HTMLElement | null
|
|
92
|
+
expect(content?.style.pointerEvents).toBe('auto')
|
|
93
|
+
wrapper.unmount()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('emits confirm and closes dialog', async () => {
|
|
97
|
+
const Host = defineComponent({
|
|
98
|
+
components: { RsConfirmDialog },
|
|
99
|
+
setup() {
|
|
100
|
+
const open = ref(true)
|
|
101
|
+
return { open }
|
|
102
|
+
},
|
|
103
|
+
template: `
|
|
104
|
+
<RsConfirmDialog
|
|
105
|
+
v-model:open="open"
|
|
106
|
+
title="保存更改?"
|
|
107
|
+
@confirm="$emit('confirmed')"
|
|
108
|
+
/>
|
|
109
|
+
`,
|
|
110
|
+
})
|
|
111
|
+
const wrapper = mount(Host, { attachTo: document.body })
|
|
112
|
+
await flushPromises()
|
|
113
|
+
const buttons = document.body.querySelectorAll('.rs-confirm-dialog__footer button')
|
|
114
|
+
const confirmBtn = buttons[buttons.length - 1] as HTMLElement
|
|
115
|
+
await confirmBtn.click()
|
|
116
|
+
await flushPromises()
|
|
117
|
+
expect(wrapper.emitted('confirmed')).toHaveLength(1)
|
|
118
|
+
expect(wrapper.findComponent(RsConfirmDialog).props('open')).toBe(false)
|
|
119
|
+
wrapper.unmount()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('emits cancel', async () => {
|
|
123
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
124
|
+
props: {
|
|
125
|
+
open: true,
|
|
126
|
+
title: '离开页面?',
|
|
127
|
+
onCancel: () => undefined,
|
|
128
|
+
},
|
|
129
|
+
attachTo: document.body,
|
|
130
|
+
})
|
|
131
|
+
await flushPromises()
|
|
132
|
+
const buttons = document.body.querySelectorAll('.rs-confirm-dialog__footer button')
|
|
133
|
+
const cancelBtn = buttons[0] as HTMLElement
|
|
134
|
+
await cancelBtn.click()
|
|
135
|
+
await flushPromises()
|
|
136
|
+
expect(wrapper.emitted('cancel')).toHaveLength(1)
|
|
137
|
+
wrapper.unmount()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('mounts portal into custom target via teleportTo', async () => {
|
|
141
|
+
const target = document.createElement('div')
|
|
142
|
+
target.id = 'rs-confirm-target'
|
|
143
|
+
document.body.appendChild(target)
|
|
144
|
+
|
|
145
|
+
const wrapper = mount(RsConfirmDialog, {
|
|
146
|
+
props: {
|
|
147
|
+
open: true,
|
|
148
|
+
title: '挂载目标',
|
|
149
|
+
teleportTo: '#rs-confirm-target',
|
|
150
|
+
},
|
|
151
|
+
attachTo: document.body,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await flushPromises()
|
|
155
|
+
expect(target.querySelector('.rs-confirm-dialog__content')).not.toBeNull()
|
|
156
|
+
wrapper.unmount()
|
|
157
|
+
})
|
|
158
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import RsContainer from '../components/RsContainer.vue'
|
|
4
|
+
|
|
5
|
+
const maxWidths = ['sm', 'md', 'lg', 'xl', 'full'] as const
|
|
6
|
+
const paddings = ['none', 'sm', 'md', 'lg'] as const
|
|
7
|
+
|
|
8
|
+
describe('RsContainer', () => {
|
|
9
|
+
it('renders slot content', () => {
|
|
10
|
+
const wrapper = mount(RsContainer, { slots: { default: '页面内容' } })
|
|
11
|
+
expect(wrapper.text()).toBe('页面内容')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('renders as div with base class by default', () => {
|
|
15
|
+
const wrapper = mount(RsContainer)
|
|
16
|
+
expect(wrapper.element.tagName).toBe('DIV')
|
|
17
|
+
expect(wrapper.classes()).toContain('rs-container')
|
|
18
|
+
expect(wrapper.classes()).toContain('rs-container--centered')
|
|
19
|
+
expect(wrapper.attributes('style')).toContain('--rs-container-max-current: var(--rs-container-max-lg);')
|
|
20
|
+
expect(wrapper.attributes('style')).toContain('--rs-container-padding-current: var(--rs-space-md);')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it.each(maxWidths)('applies %s max-width class', (maxWidth) => {
|
|
24
|
+
const wrapper = mount(RsContainer, { props: { maxWidth } })
|
|
25
|
+
expect(wrapper.attributes('style')).toContain(`--rs-container-max-current: var(--rs-container-max-${maxWidth});`)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it.each(paddings)('applies %s padding class', (padding) => {
|
|
29
|
+
const wrapper = mount(RsContainer, { props: { padding } })
|
|
30
|
+
const expected = padding === 'none' ? '0' : `var(--rs-space-${padding})`
|
|
31
|
+
expect(wrapper.attributes('style')).toContain(`--rs-container-padding-current: ${expected};`)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('applies fluid style', () => {
|
|
35
|
+
const wrapper = mount(RsContainer, { props: { fluid: true } })
|
|
36
|
+
expect(wrapper.attributes('style')).toContain('--rs-container-max-current: none;')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('omits centered class when centered is false', () => {
|
|
40
|
+
const wrapper = mount(RsContainer, { props: { centered: false } })
|
|
41
|
+
expect(wrapper.classes()).not.toContain('rs-container--centered')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders custom root tag', () => {
|
|
45
|
+
const wrapper = mount(RsContainer, { props: { tag: 'section' } })
|
|
46
|
+
expect(wrapper.element.tagName).toBe('SECTION')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('supports responsive maxWidth and padding', () => {
|
|
50
|
+
const wrapper = mount(RsContainer, {
|
|
51
|
+
props: {
|
|
52
|
+
maxWidth: { sm: 'sm', md: 'md', lg: 'xl' },
|
|
53
|
+
padding: { sm: 'sm', md: 'md', lg: 'lg' },
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
const style = wrapper.attributes('style')
|
|
57
|
+
expect(style).toContain('--rs-container-max-sm: var(--rs-container-max-sm);')
|
|
58
|
+
expect(style).toContain('--rs-container-max-md: var(--rs-container-max-md);')
|
|
59
|
+
expect(style).toContain('--rs-container-max-lg: var(--rs-container-max-xl);')
|
|
60
|
+
expect(style).toContain('--rs-container-padding-sm: var(--rs-space-sm);')
|
|
61
|
+
expect(style).toContain('--rs-container-padding-md: var(--rs-space-md);')
|
|
62
|
+
expect(style).toContain('--rs-container-padding-lg: var(--rs-space-lg);')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('enables grid layout with default columns and gap', () => {
|
|
66
|
+
const wrapper = mount(RsContainer, { props: { grid: true } })
|
|
67
|
+
expect(wrapper.classes()).toContain('rs-container--grid')
|
|
68
|
+
const style = wrapper.attributes('style')
|
|
69
|
+
expect(style).toContain('--rs-container-columns-current: 12;')
|
|
70
|
+
expect(style).toContain('--rs-container-gap-current: var(--rs-space-md);')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('supports responsive columns and gap', () => {
|
|
74
|
+
const wrapper = mount(RsContainer, {
|
|
75
|
+
props: {
|
|
76
|
+
grid: true,
|
|
77
|
+
columns: { sm: 4, md: 8, lg: 12 },
|
|
78
|
+
gap: { sm: 'xs', md: 'sm', lg: 'lg' },
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
const style = wrapper.attributes('style')
|
|
82
|
+
expect(style).toContain('--rs-container-columns-sm: 4;')
|
|
83
|
+
expect(style).toContain('--rs-container-columns-md: 8;')
|
|
84
|
+
expect(style).toContain('--rs-container-columns-lg: 12;')
|
|
85
|
+
expect(style).toContain('--rs-container-gap-sm: var(--rs-space-xs);')
|
|
86
|
+
expect(style).toContain('--rs-container-gap-md: var(--rs-space-sm);')
|
|
87
|
+
expect(style).toContain('--rs-container-gap-lg: var(--rs-space-lg);')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
|
+
import RsContextMenu from '../components/RsContextMenu.vue'
|
|
4
|
+
|
|
5
|
+
describe('RsContextMenu', () => {
|
|
6
|
+
const items = [
|
|
7
|
+
{ key: 'open', label: '打开', icon: 'folder-open' },
|
|
8
|
+
{ key: 'rename', label: '重命名', icon: 'pen-line' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
it('opens menu on contextmenu', async () => {
|
|
12
|
+
const wrapper = mount(RsContextMenu, {
|
|
13
|
+
props: { items },
|
|
14
|
+
slots: { default: '<div class="trigger">Right click</div>' },
|
|
15
|
+
attachTo: document.body,
|
|
16
|
+
})
|
|
17
|
+
expect(document.body.querySelector('.rs-context-menu__content')).toBeNull()
|
|
18
|
+
await wrapper.find('.trigger').trigger('contextmenu')
|
|
19
|
+
await flushPromises()
|
|
20
|
+
expect(document.body.querySelector('.rs-context-menu__content')).not.toBeNull()
|
|
21
|
+
wrapper.unmount()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('emits select with item key', async () => {
|
|
25
|
+
const wrapper = mount(RsContextMenu, {
|
|
26
|
+
props: { items },
|
|
27
|
+
slots: { default: '<div class="trigger">Right click</div>' },
|
|
28
|
+
attachTo: document.body,
|
|
29
|
+
})
|
|
30
|
+
await wrapper.find('.trigger').trigger('contextmenu')
|
|
31
|
+
await flushPromises()
|
|
32
|
+
const menuItems = document.body.querySelectorAll('.rs-context-menu__item')
|
|
33
|
+
;(menuItems[1] as HTMLElement).click()
|
|
34
|
+
await flushPromises()
|
|
35
|
+
expect(wrapper.emitted('select')?.[0]).toEqual(['rename'])
|
|
36
|
+
wrapper.unmount()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('renders separator and danger item', async () => {
|
|
40
|
+
const wrapper = mount(RsContextMenu, {
|
|
41
|
+
props: {
|
|
42
|
+
items: [
|
|
43
|
+
{ key: 'edit', label: '编辑' },
|
|
44
|
+
{ key: 'sep', label: '', separator: true },
|
|
45
|
+
{ key: 'delete', label: '删除', danger: true },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
slots: { default: '<div class="trigger">Right click</div>' },
|
|
49
|
+
attachTo: document.body,
|
|
50
|
+
})
|
|
51
|
+
await wrapper.find('.trigger').trigger('contextmenu')
|
|
52
|
+
await flushPromises()
|
|
53
|
+
expect(document.body.querySelector('.rs-context-menu__separator')).not.toBeNull()
|
|
54
|
+
const danger = document.body.querySelector('.rs-context-menu__item--danger')
|
|
55
|
+
expect(danger?.textContent).toContain('删除')
|
|
56
|
+
wrapper.unmount()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('renders submenu trigger', async () => {
|
|
60
|
+
const wrapper = mount(RsContextMenu, {
|
|
61
|
+
props: {
|
|
62
|
+
items: [
|
|
63
|
+
{
|
|
64
|
+
key: 'share',
|
|
65
|
+
label: '分享',
|
|
66
|
+
children: [{ key: 'link', label: '复制链接' }],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
slots: { default: '<div class="trigger">Right click</div>' },
|
|
71
|
+
attachTo: document.body,
|
|
72
|
+
})
|
|
73
|
+
await wrapper.find('.trigger').trigger('contextmenu')
|
|
74
|
+
await flushPromises()
|
|
75
|
+
expect(document.body.textContent).toContain('分享')
|
|
76
|
+
wrapper.unmount()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('renders deep nested submenu triggers', async () => {
|
|
80
|
+
const wrapper = mount(RsContextMenu, {
|
|
81
|
+
props: {
|
|
82
|
+
items: [
|
|
83
|
+
{
|
|
84
|
+
key: 'move',
|
|
85
|
+
label: '移动到',
|
|
86
|
+
children: [
|
|
87
|
+
{
|
|
88
|
+
key: 'workspace',
|
|
89
|
+
label: '工作区',
|
|
90
|
+
children: [
|
|
91
|
+
{
|
|
92
|
+
key: 'archive',
|
|
93
|
+
label: '归档',
|
|
94
|
+
children: [{ key: 'y2026', label: '2026' }],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
...Array.from({ length: 20 }, (_, i) => ({
|
|
101
|
+
key: `item-${i}`,
|
|
102
|
+
label: `项 ${i}`,
|
|
103
|
+
})),
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
slots: { default: '<div class="trigger">Right click</div>' },
|
|
107
|
+
attachTo: document.body,
|
|
108
|
+
})
|
|
109
|
+
await wrapper.find('.trigger').trigger('contextmenu')
|
|
110
|
+
await flushPromises()
|
|
111
|
+
const content = document.body.querySelector('.rs-context-menu__content')
|
|
112
|
+
expect(content).not.toBeNull()
|
|
113
|
+
expect(content?.classList.contains('rs-native-scrollbar')).toBe(true)
|
|
114
|
+
expect(document.body.textContent).toContain('移动到')
|
|
115
|
+
expect(document.body.textContent).toContain('项 19')
|
|
116
|
+
wrapper.unmount()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
|
+
import RsDatePicker from '../components/RsDatePicker.vue'
|
|
4
|
+
|
|
5
|
+
describe('RsDatePicker', () => {
|
|
6
|
+
it('renders trigger button with placeholder', () => {
|
|
7
|
+
const wrapper = mount(RsDatePicker, { props: { modelValue: '' } })
|
|
8
|
+
expect(wrapper.find('.rs-date-picker__trigger').exists()).toBe(true)
|
|
9
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toContain('选择日期')
|
|
10
|
+
expect(wrapper.classes()).toContain('rs-field')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('shows formatted value on trigger when model is set', () => {
|
|
14
|
+
const wrapper = mount(RsDatePicker, {
|
|
15
|
+
props: { modelValue: '2025-06-16' },
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.find('.rs-date-picker__trigger--placeholder').exists()).toBe(false)
|
|
18
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toBe('2025-06-16')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('renders label, hint, and required marker', () => {
|
|
22
|
+
const wrapper = mount(RsDatePicker, {
|
|
23
|
+
props: {
|
|
24
|
+
modelValue: '',
|
|
25
|
+
label: '生效日期',
|
|
26
|
+
hint: '请选择日期',
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
expect(wrapper.find('.rs-field__label').text()).toContain('生效日期')
|
|
31
|
+
expect(wrapper.find('.rs-field__required').exists()).toBe(true)
|
|
32
|
+
expect(wrapper.find('.rs-field__hint').text()).toBe('请选择日期')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('applies left label layout class', () => {
|
|
36
|
+
const wrapper = mount(RsDatePicker, {
|
|
37
|
+
props: { modelValue: '', labelPosition: 'left' },
|
|
38
|
+
})
|
|
39
|
+
expect(wrapper.classes()).toContain('rs-field--label-left')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('uses single trigger for range mode', () => {
|
|
43
|
+
const wrapper = mount(RsDatePicker, {
|
|
44
|
+
props: { modelValue: { start: '', end: '' }, range: true },
|
|
45
|
+
})
|
|
46
|
+
expect(wrapper.findAll('.rs-date-picker__trigger')).toHaveLength(1)
|
|
47
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toContain('选择日期范围')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('shows range label when range model is set', () => {
|
|
51
|
+
const wrapper = mount(RsDatePicker, {
|
|
52
|
+
props: {
|
|
53
|
+
modelValue: { start: '2025-06-01', end: '2025-06-30' },
|
|
54
|
+
range: true,
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toContain('2025')
|
|
58
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toContain('~')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('confirms single selection from popover', async () => {
|
|
62
|
+
const wrapper = mount(RsDatePicker, {
|
|
63
|
+
props: { modelValue: '', open: true },
|
|
64
|
+
attachTo: document.body,
|
|
65
|
+
})
|
|
66
|
+
await flushPromises()
|
|
67
|
+
|
|
68
|
+
const dayCell = document.body.querySelector('.rs-calendar-grid__cell:not([disabled])') as HTMLElement
|
|
69
|
+
dayCell?.click()
|
|
70
|
+
await flushPromises()
|
|
71
|
+
|
|
72
|
+
const confirmBtn = Array.from(document.body.querySelectorAll('.rs-date-picker__confirm')).find(
|
|
73
|
+
(el) => !el.hasAttribute('disabled'),
|
|
74
|
+
) as HTMLElement | undefined
|
|
75
|
+
confirmBtn?.click()
|
|
76
|
+
await flushPromises()
|
|
77
|
+
|
|
78
|
+
expect(wrapper.emitted('update:modelValue')?.length).toBeGreaterThan(0)
|
|
79
|
+
wrapper.unmount()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('disables trigger when disabled', () => {
|
|
83
|
+
const wrapper = mount(RsDatePicker, {
|
|
84
|
+
props: { modelValue: '2025-06-16', disabled: true },
|
|
85
|
+
})
|
|
86
|
+
expect(wrapper.find('.rs-date-picker__trigger').attributes('disabled')).toBeDefined()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
|
+
import RsDateTimePicker from '../components/RsDateTimePicker.vue'
|
|
4
|
+
|
|
5
|
+
describe('RsDateTimePicker', () => {
|
|
6
|
+
it('renders popover trigger with datetime placeholder', () => {
|
|
7
|
+
const wrapper = mount(RsDateTimePicker, { props: { modelValue: '' } })
|
|
8
|
+
expect(wrapper.find('.rs-date-picker__trigger').exists()).toBe(true)
|
|
9
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toContain('选择日期时间')
|
|
10
|
+
expect(wrapper.classes()).toContain('rs-field')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('shows formatted datetime on trigger when model is set', () => {
|
|
14
|
+
const wrapper = mount(RsDateTimePicker, {
|
|
15
|
+
props: { modelValue: '2025-06-16 14:30:00' },
|
|
16
|
+
})
|
|
17
|
+
expect(wrapper.find('.rs-date-picker__trigger--placeholder').exists()).toBe(false)
|
|
18
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toBe('2025-06-16 14:30:00')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('renders label, hint, and required marker', () => {
|
|
22
|
+
const wrapper = mount(RsDateTimePicker, {
|
|
23
|
+
props: {
|
|
24
|
+
modelValue: '',
|
|
25
|
+
label: '截止时间',
|
|
26
|
+
hint: '请选择日期时间',
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
expect(wrapper.find('.rs-field__label').text()).toContain('截止时间')
|
|
31
|
+
expect(wrapper.find('.rs-field__required').exists()).toBe(true)
|
|
32
|
+
expect(wrapper.find('.rs-field__hint').text()).toBe('请选择日期时间')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('applies left label layout class', () => {
|
|
36
|
+
const wrapper = mount(RsDateTimePicker, {
|
|
37
|
+
props: { modelValue: '', labelPosition: 'left' },
|
|
38
|
+
})
|
|
39
|
+
expect(wrapper.classes()).toContain('rs-field--label-left')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('uses single trigger for range mode', () => {
|
|
43
|
+
const wrapper = mount(RsDateTimePicker, {
|
|
44
|
+
props: { modelValue: { start: '', end: '' }, range: true },
|
|
45
|
+
})
|
|
46
|
+
expect(wrapper.findAll('.rs-date-picker__trigger')).toHaveLength(1)
|
|
47
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toContain('选择日期时间范围')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('opens panel with calendar and embedded time picker', async () => {
|
|
51
|
+
const wrapper = mount(RsDateTimePicker, {
|
|
52
|
+
props: { modelValue: '', open: true },
|
|
53
|
+
attachTo: document.body,
|
|
54
|
+
})
|
|
55
|
+
await flushPromises()
|
|
56
|
+
expect(document.body.querySelector('.rs-date-picker__content')).not.toBeNull()
|
|
57
|
+
expect(document.body.querySelector('.rs-date-picker__time-row')).not.toBeNull()
|
|
58
|
+
expect(document.body.querySelector('.rs-time-picker__trigger--embedded')).not.toBeNull()
|
|
59
|
+
expect(document.body.querySelector('.rs-time-columns')).toBeNull()
|
|
60
|
+
wrapper.unmount()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('disables trigger when disabled', () => {
|
|
64
|
+
const wrapper = mount(RsDateTimePicker, {
|
|
65
|
+
props: { modelValue: '2025-06-16 14:30:00', disabled: true },
|
|
66
|
+
})
|
|
67
|
+
expect(wrapper.find('.rs-date-picker__trigger').attributes('disabled')).toBeDefined()
|
|
68
|
+
})
|
|
69
|
+
})
|