fix: some warnings, refactor: changed the donation look, moved it up

This commit is contained in:
joshpatra committed 2026-04-09 16:39:13 -04:00
1 parent 41d89e0993
commit 0b6bc5d0a0
10 files changed
+388 -65

No files matched your search

+64 -8
View File
@@ -1,7 +1,9 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { JSDOM } from "jsdom";
import { vi } from "vitest";
import { applyJSDOMWindow } from "./jsdom-globals.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -11,19 +13,73 @@ function readRepoFile(relPath) {
return fs.readFileSync(path.join(repoRoot, relPath), "utf8");
}
/**
* Parse HTML into a fresh JSDOM document so tests can reload scripts without
* top-level `const` redeclaration errors (avoids document.write).
*/
export function loadHtmlString(html) {
const dom = new JSDOM(html, {
url: "https://example.org/",
pretendToBeVisual: true,
runScripts: "dangerously"
});
applyJSDOMWindow(dom.window);
}
export function loadHtml(relPath) {
document.open();
document.write(readRepoFile(relPath));
document.close();
loadHtmlString(readRepoFile(relPath));
}
const WINDOW_GLOBAL_SKIP = new Set([
"alert",
"atob",
"blur",
"btoa",
"cancelAnimationFrame",
"captureEvents",
"clearInterval",
"clearTimeout",
"close",
"confirm",
"fetch",
"focus",
"getComputedStyle",
"matchMedia",
"open",
"prompt",
"queueMicrotask",
"releaseEvents",
"requestAnimationFrame",
"setInterval",
"setTimeout",
"stop"
]);
function mirrorExtensionGlobalsFromWindow(win) {
if (!win) return;
if (win.tc) {
globalThis.tc = win.tc;
}
for (const key of Object.keys(win)) {
if (WINDOW_GLOBAL_SKIP.has(key)) continue;
if (/^[A-Z]/.test(key)) continue;
const val = win[key];
if (typeof val === "function") {
globalThis[key] = val;
}
}
}
export function loadScript(relPath) {
window.eval(
const source =
"var chrome = window.chrome || globalThis.chrome;\n" +
readRepoFile(relPath) +
"\n//# sourceURL=" +
relPath
);
readRepoFile(relPath) +
"\n//# sourceURL=" +
relPath;
const el = document.createElement("script");
el.textContent = source;
document.head.appendChild(el);
mirrorExtensionGlobalsFromWindow(window);
}
export async function flushAsyncWork() {
+79 -10
View File
@@ -1,5 +1,6 @@
const fs = require("fs");
const path = require("path");
const { JSDOM } = require("jsdom");
const { vi } = require("vitest");
const ROOT = path.resolve(__dirname, "..", "..");
@@ -18,22 +19,90 @@ function readWorkspaceFile(relPath) {
}
function loadHtmlFile(relPath) {
document.open();
document.write(readWorkspaceFile(relPath));
document.close();
loadHtmlString(readWorkspaceFile(relPath));
}
function applyJSDOMWindow(win) {
globalThis.window = win;
globalThis.document = win.document;
globalThis.navigator = win.navigator;
globalThis.customElements = win.customElements;
globalThis.HTMLElement = win.HTMLElement;
globalThis.Element = win.Element;
globalThis.Node = win.Node;
globalThis.Text = win.Text;
globalThis.DocumentFragment = win.DocumentFragment;
globalThis.Event = win.Event;
globalThis.MouseEvent = win.MouseEvent;
globalThis.KeyboardEvent = win.KeyboardEvent;
globalThis.DOMParser = win.DOMParser;
globalThis.URL = win.URL;
globalThis.Blob = win.Blob;
globalThis.FileReader = win.FileReader;
win.Date = globalThis.Date;
win.open = vi.fn();
win.close = vi.fn();
}
function loadHtmlString(html) {
document.open();
document.write(html);
document.close();
const dom = new JSDOM(html, {
url: "https://example.org/",
pretendToBeVisual: true,
runScripts: "dangerously"
});
applyJSDOMWindow(dom.window);
}
const WINDOW_GLOBAL_SKIP = new Set([
"alert",
"atob",
"blur",
"btoa",
"cancelAnimationFrame",
"captureEvents",
"clearInterval",
"clearTimeout",
"close",
"confirm",
"fetch",
"focus",
"getComputedStyle",
"matchMedia",
"open",
"prompt",
"queueMicrotask",
"releaseEvents",
"requestAnimationFrame",
"setInterval",
"setTimeout",
"stop"
]);
function mirrorExtensionGlobalsFromWindow(win) {
if (!win) return;
if (win.tc) {
globalThis.tc = win.tc;
}
for (const key of Object.keys(win)) {
if (WINDOW_GLOBAL_SKIP.has(key)) continue;
if (/^[A-Z]/.test(key)) continue;
const val = win[key];
if (typeof val === "function") {
globalThis[key] = val;
}
}
}
function evaluateScript(relPath) {
const source = readWorkspaceFile(relPath);
window.eval(
`${source}\n//# sourceURL=${workspacePath(relPath).replace(/\\/g, "/")}`
);
const absPath = workspacePath(relPath);
const source =
"var chrome = window.chrome || globalThis.chrome;\n" +
readWorkspaceFile(relPath) +
`\n//# sourceURL=${absPath.replace(/\\/g, "/")}`;
const el = document.createElement("script");
el.textContent = source;
document.head.appendChild(el);
mirrorExtensionGlobalsFromWindow(window);
}
function fireDOMContentLoaded() {
+30
View File
@@ -0,0 +1,30 @@
/**
* Point Vitest/jsdom test globals at a new JSDOM window (no document.write).
* Call after creating `new JSDOM(html, options).window`.
*/
import { vi } from "vitest";
export function applyJSDOMWindow(win) {
globalThis.window = win;
globalThis.document = win.document;
globalThis.navigator = win.navigator;
globalThis.customElements = win.customElements;
globalThis.HTMLElement = win.HTMLElement;
globalThis.Element = win.Element;
globalThis.Node = win.Node;
globalThis.Text = win.Text;
globalThis.DocumentFragment = win.DocumentFragment;
globalThis.Event = win.Event;
globalThis.MouseEvent = win.MouseEvent;
globalThis.KeyboardEvent = win.KeyboardEvent;
globalThis.DOMParser = win.DOMParser;
globalThis.URL = win.URL;
globalThis.Blob = win.Blob;
globalThis.FileReader = win.FileReader;
// Vitest fake timers patch host `Date`; jsdom’s window keeps its own otherwise.
win.Date = globalThis.Date;
win.open = vi.fn();
win.close = vi.fn();
}
+32 -17
View File
@@ -9,7 +9,9 @@ async function setupImportExport(overrides = {}) {
loadHtml("options.html");
globalThis.chrome = createChromeMock(overrides);
window.chrome = globalThis.chrome;
globalThis.restore_options = vi.fn();
const restoreSpy = vi.fn();
globalThis.restore_options = restoreSpy;
window.restore_options = restoreSpy;
loadScript("shared/import-export.js");
loadScript("importExport.js");
await flushAsyncWork();
@@ -24,8 +26,8 @@ describe("import/export flows", () => {
sync: { rememberSpeed: true },
local: { customButtonIcons: { faster: { slug: "rocket" } } }
});
const OriginalBlob = globalThis.Blob;
globalThis.Blob = class TestBlob {
const OriginalBlob = window.Blob;
class TestBlob {
constructor(parts, options) {
this.parts = parts;
this.options = options;
@@ -34,24 +36,28 @@ describe("import/export flows", () => {
async text() {
return this.parts.join("");
}
};
}
globalThis.Blob = TestBlob;
window.Blob = TestBlob;
let capturedBlob = null;
let clickedDownload = null;
Object.defineProperty(URL, "createObjectURL", {
Object.defineProperty(window.URL, "createObjectURL", {
configurable: true,
value: vi.fn((blob) => {
capturedBlob = blob;
return "blob:test";
})
});
Object.defineProperty(URL, "revokeObjectURL", {
Object.defineProperty(window.URL, "revokeObjectURL", {
configurable: true,
value: vi.fn(() => {})
});
vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(function () {
clickedDownload = this.download;
});
vi.spyOn(window.HTMLAnchorElement.prototype, "click").mockImplementation(
function () {
clickedDownload = this.download;
}
);
document.getElementById("exportSettings").click();
@@ -70,6 +76,7 @@ describe("import/export flows", () => {
expect(chrome.storage.sync.get).toHaveBeenCalled();
expect(chrome.storage.local.get).toHaveBeenCalled();
globalThis.Blob = OriginalBlob;
window.Blob = OriginalBlob;
});
it("imports wrapped backup payloads and refreshes options", async () => {
@@ -87,7 +94,7 @@ describe("import/export flows", () => {
return el;
});
globalThis.FileReader = class MockFileReader {
class MockFileReader {
readAsText(file) {
this.onload({
target: {
@@ -95,7 +102,9 @@ describe("import/export flows", () => {
}
});
}
};
}
globalThis.FileReader = MockFileReader;
window.FileReader = MockFileReader;
globalThis.importSettings();
createdInput.onchange({
@@ -145,7 +154,7 @@ describe("import/export flows", () => {
return el;
});
globalThis.FileReader = class MockFileReader {
class MockFileReader {
readAsText(file) {
this.onload({
target: {
@@ -153,7 +162,9 @@ describe("import/export flows", () => {
}
});
}
};
}
globalThis.FileReader = MockFileReader;
window.FileReader = MockFileReader;
globalThis.importSettings();
createdInput.onchange({
@@ -200,7 +211,7 @@ describe("import/export flows", () => {
return el;
});
globalThis.FileReader = class MockFileReader {
class MockFileReader {
readAsText(file) {
this.onload({
target: {
@@ -208,7 +219,9 @@ describe("import/export flows", () => {
}
});
}
};
}
globalThis.FileReader = MockFileReader;
window.FileReader = MockFileReader;
globalThis.importSettings();
createdInput.onchange({
@@ -247,7 +260,7 @@ describe("import/export flows", () => {
return el;
});
globalThis.FileReader = class MockFileReader {
class MockFileReader {
readAsText(file) {
this.onload({
target: {
@@ -255,7 +268,9 @@ describe("import/export flows", () => {
}
});
}
};
}
globalThis.FileReader = MockFileReader;
window.FileReader = MockFileReader;
globalThis.importSettings();
createdInput.onchange({
+7 -4
View File
@@ -1,10 +1,13 @@
import { describe, expect, it, vi } from "vitest";
import { createChromeMock, flushAsyncWork, loadScript } from "./helpers/browser.js";
import {
createChromeMock,
flushAsyncWork,
loadHtmlString,
loadScript
} from "./helpers/browser.js";
function loadBlankDocument() {
document.open();
document.write("<!doctype html><html><body></body></html>");
document.close();
loadHtmlString("<!doctype html><html><body></body></html>");
}
async function bootInject({ sync = {}, local = {} } = {}) {
+5 -1
View File
@@ -16,7 +16,11 @@ beforeEach(() => {
afterEach(() => {
vi.useRealTimers();
delete globalThis.SpeederShared;
delete globalThis.restore_options;
try {
delete globalThis.restore_options;
} catch {
globalThis.restore_options = undefined;
}
if (typeof document !== "undefined") {
document.head.innerHTML = "";
document.body.innerHTML = "";