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; let currentLocation: WritableSignal; let selectedConnection: WritableSignal; beforeEach(async () => { character = signal(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('[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('[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( '[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(`[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('[data-navigation]'); expect([...entries].map((entry) => entry.dataset['navigation'])).toEqual([ 'location', 'world', 'hunt', 'quests', 'inventory', 'character', ]); const locationButton = element.querySelector( '[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('[data-navigation="world"]'); const huntButton = element.querySelector('[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']), ); }); });