feat: add hunt page, combat placeholder, and Jagd navigation

Wires up Slice 0.2 end-to-end: HuntPageComponent renders the
hunting-unavailable/ready/loading/encounters-found states off
HuntingStore and WorldStore, Angreifen hands the HuntEncounter id to a
new inert CombatPlaceholderPageComponent via /combat/new, the Jagd nav
entry is enabled with router-driven active state (matching Karte's),
and the context panel now lists possible encounters for hunting-enabled
locations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-19 14:06:57 +02:00
parent 784bd3ce3a
commit 4e684dc45e
11 changed files with 664 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
import { signal, WritableSignal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
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';
@@ -19,7 +19,10 @@ describe('App', () => {
await TestBed.configureTestingModule({
imports: [AppShellComponent],
providers: [
provideRouter([]),
provideRouter([
{ path: 'world', children: [] },
{ path: 'hunt', children: [] },
]),
{
provide: WorldStore,
useValue: { character, currentLocation, selectedConnection },
@@ -28,9 +31,12 @@ describe('App', () => {
}).compileComponents();
});
it('renders the reusable game shell with only Karte available', () => {
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();
@@ -44,7 +50,13 @@ describe('App', () => {
expect(mapButton?.getAttribute('aria-current')).toBe('page');
expect(mapButton?.getAttribute('aria-label')).toBe('Karte');
for (const destination of ['hunt', 'quests', 'inventory', 'character']) {
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');
for (const destination of ['quests', 'inventory', 'character']) {
expect(
element.querySelector<HTMLButtonElement>(`[data-navigation="${destination}"]`)?.disabled,
).toBe(true);
@@ -53,6 +65,21 @@ describe('App', () => {
expect(element.textContent).not.toContain('Shop');
});
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',