mirror of
https://github.com/openai/codex.git
synced 2026-05-01 01:47:18 +00:00
137 lines
3.9 KiB
TypeScript
137 lines
3.9 KiB
TypeScript
// Dropping / pasting an image path into the chat input should immediately move
|
||
// that image into the attached-images preview and remove the path from the draft
|
||
// text.
|
||
|
||
import fs from "node:fs";
|
||
import path from "node:path";
|
||
import React from "react";
|
||
import { beforeAll, afterAll, describe, expect, it, vi } from "vitest";
|
||
|
||
import { renderTui } from "./ui-test-helpers.js";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Mocks – keep in sync with other TerminalChatInput UI tests
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// mock without type annotations to avoid Vitest transform TS errors in JS test
|
||
const createInputItemMock = vi.fn(async () => ({}));
|
||
|
||
vi.mock("../src/utils/input-utils.js", () => ({
|
||
createInputItem: createInputItemMock,
|
||
imageFilenameByDataUrl: new Map(),
|
||
}));
|
||
vi.mock("../src/approvals.js", () => ({ isSafeCommand: () => null }));
|
||
vi.mock("../src/format-command.js", () => ({
|
||
formatCommandForDisplay: (c: Array<string>): string => c.join(" "),
|
||
}));
|
||
|
||
import TerminalChatInput from "../src/components/chat/terminal-chat-input.js";
|
||
|
||
async function type(
|
||
stdin: NodeJS.WritableStream & { write(str: string): void },
|
||
text: string,
|
||
flush: () => Promise<void>,
|
||
): Promise<void> {
|
||
stdin.write(text);
|
||
await flush();
|
||
}
|
||
|
||
function props() {
|
||
return {
|
||
isNew: true,
|
||
loading: false,
|
||
submitInput: () => {},
|
||
confirmationPrompt: null,
|
||
submitConfirmation: () => {},
|
||
setLastResponseId: () => {},
|
||
setItems: () => {},
|
||
contextLeftPercent: 100,
|
||
openOverlay: () => {},
|
||
openModelOverlay: () => {},
|
||
openApprovalOverlay: () => {},
|
||
openHelpOverlay: () => {},
|
||
interruptAgent: () => {},
|
||
active: true,
|
||
onCompact: () => {},
|
||
};
|
||
}
|
||
|
||
describe("Drag-and-drop image attachment", () => {
|
||
const TMP = path.join(process.cwd(), "drag-drop-image-test");
|
||
const IMG = path.join(TMP, "dropped.png");
|
||
|
||
beforeAll(() => {
|
||
fs.mkdirSync(TMP, { recursive: true });
|
||
fs.writeFileSync(IMG, "");
|
||
});
|
||
|
||
afterAll(() => {
|
||
fs.rmSync(TMP, { recursive: true, force: true });
|
||
});
|
||
|
||
it("moves pasted path to attachment preview", async () => {
|
||
process.env.DEBUG_TCI = "1";
|
||
const orig = process.cwd();
|
||
process.chdir(TMP);
|
||
|
||
const { stdin, flush, lastFrameStripped, cleanup } = renderTui(
|
||
React.createElement(TerminalChatInput, props()),
|
||
);
|
||
|
||
await flush(); // initial render
|
||
|
||
// Simulate user pasting the bare filename (as most terminals do when you
|
||
// drag a file).
|
||
await type(stdin, "dropped.png ", flush);
|
||
|
||
await flush();
|
||
|
||
// A second flush to allow state updates triggered asynchronously by
|
||
// setState inside the onChange handler.
|
||
await flush();
|
||
|
||
let frame = lastFrameStripped();
|
||
|
||
|
||
expect(frame.match(/dropped\.png/g)?.length ?? 0).toBe(1);
|
||
|
||
// Now submit the message.
|
||
await type(stdin, "\r", flush);
|
||
await flush();
|
||
|
||
// createInputItem should have been called with the dropped image path
|
||
expect(createInputItemMock).toHaveBeenCalled();
|
||
const calls = createInputItemMock.mock.calls;
|
||
const lastCall = calls[calls.length - 1];
|
||
expect(lastCall?.[1]).toEqual(["dropped.png"]);
|
||
|
||
cleanup();
|
||
process.chdir(orig);
|
||
});
|
||
|
||
it("does NOT show slash-command overlay for absolute paths", async () => {
|
||
const orig = process.cwd();
|
||
process.chdir(TMP);
|
||
|
||
const { stdin, flush, lastFrameStripped, cleanup } = renderTui(
|
||
React.createElement(TerminalChatInput, props()),
|
||
);
|
||
|
||
await flush();
|
||
|
||
// absolute path starting with '/'
|
||
const absPath = path.join(TMP, "dropped.png");
|
||
await type(stdin, `${absPath} `, flush);
|
||
await flush();
|
||
|
||
const frame = lastFrameStripped();
|
||
|
||
// Should contain attachment preview but NOT typical slash-command suggestion like "/help"
|
||
expect(frame).toContain("dropped.png");
|
||
expect(frame).not.toContain("/help");
|
||
|
||
cleanup();
|
||
process.chdir(orig);
|
||
});
|
||
});
|