fix(ui-schedule): strengthen illustration palette contracts
This commit is contained in:
1 parent
2f348776b4
commit
773ce6f691
8 files changed
+173
-65
No files matched your search
@@ -1,11 +1,15 @@
|
||||
/** Decorative document-and-clock artwork with theme-owned colors. */
|
||||
|
||||
interface ScheduleEmptyIllustrationProps {
|
||||
readonly className?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the empty-state illustration without adding an accessible announcement.
|
||||
* @param props - page-owned illustration sizing and spacing.
|
||||
* @returns the same vector paths in either theme.
|
||||
* @param props - optional CSS class applied to the root SVG.
|
||||
* @returns the decorative inline SVG element.
|
||||
*/
|
||||
export function ScheduleEmptyIllustration({ className }: { className: string | undefined }) {
|
||||
export function ScheduleEmptyIllustration({ className }: ScheduleEmptyIllustrationProps) {
|
||||
return (
|
||||
<svg className={className} width="170" height="170" viewBox="0 0 170 170" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M163.819 97.9382C163.819 126.572 144.48 135.329 115.847 135.329C87.2129 135.329 60.1276 126.572 60.1276 97.9382C60.1276 69.3047 101.003 46.0927 129.637 46.0927C158.27 46.0927 163.819 69.3047 163.819 97.9382Z" fill="var(--dsw-alias-illustration-background)" />
|
||||
@@ -26,7 +30,7 @@ export function ScheduleEmptyIllustration({ className }: { className: string | u
|
||||
<path d="M124.522 3.63379C124.522 2.54381 124.634 1.45326 124.86 0.363281C124.886 0.242199 124.913 0.121083 124.941 2.19411e-08L125.442 0C125.47 0.121082 125.497 0.242199 125.523 0.363281C125.749 1.45326 125.861 2.54381 125.861 3.63379C125.861 4.72362 125.749 5.81349 125.523 6.90332C125.497 7.02442 125.47 7.1455 125.442 7.2666H124.941C124.913 7.1455 124.886 7.02442 124.86 6.90332C124.634 5.81349 124.522 4.72362 124.522 3.63379Z" fill="var(--dsw-alias-illustration-detail)" />
|
||||
<path d="M144.509 33.3101C144.509 31.7544 144.622 30.1987 144.848 28.6431C144.873 28.4702 144.9 28.2974 144.928 28.1245H145.43C145.458 28.2974 145.485 28.4702 145.51 28.6431C145.736 30.1987 145.849 31.7544 145.849 33.3101C145.849 34.8659 145.736 36.4222 145.51 37.978C145.485 38.1509 145.458 38.3237 145.43 38.4966H144.928C144.9 38.3237 144.873 38.1509 144.848 37.978C144.622 36.4222 144.509 34.8659 144.509 33.3101Z" fill="var(--dsw-alias-illustration-detail)" />
|
||||
<path d="M149.05 98.8527C149.05 112.66 137.857 123.853 124.05 123.853C110.242 123.853 99.0496 112.66 99.0496 98.8527C99.0496 85.0455 110.242 73.8527 124.05 73.8527C137.857 73.8527 149.05 85.0455 149.05 98.8527Z" fill="var(--dsw-alias-illustration-detail)" />
|
||||
<path d="M144.192 98.8529C144.192 109.978 135.174 118.997 124.049 118.997C112.923 118.997 103.905 109.978 103.905 98.8529C103.905 87.7278 112.923 78.709 124.049 78.709C135.174 78.709 144.192 87.7278 144.192 98.8529Z" fill="var(--dsw-alias-illustration-clock-face)" />
|
||||
<path d="M144.192 98.8529C144.192 109.978 135.174 118.997 124.049 118.997C112.923 118.997 103.905 109.978 103.905 98.8529C103.905 87.7278 112.923 78.709 124.049 78.709C135.174 78.709 144.192 87.7278 144.192 98.8529Z" fill="var(--dsw-alias-illustration-inset)" />
|
||||
<path d="M125.124 86.7281L125.123 99.2824L134.923 104.132L132.824 108.375L122.76 103.395C121.309 102.677 120.39 101.197 120.39 99.5782L120.389 86.7275L125.124 86.7281Z" fill="var(--dsw-alias-illustration-detail)" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -389,7 +389,6 @@
|
||||
}
|
||||
|
||||
.emptyIllustration {
|
||||
display: block;
|
||||
flex: none;
|
||||
width: 110px;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -891,6 +891,24 @@ describe('Task manager catalog', () => {
|
||||
expect(screen.getByRole('button', { name: en['statusFilter.all'] }).getAttribute('aria-pressed')).toBe('true')
|
||||
})
|
||||
|
||||
it('renders a decorative document-and-clock illustration in an empty catalog', () => {
|
||||
mount({ records: [], status: 'ready' })
|
||||
const illustration = screen.getByRole('status').querySelector('svg')
|
||||
expect(illustration?.getAttribute('aria-hidden')).toBe('true')
|
||||
expect(illustration?.getAttribute('focusable')).toBe('false')
|
||||
expect(illustration?.getAttribute('viewBox')).toBe('0 0 170 170')
|
||||
const paths = illustration?.querySelectorAll('path')
|
||||
expect(paths).toHaveLength(20)
|
||||
// Roles follow the approved artwork's paint order, including overlapping paper and clock layers.
|
||||
expect(paths?.[0]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-background)')
|
||||
expect(paths?.[2]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-backing)')
|
||||
expect(paths?.[3]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-surface)')
|
||||
expect(paths?.[10]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-shadow)')
|
||||
expect(paths?.[17]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-detail)')
|
||||
expect(paths?.[18]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-inset)')
|
||||
expect(emptyNewTaskButton().textContent).toBe(en['empty.action'])
|
||||
})
|
||||
|
||||
it('distinguishes an empty catalog, loading, and a failed read', () => {
|
||||
const h = mount({ records: [], status: 'loading' })
|
||||
expect(screen.getByRole('status', { name: en['list.loading'] }).textContent).toBe('')
|
||||
@@ -903,26 +921,6 @@ describe('Task manager catalog', () => {
|
||||
expect(h.props.onRetry).toHaveBeenCalledOnce()
|
||||
h.update({ status: 'ready' })
|
||||
expect(screen.getByRole('status').textContent).toContain(en['list.empty'])
|
||||
const illustration = screen.getByRole('status').querySelector('svg')
|
||||
expect(illustration?.getAttribute('aria-hidden')).toBe('true')
|
||||
expect(illustration?.getAttribute('focusable')).toBe('false')
|
||||
expect(illustration?.getAttribute('viewBox')).toBe('0 0 170 170')
|
||||
const illustrationPaths = illustration?.querySelectorAll('path')
|
||||
expect(illustrationPaths).toHaveLength(20)
|
||||
expect(illustrationPaths?.[0]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-background)')
|
||||
expect(illustrationPaths?.[2]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-backing)')
|
||||
expect(illustrationPaths?.[3]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-surface)')
|
||||
expect(illustrationPaths?.[10]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-shadow)')
|
||||
expect(illustrationPaths?.[17]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-detail)')
|
||||
expect(illustrationPaths?.[18]?.getAttribute('fill')).toBe('var(--dsw-alias-illustration-clock-face)')
|
||||
expect(new Set(Array.from(illustration?.querySelectorAll('path') ?? [], path => path.getAttribute('fill')))).toEqual(new Set([
|
||||
'var(--dsw-alias-illustration-background)',
|
||||
'var(--dsw-alias-illustration-surface)',
|
||||
'var(--dsw-alias-illustration-backing)',
|
||||
'var(--dsw-alias-illustration-shadow)',
|
||||
'var(--dsw-alias-illustration-clock-face)',
|
||||
'var(--dsw-alias-illustration-detail)',
|
||||
]))
|
||||
// The mock keeps one creation entry in the empty state whether or not a
|
||||
// query is active, so the action never disappears behind a filter.
|
||||
expect(emptyNewTaskButton().textContent).toBe(en['empty.action'])
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
en: 3eb9b0e3365b2db3
|
||||
zh: c73acc55dd897266
|
||||
/deepseek-ai-dsh-client-ui-theme/understand-the-implementation/stylesheets:
|
||||
en: ceb4c9fc94b29f20
|
||||
zh: 990da98c95093353
|
||||
en: c47fd726c865cdd3
|
||||
zh: 7c62b6e5857c29a7
|
||||
/deepseek-ai-dsh-client-ui-theme/understand-the-implementation/scrollbar-rebinding:
|
||||
en: bf76e7c34a331595
|
||||
zh: 5accd0260ef7c61e
|
||||
|
||||
@@ -69,7 +69,7 @@ In pointer modality, `html[data-input-modality='pointer'] body :focus-visible:no
|
||||
|
||||
Menu icons use `--dsw-alias-menu-icon`: neutral-bluish 800 in light mode and `label-primary-dimmed` in dark mode.
|
||||
|
||||
The `--dsw-alias-illustration-*` tokens separate decorative artwork backgrounds, foreground surfaces, backing layers, shadows, clock faces, and details. The dark palette keeps foreground surfaces brighter than backing layers and backgrounds; clock faces and shadows share the backing tone.
|
||||
The `--dsw-alias-illustration-*` tokens separate layered decorative artwork backgrounds, foreground surfaces, backing layers, shadows, inset fills, and details. Clock faces use the inset fill. The dark palette keeps foreground surfaces brighter than backing layers and backgrounds; inset fills and shadows share the backing tone.
|
||||
|
||||
`base.css` suppresses only the outline of focused elements marked `data-dsh-automatic-focus` by the [primitive focus helper](../ui-primitives/README.md); ordinary keyboard focus styling, borders, shadows, and error states remain intact.
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ Desktop 的固定快捷键 `Cmd`/`Ctrl` + `+`(或 `=`)和 `Cmd`/`Ctrl` + `-`
|
||||
|
||||
菜单图标使用 `--dsw-alias-menu-icon`:浅色模式为 neutral-bluish 800,深色模式为 `label-primary-dimmed`。
|
||||
|
||||
`--dsw-alias-illustration-*` token 分别定义装饰插画的背景、前层表面、后层、投影、表盘与细节配色。深色配色让前层表面比后层和背景更亮;表盘与投影沿用后层色调。
|
||||
`--dsw-alias-illustration-*` token 分别定义分层装饰插画的背景、前层表面、后层、投影、内嵌填充与细节配色。表盘使用内嵌填充。深色配色让前层表面比后层和背景更亮;内嵌填充与投影沿用后层色调。
|
||||
|
||||
`base.css` 仅抑制[基础控件焦点工具](../ui-primitives/README.zh.md)通过 `data-dsh-automatic-focus` 标记的聚焦元素外轮廓线;正常键盘焦点样式、边框、阴影及错误状态保持不变。
|
||||
|
||||
|
||||
@@ -59,9 +59,11 @@ body {
|
||||
--dsw-static-neutral-bluish-150: rgb(233, 236, 242);
|
||||
--dsw-static-neutral-bluish-200: rgb(225, 229, 238);
|
||||
--dsw-static-neutral-bluish-300: rgb(207, 211, 214);
|
||||
--dsw-static-neutral-bluish-310: rgb(202, 210, 219);
|
||||
--dsw-static-neutral-bluish-400: rgb(173, 178, 184);
|
||||
--dsw-static-neutral-bluish-500: rgb(151, 157, 166);
|
||||
--dsw-static-neutral-bluish-50: rgb(249, 250, 251);
|
||||
--dsw-static-neutral-bluish-55: rgb(245, 247, 250);
|
||||
--dsw-static-neutral-bluish-600: rgb(129, 133, 140);
|
||||
--dsw-static-neutral-bluish-60: rgb(245, 246, 247);
|
||||
--dsw-static-neutral-bluish-700: rgb(97, 102, 107);
|
||||
@@ -71,6 +73,7 @@ body {
|
||||
--dsw-static-neutral-bluish-850: rgb(44, 44, 46);
|
||||
--dsw-static-neutral-bluish-875: rgb(35, 35, 36);
|
||||
--dsw-static-neutral-bluish-900: rgb(27, 27, 28);
|
||||
--dsw-static-neutral-bluish-90: rgb(237, 240, 244);
|
||||
--dsw-static-neutral-bluish-950: rgb(21, 21, 23);
|
||||
--dsw-static-red-100: rgb(254, 226, 226);
|
||||
--dsw-static-red-400: rgb(242, 90, 90);
|
||||
@@ -140,9 +143,11 @@ body[data-ds-dark-theme] {
|
||||
--dsw-static-neutral-bluish-150: rgb(233, 236, 242);
|
||||
--dsw-static-neutral-bluish-200: rgb(225, 229, 238);
|
||||
--dsw-static-neutral-bluish-300: rgb(207, 211, 214);
|
||||
--dsw-static-neutral-bluish-310: rgb(202, 210, 219);
|
||||
--dsw-static-neutral-bluish-400: rgb(173, 178, 184);
|
||||
--dsw-static-neutral-bluish-500: rgb(151, 157, 166);
|
||||
--dsw-static-neutral-bluish-50: rgb(249, 250, 251);
|
||||
--dsw-static-neutral-bluish-55: rgb(245, 247, 250);
|
||||
--dsw-static-neutral-bluish-600: rgb(129, 133, 140);
|
||||
--dsw-static-neutral-bluish-60: rgb(249, 250, 251);
|
||||
--dsw-static-neutral-bluish-700: rgb(97, 102, 107);
|
||||
@@ -152,6 +157,7 @@ body[data-ds-dark-theme] {
|
||||
--dsw-static-neutral-bluish-850: rgb(44, 44, 46);
|
||||
--dsw-static-neutral-bluish-875: rgb(35, 35, 36);
|
||||
--dsw-static-neutral-bluish-900: rgb(27, 27, 28);
|
||||
--dsw-static-neutral-bluish-90: rgb(237, 240, 244);
|
||||
--dsw-static-neutral-bluish-950: rgb(21, 21, 23);
|
||||
--dsw-static-red-100: rgb(254, 226, 226);
|
||||
--dsw-static-red-400: rgb(242, 90, 90);
|
||||
@@ -206,12 +212,12 @@ body {
|
||||
--dsw-alias-button-tool-bar-fill-invisible: rgba(31, 31, 31, 0.36);
|
||||
--dsw-alias-button-tool-bar-fill: rgba(84, 85, 87, 0.5);
|
||||
--dsw-alias-button-tool-bar-hover: rgba(84, 85, 87, 0.6);
|
||||
--dsw-alias-illustration-background: rgb(245, 247, 250);
|
||||
--dsw-alias-illustration-surface: rgb(237, 240, 244);
|
||||
--dsw-alias-illustration-clock-face: var(--dsw-alias-illustration-surface);
|
||||
--dsw-alias-illustration-backing: rgb(202, 210, 219);
|
||||
--dsw-alias-illustration-shadow: rgb(237, 240, 244);
|
||||
--dsw-alias-illustration-detail: rgb(202, 210, 219);
|
||||
--dsw-alias-illustration-background: var(--dsw-static-neutral-bluish-55);
|
||||
--dsw-alias-illustration-backing: var(--dsw-static-neutral-bluish-310);
|
||||
--dsw-alias-illustration-detail: var(--dsw-static-neutral-bluish-310);
|
||||
--dsw-alias-illustration-inset: var(--dsw-alias-illustration-surface);
|
||||
--dsw-alias-illustration-shadow: var(--dsw-alias-illustration-surface);
|
||||
--dsw-alias-illustration-surface: var(--dsw-static-neutral-bluish-90);
|
||||
--dsw-alias-interactive-bg-active: rgba(38, 49, 72, 0.1);
|
||||
--dsw-alias-interactive-bg-hover-accent: rgba(38, 49, 72, 0.14);
|
||||
--dsw-alias-interactive-bg-hover-danger: rgba(236, 19, 19, 0.05);
|
||||
@@ -331,11 +337,11 @@ body[data-ds-dark-theme] {
|
||||
--dsw-alias-button-tool-bar-fill: rgba(84, 85, 87, 0.5);
|
||||
--dsw-alias-button-tool-bar-hover: rgba(84, 85, 87, 0.6);
|
||||
--dsw-alias-illustration-background: var(--dsw-static-neutral-bluish-850);
|
||||
--dsw-alias-illustration-surface: var(--dsw-static-neutral-bluish-700);
|
||||
--dsw-alias-illustration-clock-face: var(--dsw-alias-illustration-backing);
|
||||
--dsw-alias-illustration-backing: var(--dsw-static-neutral-bluish-750);
|
||||
--dsw-alias-illustration-shadow: var(--dsw-static-neutral-bluish-750);
|
||||
--dsw-alias-illustration-detail: var(--dsw-static-neutral-bluish-600);
|
||||
--dsw-alias-illustration-inset: var(--dsw-alias-illustration-backing);
|
||||
--dsw-alias-illustration-shadow: var(--dsw-alias-illustration-backing);
|
||||
--dsw-alias-illustration-surface: var(--dsw-static-neutral-bluish-700);
|
||||
--dsw-alias-interactive-bg-active: rgba(255, 255, 255, 0.14);
|
||||
--dsw-alias-interactive-bg-hover-accent: rgba(255, 255, 255, 0.24);
|
||||
--dsw-alias-interactive-bg-hover-danger: rgba(242, 90, 90, 0.15);
|
||||
|
||||
@@ -1,39 +1,140 @@
|
||||
// @vitest-environment jsdom
|
||||
/** Decorative artwork keeps separate foreground, backing, and detail colors. */
|
||||
/** Decorative artwork resolves theme tokens to distinct foreground, backing, and detail colors. */
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { afterEach, beforeEach, expect, it } from 'vitest'
|
||||
import { expect, it } from 'vitest'
|
||||
import { parseRules, varReferences } from './stylesheet-scan.ts'
|
||||
|
||||
const stylesheet = readFileSync(resolve(import.meta.dirname, '../src/styles/design-platform.css'), 'utf8')
|
||||
let style: HTMLStyleElement
|
||||
const illustration = readFileSync(resolve(import.meta.dirname, '../../ui-schedule/src/client/ScheduleEmptyIllustration.tsx'), 'utf8')
|
||||
const rules = parseRules(stylesheet)
|
||||
const PREFIX = '--dsw-alias-illustration-'
|
||||
const ROLES = ['background', 'backing', 'detail', 'inset', 'shadow', 'surface'] as const
|
||||
|
||||
beforeEach(() => {
|
||||
style = document.createElement('style')
|
||||
style.textContent = stylesheet
|
||||
document.head.append(style)
|
||||
type Rgb = [number, number, number]
|
||||
|
||||
function definitions(dark: boolean): Map<string, string> {
|
||||
const selector = dark ? 'body[data-ds-dark-theme]' : 'body'
|
||||
return new Map(rules.filter(rule => rule.selectors.includes(selector)).flatMap(rule => rule.declarations))
|
||||
}
|
||||
|
||||
function resolveColor(palette: Map<string, string>, name: string): Rgb {
|
||||
const seen = new Set<string>()
|
||||
let current = name
|
||||
while (palette.has(current) && !seen.has(current)) {
|
||||
seen.add(current)
|
||||
const value = palette.get(current)!
|
||||
const [reference] = varReferences(value)
|
||||
if (reference !== undefined) {
|
||||
expect(value, current).toMatch(/^var\(\s*--[\w-]+\s*\)$/)
|
||||
current = reference
|
||||
continue
|
||||
}
|
||||
const rgb = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(value)
|
||||
expect(rgb, `${name}: ${current} = ${value}`).not.toBeNull()
|
||||
return [Number(rgb![1]), Number(rgb![2]), Number(rgb![3])]
|
||||
}
|
||||
throw new Error(`Unresolved color ${name}: ${current}`)
|
||||
}
|
||||
|
||||
function illustrationColors(palette: Map<string, string>) {
|
||||
return Object.fromEntries(ROLES.map(role => [role, resolveColor(palette, `${PREFIX}${role}`)]))
|
||||
}
|
||||
|
||||
function luminance(rgb: Rgb): number {
|
||||
const linear = rgb.map((channel) => {
|
||||
const value = channel / 255
|
||||
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4
|
||||
})
|
||||
return linear[0]! * 0.2126 + linear[1]! * 0.7152 + linear[2]! * 0.0722
|
||||
}
|
||||
|
||||
function expectDarkLayers(palette: Map<string, string>): void {
|
||||
const color = (role: string): Rgb => resolveColor(palette, `${PREFIX}${role}`)
|
||||
expect(luminance(color('surface'))).toBeGreaterThan(luminance(color('backing')))
|
||||
expect(luminance(color('backing'))).toBeGreaterThan(luminance(color('background')))
|
||||
for (const role of ['surface', 'backing', 'background']) expect(color('detail')).not.toEqual(color(role))
|
||||
expect(color('inset')).toEqual(color('backing'))
|
||||
expect(color('shadow')).toEqual(color('backing'))
|
||||
}
|
||||
|
||||
function expectConsumerColors(source: string, palette: Map<string, string>): void {
|
||||
const consumed = new Set(varReferences(source))
|
||||
const defined = [...palette.keys()].filter(name => name.startsWith(PREFIX)).sort()
|
||||
expect([...consumed].sort()).toEqual(defined)
|
||||
expect(consumed.size).toBe(ROLES.length)
|
||||
for (const name of consumed) resolveColor(palette, name)
|
||||
}
|
||||
|
||||
const lightColors = {
|
||||
background: [245, 247, 250],
|
||||
backing: [202, 210, 219],
|
||||
detail: [202, 210, 219],
|
||||
inset: [237, 240, 244],
|
||||
shadow: [237, 240, 244],
|
||||
surface: [237, 240, 244],
|
||||
}
|
||||
|
||||
it('preserves the original light artwork colors, including surface-colored insets and shadows', () => {
|
||||
expect(illustrationColors(definitions(false))).toEqual(lightColors)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
style.remove()
|
||||
document.body.removeAttribute('data-ds-dark-theme')
|
||||
it.each([false, true])('defines the approved light static steps in the dark=%s palette', (dark) => {
|
||||
const palette = definitions(dark)
|
||||
expect(resolveColor(palette, '--dsw-static-neutral-bluish-55')).toEqual([245, 247, 250])
|
||||
expect(resolveColor(palette, '--dsw-static-neutral-bluish-90')).toEqual([237, 240, 244])
|
||||
expect(resolveColor(palette, '--dsw-static-neutral-bluish-310')).toEqual([202, 210, 219])
|
||||
})
|
||||
|
||||
it('preserves the light illustration palette and paper-colored clock face', () => {
|
||||
const colors = getComputedStyle(document.body)
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-background').trim()).toBe('rgb(245,247,250)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-surface').trim()).toBe('rgb(237,240,244)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-backing').trim()).toBe('rgb(202,210,219)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-detail').trim()).toBe('rgb(202,210,219)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-clock-face').trim()).toBe('var(--dsw-alias-illustration-surface)')
|
||||
it('resolves the dark artwork colors through the actual static palette', () => {
|
||||
expect(illustrationColors(definitions(true))).toEqual({
|
||||
background: [44, 44, 46],
|
||||
backing: [67, 69, 74],
|
||||
detail: [129, 133, 140],
|
||||
inset: [67, 69, 74],
|
||||
shadow: [67, 69, 74],
|
||||
surface: [97, 102, 107],
|
||||
})
|
||||
})
|
||||
|
||||
it('separates dark paper layers from details and uses the middle tone for the clock face', () => {
|
||||
document.body.setAttribute('data-ds-dark-theme', '')
|
||||
const colors = getComputedStyle(document.body)
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-surface').trim()).toBe('var(--dsw-static-neutral-bluish-700)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-backing').trim()).toBe('var(--dsw-static-neutral-bluish-750)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-background').trim()).toBe('var(--dsw-static-neutral-bluish-850)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-detail').trim()).toBe('var(--dsw-static-neutral-bluish-600)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-shadow').trim()).toBe('var(--dsw-static-neutral-bluish-750)')
|
||||
expect(colors.getPropertyValue('--dsw-alias-illustration-clock-face').trim()).toBe('var(--dsw-alias-illustration-backing)')
|
||||
it('keeps dark surfaces brighter than backing and background, with distinct details and backing-colored insets and shadows', () => {
|
||||
expectDarkLayers(definitions(true))
|
||||
})
|
||||
|
||||
it.each([false, true])('resolves the illustration consumer names in the dark=%s palette', (dark) => {
|
||||
expectConsumerColors(illustration, definitions(dark))
|
||||
})
|
||||
|
||||
it('rejects light static-color drift even when alias names stay unchanged', () => {
|
||||
const palette = definitions(false)
|
||||
palette.set('--dsw-static-neutral-bluish-90', 'rgb(235, 238, 242)')
|
||||
expect(() => { expect(illustrationColors(palette)).toEqual(lightColors) }).toThrow()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['surface', 'backing'],
|
||||
['backing', 'surface'],
|
||||
['background', 'backing'],
|
||||
['detail', 'surface'],
|
||||
['inset', 'surface'],
|
||||
['shadow', 'surface'],
|
||||
])('rejects a dark %s role changed to %s', (role, target) => {
|
||||
const palette = definitions(true)
|
||||
palette.set(`${PREFIX}${role}`, `var(${PREFIX}${target})`)
|
||||
expect(() => { expectDarkLayers(palette) }).toThrow()
|
||||
})
|
||||
|
||||
it.each([false, true])('rejects renamed consumers and missing definitions in the dark=%s palette', (dark) => {
|
||||
const palette = definitions(dark)
|
||||
const renamed = illustration.replaceAll(`${PREFIX}inset`, `${PREFIX}clock-face`)
|
||||
expect(() => { expectConsumerColors(renamed, palette) }).toThrow()
|
||||
const misspelled = illustration.replace(PREFIX, '--dsw-alias-illustation-')
|
||||
expect(() => { expectConsumerColors(misspelled, palette) }).toThrow()
|
||||
palette.delete(`${PREFIX}inset`)
|
||||
expect(() => { expectConsumerColors(illustration, palette) }).toThrow()
|
||||
})
|
||||
|
||||
it.each(['--dsw-static-missing', `${PREFIX}inset`])('rejects an unresolved or cyclic inset reference to %s', (target) => {
|
||||
const palette = definitions(true)
|
||||
palette.set(`${PREFIX}inset`, `var(${target})`)
|
||||
expect(() => resolveColor(palette, `${PREFIX}inset`)).toThrow('Unresolved color')
|
||||
})
|
||||
Reference in new issue
Block a user