Files
gacha-event-tracker/test/redesign-routines.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

146 lines
5.8 KiB
TypeScript

import { expect, test } from "bun:test";
import { renderToStaticMarkup as render } from "react-dom/server";
import { RoutineGroup } from "../src/client/components/redesign/RoutineGroup.tsx";
test("always expanded groups have no collapse affordance", () => {
const html = render(
<RoutineGroup title="Genshin" policy="always" focused={false}>
<p>Direct daily controls</p>
</RoutineGroup>,
);
expect(html).not.toContain("<summary");
expect(html).toContain("Direct daily controls");
});
test("focus expands groups without changing the saved default", () => {
const html = render(
<RoutineGroup title="Genshin" policy="collapsed" focused={true}>
<p>Controls</p>
</RoutineGroup>,
);
expect(html).toContain('open=""');
});
import { placementAccounts } from "../src/client/components/redesign/RoutinesPage.tsx";
import { migrateLegacy } from "../src/client/state/reader/migration.ts";
import { routineRows } from "../src/client/state/reader/routines.ts";
test("bulk pins combine changes per account and preserve the other placement", () => {
const at = "2026-10-16T12:00:00.000Z";
const state = migrateLegacy({}, ["genshin"], at, () => "account:bulk");
const a = state.accounts["account:bulk"]!;
a.routines["routine:genshin:weekly-bosses"] = {
followed: true,
checklist: false,
timeline: true,
};
const rows = routineRows(state, a.id, Date.parse(at));
expect(rows.length).toBeGreaterThan(1);
const accounts = placementAccounts(state, rows, "checklist", true);
expect(accounts).toHaveLength(1);
for (const row of rows) {
expect(accounts[0]!.routines[row.id]!.checklist).toBe(true);
expect(accounts[0]!.routines[row.id]!.timeline).toBe(
row.placement.timeline,
);
}
expect(a.routines["routine:genshin:weekly-bosses"]!.checklist).toBe(false);
});
import { RoutinesPage } from "../src/client/components/redesign/RoutinesPage.tsx";
test.each(["game", "reset"])("routines follow Game order when grouped by %s", (group) => {
const at = "2026-10-16T12:00:00.000Z";
let id = 0;
const state = migrateLegacy({}, ["hsr", "genshin", "zzz"], at, () => `account:${++id}`);
state.prefs.gameOrder = ["zzz", "genshin", "hsr"];
state.prefs.routineGroup = group;
const html = render(<RoutinesPage state={state} now={Date.parse(at)} onAction={async () => ({ ok: true })} />);
const cells = [...html.matchAll(/class="gcell"><span>([^<]*)/g)].map((m) => m[1]);
expect(cells).toEqual(["ZZZ", "Genshin", "Star Rail"]);
});
test("the routines table shows progress as text with an edit affordance, not inputs", () => {
const at = "2026-10-16T12:00:00.000Z";
const state = migrateLegacy({}, ["genshin"], at, () => "account:table");
const pin = { followed: true, checklist: true, timeline: true };
state.accounts["account:table"]!.routines["routine:genshin:weekly-bosses"] =
pin;
state.accounts["account:table"]!.routines["dailies:genshin"] = pin;
const html = render(
<RoutinesPage
state={state}
onAction={async () => ({ ok: true })}
now={Date.parse(at)}
/>,
);
expect(html).toContain('class="rt"');
expect(html).toContain('class="gcell"');
expect(html).toContain('title="Enter an amount"');
expect(html).toContain("/ 3 runs");
expect(html).not.toContain('type="number"');
expect(html).toContain('class="pin"');
// One server: the game cell does not name it.
expect(html).not.toContain(">europe<");
});
test("routine bands are named by cadence: Dailies, Weeklies", () => {
const at = "2026-10-16T12:00:00.000Z";
const state = migrateLegacy({}, ["genshin"], at, () => "account:bands");
const pin = { followed: true, checklist: true, timeline: true };
state.accounts["account:bands"]!.routines["routine:genshin:weekly-bosses"] =
pin;
state.accounts["account:bands"]!.routines["dailies:genshin"] = pin;
const html = render(
<RoutinesPage
state={state}
onAction={async () => ({ ok: true })}
now={Date.parse(at)}
/>,
);
expect(html).toContain(">Dailies<");
expect(html).toContain(">Weeklies<");
expect(html).not.toContain(">Today<");
expect(html).not.toContain(">This week<");
});
import { Fireworks } from "../src/client/components/Fireworks.tsx";
test("the grand celebration is wider than the single burst and carries the game's colour", () => {
const single = render(<Fireworks />);
const grand = render(<Fireworks grand hue="#4EA8DE" />);
const count = (html: string) => html.split('class="spark"').length - 1;
expect(count(single)).toBe(27);
expect(count(grand)).toBe(98);
expect(grand).toContain('class="fireworks grand"');
expect(grand).toContain("background:#4EA8DE");
});
test("a finished routine group is marked complete but does not celebrate on arrival", () => {
const html = render(
<RoutineGroup
title="Genshin"
policy="always"
focused={false}
done={2}
total={2}
summary="All 2 done"
>
<p>rows</p>
</RoutineGroup>,
);
expect(html).toContain('data-complete="true"');
expect(html).toContain("All 2 done");
expect(html).not.toContain("data-celebrate");
expect(html).not.toContain("fireworks");
});
test("a routine that resets with the version is not offered as Make it mine", () => {
// A reader's task cannot hold a version period (spec 2026-10-22).
const at = "2026-10-22T14:30:00.000Z";
const state = migrateLegacy({}, ["wuwa"], at, () => "account:mine");
const pin = { followed: true, checklist: true, timeline: true };
state.accounts["account:mine"]!.routines["routine:wuwa:shop-radiant-tide"] = pin;
state.accounts["account:mine"]!.routines["routine:wuwa:weekly-challenge"] = pin;
const html = render(
<RoutinesPage state={state} onAction={async () => ({ ok: true })} now={Date.parse(at)} />,
);
expect(html).toContain("Oscillated Coral: Radiant Tide");
expect(html.split("Make it mine").length - 1).toBe(1);
});