/** @vitest-environment jsdom */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { blurActiveBrowserElement, browserMediaQueryMatches, cancelBrowserAnimationFrame, configureBrowserDirectoryInput, isBrowserFontFaceLoaded, loadBrowserFontFace, loadBrowserFontStylesheet, observeBrowserResize, readBrowserCssCustomProperty, requestBrowserAnimationFrame, scheduleBrowserAnimationFrames, selectBrowserElementContents, subscribeBrowserElementEvent, subscribeBrowserWindowEvent, writeBrowserCookie, } from "./browser-transport"; beforeEach(() => { document.body.replaceChildren(); }); afterEach(() => { document.head.replaceChildren(); document.documentElement.style.removeProperty("--browser-style-test"); Reflect.deleteProperty(document, "fonts"); vi.restoreAllMocks(); vi.unstubAllGlobals(); }); describe("browser transport", () => { it("owns document focus, selection, attributes, and cookies", () => { const button = document.createElement("button"); const input = document.createElement("input"); const selectNodeContents = vi.fn(); const removeAllRanges = vi.fn(); const addRange = vi.fn(); vi.spyOn(document, "createRange").mockReturnValue({ selectNodeContents, } as unknown as Range); vi.spyOn(window, "getSelection").mockReturnValue({ addRange, removeAllRanges, } as unknown as Selection); button.textContent = "Select me"; document.body.append(button); button.focus(); selectBrowserElementContents(button); configureBrowserDirectoryInput(input); blurActiveBrowserElement(button); writeBrowserCookie("toolcraft-browser-transport=active; path=/"); expect(selectNodeContents).toHaveBeenCalledWith(button); expect(removeAllRanges).toHaveBeenCalledOnce(); expect(addRange).toHaveBeenCalledOnce(); expect(input.getAttribute("directory")).toBe(""); expect(document.activeElement).not.toBe(button); expect(document.cookie).toContain("toolcraft-browser-transport=active"); }); it("owns style, stylesheet, and font-face access", async () => { document.documentElement.style.setProperty("--browser-style-test", "#123456"); expect(readBrowserCssCustomProperty("--browser-style-test").trim()).toBe( "#123456", ); const pending = loadBrowserFontStylesheet("https://fonts.example/test.css"); const link = document.head.querySelector( "link[data-toolcraft-font-href]", ); link?.dispatchEvent(new Event("load")); await expect(pending).resolves.toBe(true); const check = vi.fn(() => false); const load = vi.fn(async () => []); Object.defineProperty(document, "fonts", { configurable: true, value: { check, load }, }); expect(isBrowserFontFaceLoaded("400 16px Test")).toBe(false); await expect(loadBrowserFontFace("400 16px Test")).resolves.toBe(true); }); it("owns window and element event subscription cleanup", () => { const windowListener = vi.fn(); const elementListener = vi.fn(); const element = document.createElement("div"); const cleanupWindow = subscribeBrowserWindowEvent("resize", windowListener); const cleanupElement = subscribeBrowserElementEvent( element, "scroll", elementListener, ); window.dispatchEvent(new Event("resize")); element.dispatchEvent(new Event("scroll")); cleanupWindow(); cleanupElement(); window.dispatchEvent(new Event("resize")); element.dispatchEvent(new Event("scroll")); expect(windowListener).toHaveBeenCalledTimes(1); expect(elementListener).toHaveBeenCalledTimes(1); }); it("owns ResizeObserver construction, observation, and cleanup", () => { const observe = vi.fn(); const disconnect = vi.fn(); const listener = vi.fn(); vi.stubGlobal("ResizeObserver", class { disconnect = disconnect; observe = observe; }); const first = document.createElement("div"); const second = document.createElement("span"); const cleanupResize = observeBrowserResize([first, second], listener); cleanupResize(); expect(observe).toHaveBeenNthCalledWith(1, first); expect(observe).toHaveBeenNthCalledWith(2, second); expect(disconnect).toHaveBeenCalledOnce(); }); it("owns animation frames and media preferences", () => { const callback = vi.fn(); cancelBrowserAnimationFrame(requestBrowserAnimationFrame(callback)); const cleanup = scheduleBrowserAnimationFrames(callback, 2); cleanup(); expect(callback).not.toHaveBeenCalled(); vi.spyOn(window, "matchMedia").mockReturnValue({ matches: true, } as MediaQueryList); expect(browserMediaQueryMatches("(prefers-reduced-motion: reduce)")).toBe(true); }); });