Files
gacha-event-tracker/test/prefs.test.ts
T
Lucas WintherandClaude Opus 5 d5dbe72e17 feat(prefs): a game we add arrives switched off
Adding a source is our decision, not the reader's. Eleven lanes became
fourteen this week, and for someone who plays two that is not a feature
arriving — it is their calendar filling with events they will never open,
which is the thing the first-run picker exists to prevent.

`knownGames` records every lane a reader has been offered; a lane missing from
it is new to them, so it is recorded and hidden on sight. The games chips in
settings list every lane, on or off, which is where they take one up.

The case that had to be right is the reader who installed before any of this
existed: they have no `knownGames` at all, and reading that as "has been
offered nothing" would switch off every game they already read. Absent means
unrecorded — the first pass records what is already on their screen and
changes nothing else. Lanes they invented are recorded but never hidden;
typing a game in is asking for it.

The decision is a pure function so this is provable rather than watched for.
One real cost, stated in the PRD rather than hidden: a reader whose game
finally arrives is not told, which makes the colophon roadmap matter more.

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

69 lines
2.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { adoptNewLanes } from "../src/client/state/usePrefs.ts";
import type { LaneId } from "../src/shared/custom.ts";
/**
* What happens to a reader's games when we add a source.
*
* Adding one is our decision, not theirs: a reader who plays two games did not
* ask for the other twelve, and a calendar that fills up on its own is the
* thing the first-run picker exists to prevent. So a lane that is new to them
* arrives switched off — and the one case that must never misfire is the
* reader who installed before any of this was recorded.
*/
const TRACKED: LaneId[] = ["genshin", "hsr", "zzz"];
describe("adoptNewLanes", () => {
test("an unrecorded reader has everything on their screen recorded, and nothing switched off", () => {
// Every existing install is in this state. Reading "no record" as "has been
// offered nothing" would switch off every game they already read.
expect(adoptNewLanes(TRACKED, undefined, [])).toEqual({
knownGames: TRACKED,
});
});
test("a lane they were never offered arrives switched off", () => {
const patch = adoptNewLanes([...TRACKED, "holodori"], TRACKED, []);
expect(patch).toEqual({
knownGames: [...TRACKED, "holodori"],
hiddenGames: ["holodori"],
});
});
test("their own game is recorded but never hidden", () => {
// They asked for it by typing it in. Hiding it would be the app arguing
// with the reader about a game they just created.
const patch = adoptNewLanes(
[...TRACKED, "mygame:limbus-company"],
TRACKED,
[],
);
expect(patch).toEqual({
knownGames: [...TRACKED, "mygame:limbus-company"],
hiddenGames: [],
});
});
test("nothing new is nothing to write", () => {
expect(adoptNewLanes(TRACKED, TRACKED, ["zzz"])).toBeNull();
});
test("an empty list is a feed that has not arrived, not a reader with no games", () => {
// Seeding from it would record nothing and then treat every real game as
// new the moment the feed lands.
expect(adoptNewLanes([], undefined, [])).toBeNull();
expect(adoptNewLanes([], TRACKED, [])).toBeNull();
});
test("a game they had already switched off is not listed twice", () => {
const patch = adoptNewLanes([...TRACKED, "wuwa"], TRACKED, ["wuwa"]);
expect(patch?.hiddenGames).toEqual(["wuwa"]);
});
test("their existing choices are left exactly as they were", () => {
const patch = adoptNewLanes([...TRACKED, "fgo"], TRACKED, ["hsr"]);
expect(patch?.hiddenGames).toEqual(["hsr", "fgo"]);
});
});