import { HttpErrorResponse } from '@angular/common/http'; import { TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; import { vi } from 'vitest'; import type { QuestView } from '../../core/api/game-api.models'; import { GameApiService } from '../../core/api/game-api.service'; import { QuestStore } from './quest.store'; function quest(over: Partial = {}): QuestView { return { key: 'trouble-beyond-the-gate', title: 'Trouble Beyond the Gate', description: 'Five pelts.', status: 'ACTIVE', objectives: [ { key: 'collect-pelts-first', description: 'Collect Ashen Pelts', type: 'COLLECT_ITEM', targetKey: 'ash-pelt', required: 5, current: 1, completed: false, }, ], currentObjectiveKey: 'collect-pelts-first', hint: null, ...over, }; } function createStore(getQuests: ReturnType): QuestStore { TestBed.configureTestingModule({ providers: [{ provide: GameApiService, useValue: { getQuests } }], }); return TestBed.inject(QuestStore); } describe('QuestStore', () => { it('loads the quest log', async () => { const store = createStore(vi.fn().mockReturnValue(of([quest()]))); await store.load(); expect(store.quests()).toHaveLength(1); expect(store.error()).toBeNull(); expect(store.loading()).toBe(false); }); it('splits the log by status', async () => { const store = createStore( vi.fn().mockReturnValue( of([ quest({ key: 'active-quest', status: 'ACTIVE' }), quest({ key: 'done-quest', status: 'COMPLETED' }), quest({ key: 'offered-quest', status: 'AVAILABLE' }), ]), ), ); await store.load(); expect(store.activeQuests().map((entry) => entry.key)).toEqual([ 'active-quest', ]); expect(store.completedQuests().map((entry) => entry.key)).toEqual([ 'done-quest', ]); expect(store.availableQuests().map((entry) => entry.key)).toEqual([ 'offered-quest', ]); }); it('keeps the previous log when a reload fails', async () => { const getQuests = vi.fn().mockReturnValue(of([quest()])); const store = createStore(getQuests); await store.load(); getQuests.mockReturnValue( throwError(() => new HttpErrorResponse({ status: 500 })), ); await store.load(); // A stale journal beats a blank one; the next load resyncs. expect(store.quests()).toHaveLength(1); expect(store.error()).toBe('Could not load your quests.'); }); it('replaces a quest in place when the NPC screen pushes an update', async () => { const store = createStore(vi.fn().mockReturnValue(of([quest()]))); await store.load(); store.setQuest(quest({ status: 'COMPLETED', currentObjectiveKey: null })); expect(store.quests()).toHaveLength(1); expect(store.completedQuests()).toHaveLength(1); }); it('adds a quest the log had never seen', async () => { // Accepting a quest at an NPC before ever opening the journal. const store = createStore(vi.fn().mockReturnValue(of([]))); await store.load(); store.setQuest(quest()); expect(store.activeQuests()).toHaveLength(1); }); });