205 lines
7.6 KiB
JavaScript
205 lines
7.6 KiB
JavaScript
import { describe, it } from './runner.mjs';
|
|
import { JSDOM } from 'jsdom';
|
|
|
|
const POPUP_HTML = `
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>VidPlex</title>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<h1>VidPlex</h1>
|
|
<section class="instructions-panel" role="region" aria-labelledby="instructions-title">
|
|
<div class="instructions-header">
|
|
<div>
|
|
<p class="instructions-kicker">Quick start</p>
|
|
<h2 id="instructions-title">How to use VidPlex</h2>
|
|
</div>
|
|
<button
|
|
id="instructions-toggle"
|
|
type="button"
|
|
class="instructions-toggle"
|
|
aria-expanded="true"
|
|
aria-controls="instructions-body"
|
|
>
|
|
Hide instructions
|
|
</button>
|
|
</div>
|
|
<div id="instructions-body" class="instructions-body">
|
|
<ol class="instructions-list">
|
|
<li>
|
|
<strong>Scan videos</strong>
|
|
<span>Click <strong>Find Videos</strong> to search the current browser window for videos.</span>
|
|
</li>
|
|
<li>
|
|
<strong>Review the list</strong>
|
|
<span>Check the popup list; each found video URL is clickable.</span>
|
|
</li>
|
|
<li>
|
|
<strong>Open the viewer</strong>
|
|
<span>Click <strong>Open Viewer</strong> to open the 2x2 grid player in a new window.</span>
|
|
</li>
|
|
<li>
|
|
<strong>Reset and rescan</strong>
|
|
<span>Click <strong>Clear</strong> to erase results and scan again.</span>
|
|
</li>
|
|
</ol>
|
|
<p class="instructions-note">Warning: Browser pages, extension pages, and restricted frames can hide videos, so results may be incomplete.</p>
|
|
<p class="instructions-learn-more">Learn more tooltip: Read the VidPlex README and audit notes.</p>
|
|
</div>
|
|
</section>
|
|
<button id="find-btn">Find Videos (all tabs)</button>
|
|
<div id="video-list" class="video-list" style="display:none;"></div>
|
|
<div id="status" class="status" style="min-height:16px;"></div>
|
|
<div id="btn-row" class="btn-row" style="display:none;">
|
|
<button id="clear-btn" class="btn-secondary">Clear</button>
|
|
<button id="open-btn">Open Viewer</button>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
|
|
function installPopupDom() {
|
|
const dom = new JSDOM(POPUP_HTML, { url: 'chrome-extension://test/src/viewer/index.html' });
|
|
const previous = {
|
|
window: globalThis.window,
|
|
document: globalThis.document,
|
|
navigator: globalThis.navigator,
|
|
chrome: globalThis.chrome,
|
|
HTMLElement: globalThis.HTMLElement,
|
|
Node: globalThis.Node,
|
|
};
|
|
|
|
globalThis.window = dom.window;
|
|
globalThis.document = dom.window.document;
|
|
Object.defineProperty(globalThis, 'navigator', {
|
|
value: dom.window.navigator,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
Object.defineProperty(globalThis, 'HTMLElement', {
|
|
value: dom.window.HTMLElement,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
Object.defineProperty(globalThis, 'Node', {
|
|
value: dom.window.Node,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
globalThis.chrome = {
|
|
storage: {
|
|
local: {
|
|
get: async () => ({}),
|
|
set: async () => undefined,
|
|
remove: async () => undefined,
|
|
},
|
|
},
|
|
tabs: { query: async () => [] },
|
|
scripting: { executeScript: async () => [] },
|
|
runtime: { getURL: (path) => `chrome-extension://test/${path}` },
|
|
windows: { create: async () => ({ id: 1 }) },
|
|
};
|
|
|
|
return {
|
|
dom,
|
|
cleanup() {
|
|
dom.window.close();
|
|
globalThis.window = previous.window;
|
|
globalThis.document = previous.document;
|
|
Object.defineProperty(globalThis, 'navigator', {
|
|
value: previous.navigator,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
globalThis.chrome = previous.chrome;
|
|
Object.defineProperty(globalThis, 'HTMLElement', {
|
|
value: previous.HTMLElement,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
Object.defineProperty(globalThis, 'Node', {
|
|
value: previous.Node,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
async function mountPopup() {
|
|
const { dom, cleanup } = installPopupDom();
|
|
const moduleUrl = new URL('../src/viewer/index.js?test=' + Date.now(), import.meta.url);
|
|
await import(moduleUrl.href);
|
|
return { dom, cleanup };
|
|
}
|
|
|
|
describe('Viewer popup instructions', () => {
|
|
it('renders the drafted instructions copy on mount with accessible labeling', async () => {
|
|
const { dom, cleanup } = await mountPopup();
|
|
try {
|
|
const doc = dom.window.document;
|
|
const region = doc.querySelector('.instructions-panel');
|
|
const title = doc.getElementById('instructions-title');
|
|
const toggle = doc.getElementById('instructions-toggle');
|
|
const body = doc.getElementById('instructions-body');
|
|
|
|
if (!region) throw new Error('Instructions region missing');
|
|
if (region.getAttribute('role') !== 'region') throw new Error('Instructions region missing role="region"');
|
|
if (region.getAttribute('aria-labelledby') !== 'instructions-title') throw new Error('Instructions region missing aria-labelledby');
|
|
if (!title || title.textContent !== 'How to use VidPlex') throw new Error('Instructions title not rendered');
|
|
if (!toggle) throw new Error('Instructions toggle missing');
|
|
if (toggle.getAttribute('aria-controls') !== 'instructions-body') throw new Error('Toggle missing aria-controls');
|
|
if (toggle.getAttribute('aria-expanded') !== 'true') throw new Error('Toggle should start expanded');
|
|
if (!body || body.hidden) throw new Error('Instructions body should be visible on mount');
|
|
|
|
const expectedTexts = [
|
|
'Scan videos',
|
|
'Click Find Videos to search the current browser window for videos.',
|
|
'Review the list',
|
|
'Check the popup list; each found video URL is clickable.',
|
|
'Open the viewer',
|
|
'Click Open Viewer to open the 2x2 grid player in a new window.',
|
|
'Reset and rescan',
|
|
'Click Clear to erase results and scan again.',
|
|
'Warning: Browser pages, extension pages, and restricted frames can hide videos, so results may be incomplete.',
|
|
'Learn more tooltip: Read the VidPlex README and audit notes.',
|
|
];
|
|
|
|
for (const text of expectedTexts) {
|
|
if (!doc.body.textContent.includes(text)) {
|
|
throw new Error(`Missing instructions copy: ${text}`);
|
|
}
|
|
}
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
});
|
|
|
|
it('toggles the instructions panel open and closed', async () => {
|
|
const { dom, cleanup } = await mountPopup();
|
|
try {
|
|
const doc = dom.window.document;
|
|
const toggle = doc.getElementById('instructions-toggle');
|
|
const body = doc.getElementById('instructions-body');
|
|
|
|
if (!toggle || !body) throw new Error('Instructions controls missing');
|
|
|
|
toggle.click();
|
|
if (toggle.getAttribute('aria-expanded') !== 'false') throw new Error('Toggle should collapse the instructions');
|
|
if (!body.hidden) throw new Error('Instructions body should be hidden after collapse');
|
|
if (toggle.textContent !== 'Show instructions') throw new Error('Collapsed label should switch to Show instructions');
|
|
|
|
toggle.click();
|
|
if (toggle.getAttribute('aria-expanded') !== 'true') throw new Error('Toggle should expand the instructions');
|
|
if (body.hidden) throw new Error('Instructions body should be visible after expanding');
|
|
if (toggle.textContent !== 'Hide instructions') throw new Error('Expanded label should switch back to Hide instructions');
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
});
|
|
});
|