Files
top-tickets/src/lib/mail/imap.test.ts
T
ogrechkoandClaude Sonnet 5 95522fdedd Fix data-URI screenshots not opening on click (Chrome blocks data: URL navigation)
The click-to-zoom feature from the previous commit worked for cid:-referenced
images but silently failed for images embedded directly as a data: URI —
confirmed with a real Playwright click: Chrome refuses to navigate a tab
(even a new one, even from a direct user click) to a data: URL, so
<a href="data:...' target="_blank"> just does nothing. The cursor still
showed zoom-in on hover since that's plain CSS, which is exactly the "лупа
появляется, но не нажимается" symptom reported.

Fix: imap.ts now extracts every data:image src out of an inbound email's
HTML into a real attachment file (deduping identical images embedded more
than once), the same way cid: images already were, and rewrites the HTML to
point at that attachment's normal /api/attachments/... URL instead. That
also shrinks messages.body_html (data URIs can be hundreds of KB sitting in
a DB column) and gets data-URI images the same "no duplicate chip below"
treatment cid: images already had.

attachments.isInline is now its own real column (backfilled from the
existing content_id-based cases) instead of being derived from content_id,
since a data-URI-derived attachment is inline but was never cid-referenced.
sanitizeEmailHtml's link-wrapping step now skips any residual data: src
defensively (unwrapped-but-visible beats a link that looks clickable but
isn't).

Verified against the real deployment with actual browser clicks (Playwright):
both a data-URI image and a cid: image now open their full-resolution
attachment in a new tab; before this fix the data-URI one silently did
nothing. Added a vitest.config.ts (needed for the new test file's @/ import
aliases) and unit tests for the extraction/dedup logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GteWhnWKTmnXcsd5jx6H7u
2026-08-19 09:07:40 +00:00

36 lines
1.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { extractDataUriImages } from "./imap";
const TINY_PNG_B64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
describe("extractDataUriImages", () => {
it("extracts a data:image src into a real attachment and rewrites the HTML to reference it", async () => {
const html = `<p>see</p><img src="data:image/png;base64,${TINY_PNG_B64}" alt="shot">`;
const { html: out, attachments } = await extractDataUriImages(html);
expect(attachments).toHaveLength(1);
expect(attachments[0].mimeType).toBe("image/png");
expect(attachments[0].isInline).toBe(true);
expect(attachments[0].contentId).toBeNull();
expect(out).toContain(`/api/attachments/${attachments[0].id}`);
expect(out).not.toContain("base64");
});
it("dedupes the same image embedded twice into a single attachment", async () => {
const src = `data:image/png;base64,${TINY_PNG_B64}`;
const html = `<img src="${src}"><p>middle</p><img src="${src}">`;
const { html: out, attachments } = await extractDataUriImages(html);
expect(attachments).toHaveLength(1);
const occurrences = out.split(`/api/attachments/${attachments[0].id}`).length - 1;
expect(occurrences).toBe(2);
});
it("leaves HTML with no data: images untouched", async () => {
const html = '<p>hi</p><img src="https://example.com/x.png">';
const { html: out, attachments } = await extractDataUriImages(html);
expect(attachments).toHaveLength(0);
expect(out).toBe(html);
});
});