51 lines
2.5 KiB
TypeScript
51 lines
2.5 KiB
TypeScript
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]]);
|
||
|
|
});
|
||
|
|
});
|