Files
gacha-event-tracker/test/redesign-daily-panel.test.tsx
T
Lucas Winther dd5d374422 fix: apply game order to dailies and routines
Account creation order leaked into daily rows and routine groups even when the reader had arranged their games. Apply the shared display rule while preserving each game's server and task order.
2026-11-02 03:35:32 +01:00

149 lines
4.8 KiB
TypeScript

import { expect, test } from "bun:test";
import { renderToStaticMarkup as render } from "react-dom/server";
import { DailyPanel } from "../src/client/components/redesign/DailyPanel.tsx";
import { migrateLegacy } from "../src/client/state/reader/migration.ts";
import { routineRows } from "../src/client/state/reader/routines.ts";
import { readerDayKey } from "../src/shared/readerClock.ts";
const at = "2026-10-16T12:00:00.000Z";
const now = Date.parse(at);
test.each([
{ order: ["zzz", "genshin", "hsr"], expected: ["ZZZ", "Genshin", "Star Rail"] },
{ order: undefined, expected: ["Genshin", "Star Rail", "ZZZ"] },
{ order: ["zzz"], expected: ["ZZZ", "Genshin", "Star Rail"] },
])("dailies and progress pips follow game order $order", ({ order, expected }) => {
let id = 0;
const state = migrateLegacy({}, ["hsr", "genshin", "zzz"], at, () => `account:${++id}`);
state.prefs.gameOrder = order;
const rows = Object.values(state.accounts).flatMap((account) =>
routineRows(state, account.id, now).filter((r) => r.id === `dailies:${account.game}`),
);
const html = render(<DailyPanel rows={rows} state={state} now={now} onAction={async () => ({ ok: true })} />);
expect([...html.matchAll(/<strong>(.*?)<\/strong>/g)].map((m) => m[1])).toEqual([...expected]);
const pips = [...html.matchAll(/<i[^>]*style="([^"]*)"/g)].map((m) => m[1]);
const rowHues = [...html.matchAll(/class="daily-row daily-hit"[^>]*style="([^"]*)"/g)].map((m) => m[1]);
expect(pips).toHaveLength(3);
expect(pips).toEqual(rowHues);
});
function setup() {
const state = migrateLegacy({}, ["genshin"], at, () => "account:daily");
state.accounts["account:daily"]!.routines["dailies:genshin"] = {
followed: true,
checklist: true,
timeline: false,
};
const rows = routineRows(state, "account:daily", now).filter(
(r) => r.id === "dailies:genshin",
);
return { state, rows };
}
test("a daily row is one tick target, with progress above the list", () => {
const { state, rows } = setup();
const html = render(
<DailyPanel
rows={rows}
state={state}
onAction={async () => ({ ok: true })}
now={now}
/>,
);
expect(html).toContain('class="daily-row daily-hit"');
expect(html).toContain('aria-pressed="false"');
expect(html).toContain("0 of 1 done");
expect(html).not.toContain("-day streak");
});
test("a ticked daily reads done, shows its streak, and the panel says all done", () => {
const { state, rows } = setup();
const today = readerDayKey(now, rows[0]!.clock);
const yesterday = new Date(Date.parse(`${today}T00:00:00Z`) - 86_400_000)
.toISOString()
.slice(0, 10);
const ticked = [{ ...rows[0]!, days: [yesterday, today] }];
const html = render(
<DailyPanel
rows={ticked}
state={state}
onAction={async () => ({ ok: true })}
now={now}
/>,
);
expect(html).toContain('aria-pressed="true"');
expect(html).toContain("All done for today");
expect(html).toContain("2-day streak");
// A fresh render is not a fresh tick: nothing pops on arrival.
expect(html).not.toContain("data-fresh");
expect(html).not.toContain("fireworks");
});
test("an event's weekly goal, met without today's tick, reads done and shows 5/5 wk", () => {
const { state, rows } = setup();
const today = readerDayKey(now, rows[0]!.clock);
const goalRow = {
...rows[0]!,
id: "genshin:to-temper-thyself",
title: "To Temper Thyself and Journey Far",
days: [],
status: {
scope: "week" as const,
measure: "days" as const,
periodStart: today,
required: 5,
count: 5,
met: true,
needed: 0,
open: 0,
shortfall: false,
},
};
const html = render(
<DailyPanel
rows={[goalRow]}
state={state}
onAction={async () => ({ ok: true })}
now={now}
/>,
);
expect(html).toContain("5/5 wk");
expect(html).toContain('data-met="true"');
// Not ticked today: the button stays unpressed, nothing is invented.
expect(html).toContain('aria-pressed="false"');
expect(html).toContain("All done for today");
expect(html).not.toContain("-day streak");
});
test("an unmet weekly goal counts toward its target, not as done", () => {
const { state, rows } = setup();
const today = readerDayKey(now, rows[0]!.clock);
const goalRow = {
...rows[0]!,
id: "genshin:to-temper-thyself",
days: [],
status: {
scope: "week" as const,
measure: "days" as const,
periodStart: today,
required: 5,
count: 3,
met: false,
needed: 2,
open: 4,
shortfall: false,
},
};
const html = render(
<DailyPanel
rows={[goalRow]}
state={state}
onAction={async () => ({ ok: true })}
now={now}
/>,
);
expect(html).toContain("3/5 wk");
expect(html).toContain("0 of 1 done");
expect(html).not.toContain("data-met");
});