Files
minabot/styles/typography.test.ts
syntaxbullet dde5e77317 feat: enhance typography and accessibility in design system
- Added new UI heading utility with specific styles in typography.css.
- Updated typography tests to include new UI heading role and ensure proper size and styling.
- Documented design system audit findings, focusing on accessibility improvements and component consistency.
- Introduced CalendarExamples component to demonstrate calendar functionality with habit tracking.
- Created a shared Field component for consistent labeling and hinting of form controls.
- Implemented comprehensive accessibility tests for calendar and editor components, ensuring proper focus management and keyboard navigation.
- Added structure tests for Field and Card components to verify correct ID generation and heading levels.
2026-09-04 13:44:31 +02:00

75 lines
3.7 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
const css = (name: string) => readFileSync(new URL(name, import.meta.url), "utf8");
function tokenLuminance(source: string, token: string) {
let hex = source.match(new RegExp(`--ds-${token}: #([\\da-f]+);`))![1]!;
if (hex.length === 3) hex = [...hex].map((c) => c + c).join("");
const channels = [0, 2, 4].map((start) => {
const value = parseInt(hex.slice(start, start + 2), 16) / 255;
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
});
return channels[0]! * 0.2126 + channels[1]! * 0.7152 + channels[2]! * 0.0722;
}
describe("shared typography contract", () => {
test("all component and page styles use the fixed shared type utilities", () => {
for (const file of ["design-system.css"]) {
const source = css(file);
expect(source).not.toMatch(/font-size\s*:/);
expect(source).not.toMatch(/font:\s*\d/);
expect(source).toContain("@apply type-");
}
const typography = css("typography.css");
expect(typography).not.toMatch(/clamp\(|\d(?:vw|vh|px)\b/);
expect(typography).toContain("--text-copy: 1rem");
expect(typography).toContain("--text-caption: 0.875rem");
for (const role of ["body", "small", "label", "ui-heading", "eyebrow", "lead", "title", "section", "display", "control"]) {
expect(typography).toContain(`@utility type-${role}`);
}
});
test("text colors meet WCAG AA normal-text contrast on paper", () => {
const source = css("design-system.css");
for (const color of ["ink", "secondary"]) {
expect((tokenLuminance(source, "paper") + 0.05) / (tokenLuminance(source, color) + 0.05)).toBeGreaterThanOrEqual(4.5);
}
});
test("UI headings are larger than supporting text and reset editorial typography", () => {
const rule = css("typography.css").match(/@utility type-ui-heading\s*\{([^}]+)\}/)![1]!;
expect(rule).toContain("@apply text-ui-heading");
const source = css("typography.css");
const size = (role: string) => Number(source.match(new RegExp(`--text-${role}: ([\\d.]+)rem`))![1]);
expect(size("ui-heading")).toBe(1.125);
expect(size("ui-heading")).toBeGreaterThan(size("caption"));
expect(rule).toContain("font-family: var(--ds-sans)");
expect(rule).toContain("font-weight: 500");
expect(rule).toContain("letter-spacing: normal");
expect(css("design-system.css")).toMatch(/\.ds-spec-heading label\s*\{\s*@apply type-ui-heading/);
});
test("control boundaries contrast with paper and use a shared token", () => {
const source = css("design-system.css");
const ratio = (tokenLuminance(source, "paper") + 0.05) / (tokenLuminance(source, "control-border") + 0.05);
expect(ratio).toBeGreaterThanOrEqual(3);
const fieldRule = source.match(/\.ds-field :is\(input, select\)\s*\{([^}]+)\}/)![1]!;
expect(fieldRule).toContain("border: 1px solid var(--ds-control-border)");
});
test("shared controls retain usable targets and native forced-colors checkboxes", () => {
const source = css("design-system.css");
const counterRule = source.match(/\.ds-counter \.ds-button\s*\{([^}]+)\}/)![1]!;
expect(counterRule).toContain("min-width: 44px");
expect(counterRule).toContain("min-height: 44px");
const dayRule = source.match(/\.ds-day\s*\{([^}]+)\}/)![1]!;
expect(dayRule).toContain("min-width: 0");
expect(dayRule).toContain("aspect-ratio: 1");
expect(source).toContain("--calendar-gap: 4px;");
expect(source).toContain("@media (max-width: 600px) and (orientation: portrait)");
expect(source).toContain(".ds-day { min-width: 24px; min-height: 24px; }");
expect(source).toMatch(/@media \(forced-colors: active\)\s*\{\s*\.ds-checkbox input\s*\{\s*appearance: auto/);
});
});