Update 3D game 2026-07-10 21:20
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { SaveRepository, type StorageAdapter } from "./saveRepository";
|
||||
|
||||
function memoryStorage(): StorageAdapter {
|
||||
const data = new Map<string, string>();
|
||||
return {
|
||||
getItem: (key) => data.get(key) ?? null,
|
||||
setItem: (key, value) => { data.set(key, value); },
|
||||
};
|
||||
}
|
||||
|
||||
describe("SaveRepository", () => {
|
||||
it("creates exactly three offline-first slot views with timestamps", () => {
|
||||
const repository = new SaveRepository(memoryStorage(), () => "2026-07-10T12:00:00.000Z");
|
||||
repository.create(2, "Seraphine");
|
||||
|
||||
const slots = repository.list(null);
|
||||
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");
|
||||
expect(slots[1].online).toBeNull();
|
||||
});
|
||||
|
||||
it("copies a local save into another slot without sharing nested state", () => {
|
||||
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.copyLocal(1, 3);
|
||||
repository.updateLocal(3, (save) => ({
|
||||
...save,
|
||||
healers: { ...save.healers, druid: { ...save.healers.druid, level: 99 } },
|
||||
}));
|
||||
|
||||
const slots = repository.list(null);
|
||||
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", () => {
|
||||
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(12);
|
||||
|
||||
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(12);
|
||||
expect(repository.list("healer@example.com")[0].local?.updatedAt).toBe(now);
|
||||
});
|
||||
|
||||
it("deletes only the local copy so the online record can restore it", () => {
|
||||
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];
|
||||
expect(slot.local).toBeNull();
|
||||
expect(slot.online).not.toBeNull();
|
||||
});
|
||||
|
||||
it("keeps class progression and inventories independent under one hunter name", () => {
|
||||
const repository = new SaveRepository(memoryStorage(), () => "2026-07-10T12:00:00.000Z");
|
||||
repository.create(1, "Aelia");
|
||||
repository.updateLocal(1, (save) => ({
|
||||
...save,
|
||||
activeClassId: "druid",
|
||||
healers: {
|
||||
...save.healers,
|
||||
druid: { level: 8, inventory: [...save.healers.druid.inventory, { ...save.healers.druid.inventory[0], id: "druid-drop" }] },
|
||||
},
|
||||
}));
|
||||
|
||||
const save = repository.list(null)[0].local!;
|
||||
expect(save.hunterName).toBe("Aelia");
|
||||
expect(save.activeClassId).toBe("druid");
|
||||
expect(save.healers.druid.level).toBe(8);
|
||||
expect(save.healers.priest.level).toBe(12);
|
||||
expect(save.healers.druid.inventory).toHaveLength(4);
|
||||
expect(save.healers.priest.inventory).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("migrates schema v1 saves into Priest progress without losing the hunter name", () => {
|
||||
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;
|
||||
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({ 1: legacy }));
|
||||
|
||||
const migrated = repository.list(null)[0].local!;
|
||||
expect(migrated.schemaVersion).toBe(2);
|
||||
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);
|
||||
});
|
||||
|
||||
it("adds newly shipped bosses to existing schema v2 collection logs", () => {
|
||||
const storage = memoryStorage();
|
||||
const repository = new SaveRepository(storage, () => "2026-07-10T12:00:00.000Z");
|
||||
const created = repository.create(1, "Veteran");
|
||||
storage.setItem("i-want-to-heal:saves:local:v1", JSON.stringify({
|
||||
1: { ...created, collections: created.collections.filter((boss) => boss.bossId !== "vexa") },
|
||||
}));
|
||||
|
||||
const migrated = repository.list(null)[0].local!;
|
||||
expect(migrated.collections.some((boss) => boss.bossId === "vexa")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user