feat(web): announce a newly discovered route
This commit is contained in:
@@ -89,6 +89,13 @@ export interface LocationInteractionResult {
|
||||
title: string;
|
||||
text: 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 {
|
||||
|
||||
@@ -122,6 +122,38 @@ describe('LocalLocationStore', () => {
|
||||
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', () => {
|
||||
const store = setup();
|
||||
|
||||
|
||||
@@ -74,9 +74,14 @@ export class LocalLocationStore {
|
||||
this.interactionErrorState.set(null);
|
||||
|
||||
try {
|
||||
this.interactionResultState.set(
|
||||
await firstValueFrom(this.api.runLocationInteraction(interactionKey)),
|
||||
);
|
||||
const result = 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) {
|
||||
this.interactionErrorState.set(this.toErrorMessage(error));
|
||||
} finally {
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
<div class="interaction-panel__copy">
|
||||
<h2 class="interaction-panel__title">{{ result.title }}</h2>
|
||||
<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>
|
||||
} @else {
|
||||
|
||||
@@ -63,6 +63,14 @@
|
||||
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 {
|
||||
justify-self: end;
|
||||
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';
|
||||
|
||||
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;
|
||||
}): Promise<{
|
||||
fixture: ComponentFixture<LocationInteractionPanelComponent>;
|
||||
@@ -110,4 +118,31 @@ describe('LocationInteractionPanelComponent', () => {
|
||||
|
||||
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