Files
gacha-event-tracker/test/zoom.test.ts
T
Lucas WintherandClaude Opus 5 ba39ad2bce feat(timeline): let the reader set the scale
One density cannot answer both questions the board is asked. A patch cycle is
six weeks, a login campaign runs for months, and 13px a day is a compromise
between "what am I in the middle of this week?" and "how do the next three
months line up?" that serves neither well.

A pair of controls steps through a ladder of day widths and the choice is
remembered, on the same argument as the view tabs: a reader who has said how
they want to read this should not say it again on the next load.

Two things it had to get right. Zooming holds the middle of the view still —
rescaling around the left edge of a three-month board throws away whatever
they had scrolled to, and re-opening at today would undo the scrolling that
got them there. And the dated ticks thin out as the scale shrinks, because a
week is 42px at the widest setting and the dates would sit on top of each
other; the gridlines stay weekly, since they carry the rhythm rather than the
reading.

The scale is stored as the measurement, not a step number, and read through
`snapDayWidth`. An export written against a different ladder then opens on
something close to what its reader chose, and a corrupt value opens on the
default rather than a board one pixel wide.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-18 04:13:31 +02:00

82 lines
2.8 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
canStep,
DAY_WIDTHS,
DEFAULT_DAY_WIDTH,
snapDayWidth,
stepDayWidth,
weekLabelStep,
} from "../src/client/state/zoom.ts";
/**
* The timeline's scale.
*
* A patch cycle is six weeks and a login campaign can run for months, so the
* reader picks how much time is on screen. What these pin down is the two ways
* that can go wrong: a stored value the ladder no longer contains, and an axis
* whose dates stop being readable once they are close enough together.
*/
describe("snapDayWidth", () => {
test("a value on the ladder is left alone", () => {
for (const width of DAY_WIDTHS) expect(snapDayWidth(width)).toBe(width);
});
test("a value between steps lands on the nearest one", () => {
// An export written against a different ladder still opens on something
// close to what its reader chose.
expect(snapDayWidth(10)).toBe(9);
expect(snapDayWidth(7)).toBe(6);
expect(snapDayWidth(1000)).toBe(48);
// Exactly between two steps takes the wider one: ties go to the more
// legible board.
expect(snapDayWidth(11)).toBe(13);
});
test("an unusable value is the default, not the nearest edge", () => {
// A corrupt number is not a preference, and a board one pixel wide is not
// a scale anyone chose.
expect(snapDayWidth(0)).toBe(DEFAULT_DAY_WIDTH);
expect(snapDayWidth(-5)).toBe(DEFAULT_DAY_WIDTH);
expect(snapDayWidth(Number.NaN)).toBe(DEFAULT_DAY_WIDTH);
expect(snapDayWidth(Number.POSITIVE_INFINITY)).toBe(DEFAULT_DAY_WIDTH);
});
});
describe("stepDayWidth", () => {
test("moves one step at a time", () => {
expect(stepDayWidth(13, 1)).toBe(20);
expect(stepDayWidth(13, -1)).toBe(9);
});
test("stops at the ends rather than wrapping", () => {
const widest = DAY_WIDTHS[DAY_WIDTHS.length - 1]!;
const narrowest = DAY_WIDTHS[0]!;
expect(stepDayWidth(widest, 1)).toBe(widest);
expect(stepDayWidth(narrowest, -1)).toBe(narrowest);
});
test("canStep says when a control has nowhere left to go", () => {
expect(canStep(DAY_WIDTHS[0]!, -1)).toBe(false);
expect(canStep(DAY_WIDTHS[0]!, 1)).toBe(true);
expect(canStep(DAY_WIDTHS[DAY_WIDTHS.length - 1]!, 1)).toBe(false);
});
});
describe("weekLabelStep", () => {
test("every Monday is dated at the default scale and closer in", () => {
expect(weekLabelStep(DEFAULT_DAY_WIDTH)).toBe(1);
expect(weekLabelStep(48)).toBe(1);
});
test("dates thin out rather than overlapping when zoomed out", () => {
// At six px a day a week is 42px and "18 Aug" is wider than that.
expect(weekLabelStep(6)).toBeGreaterThan(1);
expect(weekLabelStep(6) * 7 * 6).toBeGreaterThanOrEqual(64);
});
test("never asks for a label every zero weeks", () => {
expect(weekLabelStep(1000)).toBe(1);
});
});