Translates the five client-side error-message maps (world, hunting, combat, inventory, local-location stores) that re-translate already-English API error codes back to display text, per design doc §4. Also fixes four component specs that hard-coded the same German strings when asserting rendered error text (combat-page, hunt-page, location-interaction-panel, location-page).
140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
import { HttpErrorResponse } from '@angular/common/http';
|
|
import { TestBed } from '@angular/core/testing';
|
|
import { Subject, of, throwError } from 'rxjs';
|
|
import { vi } from 'vitest';
|
|
import { GameApiService } from '../../core/api/game-api.service';
|
|
import { LocalLocationStore } from './local-location.store';
|
|
import { WorldStore } from './world.store';
|
|
|
|
const trackResult = {
|
|
interactionKey: 'inspect-tracks',
|
|
title: 'Verdächtige Spuren',
|
|
text: 'Frische Stiefelabdrücke führen nach Osten.',
|
|
};
|
|
|
|
function setup(api: Partial<GameApiService> = {}, world: Partial<WorldStore> = {}) {
|
|
TestBed.configureTestingModule({
|
|
providers: [
|
|
LocalLocationStore,
|
|
{ provide: GameApiService, useValue: api },
|
|
{ provide: WorldStore, useValue: { load: vi.fn(), ...world } },
|
|
],
|
|
});
|
|
|
|
return TestBed.inject(LocalLocationStore);
|
|
}
|
|
|
|
describe('LocalLocationStore', () => {
|
|
it('loads the location through the world store rather than a second fetch path', async () => {
|
|
const load = vi.fn().mockResolvedValue(undefined);
|
|
const store = setup({}, { load });
|
|
|
|
await store.load();
|
|
|
|
expect(load).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('opens the panel with the result the server returned', async () => {
|
|
const store = setup({
|
|
runLocationInteraction: vi.fn().mockReturnValue(of(trackResult)),
|
|
});
|
|
|
|
await store.runInteraction('inspect-tracks');
|
|
|
|
expect(store.interactionResult()).toEqual(trackResult);
|
|
expect(store.interactionError()).toBeNull();
|
|
expect(store.interactionOpen()).toBe(true);
|
|
});
|
|
|
|
it('marks only the running interaction as pending', async () => {
|
|
const pending = new Subject<typeof trackResult>();
|
|
const store = setup({
|
|
runLocationInteraction: vi.fn().mockReturnValue(pending),
|
|
});
|
|
|
|
const running = store.runInteraction('inspect-tracks');
|
|
expect(store.interactionPending()).toBe('inspect-tracks');
|
|
|
|
pending.next(trackResult);
|
|
pending.complete();
|
|
await running;
|
|
|
|
expect(store.interactionPending()).toBeNull();
|
|
});
|
|
|
|
it('ignores a second interaction while one is still running', async () => {
|
|
const pending = new Subject<typeof trackResult>();
|
|
const runLocationInteraction = vi.fn().mockReturnValue(pending);
|
|
const store = setup({ runLocationInteraction });
|
|
|
|
const running = store.runInteraction('inspect-tracks');
|
|
await store.runInteraction('search-abandoned-wagon');
|
|
|
|
expect(runLocationInteraction).toHaveBeenCalledTimes(1);
|
|
expect(runLocationInteraction).toHaveBeenCalledWith('inspect-tracks');
|
|
|
|
pending.next(trackResult);
|
|
pending.complete();
|
|
await running;
|
|
});
|
|
|
|
it('translates a rejected interaction into a readable message without navigating', async () => {
|
|
const store = setup({
|
|
runLocationInteraction: vi.fn().mockReturnValue(
|
|
throwError(
|
|
() =>
|
|
new HttpErrorResponse({
|
|
status: 400,
|
|
error: { code: 'LOCATION_INTERACTION_UNAVAILABLE' },
|
|
}),
|
|
),
|
|
),
|
|
});
|
|
|
|
await store.runInteraction('inspect-tracks');
|
|
|
|
expect(store.interactionError()).toBe("There's nothing to discover here.");
|
|
expect(store.interactionResult()).toBeNull();
|
|
expect(store.interactionOpen()).toBe(true);
|
|
});
|
|
|
|
it('falls back to a generic message for an unmapped failure', async () => {
|
|
const store = setup({
|
|
runLocationInteraction: vi
|
|
.fn()
|
|
.mockReturnValue(throwError(() => new HttpErrorResponse({ status: 500 }))),
|
|
});
|
|
|
|
await store.runInteraction('inspect-tracks');
|
|
|
|
expect(store.interactionError()).toBe("This action isn't possible right now.");
|
|
});
|
|
|
|
it('closes the panel without clearing the location', async () => {
|
|
const store = setup({
|
|
runLocationInteraction: vi.fn().mockReturnValue(of(trackResult)),
|
|
});
|
|
|
|
await store.runInteraction('inspect-tracks');
|
|
store.closeInteraction();
|
|
|
|
expect(store.interactionOpen()).toBe(false);
|
|
expect(store.interactionResult()).toBeNull();
|
|
});
|
|
|
|
it('resolves the hotspot an action mirrors', () => {
|
|
const store = setup();
|
|
|
|
expect(
|
|
store.interactionKeyOf({
|
|
key: 'investigate-tracks',
|
|
label: 'Spuren untersuchen',
|
|
type: 'INVESTIGATE',
|
|
iconKey: 'investigate',
|
|
enabled: true,
|
|
poiKey: 'inspect-tracks',
|
|
}),
|
|
).toBe('inspect-tracks');
|
|
});
|
|
});
|