Give the composer full-width desktop space before the event timeline so capture stays prominent and the toolbar can keep its intended two-column composition. Add source-level regression tests to lock the new page hierarchy in place.
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
describe("home page hierarchy", () => {
|
|
test("desktop layout is selected with useIsMobile rather than Tailwind breakpoint classes", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain("useIsMobile");
|
|
expect(source).toContain('const mainContentClasses = "space-y-4"');
|
|
expect(source).toContain('const timelineContentClasses = "space-y-4"');
|
|
expect(source).toContain('"items-center justify-between"');
|
|
expect(source).toContain("desktopUtilityActionsClasses");
|
|
expect(source).toContain("AI capture");
|
|
expect(source).toContain("Event timeline");
|
|
});
|
|
|
|
test("desktop layout promotes AI capture to a full-width top row before the timeline grid", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain("<section className={appSectionSurfaceClasses}>");
|
|
expect(source).toContain("<section className={timelineContentClasses}>");
|
|
expect(source).not.toContain("grid-cols-[minmax(0,0.75fr)_minmax(0,1.25fr)]");
|
|
});
|
|
|
|
test("manual create is routed through a More menu instead of a primary mobile action", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain("More");
|
|
expect(source).not.toContain("New Event</button>");
|
|
});
|
|
|
|
test("mobile layout keeps capture before timeline without order utility breakpoints", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).not.toContain("order-1 lg:order-none");
|
|
expect(source).toContain("Import");
|
|
expect(source).toContain("Manual create");
|
|
});
|
|
|
|
test("mobile header gives controls their own full-width row so the title is not squeezed by actions", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain("isMobile ? \"w-full\" : undefined");
|
|
expect(source).toContain("headerActionsClasses");
|
|
expect(source).toContain('"flex w-full items-center justify-between gap-2"');
|
|
expect(source).toContain("flex flex-wrap items-center justify-end gap-2");
|
|
});
|
|
|
|
test("mobile header keeps export inside the More menu instead of a dedicated button", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain("!isMobile && (");
|
|
expect(source).toContain("Export");
|
|
expect(source).toContain("onClick={handleExport}");
|
|
});
|
|
|
|
test("mobile More trigger collapses to an icon button so utility actions fit on one row", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain('size={isMobile ? "icon" : "sm"}');
|
|
expect(source).toContain("moreTriggerLabel");
|
|
expect(source).toContain('isMobile ? null : "More"');
|
|
});
|
|
|
|
test("mobile header omits the theme toggle so auth and overflow actions have a stable hierarchy", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).not.toContain("<ModeToggle />");
|
|
});
|
|
|
|
test("mobile header uses a column layout with a dedicated action row instead of desktop justify-between framing", () => {
|
|
const source = readFileSync("src/app/page.tsx", "utf8");
|
|
|
|
expect(source).toContain("headerLayoutClasses");
|
|
expect(source).toContain('isMobile ? "flex-col items-start"');
|
|
expect(source).toContain('"flex w-full items-center justify-between gap-2"');
|
|
});
|
|
});
|