- Add hpRegenPerSecond and hpRegenSince fields to CharacterResponse - Update inventory-page.component.spec.ts fixture with new fields - Add CHARACTER_TOO_WOUNDED error mapping to combat.store - Add test for CHARACTER_TOO_WOUNDED error mapping - Update app.spec.ts and world.store.spec.ts fixtures for compilation Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
193 lines
7.4 KiB
TypeScript
193 lines
7.4 KiB
TypeScript
import { signal, WritableSignal } from '@angular/core';
|
|
import { TestBed } from '@angular/core/testing';
|
|
import { Router, provideRouter } from '@angular/router';
|
|
import { CharacterResponse } from './core/api/game-api.models';
|
|
import { WorldStore } from './features/world/world.store';
|
|
import { AppShellComponent } from './layout/app-shell/app-shell.component';
|
|
import { routes } from './app.routes';
|
|
|
|
describe('App', () => {
|
|
let character: WritableSignal<CharacterResponse | null>;
|
|
let currentLocation: WritableSignal<null>;
|
|
let selectedConnection: WritableSignal<null>;
|
|
|
|
beforeEach(async () => {
|
|
character = signal<CharacterResponse | null>(null);
|
|
currentLocation = signal(null);
|
|
selectedConnection = signal(null);
|
|
|
|
await TestBed.configureTestingModule({
|
|
imports: [AppShellComponent],
|
|
providers: [
|
|
provideRouter([
|
|
{ path: 'location', children: [] },
|
|
{ path: 'world', children: [] },
|
|
{ path: 'hunt', children: [] },
|
|
{ path: 'inventory', children: [] },
|
|
]),
|
|
{
|
|
provide: WorldStore,
|
|
useValue: { character, currentLocation, selectedConnection },
|
|
},
|
|
],
|
|
}).compileComponents();
|
|
});
|
|
|
|
it('renders the reusable game shell with Karte and Jagd available', async () => {
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
const router = TestBed.inject(Router);
|
|
await router.navigateByUrl('/world');
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
|
|
const element = fixture.nativeElement as HTMLElement;
|
|
expect(element.querySelector('app-top-bar')).not.toBeNull();
|
|
expect(element.querySelector('app-side-navigation')).not.toBeNull();
|
|
expect(element.querySelector('main')).not.toBeNull();
|
|
expect(element.querySelector('app-game-footer')).not.toBeNull();
|
|
|
|
const mapButton = element.querySelector<HTMLButtonElement>('[data-navigation="world"]');
|
|
expect(mapButton).not.toBeNull();
|
|
expect(mapButton?.disabled).toBe(false);
|
|
expect(mapButton?.getAttribute('aria-current')).toBe('page');
|
|
expect(mapButton?.getAttribute('aria-label')).toBe('Karte');
|
|
|
|
const huntButton = element.querySelector<HTMLButtonElement>('[data-navigation="hunt"]');
|
|
expect(huntButton).not.toBeNull();
|
|
expect(huntButton?.disabled).toBe(false);
|
|
expect(huntButton?.getAttribute('aria-current')).toBeNull();
|
|
expect(huntButton?.getAttribute('aria-label')).toBe('Jagd');
|
|
|
|
const inventoryButton = element.querySelector<HTMLButtonElement>(
|
|
'[data-navigation="inventory"]',
|
|
);
|
|
expect(inventoryButton).not.toBeNull();
|
|
expect(inventoryButton?.disabled).toBe(false);
|
|
expect(inventoryButton?.getAttribute('aria-label')).toBe('Inventar');
|
|
|
|
for (const destination of ['quests', 'character']) {
|
|
expect(
|
|
element.querySelector<HTMLButtonElement>(`[data-navigation="${destination}"]`)?.disabled,
|
|
).toBe(true);
|
|
}
|
|
|
|
expect(element.textContent).not.toContain('Shop');
|
|
});
|
|
|
|
it('offers Ort as the first navigation entry, marked active on /location', async () => {
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
const router = TestBed.inject(Router);
|
|
await router.navigateByUrl('/location');
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
|
|
const element = fixture.nativeElement as HTMLElement;
|
|
const entries = element.querySelectorAll<HTMLButtonElement>('[data-navigation]');
|
|
expect([...entries].map((entry) => entry.dataset['navigation'])).toEqual([
|
|
'location',
|
|
'world',
|
|
'hunt',
|
|
'quests',
|
|
'inventory',
|
|
'character',
|
|
]);
|
|
|
|
const locationButton = element.querySelector<HTMLButtonElement>(
|
|
'[data-navigation="location"]',
|
|
);
|
|
expect(locationButton?.disabled).toBe(false);
|
|
expect(locationButton?.getAttribute('aria-label')).toBe('Ort');
|
|
expect(locationButton?.getAttribute('aria-current')).toBe('page');
|
|
expect(
|
|
element.querySelector('[data-navigation="world"]')?.getAttribute('aria-current'),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('drops the shell context rail on /location, where the screen brings its own', async () => {
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
const router = TestBed.inject(Router);
|
|
await router.navigateByUrl('/location');
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
|
|
expect(fixture.nativeElement.querySelector('app-context-panel')).toBeNull();
|
|
});
|
|
|
|
it('drops the shell context rail on /inventory, which needs all three of its own columns', async () => {
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
const router = TestBed.inject(Router);
|
|
await router.navigateByUrl('/inventory');
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
|
|
expect(fixture.nativeElement.querySelector('app-context-panel')).toBeNull();
|
|
});
|
|
|
|
it('keeps the shell context rail on the map', async () => {
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
const router = TestBed.inject(Router);
|
|
await router.navigateByUrl('/world');
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
|
|
expect(fixture.nativeElement.querySelector('app-context-panel')).not.toBeNull();
|
|
});
|
|
|
|
it('marks Jagd as the active navigation entry while on /hunt', async () => {
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
const router = TestBed.inject(Router);
|
|
await router.navigateByUrl('/hunt');
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
|
|
const element = fixture.nativeElement as HTMLElement;
|
|
const mapButton = element.querySelector<HTMLButtonElement>('[data-navigation="world"]');
|
|
const huntButton = element.querySelector<HTMLButtonElement>('[data-navigation="hunt"]');
|
|
|
|
expect(huntButton?.getAttribute('aria-current')).toBe('page');
|
|
expect(mapButton?.getAttribute('aria-current')).toBeNull();
|
|
});
|
|
|
|
it('renders loaded character values supplied by the WorldStore', () => {
|
|
character.set({
|
|
id: 'character-id',
|
|
name: 'Mara Ashfall',
|
|
level: 7,
|
|
experience: 320,
|
|
silver: 150,
|
|
currentHp: 52,
|
|
maxHp: 80,
|
|
attack: 12,
|
|
hpRegenPerSecond: 1,
|
|
hpRegenSince: null,
|
|
currentLocation: { id: 'location-id', key: 'south-gate', name: 'Südtor von Graufurt' },
|
|
});
|
|
const fixture = TestBed.createComponent(AppShellComponent);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain(
|
|
'Mara Ashfall',
|
|
);
|
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('Stufe 7');
|
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('52 / 80');
|
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('150');
|
|
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('320');
|
|
});
|
|
|
|
it('lands root and unknown routes on the place the character is standing in', () => {
|
|
expect(routes.find((route) => route.path === '')).toMatchObject({
|
|
pathMatch: 'full',
|
|
redirectTo: 'location',
|
|
});
|
|
expect(routes.find((route) => route.path === '**')).toMatchObject({ redirectTo: 'location' });
|
|
});
|
|
|
|
it('keeps the map and hunt routes where they were', () => {
|
|
const shellChildren = routes.find((route) => route.children)?.children ?? [];
|
|
|
|
expect(shellChildren.map((route) => route.path)).toEqual(
|
|
expect.arrayContaining(['location', 'world', 'hunt']),
|
|
);
|
|
});
|
|
});
|