令牌驱动方案(主目标:与 dsh-web-ui 皮肤中心兼容): - 面板表面改用通用卡片令牌 --dsw-alias-bg-layer-1,不再消费宿主左导航专属 --dsw-specific-sidebar-fill——dsh-web-ui 10 款皮肤全部覆盖 --dsw-alias-* 层,换肤后侧边栏自动跟随,零每皮肤适配(#60/#105/#106)。 - effectiveTokenValue 增加 alpha 阈值(新增 colorAlpha 解析 computed 颜色): transparent 与 alpha<0.9 的半透明玻璃值(dsh-web-ui 皮肤 bg-base 为 rgba 0.16–0.7)一律回退不透明底色,终端/编辑器文字永不叠在皮肤背景画上 (#90);≥0.9 的近不透明值放行,皮肤仍可控终端表面。 独立修复(保留): - z-index 降到 DSH 浮层栈(100+)之下:面板 40 / 按钮簇 45 / 角手柄面板内 局部 2,Cordis 弹出框不再被底部工作台遮挡(#52)。 - 角手柄移入右面板、CSS 相对定位(bottom: calc(var(--dsh-sidebar-height) + 6px)),删除 JS 内联 viewport 坐标。 - 刷新按钮统一 14px(#57 前半)。 - 拖拽逐帧回归 e2e(#92):真实挂载下 rAF 采样(拖条以 cursor: col-resize 语义定位),断言拖拽期间 data-dsh-sidebar-dragging 存在、#root transition 为 none、会话列与面板边单调 1:1 跟随(实测通过)。 按 KISS 回退首版契约:类名 kebab-case 改名与 data-bs-* 语义钩子全部移除 (令牌驱动下不需要,改名引入无谓 breaking),skin-hooks 测试删除; mount e2e 改为断言 --dsh-sidebar-width 布局变量随面板挂载生效。 文档:AGENTS.md §8 重写为「皮肤兼容(令牌驱动)」;设计文档记录决策与 回退。README 与版本号不在本批更新(发布流程统一处理)。 测试:tests/theme.spec.ts(colorAlpha + effectiveTokenValue 阈值); typecheck/vitest(511)/build/consumer-types/test:mount 全绿。
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
/**
|
||
* Live theme-token access tests — issue #90's guard.
|
||
*
|
||
* `effectiveTokenValue` must treat visually inert values (CSS reset
|
||
* keywords, `transparent`, and any color below the opacity floor) as UNSET
|
||
* so callers' `|| fallback` chains fire. Skin systems set global tokens like
|
||
* `--dsw-alias-bg-base` to `transparent` or translucent glass values (the
|
||
* dsh-web-ui skins use rgba 0.16–0.7); without this guard a
|
||
* truthy-but-inert value would leave the terminal/editor see-through over
|
||
* the skin's backdrop. Effectively opaque values (>= 0.9 alpha, including a
|
||
* skin's scoped 0.96 porcelain) pass through so the skin still controls the
|
||
* surface.
|
||
*/
|
||
// @vitest-environment jsdom
|
||
import { afterEach, describe, expect, it } from 'vitest'
|
||
import { colorAlpha, effectiveTokenValue, tokenValue } from '../src/client/theme.ts'
|
||
|
||
afterEach(() => {
|
||
document.body.removeAttribute('style')
|
||
})
|
||
|
||
describe('colorAlpha', () => {
|
||
it('parses the hex family', () => {
|
||
expect(colorAlpha('#fff')).toBe(1)
|
||
expect(colorAlpha('#fff8')).toBeCloseTo(0x88 / 255)
|
||
expect(colorAlpha('#112233')).toBe(1)
|
||
expect(colorAlpha('#11223344')).toBeCloseTo(0x44 / 255)
|
||
})
|
||
|
||
it('parses rgb()/rgba() in comma and space syntax', () => {
|
||
expect(colorAlpha('rgb(1, 2, 3)')).toBe(1)
|
||
expect(colorAlpha('rgba(255, 255, 255, 0.45)')).toBeCloseTo(0.45)
|
||
expect(colorAlpha('rgb(255 255 255 / 0.7)')).toBeCloseTo(0.7)
|
||
})
|
||
|
||
it('parses hsl()/hsla()', () => {
|
||
expect(colorAlpha('hsl(200, 50%, 50%)')).toBe(1)
|
||
expect(colorAlpha('hsla(200, 50%, 50%, 0.3)')).toBeCloseTo(0.3)
|
||
})
|
||
|
||
it('treats unparseable formats as opaque', () => {
|
||
expect(colorAlpha('white')).toBeNull()
|
||
expect(colorAlpha('')).toBeNull()
|
||
})
|
||
})
|
||
|
||
describe('effectiveTokenValue', () => {
|
||
it('passes through real opaque paint values verbatim', () => {
|
||
document.body.style.setProperty('--probe', '#112233')
|
||
expect(effectiveTokenValue('--probe')).toBe('#112233')
|
||
// Effectively opaque translucency (a skin's scoped 0.96 porcelain glass)
|
||
// is a deliberate surface choice — it passes through.
|
||
document.body.style.setProperty('--probe', 'rgba(10, 22, 54, 0.96)')
|
||
expect(effectiveTokenValue('--probe')).toBe('rgba(10, 22, 54, 0.96)')
|
||
})
|
||
|
||
it('treats transparent and CSS reset keywords as unset', () => {
|
||
for (const inert of ['transparent', 'initial', 'inherit', 'unset']) {
|
||
document.body.style.setProperty('--probe', inert)
|
||
expect(effectiveTokenValue('--probe'), inert).toBe('')
|
||
}
|
||
})
|
||
|
||
it('treats translucent glass values below the opacity floor as unset', () => {
|
||
// The dsh-web-ui skins set bg-base to rgba 0.16–0.7 for glass panels;
|
||
// the terminal must fall back to an opaque background there.
|
||
for (const glass of ['rgba(255, 255, 255, 0.45)', 'rgba(20, 26, 46, 0.7)', '#11223344']) {
|
||
document.body.style.setProperty('--probe', glass)
|
||
expect(effectiveTokenValue('--probe'), glass).toBe('')
|
||
}
|
||
})
|
||
|
||
it('treats a missing token as unset', () => {
|
||
expect(effectiveTokenValue('--never-defined')).toBe('')
|
||
})
|
||
|
||
it('tokenValue still returns the raw value', () => {
|
||
document.body.style.setProperty('--probe', 'transparent')
|
||
expect(tokenValue('--probe')).toBe('transparent')
|
||
expect(effectiveTokenValue('--probe')).toBe('')
|
||
})
|
||
})
|