Menu
Public repository / master
AkurAI-Mail / src/lib/webmail/compose.test.ts
Latest change 22202d1bde69be4ace8a299dffd91f8ff24378df - Recover deployed webmail frontend (frontend parity with EC2 a6afffc) by Ólafur Búi Ólafsson
import { describe, expect, test } from "bun:test";
import { emptyCompose, escapeHtml, forwardBody, hasDraftContent, parseSendAt, replyBody, secondsUntil, stashDraft, takeDraft, QUOTE_MARKER } from "./compose";
class MemoryStorage {
data = new Map<string, string>();
limit = Infinity;
get length() { return this.data.size; }
clear() { this.data.clear(); }
key(i: number) { return [...this.data.keys()][i] ?? null; }
getItem(k: string) { return this.data.get(k) ?? null; }
removeItem(k: string) { this.data.delete(k); }
setItem(k: string, v: string) {
if (v.length > this.limit) throw new Error("QuotaExceededError");
this.data.set(k, v);
}
}
describe("draft recovery", () => {
test("only drafts with content are worth keeping", () => {
expect(hasDraftContent(emptyCompose())).toBe(false);
expect(hasDraftContent({ ...emptyCompose(), subject: "Hi" })).toBe(true);
});
test("stash then take returns the draft once", () => {
const storage = new MemoryStorage() as unknown as Storage;
const draft = { ...emptyCompose(), to: "a@x.is" };
expect(stashDraft(storage, "k", draft)).toBe(true);
expect(takeDraft(storage, "k")).toEqual(draft);
expect(takeDraft(storage, "k")).toBeNull();
});
test("drops attachments when storage is full", () => {
const memory = new MemoryStorage();
memory.limit = 200;
const draft = { ...emptyCompose(), to: "a@x.is", attachments: [{ name: "big", type: "x", size: 1, data: "A".repeat(500) }] };
expect(stashDraft(memory as unknown as Storage, "k", draft)).toBe(true);
expect(takeDraft(memory as unknown as Storage, "k")?.attachments).toEqual([]);
});
});
describe("quoting", () => {
const message = { date: "Mon", from: "A <a@x.is>", subject: "Hi & bye", bodyText: "line 1\n<b>line 2</b>" };
test("escapes HTML", () => {
expect(escapeHtml(`<a href="x">&</a>`)).toBe("<a href="x">&</a>");
});
test("reply quotes the escaped body in a blockquote", () => {
expect(replyBody(message)).toBe(`<br><br>${QUOTE_MARKER}On Mon, A <a@x.is> wrote:<blockquote>line 1<br><b>line 2</b></blockquote>`);
});
test("forward includes escaped headers", () => {
expect(forwardBody(message)).toBe(`<br><br>${QUOTE_MARKER}Forwarded message:<br>From: A <a@x.is><br>Date: Mon<br>Subject: Hi & bye<br><br>line 1<br><b>line 2</b>`);
});
});
describe("send timing", () => {
test("send later requires a future time", () => {
const now = new Date("2026-10-01T12:00:00").getTime();
expect(parseSendAt("", now)).toEqual({ error: "Pick a send time first" });
expect(parseSendAt("2026-10-01T11:00", now)).toEqual({ error: "Send time must be in the future" });
expect(parseSendAt("2026-10-01T13:00", now)).toEqual({ sendAt: Math.floor(new Date("2026-10-01T13:00").getTime() / 1000) });
});
test("undo countdown never goes negative", () => {
expect(secondsUntil(110, 100_000)).toBe(10);
expect(secondsUntil(90, 100_000)).toBe(0);
});
});