#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const vm = require('vm'); const root = path.resolve(__dirname, '..'); const sourcePath = path.join(root, 'plugins', 'browser-inbox', 'frontend', 'src', 'index.js'); const source = fs.readFileSync(sourcePath, 'utf8'); class FakeNode { constructor(tagName) { this.tagName = String(tagName || '').toUpperCase(); this.children = []; this.attributes = {}; this.listeners = {}; this.style = {}; this.className = ''; this.id = ''; this.value = ''; this.parentNode = null; this._innerHTML = ''; this._textContent = ''; } appendChild(node) { if (!(node instanceof FakeNode)) throw new TypeError('appendChild expects FakeNode'); this.children.push(node); node.parentNode = this; return node; } removeChild(node) { this.children = this.children.filter((child) => child !== node); node.parentNode = null; return node; } setAttribute(name, value) { this.attributes[name] = String(value); if (name === 'id') this.id = String(value); } getAttribute(name) { return this.attributes[name]; } addEventListener(type, handler) { this.listeners[type] = this.listeners[type] || []; this.listeners[type].push(handler); } dispatchEvent(type, event = {}) { const handlers = this.listeners[type] || []; handlers.forEach((handler) => handler({ stopPropagation() {}, preventDefault() {}, target: this, ...event, })); } click() { this.dispatchEvent('click'); } set innerHTML(value) { this._innerHTML = String(value || ''); this.children = []; } get innerHTML() { return this._innerHTML; } set textContent(value) { this._textContent = String(value || ''); this.children = []; } get textContent() { if (this.tagName === '#TEXT') return this._textContent; return this._textContent + this.children.map((child) => child.textContent).join(''); } } function walk(node, fn) { if (fn(node)) return node; for (const child of node.children) { const found = walk(child, fn); if (found) return found; } return null; } function makeDocument() { return { body: new FakeNode('body'), head: new FakeNode('head'), createElement(tagName) { return new FakeNode(tagName); }, createTextNode(text) { const node = new FakeNode('#text'); node.textContent = text; return node; }, getElementById() { return null; }, }; } function loadComponent(document) { const registry = {}; const sandbox = { console, Date, document, window: { VerstakPluginRegister(pluginId, bundle) { registry[pluginId] = bundle.components || {}; }, }, }; sandbox.window.window = sandbox.window; sandbox.window.document = document; vm.runInNewContext(source, sandbox, { filename: sourcePath }); const component = registry['verstak.browser-inbox'] && registry['verstak.browser-inbox'].BrowserInboxView; if (!component) throw new Error('BrowserInboxView was not registered'); return component; } function makeApi(initialSettings = {}) { const settings = { ...initialSettings }; const handlers = {}; const unsubscribed = []; return { settings, handlers, unsubscribed, events: { subscribe: async (name, handler) => { handlers[name] = handler; return () => { unsubscribed.push(name); delete handlers[name]; }; }, }, settings: { read: async (key) => (key ? settings[key] : { ...settings }), write: async (key, value) => { settings[key] = value; return { ...settings }; }, }, getStoredCaptures(key = 'captures') { return settings[key] || []; }, }; } async function flush() { for (let i = 0; i < 8; i += 1) await Promise.resolve(); } async function mountWithApi(api, props = { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' }, document = makeDocument()) { const component = loadComponent(document); const container = new FakeNode('div'); component.mount(container, props, api); await flush(); return { component, container, document }; } (async () => { const api = makeApi(); const { component, container } = await mountWithApi(api); for (const name of ['browser.capture.page', 'browser.capture.selection', 'browser.capture.link']) { if (typeof api.handlers[name] !== 'function') throw new Error(`${name} subscription missing`); } await api.handlers['browser.capture.selection']({ name: 'browser.capture.selection', timestamp: '2026-06-27T00:00:00Z', payload: { captureId: 'capture-1', capturedAt: '2026-06-27T00:00:00.000Z', kind: 'selection', url: 'https://example.com/article', title: 'Example Article', domain: 'example.com', text: 'Selected text from the page', browserName: 'Firefox', }, }); await flush(); const projectKey = 'captures:workspace:Project'; const clientKey = 'captures:workspace:ClientA'; const globalKey = 'captures:global'; const captures = api.getStoredCaptures(projectKey); if (captures.length !== 1) throw new Error(`expected one stored capture, got ${captures.length}`); if (captures[0].captureId !== 'capture-1') throw new Error('stored capture id mismatch'); if (api.getStoredCaptures(globalKey).length !== 0) throw new Error('workspace capture leaked into global storage'); const row = walk(container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'capture-1'); if (!row) throw new Error('capture row was not rendered'); if (!container.textContent.includes('Selected text from the page')) { throw new Error('selection text was not rendered'); } await api.handlers['browser.capture.selection']({ name: 'browser.capture.selection', timestamp: '2026-06-27T00:00:00Z', payload: { captureId: 'capture-1', capturedAt: '2026-06-27T00:00:00.000Z', kind: 'selection', url: 'https://example.com/article', title: 'Example Article', domain: 'example.com', text: 'Duplicate selected text', }, }); await flush(); if (api.getStoredCaptures(projectKey).length !== 1) throw new Error('duplicate capture was stored'); const clientView = await mountWithApi(api, { workspaceNode: { name: 'ClientA' }, workspaceRootPath: 'ClientA' }); if (walk(clientView.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'capture-1')) { throw new Error('Project capture leaked into ClientA workspace view'); } await api.handlers['browser.capture.page']({ name: 'browser.capture.page', timestamp: '2026-06-27T00:10:00Z', payload: { captureId: 'capture-2', capturedAt: '2026-06-27T00:10:00.000Z', kind: 'page', url: 'https://client.example.com/', title: 'Client Page', domain: 'client.example.com', workspaceRootPath: 'ClientA', }, }); await flush(); if (api.getStoredCaptures(clientKey).length !== 1) throw new Error('ClientA capture was not stored under ClientA workspace key'); if (!walk(clientView.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'capture-2')) { throw new Error('ClientA capture was not rendered'); } component.unmount && component.unmount(clientView.container); const globalView = await mountWithApi(api, {}); if (!walk(globalView.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'capture-1')) { throw new Error('global browser inbox did not aggregate Project capture'); } if (!walk(globalView.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'capture-2')) { throw new Error('global browser inbox did not aggregate ClientA capture'); } component.unmount && component.unmount(globalView.container); const clearButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-action') === 'clear'); if (!clearButton) throw new Error('clear button not found'); clearButton.click(); await flush(); if (api.getStoredCaptures(projectKey).length !== 0) throw new Error('clear action did not empty stored captures'); component.unmount && component.unmount(container); if (api.unsubscribed.length !== 9) throw new Error('component did not unsubscribe all capture handlers'); const persistedApi = makeApi({ 'captures:workspace:Project': [captures[0]] }); const persisted = await mountWithApi(persistedApi); if (!walk(persisted.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'capture-1')) { throw new Error('persisted capture was not rendered on mount'); } const legacyApi = makeApi({ captures: [ { captureId: 'legacy-global-capture', capturedAt: '2026-06-27T02:00:00.000Z', kind: 'page', url: 'https://legacy.example.com/', title: 'Legacy Global Capture', domain: 'legacy.example.com', }, { captureId: 'legacy-project-capture', capturedAt: '2026-06-27T02:10:00.000Z', kind: 'page', url: 'https://project.example.com/', title: 'Legacy Project Capture', domain: 'project.example.com', workspaceRootPath: 'Project', }, ], }); const legacyGlobal = await mountWithApi(legacyApi, {}); if (!walk(legacyGlobal.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-global-capture')) { throw new Error('legacy global capture was not rendered in global view'); } if (!walk(legacyGlobal.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-project-capture')) { throw new Error('legacy workspace capture was not rendered in global view'); } component.unmount && component.unmount(legacyGlobal.container); const legacyProject = await mountWithApi(legacyApi, { workspaceNode: { name: 'Project' }, workspaceRootPath: 'Project' }); if (!walk(legacyProject.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-project-capture')) { throw new Error('legacy workspace capture was not rendered in matching workspace'); } if (walk(legacyProject.container, (node) => node.getAttribute && node.getAttribute('data-browser-capture-id') === 'legacy-global-capture')) { throw new Error('legacy global capture leaked into workspace view'); } console.log('browser inbox plugin smoke passed'); })().catch((err) => { console.error(err); process.exit(1); });