test(ai-toolbar): cover editable paste behavior
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
import { buttonVariants } from "@/components/ui/button";
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
import { getAiDisabledMessage } from "@/lib/ai-feature-flags";
|
import { getAiDisabledMessage } from "@/lib/ai-feature-flags";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -175,6 +176,27 @@ const renderToolbar = async (overrides: Partial<AIToolbarProps> = {}) => {
|
|||||||
AIToolbar(createToolbarProps(overrides));
|
AIToolbar(createToolbarProps(overrides));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderToolbarBoundary = async (overrides: Partial<AIToolbarProps> = {}) => {
|
||||||
|
let capturedTextareaProps: React.ComponentProps<"textarea"> | undefined;
|
||||||
|
|
||||||
|
mock.module("@/components/ui/textarea", () => ({
|
||||||
|
Textarea: (props: React.ComponentProps<"textarea">) => {
|
||||||
|
capturedTextareaProps = props;
|
||||||
|
return actualReact.createElement("textarea", props);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { AIToolbar } = await import("@/components/ai-toolbar");
|
||||||
|
|
||||||
|
renderToStaticMarkup(
|
||||||
|
actualReact.createElement(AIToolbar, createToolbarProps(overrides)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(capturedTextareaProps).toBeDefined();
|
||||||
|
|
||||||
|
return capturedTextareaProps!;
|
||||||
|
};
|
||||||
|
|
||||||
const getExamplePromptButtonClassName = () => {
|
const getExamplePromptButtonClassName = () => {
|
||||||
const source = readToolbarSource();
|
const source = readToolbarSource();
|
||||||
const match = source.match(
|
const match = source.match(
|
||||||
@@ -292,6 +314,25 @@ describe("Event count badge – positioning contract", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("AI capture redesign", () => {
|
describe("AI capture redesign", () => {
|
||||||
|
test("textarea paste path still forwards clipboard images to onImagesSelect through the component boundary", async () => {
|
||||||
|
const onImagesSelect = mock();
|
||||||
|
const image = new File(["image-bytes"], "clipboard.png", {
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
|
|
||||||
|
const textareaProps = await renderToolbarBoundary({ onImagesSelect });
|
||||||
|
const preventDefault = mock();
|
||||||
|
|
||||||
|
textareaProps.onPaste?.({
|
||||||
|
clipboardData: createClipboardData([image]),
|
||||||
|
preventDefault,
|
||||||
|
} as unknown as React.ClipboardEvent<HTMLTextAreaElement>);
|
||||||
|
|
||||||
|
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onImagesSelect).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onImagesSelect).toHaveBeenCalledWith([image]);
|
||||||
|
});
|
||||||
|
|
||||||
test("document paste forwards clipboard images to onImagesSelect only for non-editable targets", async () => {
|
test("document paste forwards clipboard images to onImagesSelect only for non-editable targets", async () => {
|
||||||
const onImagesSelect = mock();
|
const onImagesSelect = mock();
|
||||||
const image = new File(["image-bytes"], "clipboard.png", {
|
const image = new File(["image-bytes"], "clipboard.png", {
|
||||||
@@ -317,6 +358,30 @@ describe("AI capture redesign", () => {
|
|||||||
expect(onImagesSelect).toHaveBeenCalledWith([image]);
|
expect(onImagesSelect).toHaveBeenCalledWith([image]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("document paste ignores contenteditable targets so the focused editor owns the paste", async () => {
|
||||||
|
const onImagesSelect = mock();
|
||||||
|
const image = new File(["image-bytes"], "clipboard.png", {
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
|
|
||||||
|
await renderToolbar({ onImagesSelect });
|
||||||
|
|
||||||
|
const handleDocumentPaste = getDocumentListener(
|
||||||
|
"paste",
|
||||||
|
(entry) => !entry.options?.capture,
|
||||||
|
);
|
||||||
|
const preventDefault = mock();
|
||||||
|
|
||||||
|
handleDocumentPaste({
|
||||||
|
target: { tagName: "DIV", isContentEditable: true },
|
||||||
|
clipboardData: createClipboardData([image]),
|
||||||
|
preventDefault,
|
||||||
|
} as unknown as Event);
|
||||||
|
|
||||||
|
expect(preventDefault).not.toHaveBeenCalled();
|
||||||
|
expect(onImagesSelect).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
test("Ctrl/Cmd+V fallback forwards clipboard images to onImagesSelect for non-editable targets", async () => {
|
test("Ctrl/Cmd+V fallback forwards clipboard images to onImagesSelect for non-editable targets", async () => {
|
||||||
const onImagesSelect = mock();
|
const onImagesSelect = mock();
|
||||||
const clipboardRead = mock(async () => [
|
const clipboardRead = mock(async () => [
|
||||||
@@ -350,7 +415,7 @@ describe("AI capture redesign", () => {
|
|||||||
expect(onImagesSelect.mock.calls[0]?.[0][0]?.type).toBe("image/png");
|
expect(onImagesSelect.mock.calls[0]?.[0][0]?.type).toBe("image/png");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Ctrl/Cmd+V fallback ignores editable targets", async () => {
|
test("Ctrl/Cmd+V fallback ignores contenteditable targets", async () => {
|
||||||
const onImagesSelect = mock();
|
const onImagesSelect = mock();
|
||||||
const clipboardRead = mock(async () => []);
|
const clipboardRead = mock(async () => []);
|
||||||
|
|
||||||
@@ -368,7 +433,7 @@ describe("AI capture redesign", () => {
|
|||||||
metaKey: false,
|
metaKey: false,
|
||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
target: { tagName: "INPUT", isContentEditable: false },
|
target: { tagName: "DIV", isContentEditable: true },
|
||||||
} as unknown as Event);
|
} as unknown as Event);
|
||||||
|
|
||||||
expect(clipboardRead).not.toHaveBeenCalled();
|
expect(clipboardRead).not.toHaveBeenCalled();
|
||||||
|
|||||||
Reference in New Issue
Block a user