Files
local-cal/tests/ai-toolbar-layout.test.ts

102 lines
3.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
getMatchingClassTokens,
registerAIToolbarTestHooks,
renderToolbarMarkup,
} from "./helpers/ai-toolbar-test-helpers";
registerAIToolbarTestHooks();
describe("AI toolbar layout contracts", () => {
test("desktop composer uses a dedicated multi-column branch while mobile stays single-column", async () => {
const desktopMarkup = await renderToolbarMarkup();
const mobileMarkup = await renderToolbarMarkup({}, { isMobile: true });
const desktopLayoutTokens = getMatchingClassTokens(
desktopMarkup,
(tokens) =>
tokens.includes("grid") &&
tokens.some((token) => token.startsWith("grid-cols-[minmax(0,0.7fr)")),
);
const mobileLayoutTokens = getMatchingClassTokens(
mobileMarkup,
(tokens) => tokens.includes("grid") && tokens.includes("gap-3"),
);
expect(desktopLayoutTokens).toHaveLength(1);
expect(desktopMarkup).toContain("Keyboard shortcuts");
expect(desktopMarkup).toContain("Attachments");
expect(mobileLayoutTokens).toHaveLength(1);
expect(mobileMarkup).not.toContain("Keyboard shortcuts");
expect(
mobileLayoutTokens[0].some((token) => token.startsWith("grid-cols-")),
).toBe(false);
});
test("example prompts render as a masonry-style cluster below the textarea", async () => {
const markup = await renderToolbarMarkup();
const masonryColumns = getMatchingClassTokens(
markup,
(tokens) => tokens.some((token) => token.startsWith("columns-")),
);
const masonryWrappers = getMatchingClassTokens(
markup,
(tokens) => tokens.includes("break-inside-avoid"),
);
const promptButtons = getMatchingClassTokens(
markup,
(tokens) =>
tokens.includes("justify-start") &&
tokens.includes("text-left") &&
tokens.includes("whitespace-normal"),
);
expect(markup).toContain("Try:");
expect(markup).toContain(
"Lunch with Maya next Thursday at 12:30pm at Toma, remind me 30 minutes before.",
);
expect(markup).toContain(
"Project sync tomorrow from 9am to 10am on Google Meet with a weekly repeat.",
);
expect(markup).toContain(
"Dentist appointment on May 14 at 3pm at Smile Studio, add confirmation #A4821.",
);
expect(masonryColumns).toHaveLength(1);
expect(masonryColumns[0]).toEqual(
expect.arrayContaining(["columns-2", "gap-2"]),
);
expect(masonryWrappers).toHaveLength(3);
expect(promptButtons).toHaveLength(3);
});
test("attachments render as a separate surfaced panel with count badge, picker, and empty state", async () => {
const markup = await renderToolbarMarkup();
expect(markup).toContain("Attachments");
expect(markup).toContain("0 files");
expect(markup).toContain("Attach images");
expect(markup).toContain(
"Drop or paste images here to pair them with the prompt.",
);
});
test("attachment previews render in a stacked grid instead of a multi-column strip", async () => {
const markup = await renderToolbarMarkup({
imagePreviews: ["blob:first", "blob:second"],
});
const previewGridTokens = getMatchingClassTokens(
markup,
(tokens) => tokens.includes("grid") && tokens.includes("gap-2"),
);
const multiColumnPreviewPattern =
/(?:^|\s)(?:[a-z]+:)*(?:grid-cols-(?:\[[^\]]+\]|\S+)|grid-flow-col|auto-cols-(?:\[[^\]]+\]|\S+)|columns-(?:\[[^\]]+\]|\S+)|overflow-x-auto|flex-row)/;
expect(markup).toContain("Attached image 1");
expect(markup).toContain("Attached image 2");
expect(
previewGridTokens.some(
(tokens) => !tokens.join(" ").match(multiColumnPreviewPattern),
),
).toBe(true);
});
});