mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
Merge branch 'main' into feat/typed-ai-chat
This commit is contained in:
commit
de1c9125d3
95 files changed
+3011
-382
No files matched your search
@@ -29,6 +29,8 @@
|
||||
"src/data/localGeojsonCore.js",
|
||||
"src/data/localGeojsonLod.js",
|
||||
"src/data/inputOwnership.js",
|
||||
"src/annotations/drawMarks.js",
|
||||
"src/annotations/drawMarks.test.mjs",
|
||||
"src/annotations/drawMode.js",
|
||||
"src/annotations/drawMode.test.mjs",
|
||||
"src/annotations/drawTool.js",
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
"src/ui/rail/RailChips.vue",
|
||||
"src/ui/views/panels/DisplayPanel.vue",
|
||||
"src/ui/views/panels/GeoImport.vue",
|
||||
"src/ui/views/panels/DrawControls.vue",
|
||||
"src/ui/views/panels/DrawMarkList.vue",
|
||||
"src/ui/views/panels/DrawSwatches.vue",
|
||||
"src/annotations/drawMarks.js",
|
||||
"src/ui/stores/display.js",
|
||||
"src/ui/localSdrPresentation.js",
|
||||
"src/ui/unitLabels.js",
|
||||
@@ -434,7 +438,9 @@
|
||||
"server/providers/overpass/transport.js",
|
||||
"src/sources/httpBody.js",
|
||||
"src/i18n/marker.js",
|
||||
"src/sources/rateLimit.js"
|
||||
"src/sources/rateLimit.js",
|
||||
"server/providers/common/same-site.js",
|
||||
"src/localRequestGate.mjs"
|
||||
],
|
||||
"external": []
|
||||
},
|
||||
@@ -2853,6 +2859,7 @@
|
||||
"src/layers/vessels/recordPolicy.js",
|
||||
"src/layers/vessels/snapshotRenderer.js",
|
||||
"src/data/inputOwnership.js",
|
||||
"src/annotations/drawMarks.js",
|
||||
"src/annotations/drawMode.js",
|
||||
"src/annotations/drawTool.js",
|
||||
"src/data/geoFileImport.js",
|
||||
|
||||
+12
-10
@@ -107,7 +107,7 @@ const layerState = () =>
|
||||
const manager = gev.dataManager;
|
||||
const module = manager.layers.get('directions').module;
|
||||
const row = document.querySelector(
|
||||
'#data-toggles [data-layer-id="directions"]',
|
||||
'#utilities-toggles [data-layer-id="directions"]',
|
||||
);
|
||||
const controls = module.getRowControls();
|
||||
const viewer = gev.viewer;
|
||||
@@ -152,7 +152,7 @@ const layerState = () =>
|
||||
const clickChip = async (chipId) => {
|
||||
const clicked = await page.evaluate((id) => {
|
||||
const row = document.querySelector(
|
||||
'#data-toggles [data-layer-id="directions"]',
|
||||
'#utilities-toggles [data-layer-id="directions"]',
|
||||
);
|
||||
const button = row?.querySelector(`button[data-chip-id="${id}"]`);
|
||||
if (!button || button.disabled) return false;
|
||||
@@ -232,24 +232,26 @@ try {
|
||||
});
|
||||
await sleep(3000);
|
||||
|
||||
// ── 1. Open the DATA LAYERS panel, then toggle the row on. ──────────────
|
||||
// ── 1. Open the UTILITIES panel, then toggle the row on. ────────────────
|
||||
const opened = await page.evaluate(() => {
|
||||
const panel = document.getElementById('data-panel');
|
||||
const panel = document.getElementById('utilities-panel');
|
||||
if (!panel) return false;
|
||||
if (panel.classList.contains('collapsed')) {
|
||||
panel
|
||||
.querySelector('.panel-collapse-btn[data-collapse-target="data-panel"]')
|
||||
.querySelector(
|
||||
'.panel-collapse-btn[data-collapse-target="utilities-panel"]',
|
||||
)
|
||||
?.click();
|
||||
}
|
||||
return !document
|
||||
.getElementById('data-panel')
|
||||
.getElementById('utilities-panel')
|
||||
.classList.contains('collapsed');
|
||||
});
|
||||
await sleep(700);
|
||||
check('the DATA LAYERS panel opens from its own header control', opened);
|
||||
check('the UTILITIES panel opens from its own header control', opened);
|
||||
const toggled = await page.evaluate(() => {
|
||||
const row = document.querySelector(
|
||||
'#data-toggles [data-layer-id="directions"]',
|
||||
'#utilities-toggles [data-layer-id="directions"]',
|
||||
);
|
||||
const button = row?.querySelector('.data-toggle-btn');
|
||||
if (!button) return false;
|
||||
@@ -397,7 +399,7 @@ try {
|
||||
);
|
||||
const focused = await page.evaluate(() => {
|
||||
const row = document.querySelector(
|
||||
'#data-toggles [data-layer-id="directions"]',
|
||||
'#utilities-toggles [data-layer-id="directions"]',
|
||||
);
|
||||
const items = row.querySelectorAll('.data-row-list-item');
|
||||
items[2]?.focus();
|
||||
@@ -776,7 +778,7 @@ try {
|
||||
}
|
||||
}
|
||||
const row = document.querySelector(
|
||||
'#data-toggles [data-layer-id="directions"]',
|
||||
'#utilities-toggles [data-layer-id="directions"]',
|
||||
);
|
||||
return {
|
||||
entities: left.length,
|
||||
|
||||
+198
-25
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
/** Rendered acceptance for DISPLAY ▸ Draw: real clicks, real keys, real teardown. */
|
||||
/** Rendered acceptance for UTILITIES ▸ Draw: real clicks, real keys, real teardown. */
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import puppeteer from 'puppeteer';
|
||||
@@ -58,6 +58,47 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
/** A clip of the globe canvas, clear of the panels on every side. */
|
||||
const CANVAS_CLIP = { x: 340, y: 200, width: 560, height: 380 };
|
||||
|
||||
// Clear of the open UTILITIES panel (x 100–380), whose own amber swatch
|
||||
// dims when its row's shape is hidden.
|
||||
const LINE_CLIP = { x: 390, y: 200, width: 510, height: 380 };
|
||||
/** The canvas clip as a PNG, for comparing two moments of the same view. */
|
||||
const canvasShot = () =>
|
||||
page.screenshot({ clip: LINE_CLIP, type: 'png', encoding: 'base64' });
|
||||
|
||||
/**
|
||||
* Amber pixels in one shot that are not amber in another of the same view:
|
||||
* a mark's own pixels, apart from imagery and panels that both shots share.
|
||||
*/
|
||||
function amberOnlyIn(shotA, shotB) {
|
||||
return page.evaluate(
|
||||
async (a, b) => {
|
||||
const pixels = async (base64) => {
|
||||
const bitmap = await createImageBitmap(
|
||||
await (await fetch(`data:image/png;base64,${base64}`)).blob(),
|
||||
);
|
||||
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
||||
const context = canvas.getContext('2d');
|
||||
context.drawImage(bitmap, 0, 0);
|
||||
return context.getImageData(0, 0, bitmap.width, bitmap.height).data;
|
||||
};
|
||||
const amber = (d, i) =>
|
||||
d[i] > 150 &&
|
||||
d[i + 1] > 90 &&
|
||||
d[i + 1] < 220 &&
|
||||
d[i + 2] < 130 &&
|
||||
d[i] - d[i + 2] > 70 &&
|
||||
d[i] - d[i + 1] > 20;
|
||||
const [da, db] = [await pixels(a), await pixels(b)];
|
||||
let only = 0;
|
||||
for (let i = 0; i < da.length; i += 4)
|
||||
if (amber(da, i) && !amber(db, i)) only += 1;
|
||||
return only;
|
||||
},
|
||||
shotA,
|
||||
shotB,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* How many amber pixels the clip contains. The drawn area's stroke and fill are
|
||||
* the only amber in this scene, so the count going UP after a shape is finished
|
||||
@@ -196,12 +237,14 @@ try {
|
||||
);
|
||||
check('visible tile content settles before drawing', settled);
|
||||
|
||||
// The DISPLAY rail starts collapsed on a first run: open it the way a user does.
|
||||
// UTILITIES starts collapsed on a first run: open it the way a user does.
|
||||
await page.evaluate(() => {
|
||||
const rail = document.getElementById('pp-toggles');
|
||||
if (rail?.classList.contains('collapsed'))
|
||||
const panel = document.getElementById('utilities-panel');
|
||||
if (panel?.classList.contains('collapsed'))
|
||||
document
|
||||
.querySelector('.panel-collapse-btn[data-collapse-target="pp-toggles"]')
|
||||
.querySelector(
|
||||
'.panel-collapse-btn[data-collapse-target="utilities-panel"]',
|
||||
)
|
||||
?.click();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
@@ -210,7 +253,7 @@ try {
|
||||
{ timeout: 10_000 },
|
||||
);
|
||||
check(
|
||||
'the Draw control is on the DISPLAY rail and starts off',
|
||||
'the Draw control is in the UTILITIES panel and starts off',
|
||||
await page.evaluate(() => {
|
||||
const toggle = document.getElementById('draw-toggle');
|
||||
return (
|
||||
@@ -298,8 +341,6 @@ try {
|
||||
// ── a pin, finished with Enter ──────────────────────────────────────────
|
||||
await page.click('#draw-toggle');
|
||||
await page.click('.pp-mode-btn[data-shape="pin"]');
|
||||
await page.click('#draw-label-input');
|
||||
await page.keyboard.type('Pin one');
|
||||
await clickWorld(520, 330);
|
||||
check(
|
||||
'the pointer is claimed while a session is open',
|
||||
@@ -313,7 +354,8 @@ try {
|
||||
|
||||
// ── a line along a road, with Backspace ─────────────────────────────────
|
||||
await page.click('.pp-mode-btn[data-shape="line"]');
|
||||
await clickWorld(360, 300);
|
||||
// Clear of the open UTILITIES panel, which sits beside the left rail.
|
||||
await clickWorld(420, 300);
|
||||
await clickWorld(470, 380);
|
||||
await clickWorld(600, 470);
|
||||
const beforeBackspace = (await drawState()).vertices;
|
||||
@@ -326,16 +368,9 @@ try {
|
||||
`${beforeBackspace} → ${afterBackspace}`,
|
||||
);
|
||||
await clickWorld(640, 500);
|
||||
await page.click('#draw-label-input');
|
||||
await page.keyboard.type('Market St');
|
||||
await page.keyboard.press('Enter');
|
||||
check(
|
||||
'a line finished from the label field lands on the board',
|
||||
await waitForMarks(2),
|
||||
);
|
||||
const lineMark = (await marks()).find((mark) =>
|
||||
mark.label?.startsWith('Market St'),
|
||||
);
|
||||
check('a line finished with Enter lands on the board', await waitForMarks(2));
|
||||
const lineMark = (await marks()).find((mark) => mark.type === 'route');
|
||||
check(
|
||||
'a drawn line reports its length and never a travel time',
|
||||
Boolean(lineMark) &&
|
||||
@@ -347,10 +382,8 @@ try {
|
||||
|
||||
// ── an area, coloured and labelled, finished with a double-click ────────
|
||||
await page.click('.pp-mode-btn[data-shape="area"]');
|
||||
await page.select('#draw-color-select', 'amber');
|
||||
await page.click('.draw-color[data-color="amber"]');
|
||||
const amberBefore = await amberPixels();
|
||||
await page.click('#draw-label-input');
|
||||
await page.keyboard.type('Civic Center block');
|
||||
await clickWorld(400, 280);
|
||||
await clickWorld(700, 290);
|
||||
await clickWorld(720, 520);
|
||||
@@ -373,14 +406,34 @@ try {
|
||||
afterDoubleClick === 3,
|
||||
`${afterDoubleClick} marks`,
|
||||
);
|
||||
// ── each drawn shape gets a row: named, recoloured, hidden from there ──
|
||||
const rowIds = () =>
|
||||
page.evaluate(() =>
|
||||
[...document.querySelectorAll('#draw-mark-list [data-mark-id]')].map(
|
||||
(row) => row.dataset.markId,
|
||||
),
|
||||
);
|
||||
const ids = await rowIds();
|
||||
check('every drawn shape has a row under Draw', ids.length === 3, ids);
|
||||
const areaId = ids[2];
|
||||
const rowControl = (id, control) =>
|
||||
`#draw-mark-list [data-mark-id="${id}"] [data-control="${control}"]`;
|
||||
await page.click(rowControl(areaId, 'name'));
|
||||
await page.keyboard.type('Civic Center block');
|
||||
await page.keyboard.press('Enter');
|
||||
await wait(300);
|
||||
const areaMark = (await marks()).find(
|
||||
(mark) => mark.label === 'Civic Center block',
|
||||
);
|
||||
check(
|
||||
'the label and colour chosen in the panel are what is drawn',
|
||||
'the name typed in its row and the colour picked before drawing are what is drawn',
|
||||
areaMark?.color === 'amber',
|
||||
JSON.stringify(areaMark),
|
||||
);
|
||||
check(
|
||||
'naming a shape keeps Draw on and the shape in progress untouched',
|
||||
(await drawState()).active === true,
|
||||
);
|
||||
await wait(1200);
|
||||
const amberAfter = await amberPixels();
|
||||
check(
|
||||
@@ -389,6 +442,118 @@ try {
|
||||
`${amberBefore} → ${amberAfter} amber pixels (+${amberAfter - amberBefore})`,
|
||||
);
|
||||
await shot('01-area-with-label');
|
||||
|
||||
// ── the row's own controls: recolour, hide and show, fly to ─────────────
|
||||
const markState = (id) =>
|
||||
page.evaluate((id) => {
|
||||
const mark = window.__gevAnnotations.list().find((m) => m.id === id);
|
||||
return (
|
||||
mark && { color: mark.color, hidden: mark.hidden, alpha: mark.alpha }
|
||||
);
|
||||
}, id);
|
||||
// Wait for a mark to reach a state rather than for a fixed time.
|
||||
const markReaches = (id, want) =>
|
||||
page
|
||||
.waitForFunction(
|
||||
(id, want) => {
|
||||
const mark = window.__gevAnnotations.list().find((m) => m.id === id);
|
||||
return (
|
||||
mark &&
|
||||
Object.entries(want).every(([key, value]) => mark[key] === value)
|
||||
);
|
||||
},
|
||||
{ timeout: 10_000 },
|
||||
id,
|
||||
want,
|
||||
)
|
||||
.then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
await page.click(rowControl(areaId, 'swatch'));
|
||||
await page.click(rowControl(areaId, 'color-green'));
|
||||
await markReaches(areaId, { color: 'green' });
|
||||
check(
|
||||
'a colour picked in the row recolours that shape',
|
||||
(await markState(areaId))?.color === 'green',
|
||||
);
|
||||
await page.click(rowControl(areaId, 'toggle'));
|
||||
await markReaches(areaId, { hidden: true, alpha: 0 });
|
||||
const hidden = await markState(areaId);
|
||||
check(
|
||||
'hiding a shape keeps it on the board but draws it at zero alpha',
|
||||
hidden?.hidden === true && hidden.alpha === 0,
|
||||
JSON.stringify(hidden),
|
||||
);
|
||||
await page.click(rowControl(areaId, 'toggle'));
|
||||
check(
|
||||
'showing it again brings it straight back',
|
||||
await markReaches(areaId, { hidden: false, alpha: 1 }),
|
||||
);
|
||||
// Fly-to frames the line by its whole path: once the flight settles, the
|
||||
// middle of the line sits near the middle of the view.
|
||||
await page.click(rowControl(ids[1], 'fly'));
|
||||
await page
|
||||
.waitForFunction(() => !window.__godsEyeView.viewer.camera._currentFlight, {
|
||||
timeout: 15_000,
|
||||
})
|
||||
.catch(() => {});
|
||||
await wait(300);
|
||||
const framed = await page.evaluate((id) => {
|
||||
const { viewer } = window.__godsEyeView;
|
||||
// No Cesium global in the page: borrow Cartesian3 from a live object.
|
||||
const Cartesian3 = viewer.camera.positionWC.constructor;
|
||||
const mark = window.__gevAnnotations.list().find((m) => m.id === id);
|
||||
const lons = mark.path.map((p) => p.lon);
|
||||
const lats = mark.path.map((p) => p.lat);
|
||||
const middle = Cartesian3.fromDegrees(
|
||||
(Math.min(...lons) + Math.max(...lons)) / 2,
|
||||
(Math.min(...lats) + Math.max(...lats)) / 2,
|
||||
);
|
||||
const at = viewer.scene.cartesianToCanvasCoordinates(middle);
|
||||
const canvas = viewer.scene.canvas;
|
||||
return (
|
||||
at && {
|
||||
dx: Math.abs(at.x - canvas.clientWidth / 2) / canvas.clientWidth,
|
||||
dy: Math.abs(at.y - canvas.clientHeight / 2) / canvas.clientHeight,
|
||||
}
|
||||
);
|
||||
}, ids[1]);
|
||||
check(
|
||||
'fly to frames that shape: its middle is near the middle of the view',
|
||||
Boolean(framed) && framed.dx < 0.2 && framed.dy < 0.2,
|
||||
JSON.stringify(framed),
|
||||
);
|
||||
|
||||
// ── a hidden Line leaves the globe: its flowing stroke keeps its own
|
||||
// colour, so the check is the pixels, not the mark's state ─────────────
|
||||
const lineId = ids[1];
|
||||
await page.click(rowControl(lineId, 'swatch'));
|
||||
await page.click(rowControl(lineId, 'color-amber'));
|
||||
await markReaches(lineId, { color: 'amber', alpha: 1 });
|
||||
await wait(800);
|
||||
const shown = await canvasShot();
|
||||
await page.click(rowControl(lineId, 'toggle'));
|
||||
await markReaches(lineId, { hidden: true, alpha: 0 });
|
||||
await wait(800);
|
||||
const hiddenShot = await canvasShot();
|
||||
// Amber the shown line had and the hidden one lost; amber that appears
|
||||
// only once it is hidden is noise, and must stay small beside it.
|
||||
const gone = await amberOnlyIn(shown, hiddenShot);
|
||||
const noise = await amberOnlyIn(hiddenShot, shown);
|
||||
check(
|
||||
'a hidden Line is no longer painted',
|
||||
gone > 40 && noise < gone * 0.2,
|
||||
`${gone} amber pixels went with the line, ${noise} appeared`,
|
||||
);
|
||||
await page.click(rowControl(lineId, 'toggle'));
|
||||
await wait(1200);
|
||||
const back = await amberOnlyIn(await canvasShot(), hiddenShot);
|
||||
check(
|
||||
'and paints again when shown',
|
||||
back > gone * 0.5,
|
||||
`${back} of ${gone} line pixels back`,
|
||||
);
|
||||
await shot('03-pins-and-shapes');
|
||||
|
||||
// ── Escape cancels the shape, a second Escape leaves draw mode ──────────
|
||||
@@ -419,6 +584,12 @@ try {
|
||||
afterSecondEscape.marks === 3,
|
||||
`${afterSecondEscape.marks} marks`,
|
||||
);
|
||||
await page.click(rowControl(ids[0], 'remove'));
|
||||
await waitForMarks(2);
|
||||
check(
|
||||
'remove in a row takes that one shape off the board and out of the list',
|
||||
(await rowIds()).join() === ids.slice(1).join(),
|
||||
);
|
||||
|
||||
// ── a click that would select something selects nothing while drawing ───
|
||||
const target = await findClickableTarget();
|
||||
@@ -480,10 +651,12 @@ try {
|
||||
|
||||
// ── Clear wipes the board ───────────────────────────────────────────────
|
||||
await page.evaluate(() => {
|
||||
const rail = document.getElementById('pp-toggles');
|
||||
if (rail?.classList.contains('collapsed'))
|
||||
const panel = document.getElementById('utilities-panel');
|
||||
if (panel?.classList.contains('collapsed'))
|
||||
document
|
||||
.querySelector('.panel-collapse-btn[data-collapse-target="pp-toggles"]')
|
||||
.querySelector(
|
||||
'.panel-collapse-btn[data-collapse-target="utilities-panel"]',
|
||||
)
|
||||
?.click();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
|
||||
@@ -39,14 +39,13 @@ try {
|
||||
'scene-select',
|
||||
'cctv-camera-select',
|
||||
'hud-layout-select',
|
||||
'draw-color-select',
|
||||
'radio-filter',
|
||||
'sdr-gain-select',
|
||||
].map((id) => document.getElementById(id));
|
||||
return {
|
||||
ids: selects.map((node) => node.id),
|
||||
shared:
|
||||
selects.length === 6 &&
|
||||
selects.length === 5 &&
|
||||
selects.every((node) => node.classList.contains('gev-field')),
|
||||
native: selects.every(
|
||||
(node) =>
|
||||
@@ -58,7 +57,7 @@ try {
|
||||
};
|
||||
});
|
||||
check(
|
||||
'all six panel dropdowns retain native select semantics and shared surface',
|
||||
'all five panel dropdowns retain native select semantics and shared surface',
|
||||
inventory.shared && inventory.native,
|
||||
inventory,
|
||||
);
|
||||
@@ -282,13 +281,6 @@ try {
|
||||
syncShare: false,
|
||||
});
|
||||
});
|
||||
if (
|
||||
await page.$eval(
|
||||
'#draw-toggle',
|
||||
(node) => node.getAttribute('aria-pressed') !== 'true',
|
||||
)
|
||||
)
|
||||
await page.click('#draw-toggle');
|
||||
for (const lang of ['en']) {
|
||||
await page.evaluate((lang) => {
|
||||
document.documentElement.lang = lang;
|
||||
@@ -296,12 +288,7 @@ try {
|
||||
for (const width of [1440, 620, 390]) {
|
||||
await page.setViewport({ width, height: 900 });
|
||||
const layout = await page.evaluate(() => {
|
||||
const ids = [
|
||||
'scene-select',
|
||||
'cctv-camera-select',
|
||||
'hud-layout-select',
|
||||
'draw-color-select',
|
||||
];
|
||||
const ids = ['scene-select', 'cctv-camera-select', 'hud-layout-select'];
|
||||
return ids.map((id) => {
|
||||
const node = document.getElementById(id),
|
||||
rect = node.getBoundingClientRect(),
|
||||
@@ -432,19 +419,11 @@ try {
|
||||
syncShare: false,
|
||||
});
|
||||
});
|
||||
if (
|
||||
await page.$eval(
|
||||
'#draw-toggle',
|
||||
(node) => node.getAttribute('aria-pressed') !== 'true',
|
||||
)
|
||||
)
|
||||
await page.click('#draw-toggle');
|
||||
const translated = await page.evaluate(() => {
|
||||
const nodes = [
|
||||
'scene-select',
|
||||
'cctv-camera-select',
|
||||
'hud-layout-select',
|
||||
'draw-color-select',
|
||||
].map((id) => document.getElementById(id));
|
||||
return (
|
||||
document.documentElement.lang === 'ru' &&
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Rendered acceptance for DISPLAY ▸ Import: real files through the real file
|
||||
* Rendered acceptance for UTILITIES ▸ Import: real files through the real file
|
||||
* input and a real drop, parsed by Cesium in the page.
|
||||
*
|
||||
* Proves: GeoJSON, KML, KMZ (with a packed icon and ground overlay) and GPX
|
||||
@@ -269,10 +269,12 @@ try {
|
||||
}, VIEW);
|
||||
await wait(4000);
|
||||
await page.evaluate(() => {
|
||||
const rail = document.getElementById('pp-toggles');
|
||||
if (rail?.classList.contains('collapsed'))
|
||||
const panel = document.getElementById('utilities-panel');
|
||||
if (panel?.classList.contains('collapsed'))
|
||||
document
|
||||
.querySelector('.panel-collapse-btn[data-collapse-target="pp-toggles"]')
|
||||
.querySelector(
|
||||
'.panel-collapse-btn[data-collapse-target="utilities-panel"]',
|
||||
)
|
||||
?.click();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
@@ -283,7 +285,7 @@ try {
|
||||
);
|
||||
const before = await state();
|
||||
check(
|
||||
'Import is on the DISPLAY rail with an empty, hidden list',
|
||||
'Import is in the UTILITIES panel with an empty, hidden list',
|
||||
before.rows === 0 &&
|
||||
!(await page.evaluate(() =>
|
||||
document
|
||||
|
||||
@@ -126,6 +126,14 @@ try {
|
||||
'ordinary layer rows return after reassembly',
|
||||
container.querySelectorAll('.data-toggle-row').length > 10,
|
||||
]);
|
||||
result.push([
|
||||
'Directions returns to the Utilities list after reassembly',
|
||||
Boolean(
|
||||
document.querySelector(
|
||||
'#utilities-toggles [data-layer-id="directions"]',
|
||||
) && !container.querySelector('[data-layer-id="directions"]'),
|
||||
),
|
||||
]);
|
||||
return result;
|
||||
});
|
||||
for (const [name, passed] of results) check(name, passed);
|
||||
|
||||
@@ -35,15 +35,21 @@ try {
|
||||
document.getElementById('loading-screen')?.classList.contains('hidden'),
|
||||
{ timeout: 60000 },
|
||||
);
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.styleManager.setPanelCollapsed('pp-toggles', false, {
|
||||
persist: false,
|
||||
syncShare: false,
|
||||
}),
|
||||
);
|
||||
// DISPLAY holds most segmented controls; Draw's shape group is in UTILITIES.
|
||||
await page.evaluate(() => {
|
||||
for (const panel of ['pp-toggles', 'utilities-panel'])
|
||||
window.__godsEyeView.styleManager.setPanelCollapsed(panel, false, {
|
||||
persist: false,
|
||||
syncShare: false,
|
||||
});
|
||||
});
|
||||
|
||||
const surface = await page.evaluate(() => {
|
||||
const groups = [...document.querySelectorAll('[role="radiogroup"]')];
|
||||
// Colour swatches are one-of-many too, but chips, not segments: they are
|
||||
// checked on their own below.
|
||||
const groups = [
|
||||
...document.querySelectorAll('[role="radiogroup"]:not(.draw-colors)'),
|
||||
];
|
||||
const buttons = groups.flatMap((group) => [
|
||||
...group.querySelectorAll('[role="radio"]'),
|
||||
]);
|
||||
@@ -69,6 +75,31 @@ try {
|
||||
JSON.stringify(surface),
|
||||
);
|
||||
|
||||
const swatches = await page.evaluate(() => {
|
||||
const group = document.getElementById('draw-colors');
|
||||
const chips = [...(group?.querySelectorAll('[role="radio"]') || [])];
|
||||
return {
|
||||
count: chips.length,
|
||||
round: chips.every(
|
||||
(chip) => getComputedStyle(chip).borderRadius === '50%',
|
||||
),
|
||||
colours: new Set(
|
||||
chips.map((chip) => getComputedStyle(chip).backgroundColor),
|
||||
).size,
|
||||
checked: chips.filter(
|
||||
(chip) => chip.getAttribute('aria-checked') === 'true',
|
||||
).length,
|
||||
};
|
||||
});
|
||||
check(
|
||||
"Draw's colour swatches are five round chips, one checked",
|
||||
swatches.count === 5 &&
|
||||
swatches.round &&
|
||||
swatches.colours === 5 &&
|
||||
swatches.checked === 1,
|
||||
JSON.stringify(swatches),
|
||||
);
|
||||
|
||||
await page.evaluate(() =>
|
||||
document.getElementById('models3d-mode-all').click(),
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ export function classifyNodeVersion(version = process.versions.node) {
|
||||
// NEWER than this release has verified is a warning, never a refusal: a
|
||||
// future Node must not brick a no-terminal install with advice its user
|
||||
// cannot follow. Too-old stays an error above — old runtimes genuinely fail.
|
||||
return { level: 'warn', summary: 'newer than this release has verified; Node 24.14.x or 26.x is the tested path' };
|
||||
return { level: 'warn', summary: 'newer than this release has verified; Node 24.22.x or 26.x is the tested path' };
|
||||
}
|
||||
|
||||
/** Verify that every direct package declared by this checkout is present. */
|
||||
|
||||
Reference in new issue
Block a user