mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
feat(ui): render the data layer rows through Vue from presented row state
This commit is contained in:
1 parent
ce343c1c6d
commit
d63f4d697b
16 files changed
+1434
-1552
No files matched your search
+68
-115
@@ -1,3 +1,4 @@
|
||||
import '../testSupport/registerDom.mjs';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
@@ -34,14 +35,8 @@ test('panel presentation places Transit between Street Traffic and Bike Share in
|
||||
});
|
||||
|
||||
test('partial feed controls distinguish incomplete records from stale data and outages', async () => {
|
||||
const { LayerPanel, layerFeedState } = await import('./layerPanel.js');
|
||||
const classes = new Map();
|
||||
const attrs = new Map();
|
||||
const button = {
|
||||
classList: { toggle: (key, value) => classes.set(key, value) },
|
||||
dataset: {},
|
||||
setAttribute: (key, value) => attrs.set(key, value),
|
||||
};
|
||||
const { LayerPanel, layerFeedState, presentLayerToggle } =
|
||||
await import('./layerPanel.js');
|
||||
const layer = {
|
||||
id: 'ais-live-vessels',
|
||||
name: 'Live Vessels',
|
||||
@@ -57,12 +52,12 @@ test('partial feed controls distinguish incomplete records from stale data and o
|
||||
},
|
||||
};
|
||||
const panel = LayerPanel.prototype;
|
||||
panel._syncToggleButton(button, layer);
|
||||
assert.equal(button.textContent, 'PARTIAL');
|
||||
assert.equal(button.dataset.feedState, 'partial');
|
||||
assert.equal(classes.get('feed-partial'), true);
|
||||
assert.equal(classes.get('feed-stale'), false);
|
||||
assert.match(attrs.get('aria-label'), /PARTIAL/);
|
||||
let toggle = presentLayerToggle(layer);
|
||||
assert.equal(toggle.text, 'PARTIAL');
|
||||
assert.equal(toggle.feedState, 'partial');
|
||||
assert.equal(toggle.classes['feed-partial'], true);
|
||||
assert.equal(toggle.classes['feed-stale'], false);
|
||||
assert.match(toggle.ariaLabel, /PARTIAL/);
|
||||
assert.match(
|
||||
panel._buildMetaText(layer),
|
||||
/^PARTIAL · AISStream · 2 of 3 records accepted · /,
|
||||
@@ -85,12 +80,11 @@ test('partial feed controls distinguish incomplete records from stale data and o
|
||||
);
|
||||
assert.equal(layerFeedState({ ...layer.stats, loading: true }), 'loading');
|
||||
layer.stats = { ...layer.stats, partial: false };
|
||||
panel._syncToggleButton(button, layer);
|
||||
assert.equal(button.textContent, 'ON');
|
||||
assert.equal(classes.get('feed-partial'), false);
|
||||
toggle = presentLayerToggle(layer);
|
||||
assert.equal(toggle.text, 'ON');
|
||||
assert.equal(toggle.classes['feed-partial'], false);
|
||||
layer.enabled = false;
|
||||
panel._syncToggleButton(button, layer);
|
||||
assert.equal(button.textContent, 'OFF');
|
||||
assert.equal(presentLayerToggle(layer).text, 'OFF');
|
||||
});
|
||||
|
||||
test('a stale row dates cached data by its oldest tile, not the last load', async () => {
|
||||
@@ -116,10 +110,9 @@ test('a stale row dates cached data by its oldest tile, not the last load', asyn
|
||||
|
||||
test('readout rows contain only toggles and metadata; ordinary rows retain controls', async () => {
|
||||
const { LayerPanel } = await import('./layerPanel.js');
|
||||
const { railFixture } = await import('./railTestFixture.mjs');
|
||||
const f = railFixture();
|
||||
const previousDocument = globalThis.document;
|
||||
globalThis.document = f.document;
|
||||
document.body.replaceChildren();
|
||||
const container = document.body.appendChild(document.createElement('div'));
|
||||
const find = (id) => container.querySelector(`[data-layer-id="${id}"]`);
|
||||
const ids = [
|
||||
'wind',
|
||||
'weather-radar',
|
||||
@@ -160,9 +153,9 @@ test('readout rows contain only toggles and metadata; ordinary rows retain contr
|
||||
}),
|
||||
});
|
||||
try {
|
||||
panel.mount(f.container);
|
||||
panel.mount(container);
|
||||
for (const id of ids.slice(0, -1)) {
|
||||
const row = f.find((n) => n.dataset.layerId === id);
|
||||
const row = find(id);
|
||||
assert.equal(row.children.length, 2);
|
||||
assert.ok(row.querySelector('.data-name'));
|
||||
assert.ok(row.querySelector('.data-toggle-meta'));
|
||||
@@ -173,14 +166,14 @@ test('readout rows contain only toggles and metadata; ordinary rows retain contr
|
||||
'data-toggle-controls-status',
|
||||
'data-row-list',
|
||||
])
|
||||
assert.equal(row.querySelector(`.${cls}`), null);
|
||||
assert.ok(!row.querySelector(`.${cls}`), cls);
|
||||
row.querySelector('.data-toggle-btn').click();
|
||||
}
|
||||
assert.deepEqual(
|
||||
calls.map(([id]) => id),
|
||||
ids.slice(0, -1),
|
||||
);
|
||||
const ordinary = f.find((n) => n.dataset.layerId === 'other');
|
||||
const ordinary = find('other');
|
||||
assert.equal(
|
||||
ordinary.querySelector('.data-toggle-controls-info').textContent,
|
||||
'Detailed time',
|
||||
@@ -195,19 +188,16 @@ test('readout rows contain only toggles and metadata; ordinary rows retain contr
|
||||
assert.equal(typeof refresh, 'function');
|
||||
} finally {
|
||||
panel.destroy();
|
||||
globalThis.document = previousDocument;
|
||||
document.body.replaceChildren();
|
||||
}
|
||||
});
|
||||
|
||||
test('the Recent Imagery readout mounts in its rail body like the weather readout and is rebuilt or released with the panel', async () => {
|
||||
const { LayerPanel } = await import('./layerPanel.js');
|
||||
const { railFixture } = await import('./railTestFixture.mjs');
|
||||
const f = railFixture();
|
||||
const body = f.document.createElement('div');
|
||||
f.document.getElementById = (id) =>
|
||||
id === 'recent-imagery-panel-body' ? body : null;
|
||||
const previousDocument = globalThis.document;
|
||||
globalThis.document = f.document;
|
||||
document.body.replaceChildren();
|
||||
const container = document.body.appendChild(document.createElement('div'));
|
||||
const body = document.body.appendChild(document.createElement('div'));
|
||||
body.id = 'recent-imagery-panel-body';
|
||||
const mounted = [];
|
||||
const factory = (container) => {
|
||||
const readout = {
|
||||
@@ -232,10 +222,10 @@ test('the Recent Imagery readout mounts in its rail body like the weather readou
|
||||
try {
|
||||
panel.attachRecentImagery(factory);
|
||||
assert.equal(mounted.length, 0, 'no body before the panel mounts');
|
||||
panel.mount(f.container);
|
||||
panel.mount(container);
|
||||
assert.equal(mounted.length, 1);
|
||||
assert.equal(mounted[0].container, body);
|
||||
panel.mount(f.container);
|
||||
assert.ok(mounted[0].container === body);
|
||||
panel.mount(container);
|
||||
assert.equal(mounted[0].destroyed, true, 'a remount rebuilds it');
|
||||
assert.equal(mounted.length, 2);
|
||||
panel.attachRecentImagery(null);
|
||||
@@ -248,18 +238,12 @@ test('the Recent Imagery readout mounts in its rail body like the weather readou
|
||||
assert.equal(mounted.length, 3, 'a destroyed panel mounts nothing');
|
||||
} finally {
|
||||
panel.destroy();
|
||||
globalThis.document = previousDocument;
|
||||
document.body.replaceChildren();
|
||||
}
|
||||
});
|
||||
|
||||
test('an omitted source is visibly unavailable and cannot be toggled on', async () => {
|
||||
const { LayerPanel } = await import('./layerPanel.js');
|
||||
const attrs = new Map();
|
||||
const button = {
|
||||
classList: { toggle() {} },
|
||||
dataset: {},
|
||||
setAttribute: (key, value) => attrs.set(key, value),
|
||||
};
|
||||
const { presentLayerToggle } = await import('./layerPanel.js');
|
||||
const layer = {
|
||||
id: 'flights',
|
||||
name: 'Flights',
|
||||
@@ -270,94 +254,63 @@ test('an omitted source is visibly unavailable and cannot be toggled on', async
|
||||
error: 'Data source not configured: flights',
|
||||
},
|
||||
};
|
||||
LayerPanel.prototype._syncToggleButton(button, layer);
|
||||
assert.equal(button.textContent, 'UNAVAILABLE');
|
||||
assert.equal(attrs.get('aria-disabled'), 'true');
|
||||
assert.match(button.title, /not configured/);
|
||||
LayerPanel.prototype._syncToggleButton(button, { ...layer, enabled: true });
|
||||
const toggle = presentLayerToggle(layer);
|
||||
assert.equal(toggle.text, 'UNAVAILABLE');
|
||||
assert.equal(toggle.ariaDisabled, true);
|
||||
assert.match(toggle.title, /not configured/);
|
||||
assert.equal(
|
||||
attrs.get('aria-disabled'),
|
||||
'false',
|
||||
presentLayerToggle({ ...layer, enabled: true }).ariaDisabled,
|
||||
false,
|
||||
'an active layer can always be switched off',
|
||||
);
|
||||
});
|
||||
|
||||
test('row text keeps a hyphenated code whole and never starts a line with the separator', async () => {
|
||||
const { setRowText } = await import('./layerPanel.js');
|
||||
const previousDocument = globalThis.document;
|
||||
const element = () => ({
|
||||
className: '',
|
||||
textContent: '',
|
||||
children: [],
|
||||
appendChild(child) {
|
||||
this.children.push(child);
|
||||
return child;
|
||||
},
|
||||
});
|
||||
globalThis.document = { createElement: element };
|
||||
try {
|
||||
const text = (node) =>
|
||||
node.children.length
|
||||
? node.children.map((child) => child.textContent).join('')
|
||||
: node.textContent;
|
||||
const kept = (node) =>
|
||||
node.children
|
||||
.filter((child) => child.className === 'data-keep')
|
||||
.map((child) => child.textContent);
|
||||
const { rowTextRuns } = await import('./layerRowText.js');
|
||||
const text = (runs) => runs.map((run) => run.text).join('');
|
||||
const kept = (runs) => runs.filter((run) => run.keep).map((run) => run.text);
|
||||
|
||||
const name = element();
|
||||
setRowText(name, 'Локальный ADS-B');
|
||||
assert.equal(text(name), 'Локальный ADS-B');
|
||||
assert.deepEqual(kept(name), ['ADS-B']);
|
||||
const name = rowTextRuns('Локальный ADS-B');
|
||||
assert.equal(text(name), 'Локальный ADS-B');
|
||||
assert.deepEqual(kept(name), ['ADS-B']);
|
||||
|
||||
const meta = element();
|
||||
setRowText(meta, 'UNAVAILABLE · NASA FIRMS · LIVE · KEY REQUIRED');
|
||||
assert.equal(text(meta), 'UNAVAILABLE · NASA FIRMS · LIVE · KEY REQUIRED');
|
||||
assert.deepEqual(kept(meta), [' ·', ' ·', ' ·']);
|
||||
assert.ok(
|
||||
meta.children.every((child) => !child.textContent.startsWith('·')),
|
||||
'the space before each dot is held to the piece it closes',
|
||||
);
|
||||
const meta = rowTextRuns('UNAVAILABLE · NASA FIRMS · LIVE · KEY REQUIRED');
|
||||
assert.equal(text(meta), 'UNAVAILABLE · NASA FIRMS · LIVE · KEY REQUIRED');
|
||||
assert.deepEqual(kept(meta), [' ·', ' ·', ' ·']);
|
||||
assert.ok(
|
||||
meta.every((run) => !run.text.startsWith('·')),
|
||||
'the space before each dot is held to the piece it closes',
|
||||
);
|
||||
|
||||
const plain = element();
|
||||
setRowText(plain, 'Satellites');
|
||||
assert.equal(plain.textContent, 'Satellites');
|
||||
assert.equal(plain.children.length, 0);
|
||||
assert.equal(rowTextRuns('Satellites'), null, 'plain text needs no runs');
|
||||
|
||||
const compound = element();
|
||||
setRowText(compound, 'Kartierte ALPR-Kameras · ADS-B-Flüge');
|
||||
assert.equal(text(compound), 'Kartierte ALPR-Kameras · ADS-B-Flüge');
|
||||
assert.deepEqual(
|
||||
kept(compound),
|
||||
[' ·', 'ADS-B'],
|
||||
'a compound may break at its hyphen; the code inside it stays whole',
|
||||
);
|
||||
const compound = rowTextRuns('Kartierte ALPR-Kameras · ADS-B-Flüge');
|
||||
assert.equal(text(compound), 'Kartierte ALPR-Kameras · ADS-B-Flüge');
|
||||
assert.deepEqual(
|
||||
kept(compound),
|
||||
[' ·', 'ADS-B'],
|
||||
'a compound may break at its hyphen; the code inside it stays whole',
|
||||
);
|
||||
|
||||
const long = element();
|
||||
setRowText(long, 'see https-proxy-endpoint-config-x');
|
||||
assert.deepEqual(kept(long), [], 'a long hyphenated run may still wrap');
|
||||
} finally {
|
||||
globalThis.document = previousDocument;
|
||||
}
|
||||
const long = rowTextRuns('see https-proxy-endpoint-config-x');
|
||||
assert.deepEqual(
|
||||
kept(long || []),
|
||||
[],
|
||||
'a long hyphenated run may still wrap',
|
||||
);
|
||||
});
|
||||
|
||||
test('the unavailable chip may abbreviate, but its accessible name keeps the full state', async () => {
|
||||
const { LayerPanel } = await import('./layerPanel.js');
|
||||
const attrs = new Map();
|
||||
const button = {
|
||||
classList: { toggle() {} },
|
||||
dataset: {},
|
||||
setAttribute: (key, value) => attrs.set(key, value),
|
||||
};
|
||||
LayerPanel.prototype._syncToggleButton(button, {
|
||||
const { presentLayerToggle } = await import('./layerPanel.js');
|
||||
const toggle = presentLayerToggle({
|
||||
id: 'local-firms',
|
||||
name: 'FIRMS Active Fires',
|
||||
enabled: false,
|
||||
stats: { sourceUnavailable: true, error: 'KEY REQUIRED' },
|
||||
});
|
||||
assert.equal(button.textContent, 'UNAVAILABLE');
|
||||
assert.equal(button.dataset.feedState, 'unavailable');
|
||||
assert.match(attrs.get('aria-label'), /^Active Fires: UNAVAILABLE\./);
|
||||
assert.equal(toggle.text, 'UNAVAILABLE');
|
||||
assert.equal(toggle.feedState, 'unavailable');
|
||||
assert.match(toggle.ariaLabel, /^Active Fires: UNAVAILABLE\./);
|
||||
});
|
||||
|
||||
test('the Weather readout receives enabled air-quality alongside weather layers', async () => {
|
||||
|
||||
Reference in new issue
Block a user