Merge branch 'worktree-slice-0.4-first-loot'
This commit is contained in:
@@ -4,6 +4,7 @@ import { ActivatedRoute, convertToParamMap, Router, provideRouter } from '@angul
|
||||
import { vi } from 'vitest';
|
||||
import type { Combat } from '../../../core/api/game-api.models';
|
||||
import { CombatStore } from '../combat.store';
|
||||
import { WorldStore } from '../../world/world.store';
|
||||
import { CombatPageComponent } from './combat-page.component';
|
||||
|
||||
const activeCombat: Combat = {
|
||||
@@ -23,6 +24,7 @@ const activeCombat: Combat = {
|
||||
{ round: 1, sequence: 1, type: 'DAMAGE', source: 'PLAYER', target: 'MONSTER', amount: 14 },
|
||||
{ round: 1, sequence: 2, type: 'DAMAGE', source: 'MONSTER', target: 'PLAYER', amount: 5 },
|
||||
],
|
||||
rewards: null,
|
||||
};
|
||||
|
||||
const monsterHitLine = 'Aschenratte trifft Aric Duskwalker für 5 Schaden.';
|
||||
@@ -40,6 +42,7 @@ describe('CombatPageComponent', () => {
|
||||
loadCombat: ReturnType<typeof vi.fn>;
|
||||
attack: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let worldStore: { refreshCharacter: ReturnType<typeof vi.fn> };
|
||||
let router: Router;
|
||||
|
||||
async function setup(combat: Combat | null) {
|
||||
@@ -51,12 +54,14 @@ describe('CombatPageComponent', () => {
|
||||
loadCombat: vi.fn(() => Promise.resolve()),
|
||||
attack: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
worldStore = { refreshCharacter: vi.fn(() => Promise.resolve()) };
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CombatPageComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{ provide: CombatStore, useValue: combatStore },
|
||||
{ provide: WorldStore, useValue: worldStore },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: convertToParamMap({ combatId: 'combat-1' }) } },
|
||||
@@ -209,6 +214,31 @@ describe('CombatPageComponent', () => {
|
||||
expect(monster?.classList.contains('sprite--lunge')).toBe(false);
|
||||
});
|
||||
|
||||
it('refreshes the character from the server once a combat is won', async () => {
|
||||
const fixture = await setup(activeCombat);
|
||||
combatStore.attack.mockImplementation(async () => {
|
||||
combatStore.combat.set({
|
||||
...activeCombat,
|
||||
status: 'WON',
|
||||
monster: { ...activeCombat.monster, currentHp: 0 },
|
||||
rewards: { experience: 8, silver: 6, items: [] },
|
||||
events: [
|
||||
...activeCombat.events,
|
||||
{ round: 2, sequence: 3, type: 'DAMAGE', source: 'PLAYER', target: 'MONSTER', amount: 31 },
|
||||
{ round: 2, sequence: 4, type: 'COMBAT_WON', source: 'PLAYER', target: 'MONSTER' },
|
||||
],
|
||||
});
|
||||
});
|
||||
vi.useFakeTimers();
|
||||
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
element.querySelector<HTMLButtonElement>('[data-combat-attack]')?.click();
|
||||
await vi.advanceTimersByTimeAsync(540);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(worldStore.refreshCharacter).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('disables Angriff while an action is pending', async () => {
|
||||
const fixture = await setup(activeCombat);
|
||||
combatStore.actionPending.set(true);
|
||||
@@ -258,4 +288,85 @@ describe('CombatPageComponent', () => {
|
||||
|
||||
expect(combatStore.loadCombat).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
const wonWithRewards: Combat = {
|
||||
...activeCombat,
|
||||
status: 'WON',
|
||||
monster: { ...activeCombat.monster, currentHp: 0 },
|
||||
rewards: { experience: 8, silver: 6, items: [] },
|
||||
};
|
||||
|
||||
it('shows the granted XP and silver on the victory screen', async () => {
|
||||
const fixture = await setup(wonWithRewards);
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.querySelector('[data-combat-rewards]')).toBeTruthy();
|
||||
expect(element.querySelector('[data-reward-experience]')?.textContent).toContain('8');
|
||||
expect(element.querySelector('[data-reward-silver]')?.textContent).toContain('6');
|
||||
});
|
||||
|
||||
it('renders a dropped item with its icon, name, and rarity', async () => {
|
||||
const fixture = await setup({
|
||||
...wonWithRewards,
|
||||
monster: { ...activeCombat.monster, key: 'road-bandit', name: 'Straßenräuber', currentHp: 0 },
|
||||
rewards: {
|
||||
experience: 16,
|
||||
silver: 12,
|
||||
items: [
|
||||
{
|
||||
characterItemId: 'character-item-1',
|
||||
item: {
|
||||
key: 'bandit-blade',
|
||||
name: 'Räuberklinge',
|
||||
rarity: 'COMMON',
|
||||
iconPath: '/images/items/bandit-blade.png',
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.querySelector<HTMLImageElement>('[data-item-icon]')?.getAttribute('src')).toBe(
|
||||
'/images/items/bandit-blade.png',
|
||||
);
|
||||
expect(element.querySelector('[data-item-name]')?.textContent).toContain('Räuberklinge');
|
||||
expect(element.querySelector('[data-item-rarity]')?.textContent).toContain('Gewöhnlich');
|
||||
expect(element.querySelector('[data-reward-empty]')).toBeNull();
|
||||
});
|
||||
|
||||
it('treats a victory without loot as complete, not as a failure', async () => {
|
||||
const fixture = await setup(wonWithRewards);
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.querySelector('[data-reward-empty]')?.textContent).toContain(
|
||||
'Keine besondere Beute gefunden.',
|
||||
);
|
||||
expect(element.querySelector('[role="alert"]')).toBeNull();
|
||||
// XP and silver still carry the screen.
|
||||
expect(element.querySelector('[data-reward-experience]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the persisted rewards of an already-won combat loaded from the server', async () => {
|
||||
// Simulates a browser refresh: the page loads the combat by id and shows
|
||||
// exactly what the server persisted, without rerolling anything.
|
||||
const fixture = await setup({
|
||||
...wonWithRewards,
|
||||
rewards: { experience: 16, silver: 12, items: [] },
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(combatStore.loadCombat).toHaveBeenCalledWith('combat-1');
|
||||
expect(element.querySelector('[data-reward-experience]')?.textContent).toContain('16');
|
||||
expect(element.querySelector('[data-reward-silver]')?.textContent).toContain('12');
|
||||
});
|
||||
|
||||
it('still shows a plain victory when the server reports no reward record', async () => {
|
||||
const fixture = await setup({ ...wonWithRewards, rewards: null });
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.querySelector('[data-combat-result="WON"]')).toBeTruthy();
|
||||
expect(element.querySelector('[data-combat-rewards]')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user