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 { readFileSync } from "node:fs";
|
||||
import * as React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { getAiDisabledMessage } from "@/lib/ai-feature-flags";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -175,6 +176,27 @@ const renderToolbar = async (overrides: Partial<AIToolbarProps> = {}) => {
|
||||
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 source = readToolbarSource();
|
||||
const match = source.match(
|
||||
@@ -292,6 +314,25 @@ describe("Event count badge – positioning contract", () => {
|
||||
});
|
||||
|
||||
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 () => {
|
||||
const onImagesSelect = mock();
|
||||
const image = new File(["image-bytes"], "clipboard.png", {
|
||||
@@ -317,6 +358,30 @@ describe("AI capture redesign", () => {
|
||||
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 () => {
|
||||
const onImagesSelect = mock();
|
||||
const clipboardRead = mock(async () => [
|
||||
@@ -350,7 +415,7 @@ describe("AI capture redesign", () => {
|
||||
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 clipboardRead = mock(async () => []);
|
||||
|
||||
@@ -368,7 +433,7 @@ describe("AI capture redesign", () => {
|
||||
metaKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
target: { tagName: "INPUT", isContentEditable: false },
|
||||
target: { tagName: "DIV", isContentEditable: true },
|
||||
} as unknown as Event);
|
||||
|
||||
expect(clipboardRead).not.toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user