style: 全站使用背景分层替代装饰边框并统一直角
This commit is contained in:
@@ -59,8 +59,9 @@ describe('统一目录条目', () => {
|
||||
expect(css).toMatch(/\.directory-item-title\s*\{[^}]*overflow-wrap:\s*anywhere/)
|
||||
expect(css).toMatch(/\.directory-list-content\s*\{[^}]*flex-direction:\s*row;[^}]*width:\s*max-content/)
|
||||
expect(css).toContain('grid-template-columns: clamp(240px, 22%, 280px) minmax(0, 1fr)')
|
||||
expect(css).toMatch(/\.identity-subject-list-content\s*\{[^}]*gap:\s*0;[^}]*padding:\s*0 0 8px/)
|
||||
expect(css).toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*min-height:\s*86px;[^}]*border-bottom:/)
|
||||
expect(css).toMatch(/\.identity-subject-list-content\s*\{[^}]*gap:\s*2px;[^}]*padding:\s*0 0 8px/)
|
||||
expect(css).toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*min-height:\s*86px;[^}]*padding:\s*10px 14px/)
|
||||
expect(css).not.toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*border-bottom:/)
|
||||
expect(css).toMatch(/\.identity-thumbnail\s*\{[^}]*width:\s*48px;[^}]*height:\s*64px/)
|
||||
expect(css).toMatch(/\.n-button\.identity-subject-item:not\(\.selected\)\s*\{[^}]*var\(--app-surface\)/)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { h } from 'vue'
|
||||
import { mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { NAlert, NButton, NCard, NConfigProvider, NInput, NSelect, NTable, NTag } from 'naive-ui'
|
||||
import { readFileSync, readdirSync } from 'node:fs'
|
||||
import { useTheme } from '../../composables/useTheme'
|
||||
|
||||
let wrapper: VueWrapper | undefined
|
||||
afterEach(() => {
|
||||
wrapper?.unmount()
|
||||
wrapper = undefined
|
||||
localStorage.clear()
|
||||
delete document.documentElement.dataset.theme
|
||||
document.documentElement.style.colorScheme = ''
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('全站平面主题', () => {
|
||||
it.each([false, true])('模式 %s 的真实控件无静态描边、使用直角,并保留聚焦与错误反馈', dark => {
|
||||
localStorage.setItem('drama-studio-theme', dark ? 'dark' : 'light')
|
||||
vi.stubGlobal('matchMedia', () => ({ matches: dark, addEventListener() {}, removeEventListener() {} }))
|
||||
wrapper = mount({
|
||||
setup() {
|
||||
const { theme, overrides } = useTheme()
|
||||
return () =>
|
||||
h(NConfigProvider, { theme: theme.value, themeOverrides: overrides.value }, () => [
|
||||
h(NButton, {}, () => '生成'),
|
||||
h(NInput, { class: 'normal-input' }),
|
||||
h(NInput, { status: 'error', class: 'error-input' }),
|
||||
h(NSelect, { options: [{ label: '第一集', value: 1 }] }),
|
||||
h(NAlert, { type: 'error' }, () => '生成失败'),
|
||||
h(NCard, {}, () => '内容'),
|
||||
h(NTag, {}, () => '待设计'),
|
||||
h(NTable, { striped: true }, () => h('tbody', [h('tr', [h('td', '记录')])]))
|
||||
])
|
||||
}
|
||||
})
|
||||
const value = (selector: string, name: string) =>
|
||||
(wrapper!.get(selector).element as HTMLElement).style.getPropertyValue(name)
|
||||
const control = dark ? '#2b2b2b' : '#f0f0f0'
|
||||
for (const selector of ['.n-button', '.normal-input', '.n-base-selection', '.n-alert', '.n-card', '.n-tag']) {
|
||||
expect(value(selector, '--n-border-radius')).toBe('0px')
|
||||
}
|
||||
for (const selector of ['.n-button', '.normal-input', '.n-base-selection']) {
|
||||
expect(value(selector, '--n-border')).toBe('none')
|
||||
expect(value(selector, '--n-color')).toBe(control)
|
||||
expect(value(selector, '--n-border-focus')).toContain(dark ? '#5cd693' : '#087c42')
|
||||
}
|
||||
expect(value('.n-alert', '--n-border')).toBe('none')
|
||||
expect(value('.n-tag', '--n-border')).toBe('none')
|
||||
expect(value('.n-card', '--n-border-color')).toBe('transparent')
|
||||
expect(value('.n-table', '--n-border-color')).toBe('transparent')
|
||||
expect(value('.error-input', '--n-border-error')).toContain(dark ? '#fa7373' : '#a93232')
|
||||
expect(wrapper.get('.error-input').classes()).toContain('n-input--error-status')
|
||||
// 不全局抹掉边框色:未勾选复选框和错误态继续由 Naive 的主题处理。
|
||||
const theme = wrapper.getComponent(NConfigProvider).props('themeOverrides')!
|
||||
expect(theme.common?.borderColor).not.toBe('transparent')
|
||||
expect(theme.Checkbox).not.toHaveProperty('border')
|
||||
expect(theme.Tabs?.barColor).toBe('#07c160')
|
||||
})
|
||||
|
||||
it('业务面板、列表分组和标签以背景区分,保留键盘焦点与选中指示', () => {
|
||||
// happy-dom 不计算布局;此项保护 CSS 契约,不能替代浏览器视觉验收。
|
||||
const admin = readFileSync('src/admin.css', 'utf8')
|
||||
const css = readFileSync('src/styles.css', 'utf8')
|
||||
expect(admin + css).not.toMatch(/border-radius:\s*[1-9]/)
|
||||
expect(admin + css).not.toMatch(/border(?:-[\w]+)?:\s*1px (?:solid|dashed) var\(--(?:app-border|color-line)\)/)
|
||||
expect(css).toMatch(/\.surface-inset\s*\{\s*background:\s*var\(--app-subtle\)/)
|
||||
expect(css).toMatch(/\.record-list > article:nth-child\(even\)\s*\{\s*background:/)
|
||||
expect(admin).toMatch(/\.directory-group-heading\s*\{[^}]*background:\s*var\(--app-control\)/)
|
||||
expect(admin).toMatch(/\.directory-status\s*\{[^}]*background:\s*var\(--app-control\)/)
|
||||
expect(admin).toMatch(/\.n-button\.directory-item:focus-visible\s*\{[^}]*outline:\s*2px/)
|
||||
expect(admin).toMatch(
|
||||
/\.n-button\.directory-item\.selected\s*\{[^}]*var\(--app-selected\)[^}]*var\(--app-accent\)/
|
||||
)
|
||||
})
|
||||
|
||||
it('所有页面不再通过工具类添加装饰边框或圆角', () => {
|
||||
const files = readdirSync('src', { recursive: true, encoding: 'utf8' }).filter(path => path.endsWith('.vue'))
|
||||
for (const path of files) {
|
||||
const source = readFileSync(`src/${path}`, 'utf8')
|
||||
for (const match of source.matchAll(/\bclass="([^"]*)"/g)) {
|
||||
expect(match[1]).not.toMatch(
|
||||
/(?:^|\s)(?:rounded(?:-[\w-]+)?|border|border-[trbl](?:-\d+)?|divide-[xy])(?:\s|$)/
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user