// Smoke test for the Empty Link Fixer content script. // // Runs the actual config.js + content.js against jsdom pages and verifies that // empty anchors are filled — statically, on dynamically added content, emptied // text, late href attributes and inside (open) shadow DOM — while links that // have text or media content are left alone. // // Usage: node tests/smoke.mjs (needs `npm install --no-save jsdom`) import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import assert from 'node:assert/strict'; import { JSDOM } from 'jsdom'; const dir = fileURLToPath(new URL('..', import.meta.url)); const configJs = readFileSync(`${dir}config.js`, 'utf8'); const contentJs = readFileSync(`${dir}content.js`, 'utf8'); const tick = (ms = 40) => new Promise((r) => setTimeout(r, ms)); function boot(html, { url = 'https://example.com/', hosts = ['example.com'] } = {}) { const dom = new JSDOM(html, { url, runScripts: 'outside-only', pretendToBeVisual: true, }); const { window } = dom; window.eval(configJs); window.eval(`globalThis.LINK_FIX_CONFIG = ${JSON.stringify({ hosts, includeSubdomains: true, rescanIntervalMs: 0, // disable the safety-net timer during tests debug: false, })};`); window.eval(contentJs); return dom; } const $ = (dom, sel) => dom.window.document.querySelector(sel); async function run() { // ---------------------------------------------------------------- static { const dom = boot(` Already has text icon `); await tick(); assert.equal($(dom, '#e1').textContent, 'https://example.com/a', 'empty anchor filled with href'); assert.equal($(dom, '#e2').textContent.trim(), 'Already has text', 'text anchor untouched'); assert.equal($(dom, '#e3').textContent, '', 'image anchor untouched'); assert.ok($(dom, '#e3').querySelector('img'), 'image preserved inside anchor'); assert.equal($(dom, '#e4').textContent, '', 'svg anchor untouched'); assert.ok($(dom, '#e4').querySelector('svg'), 'svg preserved inside anchor'); assert.equal($(dom, '#e5').textContent.trim(), '/ws', 'whitespace-only anchor filled'); assert.equal($(dom, '#e6').textContent, '', 'placeholder href="#" not filled'); assert.equal($(dom, '#e7').textContent, '', 'javascript: href not filled'); assert.equal($(dom, '#e8').textContent.trim(), '/hidden', 'empty anchor filled regardless of visibility'); dom.window.close(); } // ------------------------------------------------- dynamically inserted { const dom = boot('
'); await tick(); dom.window.document.querySelector('#root').innerHTML = 'keep me'; await tick(); assert.equal($(dom, 'a[href="/dyn"]').textContent.trim(), '/dyn', 'dynamically added empty anchor filled'); assert.equal($(dom, 'a[href="/kept"]').textContent.trim(), 'keep me', 'dynamically added text anchor untouched'); dom.window.close(); } // ------------------------------------------------- text emptied in place { const dom = boot('content here'); await tick(); // Simulate a framework emptying the text node in place (characterData). dom.window.document.querySelector('#t1').firstChild.nodeValue = ''; await tick(); assert.equal($(dom, '#t1').textContent.trim(), '/t1', 'anchor re-filled after its text node was emptied'); dom.window.close(); } // ------------------------------------------------- children replaced { const dom = boot('content here'); await tick(); // Simulate a re-render that wipes the inner content entirely. dom.window.document.querySelector('#t2').textContent = ''; await tick(); assert.equal($(dom, '#t2').textContent.trim(), '/t2', 'anchor re-filled after children were removed'); dom.window.close(); } // ------------------------------------------------- href added later { const dom = boot(''); await tick(); dom.window.document.querySelector('#t3').setAttribute('href', '/later'); await tick(); assert.equal($(dom, '#t3').textContent.trim(), '/later', 'anchor filled when href appears dynamically'); dom.window.close(); } // ------------------------------------------------- open shadow DOM { const dom = boot('
'); const { document } = dom.window; await tick(); const host = document.querySelector('#host'); const root = host.attachShadow({ mode: 'open' }); root.innerHTML = 'shadow text'; document.body.appendChild(host); // host enters the DOM after init await tick(); assert.equal(root.querySelector('a[href="/shadow"]').textContent.trim(), '/shadow', 'anchor inside open shadow root filled'); assert.equal(root.querySelector('a[href="/shadow2"]').textContent.trim(), 'shadow text', 'text anchor in shadow root untouched'); dom.window.close(); } // ------------------------------------------------- different site { const dom = boot('', { url: 'https://somewhere-else.com/', hosts: ['example.com'], }); await tick(); assert.equal($(dom, '#off').textContent, '', 'nothing happens on a non-configured site'); dom.window.close(); } console.log('All smoke tests passed ✔'); } run().catch((err) => { console.error(err); process.exit(1); });