feat(routines): Wuthering Waves' shop pulls, each version

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-22 17:38:41 +02:00
1 parent b3a84757a1
commit 8cf01b427f
8 files changed
+136 -12

No files matched your search

+24
View File
@@ -58,3 +58,27 @@ test("an event the reader marked daily remains a directly tickable routine", ()
expect(row?.placement.followed).toBe(true);
expect(row?.cadence).toBe("day");
});
// Spec 2026-10-22: WuWa's shop pulls reset with the version and show with the cycles.
import { routinePeriod } from "../src/client/components/redesign/TimelinePage.tsx";
test("a routine that resets with the version is a cycle row, banded to its recorded end", () => {
const now = "2026-10-22T14:30:00.000Z";
const s = migrateLegacy({}, ["wuwa"], now, () => "account:one");
s.prefs.standardPulls = true;
s.data["account:one"]!.amounts[s.accounts["account:one"]!.clocks.at(-1)!.id] = {
"routine:wuwa:shop-radiant-tide": { "2026-09-30": { value: 4, at: now } },
};
const rows = routineRows(s, "account:one", Date.parse(now));
const shop = rows.filter((r) => r.id.startsWith("routine:wuwa:shop-"));
expect(shop.map((r) => r.cadence)).toEqual(["cycle", "cycle", "cycle"]);
const radiant = shop.find((r) => r.id === "routine:wuwa:shop-radiant-tide")!;
expect(radiant.status).toMatchObject({ count: 4, required: 7, daysLeft: 20, periodStart: "2026-09-30" });
expect(routinePeriod(radiant, Date.parse(now))).toEqual({
start: Date.parse("2026-09-30T03:00:00Z"),
end: Date.parse("2026-11-11T10:30:00Z"),
});
// Past the recorded end, drawn to the estimate — a picture, not a reset.
expect(routinePeriod(radiant, Date.parse("2026-11-20T00:00:00Z")).end).toBe(
Date.parse("2026-12-23T10:30:00Z"),
);
});
+7
View File
@@ -88,3 +88,10 @@ test("the create shelf asks every mockup question and previews the result", () =
expect(html).toContain(label);
expect(html).toContain("Still needed: a name.");
});
test("a built-in that resets with the version says when, from the version table", () => {
// Spec 2026-10-22: the drawer's period line is the version caption.
const html = drawer("library", migrateLegacy({}, ["wuwa"], at, () => "account:one"));
expect(html).toContain("Oscillated Coral: Radiant Tide");
expect(html).toContain("Resets with 3.8 · 11 Nov");
});
+14
View File
@@ -118,3 +118,17 @@ test("a finished routine group is marked complete but does not celebrate on arri
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);
});
+9
View File
@@ -102,6 +102,15 @@ describe("ROUTINES", () => {
["Pioneer Podcast weekly EXP", week, { kind: "amount", target: 12000, unit: "EXP" }],
["Weekly Activities", week, { kind: "amount", target: 6000, unit: "points" }],
["Tower of Adversity", { unit: "cycle", days: 28, from: "2026-11-09" }, { kind: "days", required: 1 }],
["Oscillated Coral: Radiant Tide", { unit: "version" }, { kind: "amount", target: 7, unit: "Radiant Tide" }],
["Oscillated Coral: Forging Tide", { unit: "version" }, { kind: "amount", target: 7, unit: "Forging Tide" }],
["Oscillated Coral: Lustrous Tide", { unit: "version" }, { kind: "amount", target: 7, unit: "Lustrous Tide" }],
]);
// Limited character and weapon pulls; the standard line only on request.
expect(routinesFor("wuwa").filter((r) => r.pulls !== undefined).map((r) => [r.id, r.pulls])).toEqual([
["routine:wuwa:shop-radiant-tide", "premium"],
["routine:wuwa:shop-forging-tide", "premium"],
["routine:wuwa:shop-lustrous-tide", "standard"],
]);
expect(shape("nte")).toEqual([
["Season Pass weekly EXP", week, { kind: "amount", target: 12000, unit: "EXP" }],
+15
View File
@@ -1442,6 +1442,21 @@ describe("weekly and monthly tasks in the dailies strip", () => {
expect(longerHeading(new Set(["cycle"]))).toBe("This cycle");
});
test("a version task sits with the cycles and has no period to catch up on", () => {
// Spec 2026-10-22: there is no "Last version" editor.
const shop: StripTask = {
id: "routine:wuwa:shop-radiant-tide", game: "wuwa", title: "Oscillated Coral: Radiant Tide",
period: { unit: "version" }, measure: { kind: "amount", target: 7, unit: "Radiant Tide" },
};
const [group] = dailyGroups(["wuwa"], [], NOW, "europe", meta, startOf, false, undefined,
[shop], () => cycleStatus);
const item = group!.items.find((i) => i.key === shop.id)!;
expect(item.cadence).toBe("cycle");
expect(item.period).toBeUndefined();
expect(tasksShown([shop], { weekly: false, monthly: false, cycle: true })).toEqual([shop]);
expect(tasksShown([shop], { weekly: true, monthly: true, cycle: false })).toEqual([]);
});
test("cycle tasks sit in the lower row, are tallied apart and switched apart", () => {
const [group] = dailyGroups(["zzz"], [], NOW, "europe", meta, startOf, true, undefined,
[points, shiyu], anyStatus);