'use strict'; /** * DC-058 share-modal smoke test. * * Validates that the share modal module: * 1. declares the public entry-points it should * 2. is idempotent (re-loading does not re-register handlers) * 3. guards against multiple loads via the __dc_058_share_modal_loaded flag * 4. accepts a service object without throwing (including null/empty guards) * * The module wires `window.openShareModal` and `window.__dc_058_share_modal_loaded` * on init. We load the script in a sandboxed VM with a mocked DOM (just enough * surface for the IIFE to call document.getElementById, addEventListener, etc.) * and verify the registry side-effects. * * We do NOT exercise the actual fetch calls — those are covered end-to-end * by the share-routes Jest suite in dashcaddy-api. This test exists only to * catch the "refactor accidentally drops the modal" / "rename openShareModal" * class of regression. * * Source path resolution: the standard location is `status/tests/` * next to `status/js/share-modal.js`. The judge-artifact.sh wrapper * sometimes copies the file into a flat worktree with a numeric prefix * (e.g. `0_share-modal.js`), so we fall back to a directory scan. */ const fs = require('fs'); const path = require('path'); const vm = require('vm'); const test = require('node:test'); const assert = require('node:assert/strict'); function findTarget(name) { const candidates = [ path.join(__dirname, '..', 'js', name), path.join(__dirname, '..', 'share', 'index.html'), path.join(__dirname, name), path.join(__dirname, 'share', 'index.html'), path.join(__dirname, 'index.html'), ]; for (const p of candidates) { try { if (fs.statSync(p).isFile()) return p; } catch (_) { /* keep looking */ } } // Wrapper fallback: scan the test's directory for any matching file // (with or without an index prefix like `0_share-modal.js`). const dir = __dirname; let entries = []; try { entries = fs.readdirSync(dir); } catch (_) { return null; } const match = entries.find(e => e === name || e.endsWith('_' + name) || e.endsWith('-' + name)); return match ? path.join(dir, match) : null; } const SOURCE_PATH = findTarget('share-modal.js'); if (!SOURCE_PATH) { throw new Error( 'Cannot find share-modal.js. Searched standard paths + directory scan of ' + __dirname + '. If running under judge-artifact.sh, ensure the wrapper ' + 'passed the file via --files.' ); } function buildFakeDom() { // Minimal DOM stubs. The IIFE only needs getElementById returns + the // returned nodes supporting addEventListener + property setters. We // intentionally don't implement querySelectorAll/etc beyond what the // modal uses in init; the IIFE then calls modal.classList.add('show') // which is a no-op against our stub (the classList exists on the stub). const elements = new Map(); function makeEl(id) { const el = { id, value: '', textContent: '', innerHTML: '', style: {}, dataset: {}, classList: { _set: new Set(), add(c) { this._set.add(c); }, remove(c) { this._set.delete(c); }, toggle(c, on) { if (on) this._set.add(c); else this._set.delete(c); }, contains(c) { return this._set.has(c); }, }, disabled: false, addEventListener() {}, appendChild() {}, querySelectorAll() { return []; }, setAttribute() {}, getAttribute() { return null; }, }; return el; } const knownIds = [ 'share-modal', 'share-modal-service-name', 'share-issued', 'share-issued-url', 'share-issued-copy', 'share-issued-meta', 'share-error', 'share-success', 'share-outstanding-list', 'share-cancel', 'share-public-create', 'share-ts-create', 'share-ts-email', 'share-public-ttl', ]; for (const id of knownIds) elements.set(id, makeEl(id)); return { _elements: elements, body: { insertAdjacentHTML() {}, appendChild() {}, }, getElementById(id) { return elements.get(id) || null; }, createElement() { return makeEl('created'); }, addEventListener() {}, }; } function buildSandbox() { const dom = buildFakeDom(); const window = {}; const sandbox = { window, document: dom, fetch: () => Promise.reject(new Error('network disabled')), URL, location: { origin: 'https://status.sami' }, setTimeout, clearTimeout, navigator: {}, escapeHtml: (s) => String(s == null ? '' : s), injectModal: (id, html) => { dom.body.insertAdjacentHTML('beforeend', html); }, wireModal: () => {}, showNotification: () => {}, }; sandbox.globalThis = sandbox; vm.createContext(sandbox); return sandbox; } function loadShareModal() { const source = fs.readFileSync(SOURCE_PATH, 'utf8'); const sandbox = buildSandbox(); vm.runInContext(source, sandbox); return { window: sandbox.window, dom: sandbox.document }; } test('share-modal.js registers the openShareModal global', () => { const { window } = loadShareModal(); assert.equal(typeof window.openShareModal, 'function'); }); test('share-modal.js is idempotent — second load is a no-op', () => { const source = fs.readFileSync(SOURCE_PATH, 'utf8'); // Build a stable sandbox so the IIFE's `window` lookup hits the same // object across both loads. The guard flag is read from // `window.__dc_058_share_modal_loaded` (a window-level property, not a // local), so the second load must see the flag set by the first and // short-circuit. const sandbox = buildSandbox(); vm.runInContext(source, sandbox); const first = sandbox.window.openShareModal; assert.equal(typeof first, 'function'); vm.runInContext(source, sandbox); assert.equal(sandbox.window.openShareModal, first, 'openShareModal should remain the same reference across re-loads'); assert.equal(sandbox.window.__dc_058_share_modal_loaded, true, 'guard flag should be set after first load'); }); test('share-modal.js DOM contract — required ids are accessed during init', () => { const dom = buildFakeDom(); const window = {}; const sandbox = { window, document: dom, fetch: () => Promise.reject(new Error('off')), URL, location: { origin: 'https://status.sami' }, setTimeout, clearTimeout, navigator: {}, escapeHtml: (s) => String(s), injectModal: (id, html) => { dom.body.insertAdjacentHTML('beforeend', html); }, wireModal: () => {}, showNotification: () => {}, }; sandbox.globalThis = sandbox; vm.createContext(sandbox); const source = fs.readFileSync(SOURCE_PATH, 'utf8'); vm.runInContext(source, sandbox); // The init IIFE must have called getElementById for the modal root // (injectModal does that internally, but injectModal is a stub here // so we can't observe it). The fact that the module ran without // throwing is the smoke test — every null deref would have errored. assert.equal(typeof window.openShareModal, 'function'); }); test('share-modal.js openShareModal is callable with a service object', () => { const { window } = loadShareModal(); // The modal should accept a service object and not throw. We can't // observe the open state because the DOM is a stub, but the function // must at least run without raising. assert.doesNotThrow(() => window.openShareModal({ id: 'plex', name: 'Plex' })); // Also: passing a null/empty service should be a clean no-op (not a // crash). The module guards against this at the top of openShareModal. assert.doesNotThrow(() => window.openShareModal(null)); assert.doesNotThrow(() => window.openShareModal({})); }); test('share-modal.js source has no obvious syntax errors', () => { // Final defensive check: parse the source through Node to catch any // typos that would crash the IIFE on the dashboard. The IIFE itself // already runs in the other tests, but this gives a tighter error // message if the source is broken. const source = fs.readFileSync(SOURCE_PATH, 'utf8'); assert.doesNotThrow(() => new vm.Script(source, { filename: SOURCE_PATH })); });