test: give editor tests a document head and its window
This commit is contained in:
1 parent
f7d7e8e72c
commit
b508ff0974
3 files changed
+94
-15
No files matched your search
@@ -1,12 +1,28 @@
|
||||
const restoreUnconditionally = Symbol("restore-unconditionally");
|
||||
const noop = () => {};
|
||||
|
||||
// Linux Bun exposes `document` without a `window` binding. Test files then
|
||||
// install plain objects. ProseMirror and TipTap read `window.MutationObserver`
|
||||
// and `window.addEventListener` from that object, so a leftover stub needs
|
||||
// those methods. Do not create a window when none is installed: assigning one
|
||||
// is unnecessary on hosts whose editor already resolves `window` elsewhere.
|
||||
// Linux Bun can be left with a plain window object, or with a linkedom
|
||||
// document whose view is not installed as `window`. TipTap reads
|
||||
// `window.addEventListener` and appends CSS to `document.head`.
|
||||
export const ensureTestDocumentHead = () => {
|
||||
const doc = globalThis.document;
|
||||
if (!doc || typeof doc.createElement !== "function" || typeof doc.getElementsByTagName !== "function") return;
|
||||
if (doc.getElementsByTagName("head")[0]) return;
|
||||
const head = doc.createElement("head");
|
||||
const html = doc.documentElement;
|
||||
if (html && typeof html.insertBefore === "function") {
|
||||
html.insertBefore(head, html.firstChild ?? null);
|
||||
return;
|
||||
}
|
||||
if (typeof doc.appendChild === "function") doc.appendChild(head);
|
||||
};
|
||||
|
||||
export const ensureTestWindowDom = () => {
|
||||
const view = globalThis.document?.defaultView;
|
||||
if (view && typeof globalThis.window?.addEventListener !== "function") {
|
||||
globalThis.window = view;
|
||||
}
|
||||
ensureTestDocumentHead();
|
||||
const current = globalThis.window;
|
||||
if (!current || typeof current !== "object") return;
|
||||
if (typeof current.addEventListener !== "function") current.addEventListener = noop;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { ensureTestWindowDom, restoreTestGlobal } from "./restore-test-global.mjs";
|
||||
import { ensureTestDocumentHead, ensureTestWindowDom, restoreTestGlobal } from "./restore-test-global.mjs";
|
||||
|
||||
test("restoreTestGlobal keeps a window object when nothing was there before", () => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
|
||||
delete globalThis.window;
|
||||
const stub = { localStorage: {} };
|
||||
globalThis.window = stub;
|
||||
restoreTestGlobal("window", undefined, stub);
|
||||
@@ -26,6 +25,50 @@ test("restoreTestGlobal leaves a global that another file replaced", () => {
|
||||
else delete globalThis.document;
|
||||
});
|
||||
|
||||
test("ensureTestDocumentHead inserts a head when the document has none", () => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "document");
|
||||
const heads = [];
|
||||
const html = {
|
||||
firstChild: null,
|
||||
insertBefore(node) {
|
||||
heads.push(node);
|
||||
},
|
||||
};
|
||||
globalThis.document = {
|
||||
documentElement: html,
|
||||
createElement: (name) => ({ nodeName: name, appendChild() {} }),
|
||||
getElementsByTagName: (name) => (name === "head" ? heads : []),
|
||||
};
|
||||
ensureTestDocumentHead();
|
||||
expect(heads).toHaveLength(1);
|
||||
ensureTestDocumentHead();
|
||||
expect(heads).toHaveLength(1);
|
||||
if (descriptor) Object.defineProperty(globalThis, "document", descriptor);
|
||||
else delete globalThis.document;
|
||||
});
|
||||
|
||||
test("ensureTestWindowDom uses the document view when the leftover window cannot listen", () => {
|
||||
const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
|
||||
const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document");
|
||||
const view = {
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
};
|
||||
globalThis.document = {
|
||||
defaultView: view,
|
||||
documentElement: { insertBefore() {} },
|
||||
createElement: (name) => ({ nodeName: name }),
|
||||
getElementsByTagName: () => [{}],
|
||||
};
|
||||
globalThis.window = { edgeeverDesktop: { isAvailable: true } };
|
||||
ensureTestWindowDom();
|
||||
expect(globalThis.window).toBe(view);
|
||||
if (windowDescriptor) Object.defineProperty(globalThis, "window", windowDescriptor);
|
||||
else delete globalThis.window;
|
||||
if (documentDescriptor) Object.defineProperty(globalThis, "document", documentDescriptor);
|
||||
else delete globalThis.document;
|
||||
});
|
||||
|
||||
test("ensureTestWindowDom fills event methods on a leftover window", () => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
|
||||
const stub = { edgeeverDesktop: { isAvailable: true } };
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
import { beforeAll, describe, expect, test } from "bun:test";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { parseHTML } from "../../../../node_modules/.bun/linkedom@0.18.13/node_modules/linkedom/esm/index.js";
|
||||
|
||||
const window = parseHTML("<!DOCTYPE html><html><body></body></html>");
|
||||
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
|
||||
const originalDocument = Object.getOwnPropertyDescriptor(globalThis, "document");
|
||||
const originalNode = Object.getOwnPropertyDescriptor(globalThis, "Node");
|
||||
const originalHTMLElement = Object.getOwnPropertyDescriptor(globalThis, "HTMLElement");
|
||||
const originalGetComputedStyle = Object.getOwnPropertyDescriptor(globalThis, "getComputedStyle");
|
||||
|
||||
const linkedomWindow = parseHTML("<!DOCTYPE html><html><head></head><body></body></html>");
|
||||
try {
|
||||
Object.defineProperty(window.document, "compatMode", { value: "CSS1Compat" });
|
||||
Object.defineProperty(linkedomWindow.document, "compatMode", { value: "CSS1Compat" });
|
||||
} catch {
|
||||
// linkedom may already expose a read-only quirks flag. KaTeX still renders.
|
||||
}
|
||||
globalThis.document = window.document;
|
||||
globalThis.Node = window.Node;
|
||||
globalThis.HTMLElement = window.HTMLElement;
|
||||
globalThis.getComputedStyle = typeof window.getComputedStyle === "function"
|
||||
? window.getComputedStyle.bind(window)
|
||||
globalThis.window = linkedomWindow;
|
||||
globalThis.document = linkedomWindow.document;
|
||||
globalThis.Node = linkedomWindow.Node;
|
||||
globalThis.HTMLElement = linkedomWindow.HTMLElement;
|
||||
globalThis.getComputedStyle = typeof linkedomWindow.getComputedStyle === "function"
|
||||
? linkedomWindow.getComputedStyle.bind(linkedomWindow)
|
||||
: () => ({ getPropertyValue: () => "" });
|
||||
|
||||
const restoreGlobal = (name, descriptor) => {
|
||||
if (descriptor) Object.defineProperty(globalThis, name, descriptor);
|
||||
else delete globalThis[name];
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
restoreGlobal("window", originalWindow);
|
||||
restoreGlobal("document", originalDocument);
|
||||
restoreGlobal("Node", originalNode);
|
||||
restoreGlobal("HTMLElement", originalHTMLElement);
|
||||
restoreGlobal("getComputedStyle", originalGetComputedStyle);
|
||||
});
|
||||
|
||||
const css = (element) => (element?.style.cssText ?? "").replace(/:\s+/g, ":");
|
||||
|
||||
let preparePublishArticle;
|
||||
|
||||
Reference in new issue
Block a user