Files
gods-eye-view/src/testSupport/kartaViewFakes.mjs
T
Said TurkseverandClaude Opus 5.5 97a1a667de fix(street-level): KartaView round-2 follow-ups — context loss, size cap, slow lookups
From the second IC8 pass (launch ready; P3s closed here):
- A lost WebGL context is redrawn on a new canvas (the old one only gives
  back its lost context), through the core, and only if no newer photo is
  opening; verified live with WEBGL_lose_context, twice.
- The 16 MB answer cap counts bytes while reading: KartaView sends
  chunked answers with no Content-Length, so the header check never fired.
- A lookup gets 8 s, then 15 s once more: KartaView sometimes sits on a
  lookup for 11 s and answers the same one in 3 s.

Left as follow-ups: cancelling abandoned sequence pages; finding a page
by binary search on the index when thousands of photos are missing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 21:03:13 +01:00

82 lines
2.4 KiB
JavaScript

// DOM stand-ins for the KartaView viewer's tests (no browser in Node).
/** A DOM element with what the viewer touches: children, events, attributes. */
export function fakeElement(tag) {
const listeners = new Map();
const element = {
tagName: tag.toUpperCase(),
children: [],
parentNode: null,
style: {},
dataset: {},
attributes: {},
hidden: false,
disabled: false,
clientWidth: 400,
clientHeight: 250,
classList: {
values: new Set(),
add: (name) => element.classList.values.add(name),
remove: (name) => element.classList.values.delete(name),
},
append(...nodes) {
for (const node of nodes) {
node.parentNode = element;
element.children.push(node);
}
},
remove() {
const list = element.parentNode?.children;
if (list) list.splice(list.indexOf(element), 1);
element.parentNode = null;
},
replaceWith(next) {
const parent = element.parentNode;
if (!parent) return;
parent.children[parent.children.indexOf(element)] = next;
next.parentNode = parent;
next.ownerDocument = element.ownerDocument;
element.parentNode = null;
},
setAttribute: (name, value) => (element.attributes[name] = value),
removeAttribute: (name) => {
delete element.attributes[name];
if (name === 'src') element.src = '';
},
addEventListener: (type, handler) => listeners.set(type, handler),
removeEventListener: (type) => listeners.delete(type),
setPointerCapture() {},
/** Fire an event at the element's listener. */
fire(type, event = {}) {
return listeners.get(type)?.({
preventDefault() {
this.defaultPrevented = true;
},
stopPropagation() {},
...event,
});
},
};
return element;
}
export function fakeHost() {
const doc = { createElement: (tag) => fakeElement(tag) };
const host = fakeElement('div');
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());
}