mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
The Data, Authoring, Weather, Recent Imagery, Context, CCTV, Street Level and Radio panels each wrote the same frame by hand. They are now one kit component, UiPanel, with a title, badge/actions/default slots and v-model:collapsed, drawing Core's panel markup and classes beside the kit's; props cover the differences (sideways, glow, scroll, railScroller, tag, toggleWords). RailPanel binds it to the panel chrome store: v-panel-state writes its state classes at once, and its button's clicks reach PanelChrome. UiPanelToggle is the collapse button UiPanel and PanelCollapseButton share. UiPanel's collapsed drives the button and ui-panel--collapsed only; the owner hides the body by writing the root's collapsed class, since the rails measure a panel in the task that opens it. PanelCollapseButton takes startCollapsed instead of initial and initialTitle, so a button shows its state's glyph and words before startup presents the panel. Titles render through t() instead of data-i18n, and the unused "Expand panel" string is pruned. v-panel-state redraws the presented state after a re-render, in case a root's class binding ever changes.
118 lines
4.5 KiB
JavaScript
118 lines
4.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { renderApplicationHtml } from './testSupport/applicationHtml.mjs';
|
|
import { readStylesheet } from './testSupport/readStylesheet.mjs';
|
|
|
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const read = (file) => fs.readFileSync(path.join(ROOT, file), 'utf8');
|
|
|
|
const html = await renderApplicationHtml(read('index.html'));
|
|
const css = readStylesheet(path.join(ROOT, 'style.css'));
|
|
const panelCss = read('src/ui/styles/street-level.css');
|
|
const panelChrome = read('src/ui/panelChrome.js');
|
|
const layoutController = read('src/ui/panelLayoutController.js');
|
|
|
|
test('Street Level is a collapsible right-rail panel that starts collapsed', () => {
|
|
assert.match(
|
|
html,
|
|
/<div id="street-level-panel" class="ui-panel panel-collapsible collapsed" data-panel-id="street-level-panel">/,
|
|
);
|
|
assert.match(
|
|
html,
|
|
/<button type="button" class="[^"]*\bpanel-collapse-btn\b[^"]*"[^>]*data-collapse-target="street-level-panel"/,
|
|
);
|
|
assert.match(
|
|
html,
|
|
/<span class="ui-panel__title panel-title">STREET LEVEL<\/span>/,
|
|
);
|
|
// Provider-neutral header: no vendor mark; the inner restores its scroll.
|
|
assert.doesNotMatch(html, /sl-mark|mly-mark/);
|
|
assert.match(
|
|
html,
|
|
/<div class="street-level-panel-inner ui-scroll" data-rail-scroller>/,
|
|
);
|
|
assert.doesNotMatch(
|
|
html,
|
|
/mapillary-dock|sl-minimized|sl-3d-btn|sl-photoreal-btn/,
|
|
);
|
|
});
|
|
|
|
test('the panel is registered with panel chrome, cockpit entry and the right rail', () => {
|
|
assert.match(panelChrome, /\{ id: 'street-level-panel' \}/);
|
|
assert.match(
|
|
panelChrome,
|
|
/COCKPIT_ENTRY_COLLAPSE_PANEL_IDS = Object\.freeze\(\[[\s\S]*'street-level-panel'/,
|
|
);
|
|
// A right-rail panel's collapse button opens sideways (◀ / ▶).
|
|
assert.match(
|
|
read('src/ui/views/panels/StreetLevelPanel.vue'),
|
|
/<RailPanel id="street-level-panel"[^>]*\bsideways\b/,
|
|
);
|
|
assert.match(
|
|
layoutController,
|
|
/for \(const panel of \[[\s\S]*?this\._streetLevelPanel,[\s\S]*?\]\) \{[\s\S]*?stack\.insertBefore\(panel, globalContextPanel\)/,
|
|
);
|
|
assert.ok(css.includes('#right-context-rail > #street-level-panel'));
|
|
});
|
|
|
|
test('the key gate holds every filter, with the error line outside it', () => {
|
|
const start = html.indexOf('<fieldset id="sl-controls"');
|
|
assert.ok(start > 0);
|
|
const gate = html.slice(start, html.indexOf('</fieldset>', start));
|
|
assert.match(gate, /data-sl-pano="all"/);
|
|
assert.match(gate, /id="sl-since"/);
|
|
assert.ok(html.indexOf('id="sl-error"') < start);
|
|
// The chips stay outside the gate, so their key tooltip is reachable.
|
|
assert.match(html, /<div id="sl-provider-chips" class="sl-chips"><\/div>/);
|
|
assert.doesNotMatch(gate, /sl-provider-chips/);
|
|
assert.match(html, /id="sl-error"[^>]*role="alert"/);
|
|
});
|
|
|
|
test('each radiogroup is one tab stop, on its checked button', () => {
|
|
for (const [group, checked] of [
|
|
['data-sl-render', 'letterbox'],
|
|
['data-sl-pano', 'all'],
|
|
]) {
|
|
const radios = [
|
|
...html.matchAll(
|
|
new RegExp(`<button [^>]*${group}="([a-z]+)"[^>]*>`, 'g'),
|
|
),
|
|
];
|
|
assert.ok(radios.length >= 2, group);
|
|
for (const [tag, value] of radios)
|
|
assert.equal(
|
|
/tabindex="-1"/.test(tag),
|
|
value !== checked,
|
|
`${group}="${value}" roving tab stop`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('the Cyber theme frames the Street Level panel like its rail peers (P3)', () => {
|
|
const cyber = read('src/ui/styles/cyber.css');
|
|
const inner = (cyber.match(/\.recent-imagery-panel-inner,/g) || []).length;
|
|
const panel = (cyber.match(/#recent-imagery-panel,/g) || []).length;
|
|
assert.ok(inner > 0 && panel > 0);
|
|
assert.equal(
|
|
(cyber.match(/\.street-level-panel-inner,/g) || []).length,
|
|
inner,
|
|
);
|
|
assert.equal((cyber.match(/#street-level-panel,/g) || []).length, panel);
|
|
// A theme header rule must be able to outrank the panel's own.
|
|
assert.doesNotMatch(panelCss, /#street-level-panel \.panel-header/);
|
|
});
|
|
|
|
test('OPEN STREET VIEW keeps its width beside a long coverage line', () => {
|
|
// .scene-btn grows and shrinks (flex: 1) and loads after this stylesheet:
|
|
// only a more specific rule keeps the button from being squeezed.
|
|
assert.match(css, /\.scene-btn \{[^}]*flex: 1;/);
|
|
assert.match(
|
|
panelCss,
|
|
/\.sl-meta-row \.sl-nearest-btn \{\s*flex: 0 0 auto;\s*\}/,
|
|
);
|
|
assert.match(panelCss, /\.sl-meta-row \.sl-meta \{[^}]*min-width: 0;/);
|
|
});
|