85 lines
4.4 KiB
TypeScript
85 lines
4.4 KiB
TypeScript
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(
|
|
<DonutMetricCard label="Deviation" value={453} percentage={51} tone="deviation" />
|
|
);
|
|
|
|
expect(html).toContain('class="donut-metric-card donut-metric-card--deviation"');
|
|
expect(html).toContain('class="donut-metric-card__title">Deviation</h3>');
|
|
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('<div class="donut-metric-card__primary"><strong>51</strong><span>%</span></div>');
|
|
expect(html).toContain('<small>453</small>');
|
|
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).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(
|
|
<DonutMetricCard label="Error" value={2} percentage={0} tone="error" />
|
|
);
|
|
const clamped = renderToStaticMarkup(
|
|
<DonutMetricCard label="Results All" value={885} percentage={120} tone="all" />
|
|
);
|
|
|
|
expect(zero).toContain('stroke-dasharray="0 100"');
|
|
expect(zero).toContain('<strong>0</strong><span>%</span>');
|
|
expect(clamped).toContain('stroke-dasharray="100 0"');
|
|
expect(clamped).toContain('donut-metric-card__primary is-compact');
|
|
expect(clamped).toContain('<strong>100</strong><span>%</span>');
|
|
expect(zero).not.toContain('<animate');
|
|
});
|
|
|
|
it('supports a single suffixed score without a duplicate percentage label', () => {
|
|
const html = renderToStaticMarkup(
|
|
<DonutMetricCard label="Compliance Score" value={36} valueSuffix="%" showPercentage={false} percentage={36} tone="all" />
|
|
);
|
|
|
|
expect(html).toContain('<strong>36%</strong>');
|
|
expect(html).not.toContain('<span>%</span>');
|
|
});
|
|
|
|
it('becomes an accessible active filter control when clickable', () => {
|
|
const html = renderToStaticMarkup(
|
|
<DonutMetricCard
|
|
label="High"
|
|
value={12}
|
|
percentage={24}
|
|
tone="high"
|
|
active
|
|
onClick={() => 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');
|
|
});
|
|
});
|