Update 3D game 2026-07-10 21:20

This commit is contained in:
Warren H
2026-07-10 21:20:17 -04:00
parent 141ec64963
commit e0be0458aa
720 changed files with 366857 additions and 0 deletions
+122
View File
@@ -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);
});
});