Skip to content

Commit 168d538

Browse files
committed
Pin Ctrl+C arming, ENOENT unlink, and idle attachment clear
1 parent 64a6435 commit 168d538

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

src/tui/keybindings.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { openCommandSurface } from "./command-surfaces.js";
2424
import { focusOwner } from "./focus/focus-state.js";
2525
import { setChromeZones } from "./shell.js";
2626
import {
27+
addPendingAttachment,
2728
appendStreamRow,
2829
applyShellInterrupt,
2930
createAppShell,
@@ -517,6 +518,16 @@ const PROBES: Readonly<Record<string, { readonly group: Group; readonly probe: P
517518
shell.session = { ...shell.session, items: [] };
518519
truncateStreamRows(shell, rowsBefore);
519520
setShellRunState(shell, "idle");
521+
522+
addPendingAttachment(shell, {
523+
id: "clip",
524+
name: "clip.png",
525+
contentType: "image/png",
526+
data: new Uint8Array([1]),
527+
contentHash: "hash-clip",
528+
});
529+
press(h, chords[0]);
530+
expect(shell.pendingAttachments).toHaveLength(0);
520531
},
521532
},
522533

src/tui/prompt-slash-exit.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ describe("Ctrl+C exit", () => {
278278

279279
test("idle Ctrl+C with prompt text also drops pending attachments", async () => {
280280
await withShell(async ({ shell }) => {
281+
let exits = 0;
282+
setShellExitHandler(shell, () => {
283+
exits += 1;
284+
});
281285
addPendingAttachment(shell, pendingImage("clip"));
282286
shell.prompt.value = "look at this";
283287
expect(noticeText(shell)).toContain("1 image");
@@ -287,6 +291,10 @@ describe("Ctrl+C exit", () => {
287291
expect(shell.prompt.value).toBe("");
288292
expect(shell.pendingAttachments).toHaveLength(0);
289293
expect(noticeText(shell)).not.toContain("1 image");
294+
expect(shell.statusFlash).toBe("press ctrl+c again to exit");
295+
296+
handleCtrlC(shell, 1);
297+
expect(exits).toBe(1);
290298
});
291299
});
292300

@@ -345,6 +353,39 @@ describe("Ctrl+C exit", () => {
345353
}
346354
});
347355
});
356+
357+
test("clearPendingAttachments swallows a missing ephemeralPath", async () => {
358+
await withShell(async ({ shell }) => {
359+
const missing = join(tmpdir(), "ctrlc-attach-missing.png");
360+
addPendingAttachment(shell, pendingImage("ours", { ephemeralPath: missing }));
361+
expect(() => clearPendingAttachments(shell)).not.toThrow();
362+
expect(shell.pendingAttachments).toHaveLength(0);
363+
});
364+
});
365+
366+
test("clearPendingAttachments unlinks ephemeralPath on an attachment that also has path", async () => {
367+
await withShell(async ({ shell }) => {
368+
const dir = mkdtempSync(join(tmpdir(), "ctrlc-attach-both-"));
369+
const ephemeral = join(dir, "ours.png");
370+
const operator = join(dir, "theirs.png");
371+
writeFileSync(ephemeral, "ephemeral-bytes");
372+
writeFileSync(operator, "operator-bytes");
373+
try {
374+
addPendingAttachment(
375+
shell,
376+
pendingImage("both", { ephemeralPath: ephemeral, path: operator }),
377+
);
378+
379+
clearPendingAttachments(shell);
380+
381+
expect(shell.pendingAttachments).toHaveLength(0);
382+
expect(existsSync(ephemeral)).toBe(false);
383+
expect(existsSync(operator)).toBe(true);
384+
} finally {
385+
rmSync(dir, { recursive: true, force: true });
386+
}
387+
});
388+
});
348389
});
349390

350391
function pendingImage(id: string, extra?: Partial<PendingImageAttachment>): PendingImageAttachment {

0 commit comments

Comments
 (0)