mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Fix the follow-up review findings in import, layers, startup and engines
- KML/KMZ import: a per-file canvas budget and cache for ground overlays, limits on rings and vertices of raised polygons, overlays across 360° and near the poles, degenerate holes, polar and single-latitude fills, absolute overlays at their altitude, overlay order by drawOrder as on Cesium, and pins on tiles keep their measured height. - Points on the surface: OSM Places and Street Level cones share the surface sampler and sit on the terrain before they are measured, within a sampling budget per pass; Cockpit's contact-dot bank keeps the dot atlas, and a failed fleet model loads again after a pause. - Startup: the loading screen lifts after the share-link view is restored, the retry screen names the failed engine and takes focus, and the fallback toast stays for 6 s. - Engines: an engine switch lands a running camera flight at its destination (camera.completeFlight on both engines), the sighting card leaves focus on the engine picker after a switch, and a Cesium model error stays with the instance that failed. - The MCP panel builds again: its wind-mesh alias resolves to the stub. - QA: the wildlife switch waits for the camera to rest, GBFS --w2 says it needs the dev server, and test fixtures are shared instead of copied.
This commit is contained in:
1 parent
0ef29781e5
commit
60fb8fee07
82 files changed
+2363
-1574
No files matched your search
@@ -18,11 +18,11 @@ const browserGlobals = new Set([
|
||||
|
||||
/**
|
||||
* Inspect literal module dependencies and browser-platform references without
|
||||
* executing code. With `dynamic`, also list the import() specifiers apart.
|
||||
* executing code. With `dynamic`, also list the static (not import())
|
||||
* specifiers apart.
|
||||
*/
|
||||
export function analyzeModule(source, { dynamic = false, filename = '' } = {}) {
|
||||
const imports = [];
|
||||
const dynamicImports = [];
|
||||
const staticImports = [];
|
||||
const browser = new Set();
|
||||
function walk(node, parent, field) {
|
||||
@@ -41,9 +41,8 @@ export function analyzeModule(source, { dynamic = false, filename = '' } = {}) {
|
||||
'Computed module imports are not allowed in runtime code',
|
||||
);
|
||||
imports.push(node.source.value);
|
||||
if (node.type === 'ImportExpression')
|
||||
dynamicImports.push(node.source.value);
|
||||
else staticImports.push(node.source.value);
|
||||
if (node.type !== 'ImportExpression')
|
||||
staticImports.push(node.source.value);
|
||||
}
|
||||
if (node.type === 'CallExpression' && node.callee?.name === 'require')
|
||||
throw new Error('Runtime modules must use ES imports');
|
||||
@@ -86,7 +85,7 @@ export function analyzeModule(source, { dynamic = false, filename = '' } = {}) {
|
||||
return {
|
||||
imports,
|
||||
browser: [...browser],
|
||||
...(dynamic && { dynamicImports, staticImports }),
|
||||
...(dynamic && { staticImports }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"src/ui/views/index.js",
|
||||
"src/ui/views/CockpitView.vue",
|
||||
"src/ui/views/CommandDockView.vue",
|
||||
"src/app/engineChoice.js",
|
||||
"src/ui/views/ContextView.vue",
|
||||
"src/ui/views/panels/RadioPanel.vue",
|
||||
"src/ui/views/panels/SdrCard.vue",
|
||||
@@ -2141,6 +2142,7 @@
|
||||
"src/layers/shared/color.js",
|
||||
"src/globe/cameraMath.js",
|
||||
"src/layers/streetLevel/render/display.js",
|
||||
"src/layers/shared/render/surfacePoints.js",
|
||||
"src/globe/contract.js"
|
||||
],
|
||||
"external": ["pbf", "@mapbox/vector-tile", "mapillary-js"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* Keyless station-name regression against the real globe and bike-share tool.
|
||||
* Run with a dev or QA preview server and an explicitly assigned --url.
|
||||
* --w2 adds raster checks that import source modules: dev server only.
|
||||
* GBFS documents are synthetic; no operator feed or provider key is required.
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -138,6 +139,13 @@ async function open(mode = 'normal') {
|
||||
{ information, status, mode },
|
||||
);
|
||||
await page.goto(appUrl, { waitUntil: 'domcontentloaded' });
|
||||
if (
|
||||
extended &&
|
||||
!(await page.evaluate(() =>
|
||||
Boolean(document.querySelector('script[src="/@vite/client"]')),
|
||||
))
|
||||
)
|
||||
throw new Error('--w2 requires a Vite dev server; omit --w2 for preview');
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__godsEyeView?.dataManager && window.__godsEyeView?.voiceCommands,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./qa-gbfs-names.mjs', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const openSource = source.slice(
|
||||
source.indexOf('async function open('),
|
||||
source.indexOf('async function nextFrame('),
|
||||
);
|
||||
|
||||
function fixture({ extended, dev }) {
|
||||
const page = {
|
||||
bringToFront: async () => {},
|
||||
on() {},
|
||||
setViewport: async () => {},
|
||||
evaluateOnNewDocument: async () => {},
|
||||
goto: async () => {},
|
||||
evaluate: async () => dev,
|
||||
waitForFunction: async () => {},
|
||||
};
|
||||
const dependencies = {
|
||||
browser: { newPage: async () => page },
|
||||
extended,
|
||||
watchBrowserPage() {},
|
||||
report: {},
|
||||
appUrl: 'http://localhost:4000',
|
||||
information: {},
|
||||
status: {},
|
||||
fromDegrees: () => ({}),
|
||||
};
|
||||
return new Function(
|
||||
...Object.keys(dependencies),
|
||||
`${openSource}\nreturn open;`,
|
||||
)(...Object.values(dependencies));
|
||||
}
|
||||
|
||||
test('preview rejects the optional dev-only raster checks before importing source modules', async () => {
|
||||
await assert.rejects(
|
||||
fixture({ extended: true, dev: false })(),
|
||||
/--w2 requires a Vite dev server/,
|
||||
);
|
||||
});
|
||||
|
||||
test('normal preview checks and extended dev checks remain available', async () => {
|
||||
await fixture({ extended: false, dev: false })();
|
||||
await fixture({ extended: true, dev: true })();
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { quantile, slope } from './qa-strict-session.mjs';
|
||||
import { strictArguments, waitForCameraAtRest } from './qa-strict-harness.mjs';
|
||||
|
||||
@@ -46,31 +47,67 @@ test('the runner hands each scenario its layers and its manifest path', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('the camera is at rest once a flight has landed and it stays still', async () => {
|
||||
// A selection's flight holds the inputs off; once they are on, the camera
|
||||
// still moves one more sample before it stays put.
|
||||
const poses = [0, 400, 900, 900, 1250, 1250, 1250];
|
||||
const inputs = [false, false, false, false, true, true, true];
|
||||
// A page whose camera answers one sampled position (x, metres) and input
|
||||
// state per waitForFunction poll; `polls()` counts the polls taken.
|
||||
function cameraPage(t, positions, inputs, globals = {}) {
|
||||
let frame = 0;
|
||||
const engine = {
|
||||
camera: { pose: () => ({ position: { x: poses[frame], y: 0, z: 6.4e6 } }) },
|
||||
camera: {
|
||||
pose: () => ({ position: { x: positions[frame], y: 0, z: 6.4e6 } }),
|
||||
},
|
||||
input: { cameraInputsEnabled: () => inputs[frame] },
|
||||
};
|
||||
const previous = globalThis.window;
|
||||
globalThis.window = { __godsEyeView: { engine } };
|
||||
globalThis.window = { __godsEyeView: { engine }, ...globals };
|
||||
t.after(() => {
|
||||
globalThis.window = previous;
|
||||
});
|
||||
const page = {
|
||||
evaluate: async (fn) => fn(),
|
||||
async waitForFunction(fn) {
|
||||
for (; frame < poses.length; frame++) if (fn()) return;
|
||||
for (; frame < positions.length; frame++) if (fn()) return;
|
||||
throw new Error('timed out');
|
||||
},
|
||||
};
|
||||
try {
|
||||
await waitForCameraAtRest(page);
|
||||
// Not while the flight holds the inputs (sample 3 repeats a pose), nor
|
||||
// while the camera moves (sample 4).
|
||||
assert.equal(frame, 5);
|
||||
} finally {
|
||||
globalThis.window = previous;
|
||||
}
|
||||
return { page, polls: () => frame };
|
||||
}
|
||||
|
||||
test('the camera is at rest once a flight has landed and it stays still', async (t) => {
|
||||
// A selection's flight holds the inputs off; once they are on, the camera
|
||||
// still moves one more sample before it stays put.
|
||||
const { page, polls } = cameraPage(
|
||||
t,
|
||||
[0, 400, 900, 900, 1250, 1250, 1250],
|
||||
[false, false, false, false, true, true, true],
|
||||
);
|
||||
await waitForCameraAtRest(page);
|
||||
// Not while the flight holds the inputs (sample 3 repeats a pose), nor
|
||||
// while the camera moves (sample 4).
|
||||
assert.equal(polls(), 5);
|
||||
});
|
||||
|
||||
test('qa-wildlife-switch waits for the focus flight even when the camera was already still', async (t) => {
|
||||
// A rest the scenario recorded before the click is no rest of the flight.
|
||||
const { page, polls } = cameraPage(
|
||||
t,
|
||||
[0, 0, 100, 500, 500],
|
||||
[false, false, false, true, true],
|
||||
{
|
||||
__qaWildPose: { key: '0.0,0.0,6400000.0', at: performance.now() - 5000 },
|
||||
},
|
||||
);
|
||||
const source = readFileSync(
|
||||
new URL('./qa-wildlife-switch.mjs', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const still = new Function(
|
||||
'p',
|
||||
'waitForCameraAtRest',
|
||||
`${source.slice(
|
||||
source.indexOf(' const still ='),
|
||||
source.indexOf(' // What the engine draws'),
|
||||
)}\nreturn still;`,
|
||||
)(page, waitForCameraAtRest);
|
||||
await still();
|
||||
assert.equal(polls(), 4);
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
createStrict,
|
||||
localPreview,
|
||||
openPage,
|
||||
waitForCameraAtRest,
|
||||
waitForEngine,
|
||||
} from './qa-strict-harness.mjs';
|
||||
|
||||
@@ -86,18 +87,7 @@ await strict.run({}, async (b) => {
|
||||
},
|
||||
fromDegrees(WILDLIFE_VIEW.lon, WILDLIFE_VIEW.lat, WILDLIFE_VIEW.alt),
|
||||
);
|
||||
// Until the camera has held still for a second (a focus flight is over).
|
||||
const still = () =>
|
||||
p.waitForFunction(
|
||||
() => {
|
||||
const pose = window.__godsEyeView.engine.camera.pose().position;
|
||||
const key = [pose.x, pose.y, pose.z].map((v) => v.toFixed(1)).join();
|
||||
if (window.__qaWildPose?.key !== key)
|
||||
window.__qaWildPose = { key, at: performance.now() };
|
||||
return performance.now() - window.__qaWildPose.at > 1000;
|
||||
},
|
||||
{ timeout: 30000, polling: 100 },
|
||||
);
|
||||
const still = () => waitForCameraAtRest(p, { timeout: 30000 });
|
||||
// What the engine draws for the layer, and what its picker answers at each
|
||||
// sighting's anchor.
|
||||
const drawing = () =>
|
||||
|
||||
Reference in new issue
Block a user