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.
This commit is contained in:
@@ -174,7 +174,7 @@ describe("design-system-only routing", () => {
|
||||
expect(container.querySelector(".home-root")).toBeNull();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
expect(container.querySelectorAll('[role="tablist"]')).toHaveLength(1);
|
||||
expect(container.querySelectorAll('[role="tab"]')).toHaveLength(7);
|
||||
expect(container.querySelectorAll('[role="tab"]')).toHaveLength(4);
|
||||
expect(container.querySelectorAll('[role="tabpanel"]:not([hidden])')).toHaveLength(1);
|
||||
for (const tab of container.querySelectorAll('[role="tab"]')) {
|
||||
const panel = document.getElementById(tab.getAttribute("aria-controls")!);
|
||||
@@ -183,13 +183,24 @@ describe("design-system-only routing", () => {
|
||||
});
|
||||
}
|
||||
|
||||
test("design system playground remains interactive without API requests", async () => {
|
||||
await render("/design-system#playground");
|
||||
const tabs = Array.from(container.querySelectorAll<HTMLButtonElement>("button"));
|
||||
const detail = tabs.find(button => button.textContent?.trim() === "Habit detail")!;
|
||||
expect(detail).toBeDefined();
|
||||
await act(async () => detail.click());
|
||||
expect(detail.getAttribute("aria-pressed")).toBe("true");
|
||||
test("calendar examples update individual and combined progress without API requests", async () => {
|
||||
await render("/design-system#calendar-states");
|
||||
const calendars = container.querySelectorAll<HTMLElement>("#ds-panel-calendar-states .ds-calendar");
|
||||
expect(calendars).toHaveLength(3);
|
||||
const inspected = (index: number) => calendars[index]!.querySelector(".ds-date-inspector")!.textContent!;
|
||||
expect(inspected(0)).toContain("7 of 8 glasses");
|
||||
expect(inspected(2)).toContain("1 of 2 habits complete");
|
||||
await act(async () => container.querySelector<HTMLButtonElement>('[aria-label="Increase glasses of water"]')!.click());
|
||||
expect(inspected(0)).toContain("8 of 8 glasses");
|
||||
expect(inspected(2)).toContain("2 of 2 habits complete");
|
||||
const reading = container.querySelector<HTMLInputElement>('#ds-panel-calendar-states input[type="checkbox"]')!;
|
||||
await act(async () => reading.click());
|
||||
expect(inspected(1)).toContain("0 of 1 reading session");
|
||||
expect(inspected(2)).toContain("1 of 2 habits complete");
|
||||
const reset = [...container.querySelectorAll<HTMLButtonElement>('#ds-panel-calendar-states button')].find(button => button.textContent === "Reset examples ↺")!;
|
||||
await act(async () => reset.click());
|
||||
expect(inspected(0)).toContain("7 of 8 glasses");
|
||||
expect(reading.checked).toBe(true);
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -207,7 +218,7 @@ describe("design system tabs", () => {
|
||||
test("defaults to foundations and each tab exposes only its own panel", async () => {
|
||||
await render();
|
||||
expect(panel().id).toBe("ds-panel-foundations");
|
||||
for (const id of ["components", "containers", "calendar-states", "playground", "editing", "views", "foundations"]) {
|
||||
for (const id of ["components", "containers", "calendar-states", "foundations"]) {
|
||||
await clickTab(id);
|
||||
expect(panel().id).toBe(`ds-panel-${id}`);
|
||||
expect(container.querySelectorAll('[role="tabpanel"]:not([hidden])')).toHaveLength(1);
|
||||
@@ -221,39 +232,39 @@ describe("design system tabs", () => {
|
||||
test("supports arrow keys, Home, End, and wraparound with focus", async () => {
|
||||
await render();
|
||||
await press("foundations", "ArrowLeft");
|
||||
expect(document.activeElement).toBe(tab("views"));
|
||||
expect(panel().id).toBe("ds-panel-views");
|
||||
await press("views", "ArrowRight");
|
||||
expect(document.activeElement).toBe(tab("calendar-states"));
|
||||
expect(panel().id).toBe("ds-panel-calendar-states");
|
||||
await press("calendar-states", "ArrowRight");
|
||||
expect(document.activeElement).toBe(tab("foundations"));
|
||||
await press("foundations", "End");
|
||||
expect(document.activeElement).toBe(tab("views"));
|
||||
await press("views", "Home");
|
||||
expect(document.activeElement).toBe(tab("calendar-states"));
|
||||
await press("calendar-states", "Home");
|
||||
expect(document.activeElement).toBe(tab("foundations"));
|
||||
expect(panel().id).toBe("ds-panel-foundations");
|
||||
});
|
||||
|
||||
test("honors deep links and browser history", async () => {
|
||||
await render("/design-system#editing");
|
||||
expect(panel().id).toBe("ds-panel-editing");
|
||||
await render("/design-system#calendar-states");
|
||||
expect(panel().id).toBe("ds-panel-calendar-states");
|
||||
await clickTab("containers");
|
||||
const historyButton = (label: string) => Array.from(container.querySelectorAll("button")).find(button => button.textContent === label)!;
|
||||
await act(async () => historyButton("History back").click());
|
||||
expect(panel().id).toBe("ds-panel-editing");
|
||||
expect(panel().id).toBe("ds-panel-calendar-states");
|
||||
await act(async () => historyButton("History forward").click());
|
||||
expect(panel().id).toBe("ds-panel-containers");
|
||||
});
|
||||
|
||||
test("keeps example state and opens the playground from View habit", async () => {
|
||||
test("keeps example state and opens Calendars from View habit", async () => {
|
||||
await render("/design-system#components");
|
||||
await act(async () => panel().querySelector<HTMLButtonElement>('[aria-label="Increase example count"]')!.click());
|
||||
await clickTab("editing");
|
||||
await clickTab("calendar-states");
|
||||
await clickTab("components");
|
||||
expect(panel().querySelector("output")?.textContent).toBe("4 / 8");
|
||||
const viewHabit = Array.from(panel().querySelectorAll("button")).find(button => button.textContent?.startsWith("View habit"))!;
|
||||
await act(async () => viewHabit.click());
|
||||
expect(panel().id).toBe("ds-panel-playground");
|
||||
expect(panel().querySelector('.ds-view-switch [aria-pressed="true"]')?.textContent).toBe("Habit detail");
|
||||
expect(document.activeElement).toBe(tab("playground"));
|
||||
expect(panel().id).toBe("ds-panel-calendar-states");
|
||||
expect(panel().querySelectorAll(".ds-calendar")).toHaveLength(3);
|
||||
expect(document.activeElement).toBe(tab("calendar-states"));
|
||||
});
|
||||
|
||||
test("retains child component state and skip link does not switch tabs", async () => {
|
||||
@@ -269,8 +280,35 @@ describe("design system tabs", () => {
|
||||
expect(document.activeElement?.id).toBe("ds-main");
|
||||
});
|
||||
|
||||
for (const [retired, destination] of [["playground", "calendar-states"], ["editing", "foundations"], ["views", "foundations"]]) {
|
||||
test(`retired #${retired} links resolve to #${destination}`, async () => {
|
||||
await render(`/design-system#${retired}`);
|
||||
expect(panel().id).toBe(`ds-panel-${destination}`);
|
||||
expect(container.querySelector('[data-testid="hash"]')?.textContent).toBe(`#${destination}`);
|
||||
expect([...container.querySelectorAll('[role="tab"]')].map(item => item.textContent)).toEqual(["Foundations", "Components", "Layout", "Calendars"]);
|
||||
for (const id of ["playground", "editing", "views"]) {
|
||||
expect(container.querySelector(`#${id}`)).toBeNull();
|
||||
expect(container.querySelector(`#ds-panel-${id}`)).toBeNull();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test("an unknown section safely falls back to foundations", async () => {
|
||||
await render("/design-system#unknown");
|
||||
expect(panel().id).toBe("ds-panel-foundations");
|
||||
});
|
||||
|
||||
test("mounted panels keep unique IDs and valid accessible references", async () => {
|
||||
await render();
|
||||
const ids = Array.from(container.querySelectorAll("[id]"), element => element.id);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
for (const element of container.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], label[for]")) {
|
||||
for (const attribute of ["aria-labelledby", "aria-describedby", "aria-controls", "for"]) {
|
||||
for (const id of element.getAttribute(attribute)?.split(/\s+/).filter(Boolean) ?? []) {
|
||||
expect(document.getElementById(id)).not.toBeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user