import { readFileSync } from 'node:fs'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } from 'vitest'; import { DonutMetricCard } from '../../src/components/DonutMetricCard/DonutMetricCard'; const styles = readFileSync(new URL('../../src/styles/components.css', import.meta.url), 'utf8'); const tileContract = readFileSync(new URL('../../src/styles/tile-contract.css', import.meta.url), 'utf8'); const tokens = readFileSync(new URL('../../src/styles/ui-tokens.css', import.meta.url), 'utf8'); describe('DonutMetricCard binding design contract', () => { it('renders the shared full-circle reference with centered percentage and supporting count', () => { const html = renderToStaticMarkup( ); expect(html).toContain('class="donut-metric-card donut-metric-card--deviation"'); expect(html).toContain('class="donut-metric-card__title">Deviation'); expect(html).toContain('class="donut-metric-card__track" cx="60" cy="60" r="48" pathLength="100"'); expect(html).toContain('stroke-dasharray="51 49"'); expect(html).toContain('
51%
'); expect(html).toContain('453'); expect(html).toContain('--donut-progress-dash:51 49'); expect(styles).toContain('transform: rotate(-90deg);'); expect(styles).toContain('left: calc(100% + var(--space-1));'); expect(styles).toMatch(/\.donut-metric-card__primary\s*\{[^}]*position:\s*relative;/s); expect(styles).not.toContain('stroke-linecap: round;'); expect(styles).toContain('@keyframes donut-progress-build'); expect(styles).not.toContain('.donut-metric-card::before'); expect(styles).not.toContain('@keyframes donut-accent-build'); expect(styles).toContain('animation: donut-progress-build var(--dur-data-build) var(--ease-data-build) both;'); expect(styles).toMatch(/\.donut-metric-rack\s*\{[^}]*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);/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(styles).toMatch(/@media \(max-width: 1100px\)\s*\{[^}]*repeat\(3, minmax\(0, 1fr\)\)/s); expect(styles).toMatch(/@media \(max-width: 760px\)\s*\{[^}]*repeat\(2, minmax\(0, 1fr\)\)/s); expect(tokens).toContain('--dur-data-build: 700ms;'); expect(tokens).toContain('--donut-value-size: 26px;'); expect(tokens).toContain('--donut-percent-size: 14px;'); expect(tokens).toContain('--donut-compact-value-size: 22px;'); expect(tokens).toContain('--donut-compact-percent-size: 12px;'); expect(styles).toMatch(/\.sidebar-severity-card \.donut-metric-card__primary span\s*\{[^}]*left:\s*calc\(100% \+ var\(--donut-compact-unit-gap\)\);/s); }); it('renders an empty progress arc at zero and clamps percentages', () => { const zero = renderToStaticMarkup( ); const clamped = renderToStaticMarkup( ); expect(zero).toContain('stroke-dasharray="0 100"'); expect(zero).toContain('0%'); expect(clamped).toContain('stroke-dasharray="100 0"'); expect(clamped).toContain('donut-metric-card__primary is-compact'); expect(clamped).toContain('100%'); expect(zero).not.toContain(' { const html = renderToStaticMarkup( ); expect(html).toContain('36%'); expect(html).not.toContain('%'); }); it('becomes an accessible active filter control when clickable', () => { const html = renderToStaticMarkup( undefined} className="sidebar-severity-card" /> ); expect(html).toContain('type="button"'); expect(html).toContain('aria-pressed="true"'); expect(html).toContain('donut-metric-card--high sidebar-severity-card'); }); });