feat(web): add the quest journal
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
108
apps/web/src/app/features/quests/quest.store.spec.ts
Normal file
108
apps/web/src/app/features/quests/quest.store.spec.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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> = {}): 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<typeof vi.fn>): 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user