The new reader app replaces the old UI: Checklist with a rail (focus pills, Next to expire, today's dailies, pinned routines), Timeline in a new and a classic look, Routines, Archive and a Settings area, in a Structured (default) and a Relaxed interface. It follows the approved Round 6 B and Round 7 mockups, corrected screen by screen with the owner. Reader data moves to a v2 store (gacha-tracker:v2:*). Existing v1 data is migrated on first load, a recovery copy is written first, and the v1 keys are never removed, so a rollback finds them unchanged. Redeem codes for Genshin arrive as their own feed. Also: live's urgency ramp, editable per theme; completion and section-complete fireworks; a returning-user tour; the design mockups, specs, plans, reviews and handoff record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { runRefresh } from "../scripts/refresh-sources.ts";
|
|
import { SnapshotStore } from "../src/ingest/snapshots.ts";
|
|
import { CODE_SOURCES } from "../src/ingest/codes/registry.ts";
|
|
import { sampleEvent } from "./helpers/reader.ts";
|
|
test("event and code sources share host spacing and snapshot gates", async () => {
|
|
const root = await mkdtemp("/tmp/codes-refresh-");
|
|
try {
|
|
const html = await Bun.file(
|
|
"fixtures/genshin/game8-codes-2026-10-16.html",
|
|
).text();
|
|
const slept: number[] = [];
|
|
const code = CODE_SOURCES[0]!;
|
|
const summary = await runRefresh({
|
|
adapters: [
|
|
{ ...code, id: "event-test", parse: () => [sampleEvent] },
|
|
code,
|
|
],
|
|
store: new SnapshotStore(root),
|
|
robots: {
|
|
allows: async () => ({ allowed: true, reason: "test permission" }),
|
|
},
|
|
fetchImpl: async () => new Response(html, { status: 200 }),
|
|
userAgent: "event-clock-test",
|
|
now: () => new Date("2026-10-16T12:00:00Z"),
|
|
sleep: async (n) => {
|
|
slept.push(n);
|
|
},
|
|
dryRun: false,
|
|
only: null,
|
|
force: false,
|
|
timeoutMs: 20000,
|
|
log: () => {},
|
|
rebuildFeed: null,
|
|
});
|
|
expect(summary.changed).toBe(2);
|
|
expect(slept).toEqual([2000]);
|
|
expect(summary.outcomes[1]?.eventCount).toBe(10);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
test("the CLI recognises the code source during a request-free dry run", () => {
|
|
const p = Bun.spawnSync(
|
|
[
|
|
"bun",
|
|
"run",
|
|
"scripts/refresh-sources.ts",
|
|
"--dry-run",
|
|
"--only",
|
|
"genshin-game8-codes",
|
|
],
|
|
{ stdout: "pipe", stderr: "pipe" },
|
|
);
|
|
expect(p.exitCode).toBe(0);
|
|
expect(new TextDecoder().decode(p.stdout)).toContain("genshin-game8-codes");
|
|
});
|