mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
test(street-level): harden KartaView tests (mutation score 51% → 95%)
A mutation review (91, then 130 mutants) found tests that could not fail
(quick presses with synchronous fakes, a no-flight check with a provider
that never posed), and untested paths. New and rewritten tests kill 123 of
130 mutants; the 7 survivors are equivalent or harmless redraws.
- viewer: held-back loads for quick presses, stale sharp/flat loads,
close mid-load, ends checks for an older photo, WebGL retried per mount,
focus handed back, look direction kept on ›.
- provider: id-ordered pages with out-of-order indexes, page and probe
failures retried, overtaken lookups, cones re-centred on ‹ ›, leftover
imagery filters hide no line or cone, coverage hints, tile cache.
- source: 429 backoff and Retry-After under mock timers, hang timeouts,
the nearest query's parameters. policy: storage-host injection, FOV 360,
longitudes past ±180. panorama: a stub WebGL context (angles, lost
context, re-measuring). lineCoverage.test.mjs: tooHigh hint, passes().
- core: openImage({frame:false}) and openNearest leave the camera
(with a posing provider and a default-open control), KartaView's legend
and wording end to end; viewerHost supportsRenderModes.
- Fixed tick-count waits replaced by an until() poll.
- Browser gate fixture: odd photos archived (409 at storage2, served by
the proxy), the first lookup rate-limited (429, Retry-After: 0); the
line-click step asserts the camera stays and › skips the archived photo.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
bd0bbf5134
commit
3d4c6de21a
14 files changed
+1374
-81
No files matched your search
@@ -58,3 +58,16 @@ export function fakeHost() {
|
||||
host.ownerDocument = doc;
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait, a macrotask at a time, until `condition` holds; false if it never
|
||||
* does within `ticks`. A test asserting that something did not happen waits
|
||||
* on a sign that the work is done instead of a fixed count of ticks.
|
||||
*/
|
||||
export async function until(condition, { ticks = 200 } = {}) {
|
||||
for (let i = 0; i < ticks; i++) {
|
||||
if (await condition()) return true;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
return Boolean(await condition());
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
/** Shared Street Level test stand-ins: provider snapshot, minimal provider, ray-casting camera. */
|
||||
import * as Cesium from 'cesium';
|
||||
import { MAPILLARY_CREDIT_HTML } from '../layers/streetLevel/providers/mapillary/policy.js';
|
||||
import {
|
||||
latToTileY,
|
||||
lonToTileX,
|
||||
tileBounds,
|
||||
} from '../layers/streetLevel/tileMath.js';
|
||||
|
||||
const RAD = Math.PI / 180;
|
||||
|
||||
@@ -141,3 +146,92 @@ export function rayCamera({
|
||||
computeViewRectangle: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** The centre of the tile holding a point at zoom `z`, so a small view is one tile. */
|
||||
export function tileCentre(lon, lat, z = 14) {
|
||||
const x = lonToTileX(lon, z);
|
||||
const y = latToTileY(lat, z);
|
||||
const { west, east, south, north } = tileBounds(x, y, z);
|
||||
return {
|
||||
tile: { x, y, z },
|
||||
lon: (west + east) / 2,
|
||||
lat: (south + north) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
/** A scene collection that remembers what is on the globe. */
|
||||
function sceneCollection() {
|
||||
const items = new Set();
|
||||
return {
|
||||
items,
|
||||
add(primitive) {
|
||||
items.add(primitive);
|
||||
return primitive;
|
||||
},
|
||||
remove(primitive) {
|
||||
return items.delete(primitive);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A viewer looking straight down on a 0.004° square around `view`
|
||||
* (`{lon, lat, height}`, mutable), enough for line coverage to attach,
|
||||
* refresh and draw. Cesium's one-time terrain-height download is skipped.
|
||||
*/
|
||||
export function coverageViewer(view, t) {
|
||||
const initialize = Cesium.GroundPolylinePrimitive.initializeTerrainHeights;
|
||||
Cesium.GroundPolylinePrimitive.initializeTerrainHeights = () =>
|
||||
Promise.resolve();
|
||||
t?.after(() => {
|
||||
Cesium.GroundPolylinePrimitive.initializeTerrainHeights = initialize;
|
||||
});
|
||||
// Node has no WebGL context to report line-width limits.
|
||||
Cesium.ContextLimits._minimumAliasedLineWidth = 1;
|
||||
Cesium.ContextLimits._maximumAliasedLineWidth = 10;
|
||||
const listeners = () => {
|
||||
const set = new Set();
|
||||
return {
|
||||
set,
|
||||
addEventListener(listener) {
|
||||
set.add(listener);
|
||||
return () => set.delete(listener);
|
||||
},
|
||||
};
|
||||
};
|
||||
return {
|
||||
view,
|
||||
scene: {
|
||||
canvas: { clientWidth: 100, clientHeight: 100 },
|
||||
globe: { show: false, ellipsoid: Cesium.Ellipsoid.WGS84 },
|
||||
frameState: { mode: Cesium.SceneMode.SCENE3D },
|
||||
updateHeight: () => () => {},
|
||||
getHeight: () => undefined,
|
||||
groundPrimitives: sceneCollection(),
|
||||
primitives: sceneCollection(),
|
||||
postRender: listeners(),
|
||||
preRender: listeners(),
|
||||
},
|
||||
camera: {
|
||||
changed: new Cesium.Event(),
|
||||
moveEnd: new Cesium.Event(),
|
||||
get positionWC() {
|
||||
return Cesium.Cartesian3.fromDegrees(view.lon, view.lat, view.height);
|
||||
},
|
||||
get positionCartographic() {
|
||||
return {
|
||||
longitude: view.lon * RAD,
|
||||
latitude: view.lat * RAD,
|
||||
height: view.height,
|
||||
};
|
||||
},
|
||||
pickEllipsoid(point) {
|
||||
return Cesium.Cartesian3.fromDegrees(
|
||||
view.lon - 0.002 + (point.x / 100) * 0.004,
|
||||
view.lat + 0.002 - (point.y / 100) * 0.004,
|
||||
);
|
||||
},
|
||||
computeViewRectangle: () => undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user