169 lines
5.4 KiB
TypeScript
169 lines
5.4 KiB
TypeScript
import { describe, expect, mock, test } from "bun:test";
|
|
import {
|
|
getButtonOpeningTag,
|
|
renderToolbarActionBoundary,
|
|
registerAIToolbarMarkupHooks,
|
|
renderToolbarMarkup,
|
|
} from "./helpers/ai-toolbar-test-helpers";
|
|
|
|
registerAIToolbarMarkupHooks();
|
|
|
|
describe("AI toolbar state rendering", () => {
|
|
test("unauthenticated users see the sign-in-required callout", async () => {
|
|
const markup = await renderToolbarMarkup({ isAuthenticated: false });
|
|
|
|
expect(markup).toContain("Sign in required to generate event drafts with AI");
|
|
expect(markup).toContain(
|
|
"Sign in to turn natural language or flyers into event drafts",
|
|
);
|
|
});
|
|
|
|
test("admin-disabled state explains that AI is disabled by the administrator", async () => {
|
|
const markup = await renderToolbarMarkup({
|
|
adminAiEnabled: false,
|
|
isAuthenticated: true,
|
|
});
|
|
|
|
expect(markup).toContain("AI integrations are unavailable");
|
|
expect(markup).toContain(
|
|
"AI integrations are currently disabled by the administrator.",
|
|
);
|
|
});
|
|
|
|
test("browser-disabled state explains that AI was turned off from settings", async () => {
|
|
const markup = await renderToolbarMarkup({
|
|
adminAiEnabled: true,
|
|
aiEnabled: false,
|
|
isAuthenticated: true,
|
|
});
|
|
|
|
expect(markup).toContain("AI integrations are unavailable");
|
|
expect(markup).toContain(
|
|
"AI has been turned off in this browser from Settings.",
|
|
);
|
|
});
|
|
|
|
test("summary panel renders the summary body, updated timestamp, and dismiss affordance", async () => {
|
|
const markup = await renderToolbarMarkup({
|
|
summary: "Three events need attention.",
|
|
summaryUpdated: "Updated just now",
|
|
});
|
|
|
|
expect(markup).toContain("AI Summary");
|
|
expect(markup).toContain("Three events need attention.");
|
|
expect(markup).toContain("Updated just now");
|
|
expect(markup).toContain("Dismiss AI summary");
|
|
});
|
|
|
|
test("pending state renders loading skeletons instead of the live composer", async () => {
|
|
const markup = await renderToolbarMarkup({ isPending: true });
|
|
|
|
expect(markup).toContain("h-[160px]");
|
|
expect(markup).toContain("h-12");
|
|
expect(markup).not.toContain("Type or paste event details...");
|
|
});
|
|
|
|
test("Summarize only appears when there are events to summarize", async () => {
|
|
const emptyMarkup = await renderToolbarMarkup({ events: [] });
|
|
const populatedMarkup = await renderToolbarMarkup({
|
|
events: [{ id: "event-1" }],
|
|
});
|
|
|
|
expect(emptyMarkup).not.toContain("Summarize");
|
|
expect(populatedMarkup).toContain("Summarize");
|
|
});
|
|
|
|
test("Generate event stays disabled while loading or when both prompt and images are absent", async () => {
|
|
const emptyMarkup = await renderToolbarMarkup();
|
|
const loadingMarkup = await renderToolbarMarkup({
|
|
aiLoading: true,
|
|
aiPrompt: "Draft a kickoff",
|
|
});
|
|
const promptMarkup = await renderToolbarMarkup({
|
|
aiPrompt: "Draft a kickoff",
|
|
});
|
|
const imageMarkup = await renderToolbarMarkup({
|
|
imagePreviews: ["blob:first"],
|
|
});
|
|
|
|
expect(getButtonOpeningTag(emptyMarkup, "Generate event")).toContain(
|
|
' disabled=""',
|
|
);
|
|
expect(getButtonOpeningTag(loadingMarkup, "Generating...")).toContain(
|
|
' disabled=""',
|
|
);
|
|
expect(getButtonOpeningTag(promptMarkup, "Generate event")).not.toContain(
|
|
" disabled=",
|
|
);
|
|
expect(getButtonOpeningTag(imageMarkup, "Generate event")).not.toContain(
|
|
" disabled=",
|
|
);
|
|
});
|
|
|
|
test("AI unavailable state removes the composer so generate shortcuts are not exposed", async () => {
|
|
const markup = await renderToolbarMarkup({ aiEnabled: false });
|
|
|
|
expect(markup).toContain("AI integrations are unavailable");
|
|
expect(markup).not.toContain("Type or paste event details...");
|
|
expect(markup).not.toContain("Generate event");
|
|
});
|
|
|
|
test("example prompt buttons call onAiTemplateSelect with the selected prompt", async () => {
|
|
const onAiTemplateSelectMock = mock();
|
|
const actions = await renderToolbarActionBoundary({
|
|
onAiTemplateSelect: onAiTemplateSelectMock,
|
|
});
|
|
const promptButton = actions.getButtonByLabel("Lunch with Maya next Thursday");
|
|
|
|
promptButton.onClick?.({} as never);
|
|
|
|
expect(onAiTemplateSelectMock).toHaveBeenCalledTimes(1);
|
|
expect(onAiTemplateSelectMock).toHaveBeenCalledWith(
|
|
"Lunch with Maya next Thursday at 12:30pm at Toma, remind me 30 minutes before.",
|
|
);
|
|
});
|
|
|
|
test("Summarize button calls onAiSummarize when events are present", async () => {
|
|
const onAiSummarizeMock = mock();
|
|
const actions = await renderToolbarActionBoundary({
|
|
events: [{ id: "event-1" }],
|
|
onAiSummarize: onAiSummarizeMock,
|
|
});
|
|
|
|
actions
|
|
.getButtonByLabel("Summarize")
|
|
.onClick?.({} as never);
|
|
|
|
expect(onAiSummarizeMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("summary dismiss button calls onSummaryDismiss", async () => {
|
|
const onSummaryDismissMock = mock();
|
|
const actions = await renderToolbarActionBoundary({
|
|
summary: "Three events need attention.",
|
|
onSummaryDismiss: onSummaryDismissMock,
|
|
});
|
|
|
|
actions
|
|
.getButtonByAriaLabel("Dismiss AI summary")
|
|
.onClick?.({} as never);
|
|
|
|
expect(onSummaryDismissMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("remove image button calls onImageRemove with the preview index", async () => {
|
|
const onImageRemoveMock = mock();
|
|
const actions = await renderToolbarActionBoundary({
|
|
imagePreviews: ["blob:first", "blob:second"],
|
|
onImageRemove: onImageRemoveMock,
|
|
});
|
|
|
|
actions
|
|
.getButtonByAriaLabel("Remove image 2")
|
|
.onClick?.({} as never);
|
|
|
|
expect(onImageRemoveMock).toHaveBeenCalledTimes(1);
|
|
expect(onImageRemoveMock).toHaveBeenCalledWith(1);
|
|
});
|
|
});
|