import { readFileSync } from 'fs' import { describe, expect, it } from 'vitest' const readClientFile = (path: string) => readFileSync(`packages/client/src/${path}`, 'utf8') describe('client style system', () => { it('keeps shared page headers on one layout baseline', () => { const globalStyles = readClientFile('styles/global.scss') expect(globalStyles).toContain('min-height: 64px;') expect(globalStyles).toContain('padding: 14px 20px;') expect(globalStyles).toContain('.page-header > .header-actions') }) it('aligns SCSS surfaces and radii with the shared theme tokens', () => { const variables = readClientFile('styles/variables.scss') // CYBER 是单色暗色主题:卡片面必须是暗色,且亮度分层(page < sidebar < card < input)。 expect(variables).toContain('--bg-card: oklch(0.185 0.014 248);') expect(variables).toContain('--bg-input: oklch(0.105 0.012 252);') expect(variables).toContain('$radius-sm: 6px;') expect(variables).toContain('$radius-md: 8px;') expect(variables).toContain('$radius-lg: 8px;') // 亮色主题与 comic 风格已移除:不能再有 .dark / .comic 变体块。 expect(variables).not.toMatch(/^\.dark\s*{/m) expect(variables).not.toMatch(/^\.comic\s*{/m) }) it('bridges CYBER tokens into Arco CSS variables', () => { // 没有 ConfigProvider,Arco 的实际外观由 global.scss 里的 --color-* 覆盖决定。 const globalStyles = readClientFile('styles/global.scss') expect(globalStyles).toContain('--color-primary: oklch(0.805 0.148 188);') expect(globalStyles).toContain('--color-bg-1: oklch(0.155 0.012 250);') expect(globalStyles).toContain('--color-border-1: oklch(0.305 0.018 242);') }) it('exports the frozen Arco theme tokens', async () => { const { arcoTheme } = await import('../../packages/client/src/styles/arco-theme') expect(arcoTheme.colorPrimary).toBeTruthy() expect(arcoTheme.colorBgLayout).toBeTruthy() expect(arcoTheme.colorBgContainer).toBeTruthy() expect(arcoTheme.borderRadius).toBeTypeOf('number') expect(arcoTheme.fontFamily).toContain('PingFang SC') expect(arcoTheme.fontFamilyMono).toContain('JetBrains Mono') }) it('pins Arco to dark mode on the root element', () => { // 不 import useTheme:它在模块顶层读 localStorage,node 环境下会抛。 // 断言源码契约即可,真实行为由 useTheme 自己的测试覆盖。 const source = readClientFile('composables/useTheme.ts') expect(source).toContain("setAttribute('arco-theme'") // 单主题:不再有 light / comic 变体,只固定 dark。 expect(source).not.toMatch(/classList\.toggle\(['"](dark|comic)/) expect(source).not.toMatch(/localStorage\.getItem\(['"]hermes_(brightness|style)/) }) it('exposes the CYBER page shell so pages stop falling back to bare Arco layouts', () => { // 原型里每个页面都是 panel / panel-h / panel-b / kv / tabs 这套外壳。 // 25 个页面各写一遍同样的框会漂移,所以抽成全局类并在这里锁住。 const globalStyles = readClientFile('styles/global.scss') for (const cls of ['.cy-view', '.cy-panel', '.cy-panel-h', '.cy-panel-b', '.cy-kv', '.cy-tabs', '.cy-tab']) { expect(globalStyles).toContain(cls) } // 选中态必须是霓虹青 + 发丝下划线,不是胶囊底色。 const onRule = globalStyles.slice(globalStyles.indexOf('&.on {'), globalStyles.indexOf('&.dis {')) expect(onRule).toContain('color: $accent-primary;') expect(onRule).toContain('border-bottom-color: $accent-primary;') }) it('keeps the settings shell on the shared page header baseline', () => { // 设置页是唯一有 11 个 pane 的页面,最容易退回 Arco 的 a-tabs 默认外观。 const settingsView = readClientFile('views/hermes/SettingsView.vue') expect(settingsView).toContain('cy-tabs') expect(settingsView).toContain('cy-panel') // 不再依赖 a-tabs / a-tab-pane。 expect(settingsView).not.toContain(' settingsView.indexOf(`key: "${key}"`)) expect(positions.every(p => p > 0)).toBe(true) expect([...positions].sort((a, b) => a - b)).toEqual(positions) }) it('keeps mobile inputs at 16px so iOS does not zoom on focus', () => { const globalStyles = readClientFile('styles/global.scss') // Arco 的输入框类名,不是 naive 的 .n-input__* expect(globalStyles).toContain('.arco-input') expect(globalStyles).not.toMatch(/\.n-input/) }) })