138 lines
4.2 KiB
JavaScript
138 lines
4.2 KiB
JavaScript
import { describe, expect, test } from "bun:test";
|
|
|
|
import { PresentationController } from "./presentation-controller.js";
|
|
|
|
function installWindow() {
|
|
globalThis.HTMLElement = class HTMLElement {};
|
|
globalThis.document = {
|
|
getElementById: () => null,
|
|
};
|
|
globalThis.performance = {
|
|
now: () => Date.now(),
|
|
};
|
|
globalThis.window = {
|
|
requestAnimationFrame: (callback) => setTimeout(callback, 0),
|
|
setTimeout,
|
|
clearTimeout,
|
|
};
|
|
}
|
|
|
|
function wait(ms = 8) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
function createController(options = {}) {
|
|
installWindow();
|
|
const calls = [];
|
|
const pathCalls = [];
|
|
const connector = {
|
|
render: () => {
|
|
calls.push("connector:render");
|
|
return options.connectorReady ?? true;
|
|
},
|
|
hide: () => calls.push("connector:hide"),
|
|
};
|
|
const controller = new PresentationController({
|
|
connector,
|
|
pathFactory: (source, target, pathOptions) => {
|
|
pathCalls.push({ source, target, options: pathOptions });
|
|
return { points: [{ x: 0, y: 0 }, { x: 10, y: 10 }], start: { x: 0, y: 0 }, end: { x: 10, y: 10 } };
|
|
},
|
|
});
|
|
return { calls, controller, pathCalls };
|
|
}
|
|
|
|
function createRequest(overrides = {}) {
|
|
return {
|
|
id: overrides.id || "presentation:one",
|
|
owner: overrides.owner || "motion",
|
|
card: {
|
|
render: ({ reveal }) => overrides.calls?.push(`card:${reveal ? "show" : "stage"}`),
|
|
hide: () => overrides.calls?.push("card:hide"),
|
|
},
|
|
connector: {
|
|
enabled: true,
|
|
sourceProvider: () => ({ x: 1, y: 2, width: 12, height: 12 }),
|
|
targetProvider: () => ({ x: 50, y: 60, width: 120, height: 80 }),
|
|
readyTimeoutMs: 1,
|
|
drawMs: 1,
|
|
},
|
|
lifetime: { mode: "persistent" },
|
|
onDismiss: (reason) => overrides.calls?.push(`dismiss:${reason}`),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("PresentationController", () => {
|
|
test("persistent presentation remains active without timeout", async () => {
|
|
const { calls, controller } = createController();
|
|
const request = createRequest({ calls });
|
|
|
|
const presented = await controller.present(request);
|
|
await wait();
|
|
|
|
expect(presented).toBe(true);
|
|
expect(controller.isActive("motion")).toBe(true);
|
|
expect(calls).toContain("card:stage");
|
|
expect(calls).toContain("card:show");
|
|
});
|
|
|
|
test("timeout lifetime dismisses the active presentation", async () => {
|
|
const { calls, controller } = createController();
|
|
const request = createRequest({
|
|
calls,
|
|
lifetime: { mode: "timeout", timeoutMs: 2 },
|
|
});
|
|
|
|
await controller.present(request);
|
|
await wait(12);
|
|
|
|
expect(controller.isActive()).toBe(false);
|
|
expect(calls).toContain("dismiss:timeout");
|
|
expect(calls).toContain("card:hide");
|
|
});
|
|
|
|
test("present replaces the previous presentation", async () => {
|
|
const { calls, controller } = createController();
|
|
|
|
await controller.present(createRequest({ calls, id: "one" }));
|
|
await controller.present(createRequest({ calls, id: "two" }));
|
|
|
|
expect(controller.isActive("motion")).toBe(true);
|
|
expect(calls).toContain("dismiss:replace");
|
|
});
|
|
|
|
test("dismiss clears card and connector", async () => {
|
|
const { calls, controller } = createController();
|
|
|
|
await controller.present(createRequest({ calls }));
|
|
const dismissed = controller.dismiss("owner_stop");
|
|
|
|
expect(dismissed).toBe(true);
|
|
expect(controller.isActive()).toBe(false);
|
|
expect(calls).toContain("connector:hide");
|
|
expect(calls).toContain("card:hide");
|
|
expect(calls).toContain("dismiss:owner_stop");
|
|
});
|
|
|
|
test("update refreshes connector anchors", async () => {
|
|
const { calls, controller } = createController();
|
|
|
|
await controller.present(createRequest({ calls }));
|
|
calls.length = 0;
|
|
const updated = controller.update();
|
|
|
|
expect(updated).toBe(true);
|
|
expect(calls).toEqual(["connector:render"]);
|
|
});
|
|
|
|
test("rect source anchors are passed as center point plus sourceRect", async () => {
|
|
const { controller, pathCalls } = createController();
|
|
|
|
await controller.present(createRequest());
|
|
|
|
expect(pathCalls[0].source).toEqual({ x: 7, y: 8 });
|
|
expect(pathCalls[0].options.sourceRect).toEqual({ x: 1, y: 2, width: 12, height: 12 });
|
|
});
|
|
});
|