feat(web): announce a newly discovered route
This commit is contained in:
@@ -89,6 +89,13 @@ export interface LocationInteractionResult {
|
|||||||
title: string;
|
title: string;
|
||||||
text: string;
|
text: string;
|
||||||
img?: string;
|
img?: string;
|
||||||
|
/**
|
||||||
|
* Set only the first time an interaction reveals a route. Optional on the
|
||||||
|
* client although the API always sends it: every existing test fixture
|
||||||
|
* builds this object literally, and a required field would break them all
|
||||||
|
* for no gain.
|
||||||
|
*/
|
||||||
|
discoveredLocation?: { key: string; name: string } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrentLocationResponse {
|
export interface CurrentLocationResponse {
|
||||||
|
|||||||
@@ -122,6 +122,38 @@ describe('LocalLocationStore', () => {
|
|||||||
expect(store.interactionResult()).toBeNull();
|
expect(store.interactionResult()).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reloads the location when the interaction reveals a route', async () => {
|
||||||
|
const load = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const store = setup(
|
||||||
|
{
|
||||||
|
runLocationInteraction: vi.fn().mockReturnValue(
|
||||||
|
of({ ...trackResult, discoveredLocation: { key: 'ash-pit', name: 'Ash Pit' } }),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ load },
|
||||||
|
);
|
||||||
|
|
||||||
|
await store.runInteraction('inspect-tracks');
|
||||||
|
|
||||||
|
expect(load).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not reload the location when nothing new was revealed', async () => {
|
||||||
|
const load = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const store = setup(
|
||||||
|
{
|
||||||
|
runLocationInteraction: vi.fn().mockReturnValue(
|
||||||
|
of({ ...trackResult, discoveredLocation: null }),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ load },
|
||||||
|
);
|
||||||
|
|
||||||
|
await store.runInteraction('inspect-tracks');
|
||||||
|
|
||||||
|
expect(load).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('resolves the hotspot an action mirrors', () => {
|
it('resolves the hotspot an action mirrors', () => {
|
||||||
const store = setup();
|
const store = setup();
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,14 @@ export class LocalLocationStore {
|
|||||||
this.interactionErrorState.set(null);
|
this.interactionErrorState.set(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.interactionResultState.set(
|
const result = await firstValueFrom(this.api.runLocationInteraction(interactionKey));
|
||||||
await firstValueFrom(this.api.runLocationInteraction(interactionKey)),
|
this.interactionResultState.set(result);
|
||||||
);
|
|
||||||
|
if (result.discoveredLocation) {
|
||||||
|
// The connection list is server-filtered, so a fresh reveal only
|
||||||
|
// shows up after the location is re-read.
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.interactionErrorState.set(this.toErrorMessage(error));
|
this.interactionErrorState.set(this.toErrorMessage(error));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
<div class="interaction-panel__copy">
|
<div class="interaction-panel__copy">
|
||||||
<h2 class="interaction-panel__title">{{ result.title }}</h2>
|
<h2 class="interaction-panel__title">{{ result.title }}</h2>
|
||||||
<p class="interaction-panel__text">{{ result.text }}</p>
|
<p class="interaction-panel__text">{{ result.text }}</p>
|
||||||
|
@if (result.discoveredLocation; as discovered) {
|
||||||
|
<p class="interaction-panel__discovery" data-discovered-location role="status">
|
||||||
|
New route discovered: {{ discovered.name }}.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -63,6 +63,14 @@
|
|||||||
color: var(--ar-danger);
|
color: var(--ar-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.interaction-panel__discovery {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ar-success);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
.interaction-panel__close {
|
.interaction-panel__close {
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
padding: var(--ar-space-2) var(--ar-space-5);
|
padding: var(--ar-space-2) var(--ar-space-5);
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { LocationInteractionPanelComponent } from './location-interaction-panel.component';
|
import { LocationInteractionPanelComponent } from './location-interaction-panel.component';
|
||||||
|
|
||||||
async function setup(inputs: {
|
async function setup(inputs: {
|
||||||
result?: { interactionKey: string; title: string; text: string; img?: string } | null;
|
result?:
|
||||||
|
| {
|
||||||
|
interactionKey: string;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
img?: string;
|
||||||
|
discoveredLocation?: { key: string; name: string } | null;
|
||||||
|
}
|
||||||
|
| null;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
fixture: ComponentFixture<LocationInteractionPanelComponent>;
|
fixture: ComponentFixture<LocationInteractionPanelComponent>;
|
||||||
@@ -110,4 +118,31 @@ describe('LocationInteractionPanelComponent', () => {
|
|||||||
|
|
||||||
expect(element.querySelector('[data-interaction-panel]')).toBeNull();
|
expect(element.querySelector('[data-interaction-panel]')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('says so when the interaction revealed a route', async () => {
|
||||||
|
const { element } = await setup({
|
||||||
|
result: {
|
||||||
|
interactionKey: 'inspect-watchpost',
|
||||||
|
title: 'The Watchpost',
|
||||||
|
text: 'Fresh tracks lead east.',
|
||||||
|
discoveredLocation: { key: 'ash-pit', name: 'Ash Pit' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const banner = element.querySelector('[data-discovered-location]');
|
||||||
|
expect(banner?.textContent).toContain('Ash Pit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stays quiet when nothing new was revealed', async () => {
|
||||||
|
const { element } = await setup({
|
||||||
|
result: {
|
||||||
|
interactionKey: 'search-guard-quarters',
|
||||||
|
title: 'Guard Quarters',
|
||||||
|
text: 'Nothing but ash.',
|
||||||
|
discoveredLocation: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(element.querySelector('[data-discovered-location]')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user