166 lines
4.9 KiB
TypeScript
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('?');
|
|
});
|
|
});
|