104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
PROFILE_STORAGE_KEYS,
|
|
assignRomSecondaryClass,
|
|
createCharacter,
|
|
createOfflineSession,
|
|
listCharacters,
|
|
swapRomClasses,
|
|
updateCharacterActionBindings,
|
|
updateCharacterProgression,
|
|
type StorageLike,
|
|
} from "./accountRepository";
|
|
|
|
class MemoryStorage implements StorageLike {
|
|
private readonly values = new Map<string, string>();
|
|
getItem(key: string) { return this.values.get(key) ?? null; }
|
|
setItem(key: string, value: string) { this.values.set(key, value); }
|
|
removeItem(key: string) { this.values.delete(key); }
|
|
}
|
|
|
|
const appearance = { skinColor: 0, face: 0, hairStyle: 0, hairColor: 0, feature: 0 };
|
|
|
|
describe("RoM dual-class persistence", () => {
|
|
it("migrates v1 profiles to v2 without losing bindings or progression", () => {
|
|
const storage = new MemoryStorage();
|
|
storage.setItem(PROFILE_STORAGE_KEYS.database, JSON.stringify({
|
|
version: 1,
|
|
accounts: [],
|
|
rosters: {
|
|
"offline-roster": [{
|
|
id: "legacy",
|
|
ownerId: "offline-roster",
|
|
name: "Legacy",
|
|
raceId: "human",
|
|
classId: "priest",
|
|
gender: "female",
|
|
appearance,
|
|
level: 42,
|
|
experience: 777,
|
|
talentRanks: { legacy: 3 },
|
|
actionBindings: { "primary:face-bottom": "priest-flash-heal" },
|
|
inventory: [],
|
|
equipment: {},
|
|
location: "Dungeon Entrance",
|
|
createdAt: 1,
|
|
lastPlayedAt: 2,
|
|
}],
|
|
},
|
|
}));
|
|
expect(listCharacters("offline-roster", storage)[0]).toMatchObject({
|
|
categoryId: "wow",
|
|
secondaryClassId: null,
|
|
level: 42,
|
|
experience: 777,
|
|
talentRanks: { legacy: 3 },
|
|
actionBindings: { "primary:face-bottom": "priest-flash-heal" },
|
|
actionLoadouts: {
|
|
"priest:none": { "primary:face-bottom": "priest-flash-heal" },
|
|
},
|
|
});
|
|
expect(JSON.parse(storage.getItem(PROFILE_STORAGE_KEYS.database)!).version).toBe(7);
|
|
});
|
|
|
|
it("locks selection to level ten and preserves separate bars through atomic swaps", () => {
|
|
const storage = new MemoryStorage();
|
|
const ownerId = createOfflineSession().ownerId;
|
|
const created = createCharacter({
|
|
ownerId,
|
|
name: "Runesworn",
|
|
categoryId: "rom",
|
|
raceId: "rom-human",
|
|
classId: "rom-warrior",
|
|
gender: "male",
|
|
appearance,
|
|
}, storage).character!;
|
|
expect(assignRomSecondaryClass(ownerId, created.id, "rom-scout", storage)).toMatchObject({ ok: false });
|
|
updateCharacterProgression(ownerId, created.id, { level: 10, experience: 81, talentRanks: {} }, storage);
|
|
expect(assignRomSecondaryClass(ownerId, created.id, "rom-warden", storage)).toMatchObject({ ok: false });
|
|
expect(assignRomSecondaryClass(ownerId, created.id, "rom-scout", storage)).toMatchObject({
|
|
ok: true,
|
|
character: { classId: "rom-warrior", secondaryClassId: "rom-scout" },
|
|
});
|
|
updateCharacterActionBindings(ownerId, created.id, { "primary:face-bottom": "warrior-scout-a" }, storage);
|
|
expect(swapRomClasses(ownerId, created.id, storage)).toMatchObject({
|
|
ok: true,
|
|
character: { classId: "rom-scout", secondaryClassId: "rom-warrior", actionBindings: {} },
|
|
});
|
|
updateCharacterActionBindings(ownerId, created.id, { "primary:face-bottom": "scout-warrior-b" }, storage);
|
|
expect(swapRomClasses(ownerId, created.id, storage).character).toMatchObject({
|
|
classId: "rom-warrior",
|
|
secondaryClassId: "rom-scout",
|
|
level: 10,
|
|
experience: 81,
|
|
actionBindings: { "primary:face-bottom": "warrior-scout-a" },
|
|
});
|
|
expect(assignRomSecondaryClass(ownerId, created.id, "rom-priest", storage)).toMatchObject({ ok: false });
|
|
const saved = listCharacters(ownerId, storage)[0]!;
|
|
expect(saved.actionLoadouts).toMatchObject({
|
|
"rom-warrior:rom-scout": { "primary:face-bottom": "warrior-scout-a" },
|
|
"rom-scout:rom-warrior": { "primary:face-bottom": "scout-warrior-b" },
|
|
});
|
|
});
|
|
});
|