Closes #250: kill confirmation/force-kill (dialogs/KillDialog), the ? shortcuts overlay (dialogs/HelpOverlay), the general-purpose right-click menu (common/ContextMenu, closes on scroll/resize unlike the sidebar's own), the mobile drawer with swipe support (layout/Drawer), and the visual-viewport hook (hooks/useViewport). Each is a standalone, tested port of its vanilla-DOM source; wiring into the shell is a later issue.
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { cleanup, render } from "@testing-library/react";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { useViewport } from "./useViewport.js";
|
|
|
|
function Probe() {
|
|
useViewport();
|
|
return null;
|
|
}
|
|
|
|
class FakeVisualViewport {
|
|
height = 800;
|
|
offsetTop = 0;
|
|
private listeners: Record<string, Array<() => void>> = { resize: [], scroll: [] };
|
|
|
|
addEventListener(type: string, cb: () => void): void {
|
|
this.listeners[type].push(cb);
|
|
}
|
|
removeEventListener(type: string, cb: () => void): void {
|
|
this.listeners[type] = this.listeners[type].filter((l) => l !== cb);
|
|
}
|
|
fire(type: "resize" | "scroll"): void {
|
|
for (const l of this.listeners[type]) l();
|
|
}
|
|
}
|
|
|
|
let vv: FakeVisualViewport;
|
|
|
|
beforeEach(() => {
|
|
vv = new FakeVisualViewport();
|
|
vi.stubGlobal("visualViewport", vv);
|
|
vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) => {
|
|
cb(0);
|
|
return 0;
|
|
});
|
|
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
document.documentElement.className = "";
|
|
document.documentElement.removeAttribute("style");
|
|
});
|
|
|
|
describe("useViewport", () => {
|
|
it("mirrors the visual viewport's height and offset onto CSS custom properties", () => {
|
|
vv.height = 640;
|
|
vv.offsetTop = 12;
|
|
render(<Probe />);
|
|
const root = document.documentElement.style;
|
|
expect(root.getPropertyValue("--app-vh")).toBe("640px");
|
|
expect(root.getPropertyValue("--app-top")).toBe("12px");
|
|
});
|
|
|
|
it("no-ops when visualViewport is unavailable", () => {
|
|
vi.stubGlobal("visualViewport", undefined);
|
|
expect(() => render(<Probe />)).not.toThrow();
|
|
expect(document.documentElement.style.getPropertyValue("--app-vh")).toBe("");
|
|
});
|
|
|
|
it("marks kb-open once an editable shrinks the viewport well past the baseline", () => {
|
|
render(<Probe />);
|
|
const input = document.createElement("input");
|
|
document.body.appendChild(input);
|
|
input.focus();
|
|
|
|
vv.height = 800 - 200;
|
|
vv.fire("resize");
|
|
expect(document.documentElement.classList.contains("kb-open")).toBe(true);
|
|
|
|
input.blur();
|
|
vv.height = 800;
|
|
vv.fire("resize");
|
|
expect(document.documentElement.classList.contains("kb-open")).toBe(false);
|
|
input.remove();
|
|
});
|
|
});
|