import { afterEach, describe, expect, it, vi } from "vitest";
import {
chromeMajor,
createDefectScan,
findCaptureDefect,
scanBackfaceDefect,
type DefectEnv,
} from "./htmlInCanvasDefects";
const ua = (major: number | null, headless = false) => ({
navigator: {
userAgent:
major === null
? "Mozilla/5.0 HappyDOM"
: `Mozilla/5.0 (Macintosh) AppleWebKit/537.36 (KHTML, like Gecko) ${headless ? "HeadlessChrome" : "Chrome"}/${major}.0.0.0 Safari/537.36`,
},
});
function mount(html: string, rootStyle = ""): HTMLElement {
document.body.innerHTML = `
${html}
`;
const root = document.getElementById("root");
if (!root) throw new Error("no root");
return root;
}
const env = (over: Partial = {}): DefectEnv => ({
isAccelerated: () => false,
backface: false,
...over,
});
const code = (root: HTMLElement, e: DefectEnv) => findCaptureDefect(root, e)?.code ?? null;
/** Every Chrome the render and preview browsers can be, and an unknown one. */
const VERSIONS = [152, 155, 156, 157, 158, null] as const;
/** The defect found with the page's user agent set to `major`. */
function codeOn(major: number | null, root: HTMLElement, e: DefectEnv): string | null {
const spy = vi
.spyOn(window.navigator, "userAgent", "get")
.mockReturnValue(ua(major).navigator.userAgent);
try {
return code(root, e);
} finally {
spy.mockRestore();
}
}
afterEach(() => {
document.body.innerHTML = document.head.innerHTML = "";
vi.unstubAllGlobals();
Reflect.deleteProperty(document, "getAnimations");
});
const animating = (target: Element, frames: object[]) => {
class Effect {
target = target;
getKeyframes = () => frames;
}
vi.stubGlobal("KeyframeEffect", Effect);
Object.assign(document, { getAnimations: () => [{ effect: new Effect() }] });
};
describe("chromeMajor", () => {
it("reads Chrome and HeadlessChrome, and is null for anything else", () => {
expect(chromeMajor(ua(152, true))).toBe(152);
expect(chromeMajor(ua(157))).toBe(157);
expect(chromeMajor(ua(null))).toBeNull();
});
});
describe("nested-capture-canvas", () => {
it("is found on every version, in either API form", () => {
for (const attr of ["layoutsubtree", 'content="drawable"']) {
const root = mount(``);
for (const major of VERSIONS)
expect(codeOn(major, root, env())).toBe("nested-capture-canvas");
}
});
});
describe("accelerated-canvas", () => {
const gl = (root: HTMLElement) => env({ isAccelerated: (c) => root.contains(c) });
it("applies on every version, 157 and 158 included", () => {
const root = mount("");
for (const major of VERSIONS) {
expect(codeOn(major, root, gl(root))).toBe("accelerated-canvas");
}
});
it("finds a WebGL canvas outside the root, where a render's shader compositor puts it", () => {
const root = mount("");
const outside = document.createElement("canvas");
document.body.append(outside);
for (const major of VERSIONS) {
expect(codeOn(major, root, env({ isAccelerated: (c) => c === outside }))).toBe(
"accelerated-canvas",
);
}
});
it("ignores a canvas the page did not give a WebGL context", () => {
expect(code(mount(""), env())).toBeNull();
});
});
describe("iframe-in-root", () => {
it("applies on every version: a reload cannot be undone", () => {
const root = mount('');
for (const major of VERSIONS) expect(codeOn(major, root, env())).toBe("iframe-in-root");
});
it("is not reported for a composition without an iframe", () => {
expect(code(mount("![]()
"), env())).toBeNull();
});
it("also covers