Release v0.1.4 2026-07-12

This commit is contained in:
Warren H
2026-07-12 13:49:46 -04:00
parent b48b3a4f8f
commit bef71d391a
803 changed files with 3800 additions and 358322 deletions
+60 -19
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { SaveRepository, type StorageAdapter } from "./saveRepository";
import { buildCollections, DEFAULT_COLLECTIONS } from "./data";
import { groupDrop } from "../game/progression/loot";
function memoryStorage(): StorageAdapter {
const data = new Map<string, string>();
@@ -91,39 +91,80 @@ describe("SaveRepository", () => {
expect(save.healers.priest.inventory).toHaveLength(4);
});
it("migrates schema v1 saves into Priest progress without losing the hunter name", () => {
it("resets every legacy save into fresh v5 progression while preserving identity and timestamp", () => {
const storage = memoryStorage();
const repository = new SaveRepository(storage, () => "2026-07-10T12:00:00.000Z");
const created = repository.create(1, "Legacy");
const legacy = { ...created, schemaVersion: 1, level: 27 } as Record<string, unknown>;
delete legacy.activeClassId;
delete legacy.healers;
const legacy = {
...created,
schemaVersion: 4,
activeClassId: "druid",
playSeconds: 999,
healers: Object.fromEntries(Object.entries(created.healers).map(([id, healer]) => [id, { ...healer, level: 27 }])),
stats: { totalBossKills: 22, flawlessClears: 9, alliesSaved: 4, healingDone: 1200, bossKills: { bulldrome: 22 } },
materials: [{ id: "legacy-boss-coin", name: "Legacy coin", quantity: 99, rarity: "common", itemLevel: 1, glyph: "R" }],
collectionLog: { dropsFound: { "legacy-boss-coin": 99 }, petsFound: { "bulldrome-pet": 1 } },
gearProgress: Object.fromEntries(Object.entries(created.gearProgress).map(([id, owner]) => [id, {
...owner,
slots: Object.fromEntries(Object.entries(owner.slots).map(([slotId]) => [slotId, { level: 10 }])),
infusionAbilityId: "priest-sanctuary",
}])),
} as Record<string, unknown>;
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: legacy }));
const migrated = repository.list(null)[0].local!;
expect(migrated.schemaVersion).toBe(4);
expect(migrated.schemaVersion).toBe(5);
expect(migrated.hunterName).toBe("Legacy");
expect(migrated.activeClassId).toBe("priest");
expect(migrated.healers.priest.level).toBe(27);
expect(migrated.healers.druid.level).toBe(1);
expect(migrated.healers.shaman.inventory.length).toBeGreaterThan(0);
expect(migrated.playSeconds).toBe(0);
expect(Object.values(migrated.healers).every((healer) => healer.level === 1 && healer.inventory.length > 0)).toBe(true);
expect(migrated.stats).toEqual({ totalBossKills: 0, flawlessClears: 0, alliesSaved: 0, healingDone: 0, bossKills: {} });
expect(migrated.materials).toEqual([]);
expect(migrated.collectionLog).toEqual({ dropsFound: {}, petsFound: {} });
expect(Object.values(migrated.gearProgress).every((owner) => owner.infusionAbilityId === null && owner.passiveInfusionId === null && Object.values(owner.slots).every((slot) => slot.level === 0))).toBe(true);
expect(JSON.parse(storage.getItem("i-want-to-heal:saves:local:v1") ?? "{}")["1"].schemaVersion).toBe(5);
});
it("derives newly shipped bosses from drop tables after migrating schema v2 collections", () => {
it("resets and persists legacy cloud saves when they are listed", () => {
const storage = memoryStorage();
const repository = new SaveRepository(storage, () => "2026-07-10T12:00:00.000Z");
const created = repository.create(1, "Cloud Legacy");
const legacy = {
...created,
schemaVersion: 4,
stats: { ...created.stats, totalBossKills: 8, bossKills: { bulldrome: 8 } },
materials: [{ id: "legacy-boss-coin", name: "Legacy coin", quantity: 8, rarity: "common", itemLevel: 1, glyph: "R" }],
};
const cloudKey = "i-want-to-heal:saves:cloud:v1:cloud@example.com";
storage.setItem(cloudKey, JSON.stringify({ 1: legacy }));
const online = repository.list("cloud@example.com")[0].online!;
expect(online.schemaVersion).toBe(5);
expect(online.stats.totalBossKills).toBe(0);
expect(online.materials).toEqual([]);
expect(JSON.parse(storage.getItem(cloudKey) ?? "{}")["1"].schemaVersion).toBe(5);
});
it("preserves valid v5 progression and group-drop inventory", () => {
const storage = memoryStorage();
const repository = new SaveRepository(storage, () => "2026-07-10T12:00:00.000Z");
const created = repository.create(1, "Veteran");
const legacy = { ...created, schemaVersion: 2, collections: DEFAULT_COLLECTIONS.filter((boss) => boss.bossId !== "vexa") } as Record<string, unknown>;
delete legacy.collectionLog;
delete legacy.materials;
delete legacy.gearProgress;
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: legacy }));
const drop = groupDrop("charge", "veteran");
created.healers.priest.level = 8;
created.stats = { ...created.stats, totalBossKills: 2, bossKills: { bulldrome: 2 } };
created.materials = [{ id: drop.id, name: drop.name, quantity: 4, rarity: drop.rarity, itemLevel: drop.itemLevel, glyph: drop.glyph }];
created.collectionLog = { dropsFound: { [drop.id]: 4 }, petsFound: { "bulldrome-pet": 1 } };
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: created }));
const migrated = repository.list(null)[0].local!;
expect(buildCollections(migrated.collectionLog, migrated.stats.bossKills).some((boss) => boss.bossId === "vexa")).toBe(true);
expect(migrated.schemaVersion).toBe(5);
expect(migrated.healers.priest.level).toBe(8);
expect(migrated.stats.bossKills).toEqual({ bulldrome: 2 });
expect(migrated.materials[0]).toMatchObject({ id: drop.id, quantity: 4 });
expect(migrated.collectionLog).toEqual(created.collectionLog);
});
it("migrates valid infusion choices and discards stale ids", () => {
it("normalizes valid v5 infusion choices and discards stale ids", () => {
const storage = memoryStorage();
const repository = new SaveRepository(storage, () => "2026-07-10T12:00:00.000Z");
const created = repository.create(1, "Infused");
@@ -131,10 +172,10 @@ describe("SaveRepository", () => {
created.gearProgress.priest.passiveInfusionId = "restoring-grace";
created.gearProgress.brann.infusionAbilityId = "removed-infusion";
created.gearProgress.brann.passiveInfusionId = "deep-wells";
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: { ...created, schemaVersion: 3 } }));
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: created }));
const migrated = repository.list(null)[0].local!;
expect(migrated.schemaVersion).toBe(4);
expect(migrated.schemaVersion).toBe(5);
expect(migrated.gearProgress.priest.infusionAbilityId).toBe("priest-sanctuary");
expect(migrated.gearProgress.priest.passiveInfusionId).toBe("restoring-grace");
expect(migrated.gearProgress.brann.infusionAbilityId).toBeNull();