feat(web): add the quest journal
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
139
apps/web/src/app/features/quests/quest-page.component.spec.ts
Normal file
139
apps/web/src/app/features/quests/quest-page.component.spec.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { vi } from 'vitest';
|
||||
import type { QuestView } from '../../core/api/game-api.models';
|
||||
import { QuestPageComponent } from './quest-page.component';
|
||||
import { QuestStore } from './quest.store';
|
||||
|
||||
function quest(over: Partial<QuestView> = {}): QuestView {
|
||||
return {
|
||||
key: 'trouble-beyond-the-gate',
|
||||
title: 'Trouble Beyond the Gate',
|
||||
description: 'The warden wants five Ashen 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,
|
||||
};
|
||||
}
|
||||
|
||||
async function setup(quests: QuestView[], error: string | null = null) {
|
||||
const questsSignal = signal(quests);
|
||||
const store = {
|
||||
quests: questsSignal.asReadonly(),
|
||||
activeQuests: signal(quests.filter((entry) => entry.status === 'ACTIVE')),
|
||||
completedQuests: signal(
|
||||
quests.filter((entry) => entry.status === 'COMPLETED'),
|
||||
),
|
||||
availableQuests: signal(
|
||||
quests.filter((entry) => entry.status === 'AVAILABLE'),
|
||||
),
|
||||
loading: signal(false),
|
||||
error: signal(error),
|
||||
load: vi.fn().mockResolvedValue(undefined),
|
||||
setQuest: vi.fn(),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [QuestPageComponent],
|
||||
providers: [{ provide: QuestStore, useValue: store }],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(QuestPageComponent);
|
||||
fixture.detectChanges();
|
||||
return { fixture, store, text: () => fixture.nativeElement.textContent ?? '' };
|
||||
}
|
||||
|
||||
describe('QuestPageComponent', () => {
|
||||
it('loads the quest log on init', async () => {
|
||||
const { store } = await setup([quest()]);
|
||||
|
||||
expect(store.load).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('renders the active quest with its current objective', async () => {
|
||||
const { fixture, text } = await setup([quest()]);
|
||||
|
||||
expect(text()).toContain('Trouble Beyond the Gate');
|
||||
expect(text()).toContain('The warden wants five Ashen Pelts.');
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('[data-objective-description]')
|
||||
.textContent,
|
||||
).toContain('Collect Ashen Pelts');
|
||||
});
|
||||
|
||||
it('renders collect progress as current over required', async () => {
|
||||
const { fixture } = await setup([quest()]);
|
||||
|
||||
// The block slice §12 prints verbatim.
|
||||
expect(
|
||||
fixture.nativeElement
|
||||
.querySelector('[data-objective-progress]')
|
||||
.textContent.replace(/\s+/g, ' ')
|
||||
.trim(),
|
||||
).toBe('1 / 5');
|
||||
});
|
||||
|
||||
it('renders the blocked hint under the objective', async () => {
|
||||
const { fixture } = await setup([
|
||||
quest({
|
||||
hint: 'You cannot carry enough pelts. Return to the South Gate Warden.',
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('[data-objective-hint]').textContent,
|
||||
).toContain('You cannot carry enough pelts.');
|
||||
});
|
||||
|
||||
it('shows no hint when the step is merely unfinished', async () => {
|
||||
const { fixture } = await setup([quest()]);
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('[data-objective-hint]'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('shows an empty state when nothing is taken on', async () => {
|
||||
const { text } = await setup([]);
|
||||
|
||||
expect(text()).toContain('You have taken nothing on.');
|
||||
});
|
||||
|
||||
it('lists completed quests separately from active ones', async () => {
|
||||
const { fixture, text } = await setup([
|
||||
quest({
|
||||
key: 'done-quest',
|
||||
title: 'An Older Errand',
|
||||
status: 'COMPLETED',
|
||||
currentObjectiveKey: null,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(text()).toContain('An Older Errand');
|
||||
expect(
|
||||
fixture.nativeElement.querySelector('[data-quest-completed]'),
|
||||
).not.toBeNull();
|
||||
// The active panel is still empty, so the two do not blur together.
|
||||
expect(text()).toContain('You have taken nothing on.');
|
||||
});
|
||||
|
||||
it('offers a retry when the log could not be read', async () => {
|
||||
const { fixture, store } = await setup([], 'Could not load your quests.');
|
||||
|
||||
fixture.nativeElement.querySelector('[data-quest-retry]').click();
|
||||
|
||||
expect(store.load).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user