feat: add user instructions to viewer popup
This commit is contained in:
1 parent
c366f0b8c4
commit
97abaf42d9
9 files changed
+462
No files matched your search
@@ -0,0 +1,204 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import './schema.test.mjs';
|
||||
import './viewer_units.test.mjs';
|
||||
import './instructions_ui.test.mjs';
|
||||
import './data_flow.test.mjs';
|
||||
import './viewer_integration.test.mjs';
|
||||
import './config_validation.test.mjs';
|
||||
|
||||
Reference in new issue
Block a user