Archived
test(client): cover prompt attachment paste wiring
This commit is contained in:
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user