test(client): cover prompt attachment paste wiring

This commit is contained in:
Federico Jaramillo Martinez
2026-07-05 00:41:38 +02:00
parent a6e0f1db18
commit c0538cff65
+87 -1
View File
@@ -82,7 +82,46 @@ describe("effectivePromptAttachmentDelivery", () => {
});
});
describe("PromptEditor attachment chips", () => {
describe("PromptEditor attachment wiring", () => {
// Direct TemplateResult handler extraction keeps these node-environment tests focused on
// PromptEditor wiring without introducing a DOM/FileReader harness for the whole component.
it("captures pasted files, strips data URL prefixes, and surfaces read failures", async () => {
const editor = new PromptEditor();
const onSend = vi.fn<NonNullable<PromptEditor["onSend"]>>();
editor.onSend = onSend;
setPromptEditorPrivate(editor, "draft", "inspect attachments");
const restoreFileReader = installFileReaderStub([
{ kind: "load", result: "data:image/png;base64,UE5H" },
{ kind: "error", error: new DOMException("File unavailable", "NotReadableError") },
]);
try {
const paste = findTemplateEventHandlerAfterMarker<Event>(editor.render(), "@paste=");
const pasteEvent = pasteEventWithFiles([
new File(["png"], "shot.png", { type: "image/png" }),
new File(["pdf"], "report.pdf", { type: "application/pdf" }),
]);
const preventDefault = vi.spyOn(pasteEvent, "preventDefault");
paste(pasteEvent);
await flushMicrotasks();
expect(preventDefault).toHaveBeenCalledOnce();
expect(templateContainsValue(editor.render(), "Remove shot.png")).toBe(true);
expect(templateContainsValue(editor.render(), READ_FAILURE_MESSAGE)).toBe(true);
const send = findTemplateEventHandlerAfterMarker<Event>(editor.render(), "send-button");
send(new Event("click"));
expect(onSend).toHaveBeenCalledTimes(1);
expect(onSend).toHaveBeenCalledWith("inspect attachments", undefined, [
{ kind: "image", mimeType: "image/png", data: "UE5H", name: "shot.png" },
], "inline");
} finally {
restoreFileReader();
}
});
it("removes a pending attachment chip before sending the remaining attachments", () => {
const editor = new PromptEditor();
const onSend = vi.fn<NonNullable<PromptEditor["onSend"]>>();
@@ -111,10 +150,57 @@ describe("PromptEditor attachment chips", () => {
type TemplateEventHandler<E extends Event> = (event: E) => void;
type StubFileReaderOutcome =
| { kind: "load"; result: string }
| { kind: "error"; error: DOMException };
function setPromptEditorPrivate(editor: PromptEditor, property: string, value: unknown): void {
if (!Reflect.set(editor, property, value)) throw new Error(`Failed to set PromptEditor ${property}`);
}
function installFileReaderStub(outcomes: StubFileReaderOutcome[]): () => void {
const hadFileReader = Reflect.has(globalThis, "FileReader");
const previousFileReader = Reflect.get(globalThis, "FileReader");
class StubFileReader {
onerror: (() => void) | null = null;
onload: (() => void) | null = null;
error: DOMException | null = null;
result: string | ArrayBuffer | null = null;
readAsDataURL(): void {
const outcome = outcomes.shift();
if (outcome === undefined) throw new Error("Unexpected FileReader.readAsDataURL call");
if (outcome.kind === "error") {
this.error = outcome.error;
this.onerror?.();
return;
}
this.result = outcome.result;
this.onload?.();
}
}
Reflect.set(globalThis, "FileReader", StubFileReader);
return () => {
if (hadFileReader) {
Reflect.set(globalThis, "FileReader", previousFileReader);
return;
}
Reflect.deleteProperty(globalThis, "FileReader");
};
}
function pasteEventWithFiles(files: readonly File[]): Event {
const event = new Event("paste", { cancelable: true });
Object.defineProperty(event, "clipboardData", { value: { files } });
return event;
}
async function flushMicrotasks(): Promise<void> {
for (let remaining = 0; remaining < 10; remaining += 1) await Promise.resolve();
}
function findTemplateEventHandlerAfterMarker<E extends Event>(template: TemplateResult, marker: string): TemplateEventHandler<E> {
const handler = findOptionalTemplateEventHandlerAfterMarker<E>(template, marker);
if (handler === undefined) throw new Error(`Expected template event handler after marker ${marker}`);