fix(ai-toolbar): finalize normalized clipboard test coverage
This commit is contained in:
@@ -95,10 +95,11 @@ function ShortcutsList({ os }: { os: Os }) {
|
||||
|
||||
function isEditableTarget(target: EventTarget | null): target is HTMLElement {
|
||||
const element = target as HTMLElement | null;
|
||||
return !!element && (
|
||||
element.tagName === "TEXTAREA" ||
|
||||
element.tagName === "INPUT" ||
|
||||
element.isContentEditable
|
||||
return (
|
||||
!!element &&
|
||||
(element.tagName === "TEXTAREA" ||
|
||||
element.tagName === "INPUT" ||
|
||||
element.isContentEditable)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,8 +188,7 @@ export const AIToolbar = ({
|
||||
// focused element or OS clipboard model (X11/Wayland).
|
||||
// This is the approach used by Excalidraw's actionPaste.
|
||||
useEffect(() => {
|
||||
if (!(isAuthenticated && !isPending && canUseAi && !aiLoading))
|
||||
return;
|
||||
if (!(isAuthenticated && !isPending && canUseAi && !aiLoading)) return;
|
||||
|
||||
// ── Handler 1: paste event (works when textarea is NOT focused) ───────
|
||||
const handleDocumentPaste = (e: ClipboardEvent) => {
|
||||
@@ -281,7 +281,7 @@ export const AIToolbar = ({
|
||||
document.removeEventListener("paste", handlePasteHandled, {
|
||||
capture: true,
|
||||
});
|
||||
document.removeEventListener("keydown", handleDocumentKeydown);
|
||||
document.removeEventListener("keydown", handleDocumentKeydown);
|
||||
};
|
||||
}, [isAuthenticated, isPending, canUseAi, aiLoading]); // onImagesSelect intentionally omitted — ref stays current
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -155,7 +155,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -173,7 +173,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -193,7 +193,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -213,7 +213,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -222,7 +222,7 @@ describe("AI toolbar paste capture", () => {
|
||||
await handleDocumentKeydown(
|
||||
createDocumentKeydownEvent({
|
||||
ctrlKey: true,
|
||||
target: { tagName: "DIV", isContentEditable: true },
|
||||
target: { tagName: "DIV", isContentEditable: true } as unknown as EventTarget,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -236,7 +236,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -258,7 +258,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -282,7 +282,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -310,7 +310,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -339,7 +339,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -376,7 +376,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
@@ -403,7 +403,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
const { textareaProps } = await renderToolbarBoundary({
|
||||
onImagesSelect: syncOnImagesSelect,
|
||||
@@ -452,7 +452,7 @@ describe("AI toolbar paste capture", () => {
|
||||
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read: clipboardRead },
|
||||
} as Navigator;
|
||||
} as unknown as Navigator;
|
||||
|
||||
await renderToolbar({ onImagesSelect });
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ import {
|
||||
|
||||
registerAIToolbarMarkupHooks();
|
||||
|
||||
const makeEvent = (id = "event-1") => ({
|
||||
id,
|
||||
title: "Event",
|
||||
start: "2026-04-22T09:00:00.000Z",
|
||||
});
|
||||
|
||||
describe("AI toolbar state rendering", () => {
|
||||
test("unauthenticated users see the sign-in-required callout", async () => {
|
||||
const markup = await renderToolbarMarkup({ isAuthenticated: false });
|
||||
@@ -66,7 +72,7 @@ describe("AI toolbar state rendering", () => {
|
||||
test("Summarize only appears when there are events to summarize", async () => {
|
||||
const emptyMarkup = await renderToolbarMarkup({ events: [] });
|
||||
const populatedMarkup = await renderToolbarMarkup({
|
||||
events: [{ id: "event-1" }],
|
||||
events: [makeEvent()],
|
||||
});
|
||||
|
||||
expect(emptyMarkup).not.toContain("Summarize");
|
||||
@@ -138,7 +144,7 @@ describe("AI toolbar state rendering", () => {
|
||||
test("Summarize button calls onAiSummarize when events are present", async () => {
|
||||
const onAiSummarizeMock = mock();
|
||||
const actions = await renderToolbarActionBoundary({
|
||||
events: [{ id: "event-1" }],
|
||||
events: [makeEvent()],
|
||||
onAiSummarize: onAiSummarizeMock,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterEach, beforeEach, expect, mock } from "bun:test";
|
||||
import * as React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import type { CalendarEvent } from "@/lib/types";
|
||||
|
||||
export type AIToolbarProps = {
|
||||
adminAiEnabled: boolean;
|
||||
@@ -19,7 +20,7 @@ export type AIToolbarProps = {
|
||||
onSummaryDismiss: () => void;
|
||||
summary: string | null;
|
||||
summaryUpdated: string | null;
|
||||
events: Array<{ id?: string }>;
|
||||
events: CalendarEvent[];
|
||||
};
|
||||
|
||||
const actualReact = React;
|
||||
@@ -63,7 +64,7 @@ export const registerAIToolbarEffectHooks = () => {
|
||||
globalThis.document = {
|
||||
addEventListener: documentAddEventListener,
|
||||
removeEventListener: documentRemoveEventListener,
|
||||
} as Document;
|
||||
} as unknown as Document;
|
||||
documentAddEventListener.mockImplementation(
|
||||
(
|
||||
type: string,
|
||||
@@ -102,7 +103,7 @@ export const registerAIToolbarMarkupHooks = () => {
|
||||
globalThis.document = {
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
} as Document;
|
||||
} as unknown as Document;
|
||||
mock.module("@/hooks/use-mobile", () => ({
|
||||
useIsMobile: () => false,
|
||||
}));
|
||||
@@ -276,10 +277,16 @@ export const createDocumentKeydownEvent = (
|
||||
metaKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
target: { tagName: "DIV", isContentEditable: false },
|
||||
target: { tagName: "DIV", isContentEditable: false } as unknown as EventTarget,
|
||||
...overrides,
|
||||
}) as unknown as KeyboardEvent;
|
||||
|
||||
export const setMockNavigatorClipboardRead = (read: () => Promise<unknown>) => {
|
||||
(globalThis as { navigator?: Navigator }).navigator = {
|
||||
clipboard: { read },
|
||||
} as unknown as Navigator;
|
||||
};
|
||||
|
||||
const escapeForRegex = (value: string) =>
|
||||
value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user