Release v0.1.6 2026-07-12
This commit is contained in:
@@ -16,7 +16,7 @@ describe("SaveRepository", () => {
|
||||
const repository = new SaveRepository(memoryStorage(), () => "2026-07-10T12:00:00.000Z");
|
||||
repository.create(2, "Seraphine");
|
||||
|
||||
const slots = repository.list(null);
|
||||
const slots = repository.listLocal();
|
||||
expect(slots.map((slot) => slot.id)).toEqual([1, 2, 3]);
|
||||
expect(slots[1].local?.updatedAt).toBe("2026-07-10T12:00:00.000Z");
|
||||
expect(slots[1].local?.hunterName).toBe("Seraphine");
|
||||
@@ -34,41 +34,30 @@ describe("SaveRepository", () => {
|
||||
healers: { ...save.healers, druid: { ...save.healers.druid, level: 99 } },
|
||||
}));
|
||||
|
||||
const slots = repository.list(null);
|
||||
const slots = repository.listLocal();
|
||||
expect(slots[0].local?.healers.druid.level).toBe(1);
|
||||
expect(slots[2].local?.healers.druid.level).toBe(99);
|
||||
expect(slots[2].local?.slotId).toBe(3);
|
||||
});
|
||||
|
||||
it("uploads local state and can later overwrite it with the online version", () => {
|
||||
it("replaces a local slot with a downloaded server snapshot", () => {
|
||||
let now = "2026-07-10T12:00:00.000Z";
|
||||
const repository = new SaveRepository(memoryStorage(), () => now);
|
||||
repository.create(1, "Aelia");
|
||||
now = "2026-07-10T13:00:00.000Z";
|
||||
repository.upload(1, "healer@example.com");
|
||||
repository.updateLocal(1, (save) => ({
|
||||
...save,
|
||||
healers: { ...save.healers, priest: { ...save.healers.priest, level: 40 } },
|
||||
}));
|
||||
|
||||
expect(repository.list("healer@example.com")[0].local?.healers.priest.level).toBe(40);
|
||||
expect(repository.list("healer@example.com")[0].online?.healers.priest.level).toBe(1);
|
||||
|
||||
const serverSave = repository.create(1, "Aelia");
|
||||
serverSave.healers.priest.level = 40;
|
||||
now = "2026-07-10T14:00:00.000Z";
|
||||
repository.download(1, "healer@example.com");
|
||||
expect(repository.list("healer@example.com")[0].local?.healers.priest.level).toBe(1);
|
||||
expect(repository.list("healer@example.com")[0].local?.updatedAt).toBe(now);
|
||||
repository.replaceLocal(serverSave);
|
||||
expect(repository.listLocal()[0].local?.healers.priest.level).toBe(40);
|
||||
expect(repository.listLocal()[0].local?.updatedAt).toBe(now);
|
||||
});
|
||||
|
||||
it("deletes only the local copy so the online record can restore it", () => {
|
||||
it("deletes the local copy without inventing an online record", () => {
|
||||
const repository = new SaveRepository(memoryStorage(), () => "2026-07-10T12:00:00.000Z");
|
||||
repository.create(1, "Aelia");
|
||||
repository.upload(1, "healer");
|
||||
repository.deleteLocal(1);
|
||||
|
||||
const slot = repository.list("healer")[0];
|
||||
const slot = repository.listLocal()[0];
|
||||
expect(slot.local).toBeNull();
|
||||
expect(slot.online).not.toBeNull();
|
||||
expect(slot.online).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps class progression and inventories independent under one hunter name", () => {
|
||||
@@ -83,7 +72,7 @@ describe("SaveRepository", () => {
|
||||
},
|
||||
}));
|
||||
|
||||
const save = repository.list(null)[0].local!;
|
||||
const save = repository.listLocal()[0].local!;
|
||||
expect(save.hunterName).toBe("Aelia");
|
||||
expect(save.activeClassId).toBe("druid");
|
||||
expect(save.healers.druid.level).toBe(8);
|
||||
@@ -102,7 +91,7 @@ describe("SaveRepository", () => {
|
||||
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 } },
|
||||
stats: { totalBossKills: 22, flawlessClears: 9, alliesSaved: 4, healingDone: 1200, bossKills: { bulldrome: 22 }, highestRoguelikeRound: 12 },
|
||||
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, {
|
||||
@@ -113,39 +102,19 @@ describe("SaveRepository", () => {
|
||||
} as Record<string, unknown>;
|
||||
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: legacy }));
|
||||
|
||||
const migrated = repository.list(null)[0].local!;
|
||||
const migrated = repository.listLocal()[0].local!;
|
||||
expect(migrated.schemaVersion).toBe(5);
|
||||
expect(migrated.hunterName).toBe("Legacy");
|
||||
expect(migrated.activeClassId).toBe("priest");
|
||||
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.stats).toEqual({ totalBossKills: 0, flawlessClears: 0, alliesSaved: 0, healingDone: 0, bossKills: {}, highestRoguelikeRound: 0 });
|
||||
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("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");
|
||||
@@ -157,7 +126,7 @@ describe("SaveRepository", () => {
|
||||
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!;
|
||||
const migrated = repository.listLocal()[0].local!;
|
||||
expect(migrated.schemaVersion).toBe(5);
|
||||
expect(migrated.healers.priest.level).toBe(8);
|
||||
expect(migrated.stats.bossKills).toEqual({ bulldrome: 2 });
|
||||
@@ -176,7 +145,7 @@ describe("SaveRepository", () => {
|
||||
created.gearProgress.brann.passiveInfusionId = "deep-wells" as never;
|
||||
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: created }));
|
||||
|
||||
const migrated = repository.list(null)[0].local!;
|
||||
const migrated = repository.listLocal()[0].local!;
|
||||
expect(migrated.schemaVersion).toBe(5);
|
||||
expect(migrated.gearProgress.priest.infusionAbilityId).toBe("priest-sanctuary");
|
||||
expect(migrated.gearProgress.priest.passiveInfusionId).toBeNull();
|
||||
@@ -192,7 +161,7 @@ describe("SaveRepository", () => {
|
||||
const created = repository.create(1, "Infused");
|
||||
created.gearProgress.priest.passiveInfusionId = passiveId;
|
||||
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: created }));
|
||||
expect(repository.list(null)[0].local?.gearProgress.priest.passiveInfusionId).toBe(passiveId);
|
||||
expect(repository.listLocal()[0].local?.gearProgress.priest.passiveInfusionId).toBe(passiveId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user