feat(web): show quest markers on location hotspots

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-22 23:28:55 +02:00
parent ff8b7c8b78
commit 3a84fd5371
9 changed files with 253 additions and 6 deletions

View File

@@ -5,6 +5,7 @@ import { vi } from 'vitest';
import type {
CurrentLocationResponse,
LocationInteractionResult,
NpcMarker,
} from '../../../core/api/game-api.models';
import { burnedRoadFixture, southGateFixture } from '../current-location.fixture';
import { LocalLocationStore } from '../local-location.store';
@@ -26,9 +27,16 @@ describe('LocationPageComponent', () => {
load: ReturnType<typeof vi.fn>;
runInteraction: ReturnType<typeof vi.fn>;
closeInteraction: ReturnType<typeof vi.fn>;
markersFor: ReturnType<typeof vi.fn>;
};
/** Markers by NPC key, as the server would have decided them. */
let markers: Record<string, NpcMarker[]>;
async function setup(current: CurrentLocationResponse | null = burnedRoadFixture()) {
async function setup(
current: CurrentLocationResponse | null = burnedRoadFixture(),
npcMarkers: Record<string, NpcMarker[]> = {},
) {
markers = npcMarkers;
location = signal(current);
error = signal<string | null>(null);
interactionResult = signal<LocationInteractionResult | null>(null);
@@ -44,6 +52,7 @@ describe('LocationPageComponent', () => {
load: vi.fn().mockResolvedValue(undefined),
runInteraction: vi.fn().mockResolvedValue(undefined),
closeInteraction: vi.fn(),
markersFor: vi.fn((npcKey?: string) => markers[npcKey ?? ''] ?? []),
};
await TestBed.configureTestingModule({
@@ -238,20 +247,43 @@ describe('LocationPageComponent', () => {
expect(element.querySelectorAll('app-location-poi')).toHaveLength(1);
expect(element.querySelectorAll('[data-action]')).toHaveLength(1);
});
it('badges the hotspot of an NPC with something to ask', async () => {
const { element } = await setup(southGateFixture(southGateContent()), {
'south-gate-warden': ['QUEST_AVAILABLE'],
});
expect(
element.querySelector('[data-poi-badge]')?.textContent?.trim(),
).toBe('!');
});
it('leaves a hotspot that names no NPC unbadged', async () => {
const { element } = await setup(
southGateFixture(southGateContent({ withNpcKey: false })),
{ 'south-gate-warden': ['QUEST_AVAILABLE'] },
);
expect(element.querySelector('[data-poi-badge]')).toBeNull();
expect(store.markersFor).toHaveBeenCalledWith(undefined);
});
});
function southGateContent(): Partial<CurrentLocationResponse> {
function southGateContent({
withNpcKey = true,
}: { withNpcKey?: boolean } = {}): Partial<CurrentLocationResponse> {
return {
pointsOfInterest: [
{
key: 'gate-watch',
title: 'Gate Watch',
title: 'Halvik, Warden of the South Gate',
actionLabel: 'Talk',
type: 'NPC',
iconKey: 'speak',
xPercent: 45,
yPercent: 52,
enabled: true,
...(withNpcKey ? { npcKey: 'south-gate-warden' } : {}),
},
],
primaryActions: [