fix: address final-review findings (TopBar hydration, snapshot test, armor doc, inventory refresh test)
Fixes 4 Important findings from the final whole-branch review: - /inventory never called WorldStore.load(), leaving the TopBar stuck on "loading" and characterLevel() silently defaulting to 1 for any character above level 1. Mirrors the same guard already used in HuntPageComponent. - The combat/equipment snapshot-immutability integration test asserted only status/round, never the actual playerState snapshot the whole test claims to prove is untouched after a post-fight equip. - Documented (comment only, no behavior change) that the demo character's armor dropping from the old hardcoded 6 to 0 is an intentional, spec-sanctioned tradeoff (Slice 0.5 spec Section19), not a bug. - inventory.store.spec.ts's equip test used an identical inventory fixture before and after equip(), so a regression dropping the post-equip inventory re-fetch would still have passed. Now asserts the refetched fixture is actually reflected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,7 @@ interface SetupOptions {
|
||||
inventoryData?: InventoryResponse;
|
||||
selectedItemId?: string | null;
|
||||
selectedItem?: InventoryItem | null;
|
||||
character?: CharacterResponse | null;
|
||||
}
|
||||
|
||||
async function setup(options: SetupOptions = {}) {
|
||||
@@ -91,7 +92,10 @@ async function setup(options: SetupOptions = {}) {
|
||||
selectedItem: vi.fn(() => options.selectedItem ?? null),
|
||||
equip: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
const worldStore = { character: signal(character) };
|
||||
const worldStore = {
|
||||
character: signal(options.character === undefined ? character : options.character),
|
||||
load: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [InventoryPageComponent],
|
||||
@@ -103,7 +107,7 @@ async function setup(options: SetupOptions = {}) {
|
||||
|
||||
const fixture = TestBed.createComponent(InventoryPageComponent);
|
||||
fixture.detectChanges();
|
||||
return { fixture, inventoryStore };
|
||||
return { fixture, inventoryStore, worldStore };
|
||||
}
|
||||
|
||||
describe('InventoryPageComponent', () => {
|
||||
@@ -112,6 +116,16 @@ describe('InventoryPageComponent', () => {
|
||||
expect(inventoryStore.load).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('loads the world state on init when no character has been loaded yet (direct navigation/hard refresh)', async () => {
|
||||
const { worldStore } = await setup({ character: null });
|
||||
expect(worldStore.load).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('does not call world load again when a character is already present', async () => {
|
||||
const { worldStore } = await setup();
|
||||
expect(worldStore.load).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders one tile per owned item', async () => {
|
||||
const { fixture } = await setup();
|
||||
const tiles = (fixture.nativeElement as HTMLElement).querySelectorAll('.inventory-page__slot');
|
||||
|
||||
@@ -52,6 +52,9 @@ export class InventoryPageComponent implements OnInit {
|
||||
});
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.worldStore.character() === null) {
|
||||
void this.worldStore.load();
|
||||
}
|
||||
void this.inventoryStore.load();
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,13 @@ const equippedAfter: EquipmentResponse = {
|
||||
stats: { maxHp: 100, attack: 7, weaponDamage: 11, armor: 0 },
|
||||
};
|
||||
|
||||
const inventoryAfterEquip: InventoryResponse = {
|
||||
items: [
|
||||
{ ...inventory.items[0], equipped: false },
|
||||
{ ...inventory.items[1], equipped: true },
|
||||
],
|
||||
};
|
||||
|
||||
describe('InventoryStore', () => {
|
||||
let api: {
|
||||
getInventory: ReturnType<typeof vi.fn>;
|
||||
@@ -109,12 +116,19 @@ describe('InventoryStore', () => {
|
||||
});
|
||||
|
||||
it('equips the selected item, refreshes inventory/equipment, and refreshes the character HUD', async () => {
|
||||
api.getInventory
|
||||
.mockReturnValueOnce(of(inventory)) // initial load()
|
||||
.mockReturnValueOnce(of(inventoryAfterEquip)); // post-equip refetch
|
||||
|
||||
await store.load();
|
||||
|
||||
await store.equip('item-blade');
|
||||
|
||||
expect(api.equipItem).toHaveBeenCalledWith('item-blade');
|
||||
expect(store.equipment()).toEqual(equippedAfter);
|
||||
expect(store.inventory()).toEqual(inventoryAfterEquip);
|
||||
expect(store.inventory()?.items.find((item) => item.id === 'item-sword')?.equipped).toBe(false);
|
||||
expect(store.inventory()?.items.find((item) => item.id === 'item-blade')?.equipped).toBe(true);
|
||||
expect(worldStore.refreshCharacter).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user