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.
Files changed (220) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +25 -0
  4. package/README.en.md +99 -0
  5. package/README.md +161 -0
  6. package/package.json +106 -0
  7. package/src/__tests__/RsBadge.spec.ts +40 -0
  8. package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
  9. package/src/__tests__/RsButton.spec.ts +108 -0
  10. package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
  11. package/src/__tests__/RsCard.spec.ts +97 -0
  12. package/src/__tests__/RsCheckbox.spec.ts +36 -0
  13. package/src/__tests__/RsCodeEditor.spec.ts +111 -0
  14. package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
  15. package/src/__tests__/RsContainer.spec.ts +89 -0
  16. package/src/__tests__/RsContextMenu.spec.ts +118 -0
  17. package/src/__tests__/RsDatePicker.spec.ts +88 -0
  18. package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
  19. package/src/__tests__/RsDialog.spec.ts +404 -0
  20. package/src/__tests__/RsDrawer.spec.ts +145 -0
  21. package/src/__tests__/RsDropdown.spec.ts +135 -0
  22. package/src/__tests__/RsEmpty.spec.ts +99 -0
  23. package/src/__tests__/RsForm.spec.ts +173 -0
  24. package/src/__tests__/RsIcon.spec.ts +244 -0
  25. package/src/__tests__/RsInput.spec.ts +269 -0
  26. package/src/__tests__/RsInputNumber.spec.ts +118 -0
  27. package/src/__tests__/RsLabel.spec.ts +106 -0
  28. package/src/__tests__/RsLink.spec.ts +49 -0
  29. package/src/__tests__/RsLoading.spec.ts +84 -0
  30. package/src/__tests__/RsMarkdown.spec.ts +143 -0
  31. package/src/__tests__/RsMenu.spec.ts +119 -0
  32. package/src/__tests__/RsNewComponents.spec.ts +17 -0
  33. package/src/__tests__/RsPagination.spec.ts +158 -0
  34. package/src/__tests__/RsPopover.spec.ts +126 -0
  35. package/src/__tests__/RsScrollbar.spec.ts +53 -0
  36. package/src/__tests__/RsSelect.spec.ts +291 -0
  37. package/src/__tests__/RsSidebar.spec.ts +119 -0
  38. package/src/__tests__/RsSplitPane.spec.ts +177 -0
  39. package/src/__tests__/RsStatCard.spec.ts +79 -0
  40. package/src/__tests__/RsSteps.spec.ts +125 -0
  41. package/src/__tests__/RsTable.perf.spec.ts +103 -0
  42. package/src/__tests__/RsTable.spec.ts +1135 -0
  43. package/src/__tests__/RsTabs.spec.ts +328 -0
  44. package/src/__tests__/RsTerminal.spec.ts +218 -0
  45. package/src/__tests__/RsTimePicker.spec.ts +77 -0
  46. package/src/__tests__/RsToaster.spec.ts +95 -0
  47. package/src/__tests__/RsToolbar.spec.ts +75 -0
  48. package/src/__tests__/RsTooltip.spec.ts +111 -0
  49. package/src/__tests__/RsTree.spec.ts +572 -0
  50. package/src/__tests__/RsUpload.spec.ts +160 -0
  51. package/src/__tests__/RsVirtualList.spec.ts +77 -0
  52. package/src/__tests__/date-picker-utils.spec.ts +70 -0
  53. package/src/__tests__/dialog-viewport.spec.ts +38 -0
  54. package/src/__tests__/iso-local-datetime.spec.ts +58 -0
  55. package/src/__tests__/rs-clipboard.spec.ts +142 -0
  56. package/src/__tests__/setup.ts +12 -0
  57. package/src/__tests__/split-pane-utils.spec.ts +136 -0
  58. package/src/__tests__/table-drag.spec.ts +35 -0
  59. package/src/__tests__/table-edit-utils.spec.ts +194 -0
  60. package/src/__tests__/table-utils.spec.ts +286 -0
  61. package/src/__tests__/theme.spec.ts +22 -0
  62. package/src/__tests__/tree-utils.spec.ts +234 -0
  63. package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
  64. package/src/components/RsAvatar.vue +115 -0
  65. package/src/components/RsBadge.vue +59 -0
  66. package/src/components/RsBreadcrumb.vue +65 -0
  67. package/src/components/RsBreadcrumbItems.vue +36 -0
  68. package/src/components/RsButton.vue +393 -0
  69. package/src/components/RsCalendarGrid.vue +284 -0
  70. package/src/components/RsCard.vue +237 -0
  71. package/src/components/RsCheckbox.vue +189 -0
  72. package/src/components/RsCodeBlock.vue +278 -0
  73. package/src/components/RsCodeEditor.vue +597 -0
  74. package/src/components/RsConfigProvider.vue +92 -0
  75. package/src/components/RsConfirmDialog.vue +178 -0
  76. package/src/components/RsContainer.vue +235 -0
  77. package/src/components/RsContextMenu.vue +238 -0
  78. package/src/components/RsContextMenuItems.vue +77 -0
  79. package/src/components/RsDatePicker.vue +756 -0
  80. package/src/components/RsDateTimePicker.vue +42 -0
  81. package/src/components/RsDialog.vue +486 -0
  82. package/src/components/RsDrawer.vue +175 -0
  83. package/src/components/RsDropdown.vue +252 -0
  84. package/src/components/RsDropdownItems.vue +90 -0
  85. package/src/components/RsEmpty.vue +107 -0
  86. package/src/components/RsForm.vue +131 -0
  87. package/src/components/RsIcon.vue +104 -0
  88. package/src/components/RsInput.vue +1101 -0
  89. package/src/components/RsInputNumber.vue +532 -0
  90. package/src/components/RsLabel.vue +54 -0
  91. package/src/components/RsLink.vue +106 -0
  92. package/src/components/RsLoading.vue +171 -0
  93. package/src/components/RsMarkdown.vue +329 -0
  94. package/src/components/RsMenu.vue +256 -0
  95. package/src/components/RsMenuItems.vue +125 -0
  96. package/src/components/RsMonacoEditor.vue +541 -0
  97. package/src/components/RsPagination.vue +184 -0
  98. package/src/components/RsPopover.vue +77 -0
  99. package/src/components/RsProseEditor.vue +160 -0
  100. package/src/components/RsScrollbar.vue +171 -0
  101. package/src/components/RsSelect.vue +1045 -0
  102. package/src/components/RsSidebar.vue +121 -0
  103. package/src/components/RsSidebarGroup.vue +32 -0
  104. package/src/components/RsSidebarItem.vue +72 -0
  105. package/src/components/RsSplitPane.vue +619 -0
  106. package/src/components/RsStatCard.vue +85 -0
  107. package/src/components/RsSteps.vue +138 -0
  108. package/src/components/RsTable.vue +2664 -0
  109. package/src/components/RsTableColgroup.vue +17 -0
  110. package/src/components/RsTableHeaderRow.vue +30 -0
  111. package/src/components/RsTabs.vue +951 -0
  112. package/src/components/RsTerminal.vue +697 -0
  113. package/src/components/RsTimePicker.vue +554 -0
  114. package/src/components/RsTimePickerColumns.vue +226 -0
  115. package/src/components/RsToaster.vue +396 -0
  116. package/src/components/RsToolbar.vue +135 -0
  117. package/src/components/RsTooltip.vue +142 -0
  118. package/src/components/RsTooltipProvider.vue +20 -0
  119. package/src/components/RsTree.vue +1248 -0
  120. package/src/components/RsUpload.vue +279 -0
  121. package/src/components/RsVirtualList.vue +217 -0
  122. package/src/components/avatar-utils.ts +19 -0
  123. package/src/components/breadcrumb-utils.ts +19 -0
  124. package/src/components/class-utils.ts +29 -0
  125. package/src/components/code-editor-utils.ts +106 -0
  126. package/src/components/code-mirror-completion.ts +40 -0
  127. package/src/components/code-mirror-diagnostics.ts +52 -0
  128. package/src/components/code-mirror-ghost.ts +158 -0
  129. package/src/components/code-mirror-inline-edit.ts +55 -0
  130. package/src/components/code-mirror-intel.ts +81 -0
  131. package/src/components/code-mirror-lang.ts +128 -0
  132. package/src/components/context-menu-utils.ts +20 -0
  133. package/src/components/date-picker-utils.ts +284 -0
  134. package/src/components/date-range-validation.ts +29 -0
  135. package/src/components/date-validation.ts +38 -0
  136. package/src/components/dialog-viewport.ts +48 -0
  137. package/src/components/dialog-window.ts +253 -0
  138. package/src/components/dropdown-utils.ts +31 -0
  139. package/src/components/form-utils.ts +77 -0
  140. package/src/components/input-number-utils.ts +128 -0
  141. package/src/components/input-rules.ts +84 -0
  142. package/src/components/loading-utils.ts +8 -0
  143. package/src/components/markdown-utils.ts +126 -0
  144. package/src/components/menu-utils.ts +48 -0
  145. package/src/components/overlay-utils.ts +47 -0
  146. package/src/components/pagination-utils.ts +38 -0
  147. package/src/components/popover-utils.ts +8 -0
  148. package/src/components/reka.ts +106 -0
  149. package/src/components/resolve-radius.ts +22 -0
  150. package/src/components/resolve-size.ts +29 -0
  151. package/src/components/scrollbar-utils.ts +10 -0
  152. package/src/components/select-utils.ts +72 -0
  153. package/src/components/split-pane-utils.ts +187 -0
  154. package/src/components/steps-utils.ts +22 -0
  155. package/src/components/table/RsTableBodyRow.vue +287 -0
  156. package/src/components/table/RsTableCell.vue +239 -0
  157. package/src/components/table/RsTableCellContent.ts +18 -0
  158. package/src/components/table/RsTableCellEditor.vue +351 -0
  159. package/src/components/table/RsTableEditGutter.vue +54 -0
  160. package/src/components/table/RsTableHeaderFilter.vue +107 -0
  161. package/src/components/table/RsTableRowStatus.vue +69 -0
  162. package/src/components/table/rs-table.css +1141 -0
  163. package/src/components/table/table-context-menu.ts +53 -0
  164. package/src/components/table/table-edit-utils.ts +496 -0
  165. package/src/components/table/table-keys.ts +12 -0
  166. package/src/components/table-drag.ts +215 -0
  167. package/src/components/table-utils.ts +950 -0
  168. package/src/components/tabs-utils.ts +107 -0
  169. package/src/components/terminal-utils.ts +301 -0
  170. package/src/components/terminal-wheel.ts +49 -0
  171. package/src/components/time-picker-utils.ts +167 -0
  172. package/src/components/time-range-validation.ts +23 -0
  173. package/src/components/time-validation.ts +20 -0
  174. package/src/components/tree-utils.ts +518 -0
  175. package/src/components/upload-utils.ts +55 -0
  176. package/src/components/virtual-list-utils.ts +17 -0
  177. package/src/composables/useClickOutside.ts +22 -0
  178. package/src/composables/useRsConfig.ts +91 -0
  179. package/src/composables/useRsI18n.ts +22 -0
  180. package/src/composables/useRsTabsNav.ts +187 -0
  181. package/src/composables/useRsToast.ts +48 -0
  182. package/src/composables/useTableCellTooltip.ts +239 -0
  183. package/src/composables/useTableEdit.ts +271 -0
  184. package/src/composables/useTableRowHighlight.ts +50 -0
  185. package/src/dev/vite-codemirror-deps.ts +20 -0
  186. package/src/icons/custom/api.ts +54 -0
  187. package/src/icons/custom/clickhouse.ts +52 -0
  188. package/src/icons/custom/dameng.ts +83 -0
  189. package/src/icons/custom/ftp.ts +61 -0
  190. package/src/icons/custom/kingbase.ts +52 -0
  191. package/src/icons/custom/mongodb.ts +50 -0
  192. package/src/icons/custom/mysql.ts +51 -0
  193. package/src/icons/custom/oracle.ts +52 -0
  194. package/src/icons/custom/redis.ts +51 -0
  195. package/src/icons/custom/sqlite.ts +50 -0
  196. package/src/icons/custom/sqlserver.ts +52 -0
  197. package/src/icons/custom/vastbase.ts +57 -0
  198. package/src/icons/lucide.ts +78 -0
  199. package/src/icons/registry.ts +35 -0
  200. package/src/index.ts +425 -0
  201. package/src/lib/iso-local-datetime.ts +152 -0
  202. package/src/lib/rs-dayjs.ts +56 -0
  203. package/src/locale/messages.ts +362 -0
  204. package/src/locale/types.ts +5 -0
  205. package/src/monaco/debug-decorations.css +20 -0
  206. package/src/monaco/debug-decorations.ts +70 -0
  207. package/src/monaco/index.ts +22 -0
  208. package/src/monaco/languages/index.ts +21 -0
  209. package/src/monaco/languages/mongodb-shell.ts +125 -0
  210. package/src/monaco/languages/types.ts +55 -0
  211. package/src/monaco/setup.ts +101 -0
  212. package/src/styles.css +1040 -0
  213. package/src/theme/apply.ts +12 -0
  214. package/src/theme/brand.example.css +37 -0
  215. package/src/theme/presets.ts +69 -0
  216. package/src/theme/types.ts +81 -0
  217. package/src/utils/rs-clipboard.ts +272 -0
  218. package/src/vite-env.d.ts +7 -0
  219. package/vite-plugins/monaco-zh-nls.ts +101 -0
  220. package/vite-plugins/silence-antlr-parse-console.ts +29 -0
@@ -0,0 +1,95 @@
1
+ import { afterEach, describe, expect, it } from 'vitest'
2
+ import { flushPromises, mount } from '@vue/test-utils'
3
+ import RsToaster from '../components/RsToaster.vue'
4
+ import { RS_TOAST_DEFAULT_POSITION } from '../components/overlay-utils'
5
+ import { useRsToast } from '../composables/useRsToast'
6
+
7
+ describe('RsToaster', () => {
8
+ afterEach(() => {
9
+ document.body.innerHTML = ''
10
+ })
11
+
12
+ it('renders sonner toaster with rs-toaster class', async () => {
13
+ const wrapper = mount(RsToaster, { attachTo: document.body })
14
+ await flushPromises()
15
+ expect(document.body.querySelector('.rs-toaster')).toBeTruthy()
16
+ wrapper.unmount()
17
+ })
18
+
19
+ it('defaults position to top-center', () => {
20
+ const wrapper = mount(RsToaster)
21
+ expect(wrapper.props('position')).toBe(RS_TOAST_DEFAULT_POSITION)
22
+ wrapper.unmount()
23
+ })
24
+
25
+ it('defaults closeButton to true and richColors to false', () => {
26
+ const wrapper = mount(RsToaster)
27
+ expect(wrapper.props('closeButton')).toBe(true)
28
+ expect(wrapper.props('richColors')).toBe(false)
29
+ wrapper.unmount()
30
+ })
31
+
32
+ it('accepts custom position and closeButton props', async () => {
33
+ const wrapper = mount(RsToaster, {
34
+ props: { position: 'bottom-right', closeButton: false, richColors: true },
35
+ attachTo: document.body,
36
+ })
37
+ await flushPromises()
38
+ expect(wrapper.props('position')).toBe('bottom-right')
39
+ expect(wrapper.props('closeButton')).toBe(false)
40
+ expect(wrapper.props('richColors')).toBe(true)
41
+ expect(document.body.querySelector('[data-sonner-toaster]')).toBeTruthy()
42
+ wrapper.unmount()
43
+ })
44
+ })
45
+
46
+ describe('useRsToast', () => {
47
+ afterEach(() => {
48
+ document.body.innerHTML = ''
49
+ })
50
+
51
+ it.each(['success', 'error', 'info', 'warning'] as const)(
52
+ 'shows %s toast with semantic class',
53
+ async (type) => {
54
+ mount(RsToaster, { attachTo: document.body })
55
+ const api = useRsToast()
56
+ api[type](`${type} 消息`)
57
+ await flushPromises()
58
+ const toastEl = document.body.querySelector(`.rs-toast--${type}`)
59
+ expect(toastEl?.textContent).toContain(`${type} 消息`)
60
+ },
61
+ )
62
+
63
+ it('shows title and description', async () => {
64
+ mount(RsToaster, { attachTo: document.body })
65
+ const { success } = useRsToast()
66
+ success({
67
+ title: '保存成功',
68
+ description: '更改已同步到云端。',
69
+ })
70
+ await flushPromises()
71
+ const root = document.body.querySelector('.rs-toast--success')
72
+ expect(root?.querySelector('.rs-toast__title')?.textContent).toBe('保存成功')
73
+ expect(root?.querySelector('.rs-toast__description')?.textContent).toBe('更改已同步到云端。')
74
+ })
75
+
76
+ it('accepts string shorthand', async () => {
77
+ mount(RsToaster, { attachTo: document.body })
78
+ const { info } = useRsToast()
79
+ info('简短提示')
80
+ await flushPromises()
81
+ expect(document.body.querySelector('.rs-toast--info')?.textContent).toContain('简短提示')
82
+ })
83
+
84
+ it('dismiss removes visible toasts', async () => {
85
+ mount(RsToaster, { attachTo: document.body })
86
+ const { success, dismiss } = useRsToast()
87
+ success('待关闭')
88
+ await flushPromises()
89
+ expect(document.body.querySelector('.rs-toast')).toBeTruthy()
90
+ dismiss()
91
+ await new Promise((resolve) => setTimeout(resolve, 400))
92
+ await flushPromises()
93
+ expect(document.body.querySelector('.rs-toast[data-visible="true"]')).toBeFalsy()
94
+ })
95
+ })
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import RsToolbar from '../components/RsToolbar.vue'
4
+
5
+ const sizes = ['sm', 'md', 'lg'] as const
6
+ const borders = ['bottom', 'top', 'both', 'none'] as const
7
+
8
+ describe('RsToolbar', () => {
9
+ it('renders as header with base class by default', () => {
10
+ const wrapper = mount(RsToolbar)
11
+ expect(wrapper.element.tagName).toBe('HEADER')
12
+ expect(wrapper.classes()).toContain('rs-toolbar')
13
+ expect(wrapper.classes()).toContain('rs-toolbar--md')
14
+ expect(wrapper.classes()).toContain('rs-toolbar--border-bottom')
15
+ expect(wrapper.attributes('role')).toBe('toolbar')
16
+ })
17
+
18
+ it('renders custom root tag', () => {
19
+ const wrapper = mount(RsToolbar, { props: { tag: 'div' } })
20
+ expect(wrapper.element.tagName).toBe('DIV')
21
+ })
22
+
23
+ it.each(sizes)('applies %s size class', (size) => {
24
+ const wrapper = mount(RsToolbar, { props: { size } })
25
+ expect(wrapper.classes()).toContain(`rs-toolbar--${size}`)
26
+ })
27
+
28
+ it.each(borders)('applies %s border class', (border) => {
29
+ const wrapper = mount(RsToolbar, { props: { border } })
30
+ expect(wrapper.classes()).toContain(`rs-toolbar--border-${border}`)
31
+ })
32
+
33
+ it('applies elevated and compact modifiers', () => {
34
+ const wrapper = mount(RsToolbar, {
35
+ props: { elevated: true, compact: true },
36
+ })
37
+ expect(wrapper.classes()).toContain('rs-toolbar--elevated')
38
+ expect(wrapper.classes()).toContain('rs-toolbar--compact')
39
+ })
40
+
41
+ it('sets aria-label when provided', () => {
42
+ const wrapper = mount(RsToolbar, { props: { label: '查询工具栏' } })
43
+ expect(wrapper.attributes('aria-label')).toBe('查询工具栏')
44
+ })
45
+
46
+ it('renders default slot into start region', () => {
47
+ const wrapper = mount(RsToolbar, {
48
+ slots: { default: '<span class="title">标题</span>' },
49
+ })
50
+ expect(wrapper.find('.rs-toolbar__start .title').text()).toBe('标题')
51
+ expect(wrapper.find('.rs-toolbar__end').exists()).toBe(false)
52
+ expect(wrapper.find('.rs-toolbar__center').exists()).toBe(false)
53
+ })
54
+
55
+ it('renders left / center / right slots', () => {
56
+ const wrapper = mount(RsToolbar, {
57
+ slots: {
58
+ left: '<span class="left">左</span>',
59
+ center: '<span class="center">中</span>',
60
+ right: '<span class="right">右</span>',
61
+ },
62
+ })
63
+ expect(wrapper.find('.rs-toolbar__start .left').text()).toBe('左')
64
+ expect(wrapper.find('.rs-toolbar__center .center').text()).toBe('中')
65
+ expect(wrapper.find('.rs-toolbar__end .right').text()).toBe('右')
66
+ })
67
+
68
+ it('omits start region when no left or default slot', () => {
69
+ const wrapper = mount(RsToolbar, {
70
+ slots: { right: '<button type="button" class="run">运行</button>' },
71
+ })
72
+ expect(wrapper.find('.rs-toolbar__start').exists()).toBe(false)
73
+ expect(wrapper.find('.rs-toolbar__end .run').text()).toBe('运行')
74
+ })
75
+ })
@@ -0,0 +1,111 @@
1
+ import { describe, expect, it, vi, afterEach, beforeEach } from 'vitest'
2
+ import { flushPromises, mount } from '@vue/test-utils'
3
+ import RsTooltip from '../components/RsTooltip.vue'
4
+ import RsTooltipProvider from '../components/RsTooltipProvider.vue'
5
+
6
+ function mountTooltip(
7
+ template: string,
8
+ props: Record<string, unknown> = {},
9
+ ) {
10
+ return mount(
11
+ {
12
+ components: { RsTooltip, RsTooltipProvider },
13
+ template: `<RsTooltipProvider :delay-duration="0"><RsTooltip v-bind="props">${template}</RsTooltip></RsTooltipProvider>`,
14
+ data() {
15
+ return { props }
16
+ },
17
+ },
18
+ { attachTo: document.body },
19
+ )
20
+ }
21
+
22
+ describe('RsTooltip', () => {
23
+ beforeEach(() => {
24
+ vi.useFakeTimers()
25
+ })
26
+
27
+ afterEach(() => {
28
+ vi.useRealTimers()
29
+ })
30
+
31
+ it('shows content on pointer enter', async () => {
32
+ const wrapper = mountTooltip(
33
+ '<button type="button" class="trigger">Hover</button>',
34
+ { content: 'Tooltip text' },
35
+ )
36
+ expect(document.body.querySelector('.rs-tooltip__content')).toBeNull()
37
+ await wrapper.find('.trigger').trigger('pointermove', { pointerType: 'mouse' })
38
+ await vi.runAllTimersAsync()
39
+ await flushPromises()
40
+ expect(document.body.querySelector('.rs-tooltip__content')).not.toBeNull()
41
+ expect(document.body.textContent).toContain('Tooltip text')
42
+ wrapper.unmount()
43
+ })
44
+
45
+ it('does not show when disabled', async () => {
46
+ const wrapper = mountTooltip(
47
+ '<button type="button" class="trigger">Hover</button>',
48
+ { content: 'Hidden', disabled: true },
49
+ )
50
+ await wrapper.find('.trigger').trigger('pointermove', { pointerType: 'mouse' })
51
+ await vi.runAllTimersAsync()
52
+ await flushPromises()
53
+ expect(document.body.querySelector('.rs-tooltip__content')).toBeNull()
54
+ wrapper.unmount()
55
+ })
56
+
57
+ it('renders custom content slot', async () => {
58
+ const wrapper = mount(
59
+ {
60
+ components: { RsTooltip, RsTooltipProvider },
61
+ template: `
62
+ <RsTooltipProvider :delay-duration="0">
63
+ <RsTooltip>
64
+ <button type="button" class="trigger">Hover</button>
65
+ <template #content>
66
+ <span class="custom-tip">Custom body</span>
67
+ </template>
68
+ </RsTooltip>
69
+ </RsTooltipProvider>
70
+ `,
71
+ },
72
+ { attachTo: document.body },
73
+ )
74
+ await wrapper.find('.trigger').trigger('pointermove', { pointerType: 'mouse' })
75
+ await vi.runAllTimersAsync()
76
+ await flushPromises()
77
+ expect(document.body.querySelector('.custom-tip')).not.toBeNull()
78
+ wrapper.unmount()
79
+ })
80
+
81
+ it('unmounts without leaving tooltip content in body', async () => {
82
+ const wrapper = mountTooltip(
83
+ '<button type="button" class="trigger">Hover</button>',
84
+ { content: 'Cleanup' },
85
+ )
86
+ await wrapper.find('.trigger').trigger('pointermove', { pointerType: 'mouse' })
87
+ await vi.runAllTimersAsync()
88
+ await flushPromises()
89
+ wrapper.unmount()
90
+ expect(document.body.querySelector('.rs-tooltip__content')).toBeNull()
91
+ })
92
+
93
+ it('renders suffix help icon when icon=true', async () => {
94
+ const wrapper = mountTooltip('<span class="field-label">Database</span>', {
95
+ content: 'Initial database hint',
96
+ icon: true,
97
+ })
98
+ expect(wrapper.find('.rs-tooltip__with-icon').exists()).toBe(true)
99
+ expect(wrapper.find('.field-label').exists()).toBe(true)
100
+ const trigger = wrapper.find('.rs-tooltip__icon-trigger')
101
+ expect(trigger.exists()).toBe(true)
102
+ expect(trigger.attributes('aria-label')).toBe('Initial database hint')
103
+ expect(document.body.querySelector('.rs-tooltip__content')).toBeNull()
104
+ await trigger.trigger('pointermove', { pointerType: 'mouse' })
105
+ await vi.runAllTimersAsync()
106
+ await flushPromises()
107
+ expect(document.body.querySelector('.rs-tooltip__content')).not.toBeNull()
108
+ expect(document.body.textContent).toContain('Initial database hint')
109
+ wrapper.unmount()
110
+ })
111
+ })