Forwarding quoted the original into a new message, which is the right thing for passing on something to be read and the wrong thing for passing on something to be looked at. Quoting rewrites the body, drops the headers, and re-parents the attachments, so a bounce, a phishing report or anything else where the message itself is the evidence arrived altered. Forward as attachment sends the message whole, as a message/rfc822 part. It costs no upload at all: a message's own blobId is its RFC822 blob and already lives in the account, so this goes through the same by-reference path as attach-from-Files and a 40 MB message attaches as fast as a small one. It is in the message's own menu, the list's right-click menu, and the overflow on the reply strip at the foot of a thread, which is the one a thumb finds on a phone. Two things fixed on the way, both exposed rather than introduced by this. The filename rule was subject.replace(/[^\w.-]+/g, "_"), and \w without the u flag is ASCII: every character of a Russian, Japanese or Chinese subject failed the class, so those messages downloaded as a row of underscores. What is actually unsafe in a filename is much shorter than "not ASCII" -- path separators, the names Windows reserves, the control range -- so the rule now keeps letters from any script and drops only those. It lives in one place and the .eml download uses it too. And the composer's attachment chip set overflow/text-overflow on a span, where neither does anything, so the name never truncated and the size ran on after it on the same line. Only long names showed it, which is every .eml named from a subject.
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { emlFilename, sanitizeFilename } from "@/lib/emlName";
|
|
|
|
describe("emlFilename", () => {
|
|
it("keeps an ordinary subject, with spaces as underscores", () => {
|
|
expect(emlFilename("Quarterly report")).toBe("Quarterly_report.eml");
|
|
});
|
|
|
|
it("keeps letters from any script, which the ASCII rule threw away", () => {
|
|
// The whole point: none of these may come out as a row of underscores.
|
|
expect(emlFilename("Квартальный отчёт")).toBe("Квартальный_отчёт.eml");
|
|
expect(emlFilename("四半期報告")).toBe("四半期報告.eml");
|
|
expect(emlFilename("Rapport trimestriel été")).toBe("Rapport_trimestriel_été.eml");
|
|
});
|
|
|
|
it("keeps the punctuation that is fine in a filename", () => {
|
|
expect(emlFilename("Re- budget (v3) [final]")).toBe("Re-_budget_(v3)_[final].eml");
|
|
});
|
|
|
|
it("drops path separators and the characters Windows reserves", () => {
|
|
expect(emlFilename("a/b\\c:d*e?f\"g<h>i|j")).toBe("abcdefghij.eml");
|
|
});
|
|
|
|
it("drops control characters", () => {
|
|
expect(emlFilename("a\u0007b\u0000c")).toBe("abc.eml");
|
|
expect(emlFilename("a\u007fb")).toBe("ab.eml");
|
|
});
|
|
|
|
it("falls back when there is no subject, or nothing survives", () => {
|
|
expect(emlFilename("")).toBe("message.eml");
|
|
expect(emlFilename(null)).toBe("message.eml");
|
|
expect(emlFilename(undefined)).toBe("message.eml");
|
|
expect(emlFilename("///")).toBe("message.eml");
|
|
expect(emlFilename(" ")).toBe("message.eml");
|
|
});
|
|
|
|
it("does not end in a dot or a space, which Windows refuses", () => {
|
|
expect(emlFilename("Report.")).toBe("Report.eml");
|
|
expect(emlFilename("Report ")).toBe("Report.eml");
|
|
expect(emlFilename("...Report...")).toBe("Report.eml");
|
|
});
|
|
|
|
it("does not start with a dot, which would hide the file on Unix", () => {
|
|
expect(emlFilename(".hidden")).toBe("hidden.eml");
|
|
});
|
|
|
|
it("caps the length so it survives a filesystem limit", () => {
|
|
const name = emlFilename("x".repeat(500));
|
|
expect(name).toBe(`${"x".repeat(80)}.eml`);
|
|
});
|
|
|
|
it("exposes the stem on its own", () => {
|
|
expect(sanitizeFilename("Quarterly report")).toBe("Quarterly_report");
|
|
});
|
|
});
|