fix(boot): a stylesheet lost on the way is asked for again
A lost stylesheet left the page unstyled until a reload, and every e2e step that finds elements by testid still passed on it. Only axe noticed: the one-off target-size failure on eight scattered controls. Losing the main stylesheet on purpose gives those same eight.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { clear, events } from "./log";
|
||||
import { retryLostStylesheets } from "./stylesheetRetry";
|
||||
|
||||
const LOADED = { cssRules: [{}] };
|
||||
// A lost stylesheet still has a `sheet`, and reading its rules throws (measured: Chromium, WebKit).
|
||||
const LOST = {
|
||||
get cssRules(): never {
|
||||
throw new DOMException("Cannot access rules", "SecurityError");
|
||||
},
|
||||
};
|
||||
|
||||
function link(href: string, loaded: boolean, sheet: unknown = loaded ? LOADED : LOST) {
|
||||
const el = document.createElement("link");
|
||||
el.rel = "stylesheet";
|
||||
el.href = href;
|
||||
el.setAttribute("crossorigin", "");
|
||||
Object.defineProperty(el, "sheet", { value: sheet, configurable: true });
|
||||
document.head.append(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
const links = () => [...document.head.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')];
|
||||
const logged = () => events().map((e) => e.event);
|
||||
|
||||
describe("retryLostStylesheets", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
clear();
|
||||
});
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
document.head.innerHTML = "";
|
||||
});
|
||||
|
||||
it("leaves a loaded stylesheet alone", () => {
|
||||
const fine = link("/a.css", true);
|
||||
retryLostStylesheets();
|
||||
vi.runAllTimers();
|
||||
expect(links()).toEqual([fine]);
|
||||
expect(logged()).toEqual([]);
|
||||
});
|
||||
|
||||
it("asks again for a lost one, in its place, keeping its attributes", () => {
|
||||
const first = link("/a.css", true);
|
||||
const lost = link("/b.css", false);
|
||||
const last = link("/c.css", true);
|
||||
retryLostStylesheets();
|
||||
vi.advanceTimersByTime(0);
|
||||
const [a, b, c] = links();
|
||||
expect(a).toBe(first);
|
||||
expect(b).not.toBe(lost);
|
||||
expect(b!.getAttribute("href")).toBe("/b.css");
|
||||
expect(b!.hasAttribute("crossorigin")).toBe(true);
|
||||
expect(c).toBe(last);
|
||||
expect(logged()).toEqual(["app.stylesheet_lost"]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["no sheet at all", null],
|
||||
["an empty sheet", { cssRules: [] }],
|
||||
])("takes a link with %s for lost too", (_, sheet) => {
|
||||
const lost = link("/a.css", false, sheet);
|
||||
retryLostStylesheets();
|
||||
vi.advanceTimersByTime(0);
|
||||
expect(links()[0]).not.toBe(lost);
|
||||
});
|
||||
|
||||
it("says so once the new request has loaded", () => {
|
||||
link("/a.css", false);
|
||||
retryLostStylesheets();
|
||||
vi.advanceTimersByTime(0);
|
||||
links()[0]!.dispatchEvent(new Event("load"));
|
||||
vi.runAllTimers();
|
||||
expect(logged()).toEqual(["app.stylesheet_lost", "app.stylesheet_recovered"]);
|
||||
});
|
||||
|
||||
it("waits longer each time and then gives up, reporting it", () => {
|
||||
link("/a.css", false);
|
||||
retryLostStylesheets();
|
||||
const fail = () => links()[0]!.dispatchEvent(new Event("error"));
|
||||
|
||||
vi.advanceTimersByTime(0);
|
||||
const second = links()[0];
|
||||
fail();
|
||||
vi.advanceTimersByTime(999);
|
||||
expect(links()[0]).toBe(second);
|
||||
vi.advanceTimersByTime(1);
|
||||
const third = links()[0];
|
||||
expect(third).not.toBe(second);
|
||||
|
||||
fail();
|
||||
vi.advanceTimersByTime(2999);
|
||||
expect(links()[0]).toBe(third);
|
||||
vi.advanceTimersByTime(1);
|
||||
const fourth = links()[0];
|
||||
expect(fourth).not.toBe(third);
|
||||
|
||||
fail();
|
||||
vi.runAllTimers();
|
||||
expect(links()[0]).toBe(fourth);
|
||||
expect(logged()).toEqual(["app.stylesheet_lost", "app.stylesheet_gave_up"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user