Files
timelog/tests/trend-chart.test.ts
minhtrannhatandClaude Opus 5.5 45891da656 Fix the time trend chart for all ranges
All bars now share one baseline, because each column has a label row
of the same height. Before, only columns with a day number had a label
row, so their bars sat higher.

Only the peak bar has a value label, so labels no longer overlap in a
31-day month. A readout line above the chart shows the date and value
of the bar that the user hovers, taps or focuses.

Day numbers are no longer cut, a period with no time has no bar, the
axis line sits at the bar baseline, and bars are 24px wide or less.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 15:18:52 -05:00

57 lines
2.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { rangeWindow, type RangeKind } from "../src/client/analytics.ts";
import { trendChart } from "../src/client/trend-chart.ts";
const HOUR = 3_600_000;
const format = (ms: number) => `${ms / HOUR}h`;
const count = (html: string, text: string) => html.split(text).length - 1;
const base = new Date(2026, 10, 8, 12);
describe("trend chart", () => {
for (const kind of ["day", "week", "month", "year"] as RangeKind[]) {
test(`${kind}: every column has a label row, so all bars share one baseline`, () => {
const range = rangeWindow(kind, 0, base);
const html = trendChart(range.bins, range.bins.map(() => 0), format, +base);
expect(count(html, 'class="trend-label"')).toBe(range.bins.length);
});
test(`${kind}: every bin has a full name for the readout`, () => {
for (const bin of rangeWindow(kind, 0, base).bins) expect(bin.name.length).toBeGreaterThan(1);
});
}
test("only the peak bar has a value label", () => {
const { bins } = rangeWindow("month", 0, base);
const trend = bins.map((_, i) => (i === 3 ? 3 * HOUR : i < 8 ? HOUR : 0));
const html = trendChart(bins, trend, format, +base);
expect(count(html, 'class="trend-value"')).toBe(1);
expect(html).toContain('<span class="trend-value">3h</span>');
});
test("a bin with no time has no bar", () => {
const { bins } = rangeWindow("week", 0, base);
const trend = bins.map((_, i) => (i === 0 ? HOUR : 0));
expect(count(trendChart(bins, trend, format, +base), 'class="trend-fill"')).toBe(1);
});
test("each column gives its date and value to the readout", () => {
const { bins } = rangeWindow("month", 0, base);
const html = trendChart(bins, bins.map((_, i) => (i === 5 ? 2 * HOUR : 0)), format, +base);
expect(html).toContain(`data-readout="${bins[5]!.name} · 2h"`);
expect(html).toContain(`data-readout="${bins[6]!.name} · 0h"`);
});
test("the readout starts on the peak, or says the period is empty", () => {
const { bins } = rangeWindow("year", 0, base);
const html = trendChart(bins, bins.map((_, i) => (i === 10 ? 5 * HOUR : 0)), format, +base);
expect(html).toContain(`>Peak · ${bins[10]!.name} · 5h<`);
expect(trendChart(bins, bins.map(() => 0), format, +base)).toContain(">No time logged in this period.<");
});
test("the current bin is marked", () => {
const { bins } = rangeWindow("week", 0, base);
const html = trendChart(bins, bins.map(() => 0), format, +base);
expect(count(html, "trend-col current")).toBe(1);
});
});