summaryrefslogtreecommitdiff
path: root/tests/ui-smoke-tests.js
diff options
context:
space:
mode:
Diffstat (limited to 'tests/ui-smoke-tests.js')
-rw-r--r--tests/ui-smoke-tests.js156
1 files changed, 156 insertions, 0 deletions
diff --git a/tests/ui-smoke-tests.js b/tests/ui-smoke-tests.js
new file mode 100644
index 0000000..84fe4c4
--- /dev/null
+++ b/tests/ui-smoke-tests.js
@@ -0,0 +1,156 @@
+/**
+ * UI smoke tests: load the real page scripts (popup.js / options.js) as ES
+ * modules against a mocked browser API and verify that they initialize the
+ * DOM. This catches import/export mismatches that pure unit tests miss
+ * (e.g. a named import that the module does not export -> SyntaxError and
+ * the whole page script never runs).
+ */
+
+import { LANGUAGES } from '../src/shared/constants.js';
+
+const { describe, it, assertTrue, assertEqual } = window.TestRunner;
+
+/**
+ * Install a fake `browser` global before importing page scripts.
+ */
+function installFakeBrowser() {
+ const data = new Map();
+ const listeners = [];
+ const local = {
+ async get(defaults) {
+ const result = {};
+ for (const key of Object.keys(defaults)) {
+ result[key] = data.has(key) ? data.get(key) : defaults[key];
+ }
+ return result;
+ },
+ async set(obj) {
+ const changes = {};
+ for (const [key, value] of Object.entries(obj)) {
+ changes[key] = { oldValue: data.get(key), newValue: value };
+ data.set(key, value);
+ }
+ for (const listener of listeners) listener(changes, 'local');
+ }
+ };
+ globalThis.browser = {
+ storage: {
+ local,
+ onChanged: {
+ addListener(fn) {
+ listeners.push(fn);
+ }
+ }
+ },
+ runtime: {
+ /** @returns {Promise<{ok: boolean, models?: string[]}>} */
+ sendMessage: async () => ({ ok: true, models: ['fake-model'] }),
+ openOptionsPage: () => {}
+ }
+ };
+ return globalThis.browser;
+}
+
+/**
+ * Run an ES module script by importing it with a cache-busting query.
+ * The page's markup is added inside a sandbox container; the script runs,
+ * async init settles, assertions are collected via a probe callback, and
+ * finally the sandbox is removed and the runner's own elements restored.
+ * @param {string} scriptPath
+ * @param {string} markup
+ * @param {() => *} probe assertions to run while the sandbox is attached
+ */
+async function runPageScript(scriptPath, markup, probe) {
+ installFakeBrowser();
+ // Preserve the runner's result elements across the DOM swap.
+ const results = document.getElementById('results');
+ const summary = document.getElementById('summary');
+ const resultsParent = results ? results.parentNode : null;
+ const summaryParent = summary ? summary.parentNode : null;
+ const resultsNext = results ? results.nextSibling : null;
+ const summaryNext = summary ? summary.nextSibling : null;
+
+ const sandbox = document.createElement('div');
+ sandbox.id = 'smoke-sandbox';
+ sandbox.innerHTML = markup;
+ document.body.append(sandbox);
+
+ try {
+ // Import with cache-buster so repeated runs re-execute the script.
+ await import(`${scriptPath}?smoke=${Date.now()}-${Math.random().toString(36).slice(2)}`);
+ // Let async init settle.
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ return probe();
+ } finally {
+ sandbox.remove();
+ if (results && resultsParent) resultsParent.insertBefore(results, resultsNext);
+ if (summary && summaryParent) summaryParent.insertBefore(summary, summaryNext);
+ // Reset the fake browser so later tests use the real API if needed.
+ delete globalThis.browser;
+ }
+}
+
+describe('popup page script smoke', () => {
+ it('initializes language options and model select from markup', async () => {
+ const r = await runPageScript('../src/popup/popup.js', `
+ <input type="checkbox" id="enabled-toggle">
+ <select id="model-select"></select>
+ <button id="reload-models"></button>
+ <p id="model-status" hidden></p>
+ <select id="language-select"></select>
+ <button id="open-options"></button>
+ `, () => ({
+ langOptions: document.getElementById('language-select').options.length,
+ langValue: document.getElementById('language-select').value,
+ modelOptions: document.getElementById('model-select').options.length,
+ firstModel: document.getElementById('model-select').options[0]?.textContent ?? null
+ }));
+ assertEqual(r.langOptions, LANGUAGES.length);
+ assertEqual(r.langValue, 'ja'); // default target language
+ assertTrue(r.modelOptions > 0, 'model select should be populated');
+ assertEqual(r.firstModel, 'fake-model');
+ });
+});
+
+describe('options page script smoke', () => {
+ it('initializes language options and loads models into the select', async () => {
+ const r = await runPageScript('../src/options/options.js', `
+ <form id="options-form">
+ <input type="checkbox" id="enabled">
+ <input type="text" id="ollama-host">
+ <select id="model"></select>
+ <button id="reload-models" type="button"></button>
+ <p id="model-status" hidden></p>
+ <select id="target-language"></select>
+ <select id="thinking">
+ <option value="off">off</option><option value="on">on</option>
+ <option value="low">low</option><option value="medium">medium</option>
+ <option value="high">high</option>
+ </select>
+ <input type="range" id="temperature" min="0" max="2" step="0.1">
+ <output id="temperature-value"></output>
+ <input type="range" id="context-length" min="0" max="5" step="1">
+ <output id="context-length-value"></output>
+ <input type="radio" name="seed-mode" value="random">
+ <input type="radio" name="seed-mode" value="fixed">
+ <div id="seed-value-row"></div>
+ <input type="number" id="seed-value">
+ <textarea id="user-system-prompt"></textarea>
+ <div id="advanced-fields"></div>
+ <textarea id="stop"></textarea>
+ <span id="save-status"></span>
+ </form>
+ `, () => ({
+ langOptions: document.getElementById('target-language').options.length,
+ langValue: document.getElementById('target-language').value,
+ modelOptions: document.getElementById('model').options.length,
+ firstModel: document.getElementById('model').options[0]?.textContent ?? null,
+ host: document.getElementById('ollama-host').value
+ }));
+ assertEqual(r.langOptions, LANGUAGES.length);
+ assertEqual(r.langValue, 'ja');
+ assertTrue(r.modelOptions > 0, 'model select should be populated by loadModels');
+ assertEqual(r.firstModel, 'fake-model');
+ assertEqual(r.host, 'http://localhost:11434');
+ });
+}); \ No newline at end of file