mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(ui): render the location tray's pills, mini status and search error from a store
LocationControls (./ui/location) no longer builds the city and landmark pills or writes the mini status and the search error: it presents the tray and hands it to its publish option, so the entry stays Vue-free. The shell publishes it to a new gev-location store and connects the controls, and the dock renders it and calls chooseCity() and choosePoi(). The landmark row still expands a frame after its pills render. The search field, its toggle and the reset buttons keep their listeners. The location QA harness reads the rendered elements.
This commit is contained in:
1 parent
4966877859
commit
5f8f9bb921
14 files changed
+333
-141
No files matched your search
@@ -7,6 +7,8 @@ import { createPinia } from 'pinia';
|
||||
import { createApp, h, nextTick } from 'vue';
|
||||
import { setLocale } from '../i18n/index.js';
|
||||
import { useDisplayStore } from './stores/display.js';
|
||||
import { useLocationStore } from './stores/location.js';
|
||||
import { LocationControls } from './locationControls.js';
|
||||
import CommandDockView from './views/CommandDockView.vue';
|
||||
import SceneChromeView from './views/SceneChromeView.vue';
|
||||
import { VisualSettings } from './visualSettings.js';
|
||||
@@ -152,3 +154,101 @@ test('the map source row renders the published chips and selects through the con
|
||||
assert.equal($('#map-stack-status').classList.contains('warn'), false);
|
||||
assert.equal(document.querySelectorAll('.map-stack-chip').length, 0);
|
||||
});
|
||||
|
||||
test('the location tray renders what LocationControls publishes', async (t) => {
|
||||
const { $, pinia } = fixture(t);
|
||||
assert.equal($('#location-mini-city').textContent, '📍 Location: --');
|
||||
assert.equal($('#location-mini-poi').textContent, 'Landmark: --');
|
||||
const store = useLocationStore(pinia);
|
||||
const calls = [];
|
||||
const frames = [];
|
||||
const controls = new LocationControls({
|
||||
elements: {
|
||||
search: $('#location-search'),
|
||||
searchToggle: $('#search-toggle'),
|
||||
resetButtons: [],
|
||||
},
|
||||
cities: {
|
||||
paris: { name: 'Paris', pois: [{ name: 'Louvre' }, { name: 'Eiffel' }] },
|
||||
rome: { name: 'Rome', pois: [{ name: 'Colosseum' }] },
|
||||
},
|
||||
getExpandedCity: () => null,
|
||||
onCity: (id) => calls.push(['city', id]),
|
||||
onPoi: (id, index) => calls.push(['poi', id, index]),
|
||||
onSearch() {},
|
||||
onReset() {},
|
||||
publish: (view) => store.publish(view),
|
||||
requestFrame: (fn) => frames.push(fn),
|
||||
cancelFrame() {},
|
||||
});
|
||||
t.after(() => controls.destroy());
|
||||
store.connect(controls);
|
||||
await nextTick();
|
||||
const pills = [
|
||||
...document.querySelectorAll('#location-pills .location-pill'),
|
||||
];
|
||||
assert.deepEqual(
|
||||
pills.map((pill) => `${pill.dataset.locationId}:${pill.textContent}`),
|
||||
['paris:Paris', 'rome:Rome'],
|
||||
);
|
||||
pills[1].click();
|
||||
assert.deepEqual(calls, [['city', 'rome']]);
|
||||
pills[1].classList.add('voice-highlight');
|
||||
controls.highlightCity('rome');
|
||||
controls.showPois('paris');
|
||||
await nextTick();
|
||||
assert.equal(pills[1].classList.contains('active'), true);
|
||||
assert.equal(pills[1].classList.contains('voice-highlight'), true);
|
||||
const row = $('#poi-row');
|
||||
const pois = [...row.querySelectorAll('.poi-pill')];
|
||||
assert.deepEqual(
|
||||
pois.map((poi) => poi.textContent),
|
||||
['QLouvre', 'WEiffel'],
|
||||
);
|
||||
assert.equal(row.classList.contains('expanded'), false);
|
||||
frames.shift()();
|
||||
await nextTick();
|
||||
assert.equal(row.classList.contains('expanded'), true);
|
||||
assert.equal($('#location-bar-divider').classList.contains('visible'), true);
|
||||
controls.highlightPoi(1);
|
||||
await nextTick();
|
||||
assert.equal(pois[1].classList.contains('active'), true);
|
||||
pois[1].click();
|
||||
assert.deepEqual(calls.at(-1), ['poi', 'paris', 1]);
|
||||
// Switching cities while the row is open swaps its pills and keeps it open.
|
||||
controls.showPois('rome');
|
||||
await nextTick();
|
||||
assert.equal(row.classList.contains('expanded'), true);
|
||||
const romePois = [...row.querySelectorAll('.poi-pill')];
|
||||
assert.deepEqual(
|
||||
romePois.map((poi) => poi.textContent),
|
||||
['QColosseum'],
|
||||
);
|
||||
assert.equal(romePois[0].classList.contains('active'), false);
|
||||
romePois[0].click();
|
||||
assert.deepEqual(calls.at(-1), ['poi', 'rome', 0]);
|
||||
frames.shift()();
|
||||
controls.renderStatus({
|
||||
city: { name: 'Paris' },
|
||||
currentPoi: { name: 'Eiffel' },
|
||||
});
|
||||
controls.setSearchError('No match');
|
||||
await nextTick();
|
||||
assert.match($('#location-mini-city').textContent, /Paris/);
|
||||
assert.match($('#location-mini-poi').textContent, /Eiffel/);
|
||||
assert.equal($('#location-search-error').textContent, 'No match');
|
||||
const search = $('#location-search');
|
||||
assert.equal(search.getAttribute('aria-invalid'), 'true');
|
||||
assert.equal(
|
||||
search.getAttribute('aria-describedby'),
|
||||
'location-search-error',
|
||||
);
|
||||
controls.setSearchError(null);
|
||||
controls.hidePois();
|
||||
await nextTick();
|
||||
assert.equal(search.hasAttribute('aria-invalid'), false);
|
||||
assert.equal(search.hasAttribute('aria-describedby'), false);
|
||||
assert.equal($('#location-search-error').textContent, '');
|
||||
assert.equal(row.classList.contains('expanded'), false);
|
||||
assert.equal($('#location-bar-divider').classList.contains('visible'), false);
|
||||
});
|
||||
Reference in new issue
Block a user