diff --git a/tests/ai-toolbar.test.ts b/tests/ai-toolbar.test.ts index 7d59572..68eba22 100644 --- a/tests/ai-toolbar.test.ts +++ b/tests/ai-toolbar.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; import * as React from "react"; import { renderToStaticMarkup } from "react-dom/server"; +import { imageFileKey } from "@/lib/multi-image"; type AIToolbarProps = { adminAiEnabled: boolean; @@ -586,6 +587,19 @@ describe("AI toolbar paste capture", () => { expect(imageTriggerOpen).toHaveBeenCalledTimes(1); }); + test("Mod+A without Shift does not open the image picker", async () => { + const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary(); + const event = createTextareaKeydownEvent({ + key: "A", + ctrlKey: true, + }); + + textareaProps.onKeyDown?.(event); + + expect(event.preventDefault).not.toHaveBeenCalled(); + expect(imageTriggerOpen).not.toHaveBeenCalled(); + }); + test("Shift+Cmd+A also opens the image picker when the composer is idle", async () => { const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary(); const metaEvent = createTextareaKeydownEvent({ @@ -748,6 +762,69 @@ describe("AI toolbar paste capture", () => { expect(onImagesSelect).toHaveBeenCalledTimes(1); }); + test("Shift+Mod+V does not trigger the async clipboard fallback", async () => { + const onImagesSelect = mock(); + const clipboardRead = mock(async () => []); + + (globalThis as { navigator?: Navigator }).navigator = { + clipboard: { read: clipboardRead }, + } as Navigator; + + await renderToolbar({ onImagesSelect }); + + const handleDocumentKeydown = getDocumentListener("keydown"); + + await handleDocumentKeydown( + createDocumentKeydownEvent({ ctrlKey: true, shiftKey: true }), + ); + + expect(clipboardRead).not.toHaveBeenCalled(); + expect(onImagesSelect).not.toHaveBeenCalled(); + }); + + test("Alt+Mod+V does not trigger the async clipboard fallback", async () => { + const onImagesSelect = mock(); + const clipboardRead = mock(async () => []); + + (globalThis as { navigator?: Navigator }).navigator = { + clipboard: { read: clipboardRead }, + } as Navigator; + + await renderToolbar({ onImagesSelect }); + + const handleDocumentKeydown = getDocumentListener("keydown"); + + await handleDocumentKeydown( + createDocumentKeydownEvent({ ctrlKey: true, altKey: true }), + ); + + expect(clipboardRead).not.toHaveBeenCalled(); + expect(onImagesSelect).not.toHaveBeenCalled(); + }); + + test("async clipboard fallback ignores editable targets", async () => { + const onImagesSelect = mock(); + const clipboardRead = mock(async () => []); + + (globalThis as { navigator?: Navigator }).navigator = { + clipboard: { read: clipboardRead }, + } as Navigator; + + await renderToolbar({ onImagesSelect }); + + const handleDocumentKeydown = getDocumentListener("keydown"); + + await handleDocumentKeydown( + createDocumentKeydownEvent({ + ctrlKey: true, + target: { tagName: "DIV", isContentEditable: true }, + }), + ); + + expect(clipboardRead).not.toHaveBeenCalled(); + expect(onImagesSelect).not.toHaveBeenCalled(); + }); + test("Ctrl+Meta+V does not trigger the async clipboard fallback", async () => { const onImagesSelect = mock(); const clipboardRead = mock(async () => []); @@ -918,6 +995,9 @@ describe("AI toolbar paste capture", () => { expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified).toBe( asyncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified, ); + expect( + imageFileKey(syncOnImagesSelect.mock.calls[0]?.[0][0]), + ).toBe(imageFileKey(asyncOnImagesSelect.mock.calls[0]?.[0][0])); }); test("async clipboard fallback does not double-handle a paste already handled by the synchronous document paste flow", async () => {