import { describe, expect, test } from "bun:test"; import { exportForestCsv, importForestCsv, parseCsv } from "../src/client/csv.ts"; import { overlapMs, rangeWindow, totals } from "../src/client/analytics.ts"; import { normalizeTag } from "../src/shared/tag.ts"; describe("Forest CSV", () => { test("parses quotes, commas, and newlines", () => { expect(parseCsv('a,b\r\n"one,two","line 1\nline 2"\r\n')).toEqual([ ["a", "b"], ["one,two", "line 1\nline 2"], ]); }); test("imports the supplied Forest export and round trips its data", async () => { const source = [...new Bun.Glob("*.csv").scanSync(".")][0]; expect(source).toBeDefined(); const records = await importForestCsv(await Bun.file(source!).text()); expect(records).toHaveLength(2709); expect(new Set(records.map(record => record.id)).size).toBe(records.length); expect(records.filter(record => record.isSuccess === false)).toHaveLength(28); expect(records.filter(record => record.tag === "Job Hunting")).toHaveLength(3); expect(records.filter(record => record.tag === "Music")).toHaveLength(12); expect(records.filter(record => record.tag === "Paperwork")).toHaveLength(2); const output = parseCsv(exportForestCsv(records)); expect(output).toHaveLength(2710); expect(output[0]).toEqual(["Start Time", "End Time", "Tag", "Note", "Tree Type", "Is Success"]); expect(output[1]).toEqual([ "2021-04-28T20:38:16.930-0400", "2021-04-28T21:03:16.930-0400", "Study", "", "Cedar", "True", ]); }); }); test("tag capitalization keeps mixed-case names and short acronyms", () => { expect(normalizeTag(" job hunting ")).toBe("Job Hunting"); expect(normalizeTag("iOS AI study")).toBe("iOS AI Study"); }); describe("insights", () => { test("splits a session crossing midnight and counts early stops", () => { const start = +new Date(2026, 8, 20, 23, 30); const end = +new Date(2026, 8, 21, 0, 30); const record = { id: "session_test", startMs: start, endMs: end, targetSeconds: 7200, tag: "Study", note: "", treeType: "", isSuccess: false, source: "timer" as const, rawStart: null, rawEnd: null, deleted: false, }; const first = rangeWindow("day", 0, new Date(2026, 8, 20)); const second = rangeWindow("day", 0, new Date(2026, 8, 21)); expect(overlapMs(record, first.start, first.end)).toBe(30 * 60 * 1000); expect(totals([record], second).totalMs).toBe(30 * 60 * 1000); expect(totals([record], first).tags).toEqual([["Study", 30 * 60 * 1000]]); }); });