diff --git a/test/popup.js b/test/popup.js new file mode 100644 index 0000000..dea2521 --- /dev/null +++ b/test/popup.js @@ -0,0 +1,102 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const { JSDOM } = require("jsdom"); + +const html = fs.readFileSync(path.join(__dirname, "../popup/popup.html"), "utf8") + .replace('', ""); +const source = fs.readFileSync(path.join(__dirname, "../popup/popup.js"), "utf8"); + +function tick() { return new Promise((resolve) => setTimeout(resolve, 0)); } + +async function openPopup({ url = "https://chatgpt.com/c/123", settings = {}, storageError = null } = {}) { + const writes = []; + const dom = new JSDOM(html, { url: "chrome-extension://test/popup/popup.html", runScripts: "dangerously" }); + const runtime = { lastError: null }; + dom.window.chrome = { + runtime, + storage: { + local: { + get(defaults, callback) { + runtime.lastError = storageError ? { message: storageError } : null; + callback({ ...defaults, ...settings }); + runtime.lastError = null; + }, + set(patch, callback) { + writes.push(patch); + if (callback) callback(); + } + }, + onChanged: { addListener() {} } + }, + tabs: { + query(_query, callback) { callback([{ url }]); } + } + }; + const script = dom.window.document.createElement("script"); + script.textContent = source; + dom.window.document.body.appendChild(script); + await tick(); + return { dom, document: dom.window.document, writes }; +} + +const tests = []; +function test(name, fn) { tests.push({ name, fn }); } + +test("shows the current host and an active text status", async () => { + const { document } = await openPopup(); + assert.equal(document.getElementById("host").textContent, "chatgpt.com"); + assert.match(document.getElementById("siteStatus").textContent, /active/i); + assert.equal(document.getElementById("siteStatus").getAttribute("role"), "status"); +}); + +test("exposes the approved settings and persists direction changes", async () => { + const { dom, document, writes } = await openPopup(); + for (const id of ["enabled", "thisSite", "fixDirection", "useFont", "fixInputs"]) { + assert.ok(document.getElementById(id), `missing #${id}`); + } + const direction = document.getElementById("fixDirection"); + direction.checked = false; + direction.dispatchEvent(new dom.window.Event("change", { bubbles: true })); + assert.equal(JSON.stringify(writes.at(-1)), JSON.stringify({ fixDirection: false })); +}); + +test("describes unsupported browser pages and disables the site switch", async () => { + const { document } = await openPopup({ url: "chrome://extensions" }); + assert.equal(document.getElementById("host").textContent, "Unavailable"); + assert.match(document.getElementById("siteStatus").textContent, /cannot be modified/i); + assert.equal(document.getElementById("thisSite").disabled, true); +}); + +test("a disabled master switch clearly disables dependent controls", async () => { + const { document } = await openPopup({ settings: { enabled: false } }); + assert.equal(document.getElementById("thisSite").disabled, true); + assert.equal(document.getElementById("fixDirection").disabled, true); + assert.equal(document.getElementById("useFont").disabled, true); + assert.equal(document.getElementById("fixInputs").disabled, true); + assert.match(document.getElementById("siteStatus").textContent, /paused/i); +}); + +test("storage failures produce a safe error state", async () => { + const { document } = await openPopup({ storageError: "Storage unavailable" }); + assert.match(document.getElementById("siteStatus").textContent, /settings unavailable/i); + assert.equal(document.getElementById("thisSite").disabled, true); +}); + +(async () => { + let passed = 0; + for (const { name, fn } of tests) { + try { + await fn(); + passed += 1; + console.log(`PASS ${name}`); + } catch (error) { + console.error(`FAIL ${name}`); + console.error(error.message); + } + } + console.log(`\n${passed}/${tests.length} passed`); + process.exitCode = passed === tests.length ? 0 : 1; +})();