Files
ashen-realms/apps/web/src/app/features/world/location-poi/location-poi.component.spec.ts
Bastian Wagner 3a84fd5371 feat(web): show quest markers on location hotspots
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:28:55 +02:00

166 lines
4.9 KiB
TypeScript

import { ComponentFixture, TestBed } from '@angular/core/testing';
import type {
LocationPointOfInterest,
NpcMarker,
} from '../../../core/api/game-api.models';
import { LocationPoiComponent } from './location-poi.component';
const wagon: LocationPointOfInterest = {
key: 'search-abandoned-wagon',
title: 'Abandoned Wagon',
actionLabel: 'Search',
type: 'SEARCH',
iconKey: 'search',
xPercent: 80,
yPercent: 68,
enabled: true,
};
async function setup(
poi: LocationPointOfInterest = wagon,
busy = false,
markers: NpcMarker[] = [],
): Promise<{
fixture: ComponentFixture<LocationPoiComponent>;
activated: LocationPointOfInterest[];
button: HTMLButtonElement;
}> {
await TestBed.configureTestingModule({
imports: [LocationPoiComponent],
}).compileComponents();
const fixture = TestBed.createComponent(LocationPoiComponent);
fixture.componentRef.setInput('poi', poi);
fixture.componentRef.setInput('busy', busy);
fixture.componentRef.setInput('markers', markers);
const activated: LocationPointOfInterest[] = [];
fixture.componentInstance.activate.subscribe((value) => activated.push(value));
fixture.detectChanges();
return {
fixture,
activated,
button: fixture.nativeElement.querySelector('button') as HTMLButtonElement,
};
}
describe('LocationPoiComponent', () => {
it('anchors itself with the percentage coordinates so it scales with the artwork', async () => {
const { fixture } = await setup();
const host = fixture.nativeElement as HTMLElement;
expect(host.style.left).toBe('80%');
expect(host.style.top).toBe('68%');
});
it('renders title and action label', async () => {
const { fixture } = await setup();
const text = (fixture.nativeElement as HTMLElement).textContent ?? '';
expect(text).toContain('Abandoned Wagon');
expect(text).toContain('Search');
});
it('emits the whole hotspot when clicked', async () => {
const { button, activated } = await setup();
button.click();
expect(activated).toEqual([wagon]);
});
it('is reachable and activatable from the keyboard', async () => {
const { button, activated } = await setup();
// A native button gives Enter and Space for free; assert it stayed a
// button rather than becoming a click-only div.
expect(button.tagName).toBe('BUTTON');
expect(button.tabIndex).toBe(0);
button.focus();
expect(document.activeElement).toBe(button);
button.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
button.click();
expect(activated).toHaveLength(1);
});
it('does nothing when the hotspot is disabled', async () => {
const { button, activated } = await setup({ ...wagon, enabled: false });
expect(button.disabled).toBe(true);
button.click();
expect(activated).toEqual([]);
});
it('does nothing while an interaction is already running', async () => {
const { button, activated } = await setup(wagon, true);
button.click();
expect(activated).toEqual([]);
});
it('describes itself with both title and action for screen readers', async () => {
const { button } = await setup();
expect(button.getAttribute('aria-label')).toBe('Abandoned Wagon: Search');
});
it('falls back to the title alone when a hotspot has no action label', async () => {
const { button } = await setup({
...wagon,
actionLabel: undefined,
});
expect(button.getAttribute('aria-label')).toBe('Abandoned Wagon');
});
it('renders no badge without markers', async () => {
const { fixture } = await setup();
expect(fixture.nativeElement.querySelector('[data-poi-badge]')).toBeNull();
});
it('renders a badge for a quest that can be started here', async () => {
const { fixture, button } = await setup(wagon, false, ['QUEST_AVAILABLE']);
expect(
fixture.nativeElement.querySelector('[data-poi-badge]').textContent.trim(),
).toBe('!');
expect(button.getAttribute('aria-label')).toBe(
'Abandoned Wagon: Search, quest available',
);
});
it('renders a badge when the current step waits here', async () => {
const { fixture, button } = await setup(wagon, false, ['QUEST_TURN_IN']);
expect(
fixture.nativeElement.querySelector('[data-poi-badge]').textContent.trim(),
).toBe('?');
expect(button.getAttribute('aria-label')).toContain('quest step ready');
});
it('ignores markers that are not quest markers', async () => {
const { fixture } = await setup(wagon, false, ['MERCHANT', 'EXCHANGE']);
expect(fixture.nativeElement.querySelector('[data-poi-badge]')).toBeNull();
});
it('renders one badge at most, preferring the waiting step', async () => {
const { fixture } = await setup(wagon, false, [
'MERCHANT',
'QUEST_AVAILABLE',
'QUEST_TURN_IN',
]);
const badges = fixture.nativeElement.querySelectorAll('[data-poi-badge]');
expect(badges).toHaveLength(1);
expect(badges[0].textContent.trim()).toBe('?');
});
});