v4's Simple mode is the Editor with about ten `display: none !important` rules over it, which is not a smaller app but the same one wearing a mask. It broke in the way that arrangement always breaks: a collapsed column was marked [hidden], nothing can slide a display:none element into view, and the panel button did nothing at all and said nothing. So the same shape is built the other way round -- a region that does not exist yet is ABSENT, and the toggles live in rails that are siblings of the panels, never inside what they hide. Two kit elements, because both are layout primitives rather than app code: frame.app establishes the one definite height everything else fills, and workspace.docked is the centre-shrinks-between-two-panels arrangement. Three faults found by looking at it rather than by reasoning about it: * The prompt was in the DOM with no pixels. A vertical split inside a parent whose height came from its content resolves its second pane to zero, and nothing anywhere reports that. * The whole app rendered as a strip at the top of an empty window: `100%` of a `min-height` is `auto`. * Two fixed-width panels plus their rails are wider than a 480px window, and the page scrolled sideways -- taking the transport bar with it. The collapse is deliberately not animated: a remembered-closed panel would animate shut on every load, which reads as the app doing something rather than as a preference being restored. Also: jsdom gives a document with no url an OPAQUE ORIGIN, where reading window.localStorage throws on the property access itself. Everything that remembers a preference was silently taking its fallback path in every jsdom test, so "it is remembered" could not be tested at the cheap layer and looked like it worked.
39 lines
1.6 KiB
JavaScript
39 lines
1.6 KiB
JavaScript
// A DOM for the internals that genuinely need one.
|
|
//
|
|
// jsdom is honest about structure and events and dishonest about layout -- it
|
|
// has no real box model, so anything that depends on measured geometry belongs
|
|
// in the browser (see the catalogue and the Playwright pass), not here.
|
|
|
|
import { JSDOM } from "jsdom";
|
|
|
|
const GLOBALS = [
|
|
"window", "document", "Node", "HTMLElement", "Event", "KeyboardEvent",
|
|
"CustomEvent", "getComputedStyle", "DocumentFragment", "Text",
|
|
];
|
|
|
|
export function setupDom(html = "<!doctype html><html><body></body></html>") {
|
|
// A url, because without one jsdom gives the document an OPAQUE ORIGIN and
|
|
// `window.localStorage` throws a SecurityError on the property access itself
|
|
// -- not on get or set. Anything that remembers a preference then silently
|
|
// took its fallback path in every test, so "it is remembered" was untestable
|
|
// here and looked like it worked.
|
|
const dom = new JSDOM(html, { pretendToBeVisual: true, url: "http://localhost/funpack/" });
|
|
for (const name of GLOBALS) globalThis[name] = dom.window[name];
|
|
globalThis.window = dom.window;
|
|
globalThis.document = dom.window.document;
|
|
return dom;
|
|
}
|
|
|
|
export function teardownDom() {
|
|
for (const name of GLOBALS) delete globalThis[name];
|
|
}
|
|
|
|
/** Dispatch an event as it would arrive from a real interaction. */
|
|
export const fire = (node, type, init = {}) =>
|
|
node.dispatchEvent(new globalThis.window.Event(type, { bubbles: true, cancelable: true, ...init }));
|
|
|
|
export const key = (node, k) =>
|
|
node.dispatchEvent(new globalThis.window.KeyboardEvent("keydown", {
|
|
key: k, bubbles: true, cancelable: true,
|
|
}));
|