Skip to content

Testing

@data-xplorer/core/headless drives the explorer without a canvas. Nothing in it imports PixiJS, and it needs no DOM, so it runs in bun test, Vitest or Node’s test runner.

createTestStack(source, options?) loads the first scene and returns a stack that navigates the way the explorer does, with every move un-animated. This file is not an illustration: it runs in this repository’s CI on every change.

testing.test.ts
// Runs in this repository's CI with `bun test`; Vitest and Node's test runner work the same way.
import { expect, test } from "bun:test";
import { createSceneSource } from "@data-xplorer/core";
import { createTestStack } from "@data-xplorer/core/headless";
const folders: Record<string, string[]> = { "/": ["/src", "/docs"], "/src": ["/src/lib"], "/src/lib": [], "/docs": [] };
const source = createSceneSource<string>({
load: (focus) => ({
id: focus ?? "/",
heading: focus ?? "my-app",
clusters: folders[focus ?? "/"]!.map((path) => ({
id: path,
label: path,
weight: 1,
href: `#${path}`,
drillsInto: path,
shape: "cloud" as const,
})),
nodes: [],
edges: [],
}),
parentOf: (path) => {
const parent = path.slice(0, path.lastIndexOf("/")) || "/";
return { kind: "anchored", focus: parent === "/" ? null : parent, anchorId: path };
},
});
test("a folder opens, and zooming out comes back", async () => {
const explorer = await createTestStack(source); // no canvas, no animation, no DOM
expect(explorer.current.heading).toBe("my-app");
await explorer.enter("/src"); // an item's id in the current scene, as a double-click would
expect(explorer.current.focus).toBe("/src");
expect(explorer.breadcrumb()).toEqual(["my-app", "/src"]);
await explorer.zoomOut(); // back the way it came
expect(explorer.current.focus).toBeNull();
});
test("a deep link starts deep, and zooming out follows parentOf", async () => {
const explorer = await createTestStack(source, { focus: "/src/lib" });
await explorer.zoomOut();
expect(explorer.current.focus).toBe("/src");
});
test("every item is laid out inside the scene", async () => {
const explorer = await createTestStack(source);
for (const node of explorer.current.renderNodes) expect(node.size).toBeGreaterThan(0);
});
Member Does
current The scene the viewer is in: focus, heading, the scene your source returned, and renderNodes (each item as laid out, with x, y and size).
depth How many scenes deep the viewer is from where they started.
enter(itemId) Enters that item. A non-drillable item, or a failed load, leaves the stack where it was.
zoomOut() Leaves the way it came in; at the starting scene, follows your parentOf.
enterSibling(itemId), jumpTo(focus) Moves sideways, or jumps as a deep link would.
breadcrumb() The headings from the start to here.
stack, rig The underlying scene stack and camera, for anything else.

./headless also exports resolveOptions (every option with its default), parseColour, appearanceFor (the colour and shape the canvas picks for an item) and the stack and camera the test stack is built from.