fix(ai-toolbar): align attach and fallback modifier rules
This commit is contained in:
@@ -349,6 +349,8 @@ export const AIToolbar = ({
|
|||||||
e.key === "A" &&
|
e.key === "A" &&
|
||||||
e.shiftKey &&
|
e.shiftKey &&
|
||||||
(e.metaKey || e.ctrlKey) &&
|
(e.metaKey || e.ctrlKey) &&
|
||||||
|
!(e.metaKey && e.ctrlKey) &&
|
||||||
|
!e.altKey &&
|
||||||
!aiLoading
|
!aiLoading
|
||||||
) {
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -600,6 +600,36 @@ describe("AI toolbar paste capture", () => {
|
|||||||
expect(imageTriggerOpen).toHaveBeenCalledTimes(1);
|
expect(imageTriggerOpen).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Shift+Mod+A ignores Alt-modified submissions", async () => {
|
||||||
|
const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary();
|
||||||
|
const event = createTextareaKeydownEvent({
|
||||||
|
key: "A",
|
||||||
|
ctrlKey: true,
|
||||||
|
shiftKey: true,
|
||||||
|
altKey: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
textareaProps.onKeyDown?.(event);
|
||||||
|
|
||||||
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
|
expect(imageTriggerOpen).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Shift+Mod+A ignores combined Ctrl+Meta modifiers", async () => {
|
||||||
|
const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary();
|
||||||
|
const event = createTextareaKeydownEvent({
|
||||||
|
key: "A",
|
||||||
|
ctrlKey: true,
|
||||||
|
metaKey: true,
|
||||||
|
shiftKey: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
textareaProps.onKeyDown?.(event);
|
||||||
|
|
||||||
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
|
expect(imageTriggerOpen).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
test("Shift+Mod+A stays disabled while generation is in progress", async () => {
|
test("Shift+Mod+A stays disabled while generation is in progress", async () => {
|
||||||
const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary({
|
const { textareaProps, imageTriggerOpen } = await renderToolbarBoundary({
|
||||||
aiLoading: true,
|
aiLoading: true,
|
||||||
@@ -693,6 +723,31 @@ describe("AI toolbar paste capture", () => {
|
|||||||
expect(onImagesSelect.mock.calls[0]?.[0][0]?.type).toBe("image/png");
|
expect(onImagesSelect.mock.calls[0]?.[0][0]?.type).toBe("image/png");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Cmd+V fallback forwards clipboard images for non-editable targets", async () => {
|
||||||
|
const onImagesSelect = mock();
|
||||||
|
const clipboardRead = mock(async () => [
|
||||||
|
{
|
||||||
|
types: ["image/png"],
|
||||||
|
getType: async () => new Blob(["image-bytes"], { type: "image/png" }),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
(globalThis as { navigator?: Navigator }).navigator = {
|
||||||
|
clipboard: { read: clipboardRead },
|
||||||
|
} as Navigator;
|
||||||
|
|
||||||
|
await renderToolbar({ onImagesSelect });
|
||||||
|
|
||||||
|
const handleDocumentKeydown = getDocumentListener("keydown");
|
||||||
|
|
||||||
|
await handleDocumentKeydown(
|
||||||
|
createDocumentKeydownEvent({ metaKey: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(clipboardRead).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onImagesSelect).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
test("Ctrl+Meta+V does not trigger the async clipboard fallback", async () => {
|
test("Ctrl+Meta+V does not trigger the async clipboard fallback", async () => {
|
||||||
const onImagesSelect = mock();
|
const onImagesSelect = mock();
|
||||||
const clipboardRead = mock(async () => []);
|
const clipboardRead = mock(async () => []);
|
||||||
@@ -857,6 +912,9 @@ describe("AI toolbar paste capture", () => {
|
|||||||
expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.name).toBe(
|
expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.name).toBe(
|
||||||
asyncOnImagesSelect.mock.calls[0]?.[0][0]?.name,
|
asyncOnImagesSelect.mock.calls[0]?.[0][0]?.name,
|
||||||
);
|
);
|
||||||
|
expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.size).toBe(
|
||||||
|
asyncOnImagesSelect.mock.calls[0]?.[0][0]?.size,
|
||||||
|
);
|
||||||
expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified).toBe(
|
expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified).toBe(
|
||||||
asyncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified,
|
asyncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user