AkurAI Build
Menu

Public repository / 3af23b0e937520000e1aa43c7e2eb9de2675abd1

AkurAI-Mail / src/framework/list-island.test.js

Latest change 3af23b0e937520000e1aa43c7e2eb9de2675abd1 - OLI-248: webmail list and reader as framework islands on a signals store by Ólafur Búi Ólafsson

import { describe, expect, test } from "bun:test";
import { folderIcon, listWindow, senderLabel, shortDate, threadHeight, ROW_HEIGHT, CHILD_ROW_HEIGHT } from "./list-island.js";

const threads = Array.from({ length: 40 }, (_, i) => ({ key: `t${i}`, messages: i === 5 ? [1, 2, 3] : [1] }));

describe("list window", () => {
  test("draws only the rows near the viewport with honest padding", () => {
    const top = listWindow(threads, {}, 0, 600);
    expect(top.threads.map((t) => t.key)).toEqual(threads.slice(0, 12).map((t) => t.key));
    expect(top.padTop).toBe(0);
    expect(top.padBottom).toBe((40 - 12) * ROW_HEIGHT);
    const mid = listWindow(threads, {}, 20 * ROW_HEIGHT, 600);
    expect(mid.threads[0]?.key).toBe("t14");
    expect(mid.padTop).toBe(14 * ROW_HEIGHT);
  });

  test("expanded threads grow by their replies", () => {
    expect(threadHeight(threads[5], {})).toBe(ROW_HEIGHT);
    expect(threadHeight(threads[5], { t5: true })).toBe(ROW_HEIGHT + 2 * CHILD_ROW_HEIGHT);
    const open = listWindow(threads, { t5: true }, 0, 600);
    expect(open.padBottom).toBe(40 * ROW_HEIGHT + 2 * CHILD_ROW_HEIGHT - (open.padTop + open.threads.reduce((sum, t) => sum + threadHeight(t, { t5: true }), 0)));
  });

  test("an empty list has no rows and no padding", () => {
    expect(listWindow([], {}, 0, 600)).toEqual({ threads: [], padTop: 0, padBottom: 0 });
  });
});

describe("row labels", () => {
  test("folder icons fall back to a document", () => {
    expect(folderIcon("Inbox")).toBe("Inbox");
    expect(folderIcon("Trash")).toBe("Trash2");
    expect(folderIcon("Projects")).toBe("FileText");
  });

  test("sender label prefers from, then to", () => {
    expect(senderLabel({ from: "a@b.is" })).toBe("a@b.is");
    expect(senderLabel({ to: "c@d.is" })).toBe("c@d.is");
    expect(senderLabel({})).toBe("(unknown)");
  });

  test("short date shows the time today and the day otherwise", () => {
    const today = new Date("2026-10-03T12:00:00");
    expect(shortDate("2026-10-03T09:05:00", today)).toMatch(/09:05|9:05/);
    expect(shortDate("2026-09-01T09:05:00", today)).toMatch(/Sep 1/);
    expect(shortDate("not a date", today)).toBe("not a date");
  });
});