Files
bitvid/tests/position-floating-panel.test.mjs
T
google-labs-jules[bot] 05c091c26f Fix unit tests: Expose HTMLStyleElement in JSDOM setup
This change addresses a `ReferenceError: HTMLStyleElement is not defined` failure in `tests/position-floating-panel.test.mjs`. The `popoverEngine.js` source code performs an `instanceof HTMLStyleElement` check, which requires the class to be available in the global scope. While JSDOM provides this class, the test setup was not exposing it to the global object.

- Updated `tests/position-floating-panel.test.mjs` to add `global.HTMLStyleElement = windowRef.HTMLStyleElement;` in the `beforeEach` block.
2026-01-14 03:52:53 +00:00

419 lines
11 KiB
JavaScript

import test, { beforeEach, afterEach, mock } from "node:test";
import assert from "node:assert/strict";
import { JSDOM } from "jsdom";
import { register } from "node:module";
let dom;
let documentRef;
let windowRef;
let createPopover;
let computePositionStub;
let autoUpdateStub;
let mockedPosition;
class StubObserver {
constructor() {
this.observe = () => {};
this.unobserve = () => {};
this.disconnect = () => {};
}
}
function setupBoundingClientRect(element, rect) {
element.getBoundingClientRect = () => ({ ...rect });
Object.defineProperties(element, {
offsetWidth: { value: rect.width, configurable: true },
offsetHeight: { value: rect.height, configurable: true },
});
}
function attachFocus(element, activeElementRef) {
element.focus = () => {
activeElementRef.active = element;
if (typeof element.setAttribute === "function") {
element.setAttribute("data-focused", "true");
}
};
}
function setMockedPosition(overrides = {}) {
mockedPosition = {
x: 0,
y: 0,
placement: "bottom-start",
middlewareData: {},
strategy: "fixed",
...overrides,
};
}
beforeEach(async () => {
dom = new JSDOM(
`<!DOCTYPE html><html><body data-ds="new"><div id="root"></div></body></html>`,
{
pretendToBeVisual: true,
url: "https://example.com",
},
);
windowRef = dom.window;
documentRef = windowRef.document;
windowRef.innerWidth = 320;
windowRef.innerHeight = 320;
Object.defineProperty(documentRef.documentElement, "clientWidth", {
value: 320,
configurable: true,
});
Object.defineProperty(documentRef.documentElement, "clientHeight", {
value: 320,
configurable: true,
});
global.window = windowRef;
global.document = documentRef;
global.HTMLElement = windowRef.HTMLElement;
global.Element = windowRef.Element;
global.Node = windowRef.Node;
global.HTMLStyleElement = windowRef.HTMLStyleElement;
global.getComputedStyle = windowRef.getComputedStyle.bind(windowRef);
global.ResizeObserver = StubObserver;
if (!global.PointerEvent) {
global.PointerEvent = windowRef.PointerEvent || windowRef.Event;
}
windowRef.scrollTo = () => {};
const rootStyle = documentRef.createElement("style");
rootStyle.id = "testRootVars";
rootStyle.textContent = `
:root {
--popover-inline-safe-max: calc(100vw - var(--space-xl));
--overlay-panel-padding-block: 240px;
}
`;
documentRef.head.appendChild(rootStyle);
setMockedPosition();
computePositionStub = mock.fn(async () => ({ ...mockedPosition }));
autoUpdateStub = mock.fn((reference, floating, update) => {
if (typeof update === "function") {
update();
}
return () => {};
});
globalThis.__floatingUiMock = {
arrow: (...args) => ({ name: "arrow", args }),
autoUpdate: autoUpdateStub,
computePosition: computePositionStub,
flip: (...args) => ({ name: "flip", args }),
offset: (...args) => ({ name: "offset", args }),
shift: (...args) => ({ name: "shift", args }),
};
register(new URL("./ui/mocks/floating-ui-test-loader.mjs", import.meta.url));
({ default: createPopover } = await import("../js/ui/overlay/popoverEngine.js"));
});
afterEach(() => {
delete global.window;
delete global.document;
delete global.HTMLElement;
delete global.Element;
delete global.Node;
delete global.getComputedStyle;
delete global.ResizeObserver;
delete globalThis.__floatingUiMock;
mock.restoreAll();
if (dom) {
dom.window.close();
}
dom = null;
documentRef = null;
windowRef = null;
createPopover = null;
computePositionStub = null;
autoUpdateStub = null;
mockedPosition = null;
});
test("flips placement when bottom placement would collide with viewport", async () => {
const trigger = documentRef.createElement("button");
trigger.id = "trigger";
trigger.type = "button";
trigger.textContent = "Open";
trigger.setAttribute("tabindex", "0");
documentRef.body.appendChild(trigger);
const active = { active: null };
attachFocus(trigger, active);
setupBoundingClientRect(trigger, {
x: 180,
y: 260,
top: 260,
bottom: 300,
left: 180,
right: 220,
width: 40,
height: 40,
});
setMockedPosition({ x: 96.3, y: 80.7, placement: "top-start" });
const popover = createPopover(
trigger,
({ container }) => {
const panel = documentRef.createElement("div");
panel.className = "popover__panel card";
panel.dataset.testPanel = "collision";
setupBoundingClientRect(panel, {
x: 0,
y: 0,
top: 0,
bottom: 140,
left: 0,
right: 220,
width: 220,
height: 140,
});
panel.focus = () => {
active.active = panel;
};
const menu = documentRef.createElement("ul");
menu.className = "menu";
for (let index = 0; index < 2; index += 1) {
const item = documentRef.createElement("button");
item.className = "menu__item";
item.setAttribute("role", "menuitem");
item.textContent = `Action ${index + 1}`;
item.focus = () => {
active.active = item;
};
menu.appendChild(item);
}
panel.appendChild(menu);
container.appendChild(panel);
return panel;
},
{ document: documentRef },
);
const opened = await popover.open();
assert.equal(opened, true, "popover should open");
const panel = documentRef.querySelector('[data-test-panel="collision"]');
assert.ok(panel, "panel should render");
const panelStyles = windowRef.getComputedStyle(panel);
assert.equal(panel.dataset.popoverState, "open");
assert.equal(panel.dataset.state, "open");
assert.equal(panel.dataset.popoverPlacement, "top-start");
assert.equal(panelStyles.getPropertyValue("--popover-strategy").trim(), "fixed");
assert.equal(panelStyles.getPropertyValue("--popover-left").trim(), "96px");
assert.equal(panelStyles.getPropertyValue("--popover-top").trim(), "81px");
assert.equal(
panelStyles.getPropertyValue("--popover-max-width").trim(),
"calc(100vw - var(--space-xl))",
);
const top = Number.parseInt(panelStyles.getPropertyValue("--popover-top"), 10);
const left = Number.parseInt(panelStyles.getPropertyValue("--popover-left"), 10);
assert.ok(Number.isFinite(top));
assert.ok(Number.isFinite(left));
assert.ok(top >= 0, "top should stay within viewport");
assert.ok(left >= 0, "left should stay within viewport");
assert.ok(top + panel.offsetHeight <= windowRef.innerHeight);
assert.ok(left + panel.offsetWidth <= windowRef.innerWidth);
popover.destroy();
});
test("closes previously open popovers without restoring focus", async () => {
const container = documentRef.getElementById("root");
const active = { active: null };
const createTrigger = (id, rectLeft) => {
const button = documentRef.createElement("button");
button.id = id;
button.type = "button";
button.textContent = id;
button.setAttribute("tabindex", "0");
attachFocus(button, active);
setupBoundingClientRect(button, {
x: rectLeft,
y: 80,
top: 80,
bottom: 120,
left: rectLeft,
right: rectLeft + 40,
width: 40,
height: 40,
});
container.appendChild(button);
return button;
};
const triggerA = createTrigger("triggerA", 40);
const triggerB = createTrigger("triggerB", 160);
let panelA;
let panelB;
const popoverA = createPopover(
triggerA,
({ container: portal }) => {
const panel = documentRef.createElement("div");
panel.className = "popover__panel";
panel.dataset.testPanel = "panel-a";
setupBoundingClientRect(panel, {
x: 0,
y: 0,
top: 0,
bottom: 100,
left: 0,
right: 160,
width: 160,
height: 100,
});
panel.focus = () => {
active.active = panel;
};
portal.appendChild(panel);
panelA = panel;
return panel;
},
{ document: documentRef },
);
const popoverB = createPopover(
triggerB,
({ container: portal }) => {
const panel = documentRef.createElement("div");
panel.className = "popover__panel";
panel.dataset.testPanel = "panel-b";
setupBoundingClientRect(panel, {
x: 0,
y: 0,
top: 0,
bottom: 100,
left: 0,
right: 160,
width: 160,
height: 100,
});
panel.focus = () => {
active.active = panel;
};
portal.appendChild(panel);
panelB = panel;
return panel;
},
{ document: documentRef },
);
await popoverA.open();
assert.equal(popoverA.isOpen(), true);
assert.equal(triggerA.getAttribute("aria-expanded"), "true");
assert.equal(panelA?.dataset.popoverState, "open");
assert.equal(panelA?.dataset.state, "open");
await popoverB.open();
assert.equal(popoverB.isOpen(), true);
assert.equal(triggerB.getAttribute("aria-expanded"), "true");
assert.equal(panelB?.dataset.popoverState, "open");
assert.equal(panelB?.dataset.state, "open");
assert.equal(popoverA.isOpen(), false, "first popover should close");
assert.equal(triggerA.getAttribute("aria-expanded"), "false");
assert.equal(panelA?.dataset.popoverState, "closed");
assert.equal(panelA?.dataset.state, "closed");
assert.notEqual(active.active, triggerA, "focus should not jump back to first trigger");
popoverA.destroy();
popoverB.destroy();
});
test("restores focus to the trigger when closed", async () => {
const trigger = documentRef.createElement("button");
trigger.id = "restoreTrigger";
trigger.type = "button";
trigger.textContent = "Restore";
trigger.setAttribute("tabindex", "0");
documentRef.body.appendChild(trigger);
const active = { active: null };
attachFocus(trigger, active);
setupBoundingClientRect(trigger, {
x: 40,
y: 40,
top: 40,
bottom: 80,
left: 40,
right: 80,
width: 40,
height: 40,
});
let firstItem = null;
const popover = createPopover(
trigger,
({ container }) => {
const panel = documentRef.createElement("div");
panel.className = "popover__panel";
panel.dataset.testPanel = "restore";
setupBoundingClientRect(panel, {
x: 0,
y: 0,
top: 0,
bottom: 80,
left: 0,
right: 160,
width: 160,
height: 80,
});
panel.focus = () => {
active.active = panel;
};
const menu = documentRef.createElement("ul");
menu.className = "menu";
firstItem = documentRef.createElement("button");
firstItem.className = "menu__item";
firstItem.setAttribute("role", "menuitem");
firstItem.textContent = "Play";
attachFocus(firstItem, active);
menu.appendChild(firstItem);
const secondItem = documentRef.createElement("button");
secondItem.className = "menu__item";
secondItem.setAttribute("role", "menuitem");
secondItem.textContent = "Share";
attachFocus(secondItem, active);
menu.appendChild(secondItem);
panel.appendChild(menu);
container.appendChild(panel);
return panel;
},
{ document: documentRef },
);
trigger.focus();
await popover.open();
assert.equal(popover.isOpen(), true);
assert.equal(active.active, firstItem);
const closed = popover.close();
assert.equal(closed, true);
assert.equal(popover.isOpen(), false);
assert.equal(trigger.getAttribute("aria-expanded"), "false");
assert.equal(active.active, trigger, "focus should return to trigger");
popover.destroy();
});