@ploykit/ui 0.6.0 → 0.6.2

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 (222) hide show
  1. package/dist/AppProviders.js +2 -2
  2. package/dist/components/ConfirmDialog.js +1 -1
  3. package/dist/components/CronInput.js +3 -3
  4. package/dist/components/DataTable.js +4 -4
  5. package/dist/components/FormField.js +1 -1
  6. package/dist/components/ImpersonationBanner.js +1 -1
  7. package/dist/components/NotificationBell.js +1 -1
  8. package/dist/components/OnboardingChecklist.js +3 -3
  9. package/dist/components/Page.js +2 -2
  10. package/dist/components/ReauthDialog.js +4 -4
  11. package/dist/components/SecretModal.js +2 -2
  12. package/dist/components/SettingsCard.js +2 -2
  13. package/dist/components/SiteBanner.js +1 -1
  14. package/dist/components/SiteBannerLayer.js +1 -1
  15. package/dist/components/StatusBadge.js +1 -1
  16. package/dist/components/UserMenu.js +3 -3
  17. package/dist/components/WorkspaceSwitcher.js +7 -7
  18. package/dist/components/ui/alert-dialog.js +2 -2
  19. package/dist/components/ui/avatar.js +1 -1
  20. package/dist/components/ui/badge.js +1 -1
  21. package/dist/components/ui/breadcrumb.js +1 -1
  22. package/dist/components/ui/button.js +1 -1
  23. package/dist/components/ui/card.js +1 -1
  24. package/dist/components/ui/checkbox.js +1 -1
  25. package/dist/components/ui/dialog.js +2 -2
  26. package/dist/components/ui/dropdown-menu.js +1 -1
  27. package/dist/components/ui/empty.js +1 -1
  28. package/dist/components/ui/field.js +3 -3
  29. package/dist/components/ui/input.js +1 -1
  30. package/dist/components/ui/label.js +1 -1
  31. package/dist/components/ui/popover.js +1 -1
  32. package/dist/components/ui/progress.js +1 -1
  33. package/dist/components/ui/select.js +1 -1
  34. package/dist/components/ui/separator.js +1 -1
  35. package/dist/components/ui/sheet.js +2 -2
  36. package/dist/components/ui/sidebar.js +8 -8
  37. package/dist/components/ui/skeleton.js +1 -1
  38. package/dist/components/ui/switch.js +1 -1
  39. package/dist/components/ui/tabs.js +1 -1
  40. package/dist/components/ui/textarea.js +1 -1
  41. package/dist/components/ui/tooltip.js +1 -1
  42. package/dist/index.js +78 -78
  43. package/dist/layouts/AppShell.js +4 -4
  44. package/dist/layouts/SettingsShell.js +4 -4
  45. package/dist/pages/LoginPage.js +5 -5
  46. package/dist/pages/account/NotificationPreferencesPage.js +3 -3
  47. package/dist/pages/account/ProfileSettingsPage.js +9 -9
  48. package/dist/pages/account/SecuritySettingsPage.js +13 -13
  49. package/dist/pages/account/TokensPage.js +12 -12
  50. package/dist/pages/admin/AdminSettingsPage.js +11 -11
  51. package/dist/pages/admin/AdminShell.js +3 -3
  52. package/dist/pages/admin/AuditPage.js +8 -8
  53. package/dist/pages/admin/OrdersPage.js +12 -12
  54. package/dist/pages/admin/OverviewPage.js +13 -13
  55. package/dist/pages/admin/SSOPage.js +6 -6
  56. package/dist/pages/admin/UserDetailPage.js +13 -13
  57. package/dist/pages/admin/UsersPage.js +13 -13
  58. package/dist/pages/admin/WebhookDeliveriesPage.js +9 -9
  59. package/dist/pages/admin/WorkspaceDetailPage.js +14 -14
  60. package/dist/pages/admin/WorkspacesPage.js +9 -9
  61. package/dist/pages/admin/shared.js +1 -1
  62. package/dist/pages/auth/ForgotPasswordPage.js +4 -4
  63. package/dist/pages/auth/InviteAcceptPage.js +6 -6
  64. package/dist/pages/auth/RegisterPage.js +7 -7
  65. package/dist/pages/auth/ResetPasswordPage.js +5 -5
  66. package/dist/pages/auth/VerifyEmailPage.js +3 -3
  67. package/dist/pages/workspace/BillingPage.js +11 -11
  68. package/dist/pages/workspace/BillingSuccessPage.js +2 -2
  69. package/dist/pages/workspace/MembersPage.js +15 -15
  70. package/dist/pages/workspace/RolePermissionsPage.js +9 -9
  71. package/dist/pages/workspace/SchedulesPage.js +12 -12
  72. package/dist/pages/workspace/UsagePage.js +6 -6
  73. package/dist/pages/workspace/WebhooksPage.js +16 -16
  74. package/dist/pages/workspace/WorkspaceAuditPage.js +7 -7
  75. package/dist/pages/workspace/WorkspaceGeneralPage.js +9 -9
  76. package/package.json +6 -5
  77. package/src/AppProviders.tsx +29 -0
  78. package/src/components/ConfirmDialog.tsx +77 -0
  79. package/src/components/CronInput.tsx +195 -0
  80. package/src/components/DataTable.tsx +152 -0
  81. package/src/components/ErrorBoundary.tsx +46 -0
  82. package/src/components/FormField.tsx +56 -0
  83. package/src/components/ImpersonationBanner.tsx +23 -0
  84. package/src/components/NotificationBell.tsx +135 -0
  85. package/src/components/OnboardingChecklist.tsx +114 -0
  86. package/src/components/Page.tsx +67 -0
  87. package/src/components/ReauthDialog.tsx +94 -0
  88. package/src/components/SecretModal.tsx +147 -0
  89. package/src/components/SettingsCard.tsx +39 -0
  90. package/src/components/SiteBanner.tsx +32 -0
  91. package/src/components/SiteBannerLayer.tsx +29 -0
  92. package/src/components/StatusBadge.tsx +12 -0
  93. package/src/components/UserMenu.tsx +103 -0
  94. package/src/components/WorkspaceSwitcher.tsx +159 -0
  95. package/src/components/__tests__/AppProviders.test.tsx +111 -0
  96. package/src/components/__tests__/CronInput.test.tsx +180 -0
  97. package/src/components/__tests__/DataTable.test.tsx +208 -0
  98. package/src/components/__tests__/ErrorBoundary.test.tsx +61 -0
  99. package/src/components/__tests__/ImpersonationBanner.test.tsx +59 -0
  100. package/src/components/__tests__/NotificationBell.test.tsx +207 -0
  101. package/src/components/__tests__/OnboardingChecklist.test.tsx +97 -0
  102. package/src/components/__tests__/SecretModal.test.tsx +112 -0
  103. package/src/components/__tests__/SiteBannerLayer.test.tsx +44 -0
  104. package/src/components/__tests__/UserMenu.test.tsx +98 -0
  105. package/src/components/__tests__/WorkspaceSwitcher.test.tsx +162 -0
  106. package/src/components/__tests__/form.test.tsx +133 -0
  107. package/src/components/__tests__/semantic-tokens.test.ts +41 -0
  108. package/src/components/__tests__/smoke.test.tsx +11 -0
  109. package/src/components/__tests__/toast.test.tsx +27 -0
  110. package/src/components/toast.tsx +23 -0
  111. package/src/components/ui/__tests__/alert-dialog.test.tsx +66 -0
  112. package/src/components/ui/__tests__/avatar.test.tsx +30 -0
  113. package/src/components/ui/__tests__/checkbox.test.tsx +18 -0
  114. package/src/components/ui/__tests__/dropdown-menu.test.tsx +33 -0
  115. package/src/components/ui/__tests__/field.test.tsx +43 -0
  116. package/src/components/ui/__tests__/overlay-size.test.tsx +84 -0
  117. package/src/components/ui/__tests__/popover.test.tsx +32 -0
  118. package/src/components/ui/__tests__/progress.test.tsx +24 -0
  119. package/src/components/ui/__tests__/select-callback.test.tsx +72 -0
  120. package/src/components/ui/__tests__/select.test.tsx +48 -0
  121. package/src/components/ui/__tests__/sidebar.test.tsx +121 -0
  122. package/src/components/ui/__tests__/stock-mirror.test.ts +39 -0
  123. package/src/components/ui/__tests__/subpath.test.tsx +38 -0
  124. package/src/components/ui/__tests__/switch.test.tsx +24 -0
  125. package/src/components/ui/__tests__/tabs.test.tsx +24 -0
  126. package/src/components/ui/__tests__/textarea.test.tsx +41 -0
  127. package/src/components/ui/alert-dialog.tsx +187 -0
  128. package/src/components/ui/avatar.tsx +108 -0
  129. package/src/components/ui/badge.tsx +51 -0
  130. package/src/components/ui/breadcrumb.tsx +124 -0
  131. package/src/components/ui/button.tsx +57 -0
  132. package/src/components/ui/card.tsx +102 -0
  133. package/src/components/ui/checkbox.tsx +28 -0
  134. package/src/components/ui/dialog.tsx +166 -0
  135. package/src/components/ui/dropdown-menu.tsx +265 -0
  136. package/src/components/ui/empty.tsx +103 -0
  137. package/src/components/ui/field.tsx +238 -0
  138. package/src/components/ui/input.tsx +19 -0
  139. package/src/components/ui/label.tsx +17 -0
  140. package/src/components/ui/popover.tsx +89 -0
  141. package/src/components/ui/progress.tsx +82 -0
  142. package/src/components/ui/select.tsx +235 -0
  143. package/src/components/ui/separator.tsx +24 -0
  144. package/src/components/ui/sheet.tsx +146 -0
  145. package/src/components/ui/sidebar.tsx +723 -0
  146. package/src/components/ui/skeleton.tsx +13 -0
  147. package/src/components/ui/switch.tsx +29 -0
  148. package/src/components/ui/tabs.tsx +79 -0
  149. package/src/components/ui/textarea.tsx +26 -0
  150. package/src/components/ui/tooltip.tsx +63 -0
  151. package/src/dev.css +1 -0
  152. package/src/hooks/use-mobile.ts +19 -0
  153. package/src/index.ts +150 -0
  154. package/src/layouts/AppShell.tsx +163 -0
  155. package/src/layouts/AuthShell.tsx +24 -0
  156. package/src/layouts/SettingsShell.tsx +129 -0
  157. package/src/layouts/__tests__/AppShell.test.tsx +163 -0
  158. package/src/layouts/__tests__/SettingsShell.test.tsx +146 -0
  159. package/src/lib/__tests__/api-error.test.ts +163 -0
  160. package/src/lib/__tests__/utils.test.ts +21 -0
  161. package/src/lib/api-error.ts +98 -0
  162. package/src/lib/utils.ts +109 -0
  163. package/src/pages/LandingPage.tsx +88 -0
  164. package/src/pages/LoginPage.tsx +291 -0
  165. package/src/pages/__tests__/LandingPage.test.tsx +58 -0
  166. package/src/pages/__tests__/LoginPage.test.tsx +229 -0
  167. package/src/pages/__tests__/RegisterPage.test.tsx +138 -0
  168. package/src/pages/__tests__/ssr-smoke.test.tsx +99 -0
  169. package/src/pages/account/NotificationPreferencesPage.tsx +138 -0
  170. package/src/pages/account/ProfileSettingsPage.tsx +189 -0
  171. package/src/pages/account/SecuritySettingsPage.tsx +214 -0
  172. package/src/pages/account/TokensPage.tsx +211 -0
  173. package/src/pages/account/__tests__/NotificationPreferencesPage.test.tsx +150 -0
  174. package/src/pages/account/__tests__/ProfileSettingsPage.test.tsx +173 -0
  175. package/src/pages/account/__tests__/SecuritySettingsPage.test.tsx +212 -0
  176. package/src/pages/account/__tests__/TokensPage.test.tsx +180 -0
  177. package/src/pages/admin/AdminSettingsPage.tsx +564 -0
  178. package/src/pages/admin/AdminShell.tsx +199 -0
  179. package/src/pages/admin/AuditPage.tsx +338 -0
  180. package/src/pages/admin/OrdersPage.tsx +642 -0
  181. package/src/pages/admin/OverviewPage.tsx +559 -0
  182. package/src/pages/admin/SSOPage.tsx +166 -0
  183. package/src/pages/admin/UserDetailPage.tsx +641 -0
  184. package/src/pages/admin/UsersPage.tsx +462 -0
  185. package/src/pages/admin/WebhookDeliveriesPage.tsx +318 -0
  186. package/src/pages/admin/WorkspaceDetailPage.tsx +551 -0
  187. package/src/pages/admin/WorkspacesPage.tsx +299 -0
  188. package/src/pages/admin/__tests__/AdminSettingsPage.test.tsx +260 -0
  189. package/src/pages/admin/__tests__/OrdersPageMarkPaid.test.tsx +207 -0
  190. package/src/pages/admin/__tests__/OverviewPageActivity.test.tsx +213 -0
  191. package/src/pages/admin/__tests__/SSOPage.test.tsx +128 -0
  192. package/src/pages/admin/__tests__/UsersPageActions.test.tsx +188 -0
  193. package/src/pages/admin/__tests__/WorkspaceDetailPageTransfer.test.tsx +170 -0
  194. package/src/pages/admin/__tests__/WorkspacesPagePlans.test.tsx +138 -0
  195. package/src/pages/admin/shared.ts +42 -0
  196. package/src/pages/auth/ForgotPasswordPage.tsx +83 -0
  197. package/src/pages/auth/InviteAcceptPage.tsx +105 -0
  198. package/src/pages/auth/RegisterPage.tsx +204 -0
  199. package/src/pages/auth/ResetPasswordPage.tsx +150 -0
  200. package/src/pages/auth/VerifyEmailPage.tsx +76 -0
  201. package/src/pages/auth/__tests__/ForgotPasswordPage.test.tsx +67 -0
  202. package/src/pages/auth/__tests__/InviteAcceptPage.test.tsx +86 -0
  203. package/src/pages/auth/__tests__/ResetPasswordPage.test.tsx +115 -0
  204. package/src/pages/auth/__tests__/VerifyEmailPage.test.tsx +74 -0
  205. package/src/pages/workspace/BillingPage.tsx +361 -0
  206. package/src/pages/workspace/BillingSuccessPage.tsx +115 -0
  207. package/src/pages/workspace/MembersPage.tsx +568 -0
  208. package/src/pages/workspace/RolePermissionsPage.tsx +202 -0
  209. package/src/pages/workspace/SchedulesPage.tsx +387 -0
  210. package/src/pages/workspace/UsagePage.tsx +207 -0
  211. package/src/pages/workspace/WebhooksPage.tsx +506 -0
  212. package/src/pages/workspace/WorkspaceAuditPage.tsx +229 -0
  213. package/src/pages/workspace/WorkspaceGeneralPage.tsx +184 -0
  214. package/src/pages/workspace/__tests__/BillingPage.test.tsx +302 -0
  215. package/src/pages/workspace/__tests__/BillingSuccessPage.test.tsx +134 -0
  216. package/src/pages/workspace/__tests__/MembersPage.test.tsx +347 -0
  217. package/src/pages/workspace/__tests__/RolePermissionsPage.test.tsx +138 -0
  218. package/src/pages/workspace/__tests__/SchedulesPage.test.tsx +239 -0
  219. package/src/pages/workspace/__tests__/UsagePage.test.tsx +223 -0
  220. package/src/pages/workspace/__tests__/WebhooksPage.test.tsx +309 -0
  221. package/src/pages/workspace/__tests__/WorkspaceAuditPage.test.tsx +112 -0
  222. package/src/pages/workspace/__tests__/WorkspaceGeneralPage.test.tsx +164 -0
@@ -0,0 +1,180 @@
1
+ import '@testing-library/jest-dom/vitest'
2
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
4
+ import { TokensPage } from '../TokensPage'
5
+
6
+ const mocks = vi.hoisted(() => ({
7
+ tokens: [] as Array<Record<string, unknown>>,
8
+ loading: false,
9
+ create: vi.fn(),
10
+ revoke: vi.fn(),
11
+ confirm: vi.fn(),
12
+ }))
13
+ vi.mock('../../../../../hooks/src/hooks/useTokens', () => ({
14
+ useTokens: () => ({ tokens: mocks.tokens, loading: mocks.loading, error: null, refetch: vi.fn() }),
15
+ useCreateToken: () => ({ create: mocks.create, loading: false, error: null }),
16
+ useRevokeToken: () => ({ revoke: mocks.revoke, loading: false, error: null }),
17
+ }))
18
+ vi.mock('../../../components/ConfirmDialog', () => ({
19
+ useConfirm: () => mocks.confirm,
20
+ }))
21
+
22
+ function pat(over: Record<string, unknown> = {}) {
23
+ return {
24
+ id: 'p-1', name: 'ci-deploy', prefix: 'tk_ab12cd',
25
+ created_at: '2026-10-01T00:00:00Z', ...over,
26
+ }
27
+ }
28
+
29
+ beforeEach(() => {
30
+ mocks.create.mockReset().mockResolvedValue({ pat: pat(), token: 'tk_plainvalue123' })
31
+ mocks.revoke.mockReset().mockResolvedValue(undefined)
32
+ mocks.confirm.mockReset().mockResolvedValue(true)
33
+ mocks.loading = false
34
+ mocks.tokens = []
35
+ })
36
+
37
+ describe('TokensPage', () => {
38
+ it('加载中渲染 Skeleton 行', () => {
39
+ mocks.loading = true
40
+ render(<TokensPage />)
41
+ expect(document.querySelectorAll('[data-slot="skeleton"]').length).toBeGreaterThan(0)
42
+ })
43
+
44
+ it('空列表渲染空态', () => {
45
+ render(<TokensPage />)
46
+ expect(screen.getByText('还没有令牌')).toBeInTheDocument()
47
+ })
48
+
49
+ it('渲染令牌行:名称/前缀/未用过提示/永不过期徽标', () => {
50
+ mocks.tokens = [pat()]
51
+ render(<TokensPage />)
52
+ expect(screen.getByText('ci-deploy')).toBeInTheDocument()
53
+ expect(screen.getByText('tk_ab12cd…')).toBeInTheDocument()
54
+ expect(screen.getByText('从未使用')).toBeInTheDocument()
55
+ expect(screen.getByText('永不过期')).toBeInTheDocument()
56
+ })
57
+
58
+ it('创建令牌:对话框填 name/ttl → create 携带正确参数 → SecretModal 展示明文', async () => {
59
+ render(<TokensPage />)
60
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
61
+
62
+ const nameInput = await screen.findByLabelText('名称')
63
+ fireEvent.change(nameInput, { target: { value: 'ci-deploy' } })
64
+ fireEvent.change(screen.getByLabelText('有效期(小时,可选)'), { target: { value: '72' } })
65
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
66
+
67
+ await waitFor(() => expect(mocks.create).toHaveBeenCalledWith('ci-deploy', 72))
68
+
69
+ expect(await screen.findByText('个人访问令牌 创建成功')).toBeInTheDocument()
70
+ expect(screen.getByText('tk_plainvalue123')).toBeInTheDocument()
71
+ })
72
+
73
+ it('吊销令牌:确认后 revoke(id)', async () => {
74
+ mocks.confirm.mockResolvedValueOnce(true)
75
+ mocks.tokens = [pat({ id: 'p-9' })]
76
+ render(<TokensPage />)
77
+
78
+ fireEvent.click(screen.getByRole('button', { name: '吊销令牌 ci-deploy' }))
79
+ await waitFor(() => expect(mocks.revoke).toHaveBeenCalledWith('p-9'))
80
+ })
81
+
82
+ it('P2-1:TTL 为 0 → 拒绝创建并提示,不静默创建永不过期凭证', async () => {
83
+ render(<TokensPage />)
84
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
85
+
86
+ const nameInput = await screen.findByLabelText('名称')
87
+ fireEvent.change(nameInput, { target: { value: 'ci-deploy' } })
88
+ fireEvent.change(screen.getByLabelText('有效期(小时,可选)'), { target: { value: '0' } })
89
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
90
+
91
+ expect(await screen.findByText(/有效期必须是正整数/)).toBeInTheDocument()
92
+ expect(mocks.create).not.toHaveBeenCalled()
93
+ })
94
+
95
+ it('P2-1:TTL 为负数 → 拒绝创建', async () => {
96
+ render(<TokensPage />)
97
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
98
+
99
+ const nameInput = await screen.findByLabelText('名称')
100
+ fireEvent.change(nameInput, { target: { value: 'ci-deploy' } })
101
+ fireEvent.change(screen.getByLabelText('有效期(小时,可选)'), { target: { value: '-5' } })
102
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
103
+
104
+ expect(await screen.findByText(/有效期必须是正整数/)).toBeInTheDocument()
105
+ expect(mocks.create).not.toHaveBeenCalled()
106
+ })
107
+
108
+ it('P2-1:TTL 为非数字 → 拒绝创建', async () => {
109
+ render(<TokensPage />)
110
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
111
+
112
+ const nameInput = await screen.findByLabelText('名称')
113
+ fireEvent.change(nameInput, { target: { value: 'ci-deploy' } })
114
+ fireEvent.change(screen.getByLabelText('有效期(小时,可选)'), { target: { value: 'abc' } })
115
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
116
+
117
+ expect(await screen.findByText(/有效期必须是正整数/)).toBeInTheDocument()
118
+ expect(mocks.create).not.toHaveBeenCalled()
119
+ })
120
+
121
+ it('P2-1:TTL 留空 → 不传过期时间(显式永不过期)', async () => {
122
+ render(<TokensPage />)
123
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
124
+
125
+ const nameInput = await screen.findByLabelText('名称')
126
+ fireEvent.change(nameInput, { target: { value: 'ci-deploy' } })
127
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
128
+
129
+ await waitFor(() => expect(mocks.create).toHaveBeenCalledWith('ci-deploy', undefined))
130
+ })
131
+
132
+ it('B2:TTL placeholder 明示"不建议"——0 与留空的语义不再含混', async () => {
133
+ render(<TokensPage />)
134
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
135
+ const ttlInput = await screen.findByLabelText('有效期(小时,可选)')
136
+ expect(ttlInput).toHaveAttribute('placeholder', '留空 = 永不过期(不建议)')
137
+ })
138
+
139
+ it('B3:名称 22 个汉字(66 字节)超服务端 64 字节上限 → 前端拒绝并按字节提示', async () => {
140
+ render(<TokensPage />)
141
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
142
+
143
+ const nameInput = await screen.findByLabelText('名称')
144
+ fireEvent.change(nameInput, { target: { value: '汉'.repeat(22) } })
145
+ fireEvent.change(screen.getByLabelText('有效期(小时,可选)'), { target: { value: '72' } })
146
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
147
+
148
+ expect(await screen.findByText(/不能超过 64 字节/)).toBeInTheDocument()
149
+ expect(screen.getByText(/当前 66 字节/)).toBeInTheDocument()
150
+ expect(mocks.create).not.toHaveBeenCalled()
151
+ })
152
+
153
+ it('B3:名称 21 个汉字(63 字节)恰在服务端上限内 → 正常提交(字节口径对齐)', async () => {
154
+ const name = '汉'.repeat(21)
155
+ render(<TokensPage />)
156
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
157
+
158
+ const nameInput = await screen.findByLabelText('名称')
159
+ fireEvent.change(nameInput, { target: { value: name } })
160
+ fireEvent.click(screen.getByRole('button', { name: '创建' }))
161
+
162
+ await waitFor(() => expect(mocks.create).toHaveBeenCalledWith(name, undefined))
163
+ })
164
+
165
+ it('B3:名称输入带 maxLength=64 粗预约束(ASCII 直拦,CJK 由字节校验兜底)', async () => {
166
+ render(<TokensPage />)
167
+ fireEvent.click(screen.getByRole('button', { name: '新建令牌' }))
168
+ const nameInput = await screen.findByLabelText('名称')
169
+ expect(nameInput).toHaveAttribute('maxlength', '64')
170
+ })
171
+
172
+ it('B1:名称单元格 truncate + title 全文——超长名不再撑宽表格(滚动层在 DataTable)', () => {
173
+ const longName = 'a'.repeat(60)
174
+ mocks.tokens = [pat({ name: longName })]
175
+ render(<TokensPage />)
176
+ const cell = screen.getByText(longName)
177
+ expect(cell.className).toContain('truncate')
178
+ expect(cell).toHaveAttribute('title', longName)
179
+ })
180
+ })