Files
hermes-ui/tests/client/no-naive-ui.test.ts
T
toor 836d0228d2 Phase 007: migrate every remaining view from naive-ui to Arco Design Vue
The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.

The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.

Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
  - dead .n-* class names in style blocks
  - dead --n-* custom properties
  - naive-ui in package.json

Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
  - a-input-password defaults to masked; passing default-visibility="false"
    would have exposed API keys in clear text
  - a-upload fires a real XHR unless auto-upload is false
  - nested Trigger components fight over the first element child, so the
    group-chat settings dropdown was flattened
  - Arco renders tooltip default-slot text inline, which was silently
    collapsing the chat and group-chat toolbars
2026-10-18 22:57:25 +08:00

78 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readdirSync, readFileSync, statSync } from 'fs'
import { join } from 'path'
import { describe, expect, it } from 'vitest'
const SRC_ROOT = 'packages/client/src'
const listFiles = (dir: string): string[] =>
readdirSync(dir).flatMap((name) => {
const full = join(dir, name)
return statSync(full).isDirectory() ? listFiles(full) : [full]
})
const sourceFiles = listFiles(SRC_ROOT).filter((file) =>
/\.(vue|ts|js)$/.test(file),
)
/**
* naive-ui 的类名在迁移时最容易漏:`grep naive-ui` 只匹配 import,
* 匹配不到样式块里的 `:deep(.n-button__content)` 这种写法,也不匹配
* JS 里的字符串字面量(`document.querySelector('.n-modal-mask')`)。
* 这两种都会在 Arco 下静默失效,不报错、测试也测不出来。
*/
const NAIVE_CLASS_PATTERN = /\.n-[a-z][\w-]*/i
const findOffenders = (pattern: RegExp) =>
sourceFiles
.filter((file) => pattern.test(readFileSync(file, 'utf8')))
.map((file) => file.replace(`${SRC_ROOT}/`, ''))
describe('naive-ui removal', () => {
it('has source files to scan', () => {
expect(sourceFiles.length).toBeGreaterThan(100)
})
it('never imports from naive-ui', () => {
expect(findOffenders(/from\s+['"]naive-ui['"]/)).toEqual([])
})
it('never imports naive-ui types or helpers by deep path', () => {
// packages/client/src 下没有深层导入,但防止将来有人从 @css-render 绕进来
expect(
findOffenders(/from\s+['"](vueuc|@css-render\/vue3-ssr|vdirs|seemly)['"]/),
).toEqual([])
})
it('never references naive-ui CSS class names', () => {
// 覆盖样式块里的 :deep(.n-*) 与 JS 字符串字面量两种写法
expect(findOffenders(NAIVE_CLASS_PATTERN)).toEqual([])
})
it('never registers naive-ui components globally', () => {
const main = readFileSync(join(SRC_ROOT, 'main.ts'), 'utf8')
expect(main).toContain('ArcoVue')
expect(main).not.toMatch(/N[A-Z][A-Za-z]*Provider/)
})
it('does not depend on naive-ui in package.json', () => {
const pkg = JSON.parse(readFileSync('package.json', 'utf8'))
const all = { ...pkg.dependencies, ...pkg.devDependencies }
expect(Object.keys(all)).not.toContain('naive-ui')
for (const dep of Object.keys(all)) {
expect(dep).not.toBe('vueuc')
expect(dep).not.toBe('vdirs')
}
})
it('does not chunk naive-ui in the vite config', () => {
const config = readFileSync('vite.config.ts', 'utf8')
expect(config).not.toMatch(/['"]naive-ui['"]/)
// Arco 的 scope 名含 "vue" 子串,判断必须排在 vue-vendor 之前,
// 否则整个 UI 库会被打进 vue chunk。见 vite.config.ts 的注释。
const arcoIndex = config.indexOf('@arco-design')
const vueIndex = config.indexOf('vue-vendor')
expect(arcoIndex).toBeGreaterThan(-1)
expect(arcoIndex).toBeLessThan(vueIndex)
})
})