90 lines
6.8 KiB
TypeScript
90 lines
6.8 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
const read = (path: string) => readFileSync(new URL(path, import.meta.url), 'utf8');
|
|
const tokens = read('../../src/styles/ui-tokens.css');
|
|
const components = read('../../src/styles/components.css');
|
|
const tileContract = read('../../src/styles/tile-contract.css');
|
|
const datahub = read('../../src/styles/datahub.css');
|
|
const datahubRedesign = read('../../src/styles/datahub-redesign.css');
|
|
const legacy = read('../../src/styles/legacy-views.css');
|
|
const layout = read('../../src/styles/layout.css');
|
|
const uiComponents = read('../../src/styles/ui-components.css');
|
|
const policy = read('../../src/styles/policy-analysis.css');
|
|
const pathfinder = read('../../src/styles/mitre-graph.css');
|
|
const viewer = read('../../src/features/dataset-viewer/DatasetViewer.tsx');
|
|
const overview = read('../../src/features/project-import/DatahubHistoryOverview.tsx');
|
|
const guidance = read('../../AGENTS.md');
|
|
|
|
describe('binding segment and tile grammar', () => {
|
|
it('defines one semantic geometry and surface contract for both themes', () => {
|
|
for (const token of [
|
|
'--segment-surface:', '--segment-border:', '--segment-radius: 10px;',
|
|
'--segment-inset: var(--space-3);', '--segment-gap: var(--space-3);',
|
|
'--tile-surface:', '--tile-surface-hover:', '--tile-border:',
|
|
'--tile-selected-border: var(--marking-color);', '--tile-radius: var(--radius-sm);',
|
|
'--tile-compact-width: 186px;', '--tile-content-width: 320px;',
|
|
'--tile-card-shadow:'
|
|
]) expect(tokens).toContain(token);
|
|
|
|
expect(tokens.match(/--segment-surface: transparent;/g)).toHaveLength(2);
|
|
expect(tokens.match(/--segment-border: transparent;/g)).toHaveLength(2);
|
|
expect(tokens.match(/--tile-border: transparent;/g)).toHaveLength(2);
|
|
expect(tokens.match(/--tile-card-shadow: none;/g)).toHaveLength(2);
|
|
expect(tokens.match(/--marking-color: #E9A63C;/g)).toHaveLength(2);
|
|
expect(tokens.match(/--selection-border: var\(--marking-color\);/g)).toHaveLength(2);
|
|
expect(tokens.match(/--selection-marker: var\(--marking-color\);/g)).toHaveLength(2);
|
|
|
|
expect(tokens).toContain('--donut-rack-surface: var(--segment-surface);');
|
|
expect(tokens).toContain('--donut-card-surface: var(--tile-surface);');
|
|
expect(tokens).toContain('--donut-card-shadow: var(--tile-card-shadow);');
|
|
});
|
|
|
|
it('binds the shared grammar to Datahub, dashboard, sidebar, Work, policy and Pathfinder tiles', () => {
|
|
expect(components).toMatch(/\.donut-metric-rack\s*\{[^}]*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);[^}]*padding:\s*var\(--segment-inset\);[^}]*border-radius:\s*var\(--segment-radius\);/s);
|
|
expect(tileContract).toMatch(/\.donut-metric-rack\s*\{[^}]*grid-template-columns:\s*repeat\(auto-fill, var\(--tile-compact-width\)\);[^}]*justify-content:\s*start;/s);
|
|
expect(components).toMatch(/\.donut-metric-card\s*\{[^}]*border-radius:\s*var\(--tile-radius\);[^}]*background:\s*var\(--tile-surface\);[^}]*box-shadow:\s*var\(--tile-card-shadow\);/s);
|
|
expect(components).toMatch(/\.sidebar-severity-rack\s*\{[^}]*gap:\s*var\(--segment-gap\);[^}]*background:\s*var\(--segment-surface\);/s);
|
|
|
|
expect(datahubRedesign).toMatch(/\.datahub-tenant-card-rack\s*\{[^}]*repeat\(auto-fill, var\(--tile-compact-width\)\);[^}]*justify-content:\s*start;/s);
|
|
expect(datahubRedesign).toMatch(/\.datahub-tenant-card\.is-selected\s*\{[^}]*border-color:\s*var\(--tile-selected-border\);/s);
|
|
expect(overview).toContain('donut-metric-card donut-metric-card--all datahub-tenant-card datahub-tenant-add');
|
|
|
|
expect(datahub).toMatch(/\.dashboard-status-rack\s*\{[^}]*padding:\s*var\(--segment-inset\);[^}]*background:\s*var\(--segment-surface\);/s);
|
|
expect(datahub).toMatch(/\.dashboard-status-metric\s*\{[^}]*border-radius:\s*var\(--tile-radius\);[^}]*box-shadow:\s*var\(--tile-card-shadow\);/s);
|
|
expect(datahub).toMatch(/\.dashboard-distribution-grid\s*\{[^}]*border-radius:\s*var\(--segment-radius\);/s);
|
|
expect(datahub).toMatch(/\.db-structure-card\s*\{[^}]*background:\s*var\(--tile-surface\);[^}]*box-shadow:\s*var\(--tile-card-shadow\);/s);
|
|
|
|
expect(layout).toMatch(/\.tile-view-scroll\s*\{[^}]*padding:\s*var\(--panel-inset\);/s);
|
|
expect(legacy).toMatch(/\.tile-grid\s*\{[^}]*justify-content:\s*start;[^}]*border-radius:\s*var\(--segment-radius\);/s);
|
|
expect(legacy).toMatch(/\.data-tile\s*\{[^}]*border-radius:\s*var\(--tile-radius\);[^}]*box-shadow:\s*var\(--tile-card-shadow\);/s);
|
|
expect(viewer).toContain('gridTemplateColumns: `repeat(${tileColumns}, minmax(var(--tile-content-width), 1fr))`');
|
|
expect(viewer).toContain('const TILE_GAP = 12;');
|
|
expect(viewer).toContain('const TILE_ROW_HEIGHT = 172;');
|
|
|
|
expect(policy).toContain('grid-template-columns: repeat(auto-fill, var(--tile-compact-width));');
|
|
expect(pathfinder).toMatch(/\.finding-path-card\s*\{[^}]*border-radius:\s*var\(--tile-radius\);[^}]*box-shadow:\s*var\(--tile-card-shadow\);/s);
|
|
});
|
|
|
|
it('keeps the rules binding and prevents status-filled or stretched substitute tiles', () => {
|
|
expect(guidance).toContain('Treat segments and tiles as a binding two-level visual grammar independent of their content.');
|
|
expect(guidance).toContain('Segments are visually transparent and have no visible border or shadow.');
|
|
expect(guidance).toContain('without a visible normal-state border, radius, or shadow');
|
|
expect(guidance).toContain('Hover changes the surface only');
|
|
expect(guidance).toContain('persistent marking and selection color is always the shared yellow');
|
|
expect(guidance).toContain('never stretch a partial final row merely to fill the container');
|
|
expect(guidance).toContain('Never fill the complete tile with a status color.');
|
|
expect(guidance).toContain('Tile content must never cross the tile boundary or cover a neighboring tile.');
|
|
expect(guidance).toContain('never split ordinary words or numeric values across lines');
|
|
expect(datahub).toMatch(/\.dashboard-status-metric\s*\{[^}]*min-width:\s*0;[^}]*overflow:\s*hidden;/s);
|
|
expect(datahub).toMatch(/\.dashboard-status-copy\s*\{[^}]*min-width:\s*0;[^}]*overflow:\s*hidden;/s);
|
|
expect(datahub).toMatch(/\.dashboard-status-copy\s*>\s*span\s*\{[^}]*overflow-wrap:\s*normal;[^}]*word-break:\s*normal;/s);
|
|
expect(datahub).toMatch(/\.dashboard-status-copy\s*>\s*strong\s*\{[^}]*display:\s*grid;[^}]*white-space:\s*normal;/s);
|
|
expect(legacy).toMatch(/\.data-tile\s*\{[^}]*min-width:\s*0;[^}]*overflow:\s*hidden;/s);
|
|
expect(uiComponents).toMatch(/\.tile\s*\{[^}]*min-width:\s*0;[^}]*overflow:\s*hidden;/s);
|
|
expect(datahubRedesign).not.toMatch(/\.datahub-tenant-card(?::hover|:focus-within|\s*\{)[^}]*translateY/s);
|
|
expect(legacy).not.toMatch(/\.data-tile(?::hover|:focus-visible|\s*\{)[^}]*translateY/s);
|
|
expect(datahub).not.toMatch(/\.dashboard-status-metric(?::hover|\s*\{)[^}]*translateY/s);
|
|
});
|
|
});
|