| 1 | // Run: tsx src/__tests__/attachment-display.test.ts |
| 2 | |
| 3 | import { baseName, formatAttachmentRefForDisplay, formatAttachmentRefForSubmit, parseAttachmentRefsForDisplay, replaceAttachmentRefsForDisplay, restoreAttachmentRefsForSubmit, sortDisplayAttachments } from "../lib/attachmentDisplay"; |
| 4 | import { appendHistoryAttachmentRefs } from "../lib/historyAttachmentRefs"; |
| 5 | |
| 6 | let passed = 0; |
| 7 | let failed = 0; |
| 8 | |
| 9 | function eq(a: unknown, b: unknown, label: string) { |
| 10 | if (JSON.stringify(a) === JSON.stringify(b)) { |
| 11 | process.stdout.write(` PASS ${label}\n`); |
| 12 | passed += 1; |
| 13 | } else { |
| 14 | process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`); |
| 15 | failed += 1; |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | console.log("\nattachment display"); |
| 20 | |
| 21 | const named = parseAttachmentRefsForDisplay( |
| 22 | "review @[DS30000.sl2](.reasonix/attachments/clipboard-20260610-121238.444775-000002.sl2) and @[park.png](.reasonix/attachments/clipboard-20260610-121238.444775-000001.png)", |
| 23 | ); |
| 24 | |
| 25 | eq(named.text, "review and", "removes named display refs from message text"); |
| 26 | eq( |
| 27 | named.attachments.map((a) => ({ path: a.path, name: a.name, kind: a.kind, ext: a.ext })), |
| 28 | [ |
| 29 | { |
| 30 | path: ".reasonix/attachments/clipboard-20260610-121238.444775-000002.sl2", |
| 31 | name: "DS30000.sl2", |
| 32 | kind: "file", |
| 33 | ext: "SL2", |
| 34 | }, |
| 35 | { |
| 36 | path: ".reasonix/attachments/clipboard-20260610-121238.444775-000001.png", |
| 37 | name: "park.png", |
| 38 | kind: "image", |
| 39 | ext: "PNG", |
| 40 | }, |
| 41 | ], |
| 42 | "preserves original display names for attachment cards", |
| 43 | ); |
| 44 | eq( |
| 45 | sortDisplayAttachments(named.attachments).map((a) => a.name), |
| 46 | ["park.png", "DS30000.sl2"], |
| 47 | "sorts images before files while keeping groups stable", |
| 48 | ); |
| 49 | eq( |
| 50 | replaceAttachmentRefsForDisplay("see @[DS30000.sl2](.reasonix/attachments/clipboard-20260610-121238.444775-000002.sl2)"), |
| 51 | "see [file:DS30000.sl2]", |
| 52 | "compact previews use named display refs", |
| 53 | ); |
| 54 | eq( |
| 55 | restoreAttachmentRefsForSubmit("review @[DS30000.sl2](.reasonix/attachments/clipboard-20260610-121238.444775-000002.sl2), then @[park.png](.reasonix/attachments/clipboard-20260610-121238.444775-000001.png)"), |
| 56 | "review @.reasonix/attachments/clipboard-20260610-121238.444775-000002.sl2, then @.reasonix/attachments/clipboard-20260610-121238.444775-000001.png", |
| 57 | "restores named display refs for submit", |
| 58 | ); |
| 59 | eq( |
| 60 | formatAttachmentRefForDisplay({ path: ".reasonix/attachments/clipboard-20260610-121238.444775-000001.png", name: "park.png", source: "attachment" }), |
| 61 | "@[park.png](.reasonix/attachments/clipboard-20260610-121238.444775-000001.png)", |
| 62 | "formats attachment display refs for edit replay", |
| 63 | ); |
| 64 | eq( |
| 65 | formatAttachmentRefForSubmit({ path: ".reasonix/attachments/clipboard-20260610-121238.444775-000001.png" }), |
| 66 | "@.reasonix/attachments/clipboard-20260610-121238.444775-000001.png", |
| 67 | "formats raw attachment refs for edit replay submit", |
| 68 | ); |
| 69 | const session = parseAttachmentRefsForDisplay(appendHistoryAttachmentRefs("see", [{ digest: "aa".repeat(32), name: "shot.png", mime: "image/png" }])); |
| 70 | eq(session.attachments[0]?.path, `attachment:${"aa".repeat(32)}`, "history attachments use digest paths"); |
| 71 | eq(session.attachments[0]?.kind, "image", "history attachments are images"); |
| 72 | const parsedDraft = parseAttachmentRefsForDisplay("see @[photo.png](draft:0123456789abcdef0123456789abcdef)"); |
| 73 | eq(parsedDraft.attachments[0]?.path, "draft:0123456789abcdef0123456789abcdef", "draft credentials parse as display attachments"); |
| 74 | eq( |
| 75 | [...session.attachments, ...parsedDraft.attachments].map((a) => a.path), |
| 76 | [session.attachments[0].path, parsedDraft.attachments[0].path], |
| 77 | "projects structured history attachments beside parsed draft refs", |
| 78 | ); |
| 79 | |
| 80 | eq(baseName("C:\\Users\\Abyss\\Desktop\\DS30000.sl2"), "DS30000.sl2", "extracts Windows path basenames"); |
| 81 | eq(baseName("/Users/abyss/Desktop/park.png"), "park.png", "extracts POSIX path basenames"); |
| 82 | |
| 83 | const escaped = parseAttachmentRefsForDisplay("look at @docs/my\\ report\\ 2026.pdf now"); |
| 84 | eq(escaped.text, "look at now", "removes an escaped workspace ref from message text"); |
| 85 | eq( |
| 86 | escaped.attachments.map((a) => ({ path: a.path, name: a.name, kind: a.kind, source: a.source })), |
| 87 | [{ path: "docs/my report 2026.pdf", name: "my report 2026.pdf", kind: "file", source: "workspace" }], |
| 88 | "escaped workspace ref becomes one full badge with the unescaped path", |
| 89 | ); |
| 90 | eq( |
| 91 | formatAttachmentRefForDisplay({ path: "docs/my report 2026.pdf", name: "my report 2026.pdf", source: "workspace" }), |
| 92 | "@docs/my\\ report\\ 2026.pdf", |
| 93 | "workspace display refs escape whitespace for lossless edit replay", |
| 94 | ); |
| 95 | eq( |
| 96 | formatAttachmentRefForSubmit({ path: "docs/my report 2026.pdf" }), |
| 97 | "@docs/my\\ report\\ 2026.pdf", |
| 98 | "workspace submit refs escape whitespace so @-token parsing resolves them", |
| 99 | ); |
| 100 | |
| 101 | console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`); |
| 102 | if (failed > 0) process.exit(1); |
| 103 |