mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
150 lines
4.7 KiB
JavaScript
150 lines
4.7 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { LocationControls } from './locationControls.js';
|
|
|
|
function node() {
|
|
const classes = new Set();
|
|
return {
|
|
children: [],
|
|
dataset: {},
|
|
listeners: new Map(),
|
|
className: '',
|
|
textContent: '',
|
|
classList: {
|
|
add: (...names) => names.forEach((name) => classes.add(name)),
|
|
remove: (...names) => names.forEach((name) => classes.delete(name)),
|
|
contains: (name) => classes.has(name),
|
|
toggle(name, enabled = !classes.has(name)) {
|
|
if (enabled) classes.add(name);
|
|
else classes.delete(name);
|
|
},
|
|
},
|
|
addEventListener(name, callback) {
|
|
if (!this.listeners.has(name)) this.listeners.set(name, new Set());
|
|
this.listeners.get(name).add(callback);
|
|
},
|
|
removeEventListener(name, callback) {
|
|
this.listeners.get(name)?.delete(callback);
|
|
},
|
|
fire(name, event = {}) {
|
|
for (const callback of this.listeners.get(name) || []) callback(event);
|
|
},
|
|
appendChild(child) {
|
|
this.children.push(child);
|
|
child.parent = this;
|
|
},
|
|
append(...children) {
|
|
for (const child of children)
|
|
if (typeof child !== 'string') this.appendChild(child);
|
|
},
|
|
replaceChildren() {
|
|
this.children = [];
|
|
},
|
|
remove() {
|
|
if (this.parent)
|
|
this.parent.children = this.parent.children.filter(
|
|
(child) => child !== this,
|
|
);
|
|
},
|
|
querySelectorAll(selector) {
|
|
return this.children.flatMap((child) => [
|
|
...(child.className === selector.slice(1) ? [child] : []),
|
|
...child.querySelectorAll(selector),
|
|
]);
|
|
},
|
|
focus() {
|
|
this.focused = true;
|
|
},
|
|
};
|
|
}
|
|
function fixture() {
|
|
const elements = {
|
|
pills: node(),
|
|
poiRow: node(),
|
|
divider: node(),
|
|
search: node(),
|
|
searchToggle: node(),
|
|
resetButtons: [node(), node()],
|
|
statusCity: node(),
|
|
statusPoi: node(),
|
|
};
|
|
const doc = node();
|
|
doc.createElement = node;
|
|
doc.body = node();
|
|
const cities = {
|
|
a: { name: 'City A', pois: [{ name: 'First' }, { name: 'Second' }] },
|
|
b: { name: 'City B', pois: [{ name: 'Elsewhere' }] },
|
|
};
|
|
const calls = [];
|
|
const frames = new Map();
|
|
const cancelled = [];
|
|
let next = 0;
|
|
const controls = new LocationControls({
|
|
elements,
|
|
cities,
|
|
getExpandedCity: () => 'a',
|
|
onCity: (id) => calls.push(['city', id]),
|
|
onPoi: (id, index) => calls.push(['poi', id, index]),
|
|
onSearch: (query) => calls.push(['search', query]),
|
|
onReset: () => calls.push(['reset']),
|
|
doc,
|
|
requestFrame: (fn) => {
|
|
const id = next++;
|
|
frames.set(id, fn);
|
|
return id;
|
|
},
|
|
cancelFrame: (id) => cancelled.push(id),
|
|
});
|
|
return { elements, doc, controls, calls, frames, cancelled };
|
|
}
|
|
test('hiding a POI row cancels frame zero and rejects an already queued expansion', () => {
|
|
const f = fixture();
|
|
f.controls.showPois('a');
|
|
const callback = f.frames.get(0);
|
|
f.controls.hidePois();
|
|
callback();
|
|
assert.deepEqual(f.cancelled, [0]);
|
|
assert.equal(f.elements.poiRow.classList.contains('expanded'), false);
|
|
assert.equal(f.elements.divider.classList.contains('visible'), false);
|
|
});
|
|
test('replacing POIs removes old click actions and presents the final row', () => {
|
|
const f = fixture();
|
|
f.controls.showPois('a');
|
|
const old = f.elements.poiRow.children[0];
|
|
f.controls.showPois('b');
|
|
old.fire('click');
|
|
assert.deepEqual(f.calls, []);
|
|
f.elements.poiRow.children[0].fire('click');
|
|
assert.deepEqual(f.calls, [['poi', 'b', 0]]);
|
|
f.frames.get(0)();
|
|
assert.equal(f.elements.poiRow.classList.contains('expanded'), false);
|
|
f.frames.get(1)();
|
|
assert.equal(f.elements.poiRow.classList.contains('expanded'), true);
|
|
});
|
|
test('destruction revokes document, search, reset, city and POI actions and removes orbit UI', () => {
|
|
const f = fixture();
|
|
f.controls.showPois('a');
|
|
f.controls.createOrbitIndicator();
|
|
assert.equal(f.doc.body.children.length, 1);
|
|
const city = f.elements.pills.children[0];
|
|
const poi = f.elements.poiRow.children[0];
|
|
f.controls.destroy();
|
|
f.controls.destroy();
|
|
city.fire('click');
|
|
poi.fire('click');
|
|
f.elements.search.value = 'Place';
|
|
f.elements.search.fire('keydown', { key: 'Enter' });
|
|
f.doc.fire('keydown', { key: 'Q' });
|
|
f.elements.resetButtons[0].fire('click');
|
|
f.frames.get(0)();
|
|
assert.deepEqual(f.calls, []);
|
|
assert.equal(f.doc.body.children.length, 0);
|
|
});
|
|
test('location and POI keys route once while form controls retain typing', () => {
|
|
const f = fixture();
|
|
f.doc.fire('keydown', { key: 'W', target: { matches: () => false } });
|
|
f.doc.fire('keydown', { key: 'Q', target: { matches: () => true } });
|
|
f.elements.resetButtons[1].fire('click');
|
|
assert.deepEqual(f.calls, [['poi', 'a', 1], ['reset']]);
|
|
});
|