#!/usr/bin/env node // Source-only DOM regressions. No browser, server, build, network or app lifecycle. // Usage: node scripts/test-app-starter-component.cjs /path/to/frontend/node_modules [App.vue] const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const { createRequire } = require('node:module') const { createHash } = require('node:crypto') const deps = path.resolve(process.argv[2] || 'neode-ui/node_modules') const requireDep = createRequire(path.join(deps, '.starter-test.cjs')) const { JSDOM } = requireDep('jsdom') const dom = new JSDOM('
', { url: 'https://starter.invalid/' }) for (const key of ['window', 'document', 'HTMLElement', 'SVGElement', 'Element', 'Node']) { global[key] = key === 'window' ? dom.window : dom.window[key] } const { parse, compileScript } = requireDep('@vue/compiler-sfc') const ts = requireDep('typescript') const { createApp, nextTick } = requireDep('vue') const source = process.argv[3] ? path.resolve(process.argv[3]) : path.resolve(__dirname, '../examples/archipelago-app-starter/src/App.vue') const input = fs.readFileSync(source, 'utf8') const parsed = parse(input, { filename: source }) assert.deepEqual(parsed.errors, []) const compiled = compileScript(parsed.descriptor, { id: 'starter-test', inlineTemplate: true }) const transformed = ts.transpileModule(compiled.content, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, reportDiagnostics: true, }) assert.deepEqual(transformed.diagnostics, []) const component = { exports: {} } const localRequire = specifier => specifier.startsWith('.') ? require(path.resolve(path.dirname(source), specifier)) : requireDep(specifier) new Function('require', 'module', 'exports', transformed.outputText)(localRequire, component, component.exports) const { STORAGE_KEY } = require(path.join(path.dirname(source), 'items.mjs')) const root = document.getElementById('app') let app let passed = 0 async function mount(raw = null) { if (app) app.unmount() window.localStorage.clear() if (raw !== null) window.localStorage.setItem(STORAGE_KEY, raw) app = createApp(component.exports.default) app.mount(root) await nextTick() } const button = label => [...root.querySelectorAll('button')].find(node => node.textContent.trim() === label) async function click(label) { const node = button(label) assert.ok(node, `Missing button ${label}`) node.focus() node.click() await nextTick() await nextTick() } async function check(name, run) { await run() passed += 1 console.log(`PASS ${name}`) } async function main() { await check('empty add persists and transfers focus from removed empty action', async () => { await mount() assert.match(root.textContent, /No items yet/) await click('Create first item') assert.equal(root.querySelectorAll('li').length, 1) assert.equal(document.activeElement, button('Add item')) assert.equal(JSON.parse(window.localStorage.getItem(STORAGE_KEY))[0].id, 1) }) await check('remount restores actual local data', async () => { const saved = window.localStorage.getItem(STORAGE_KEY) await mount(saved) assert.equal(root.querySelectorAll('li').length, 1) assert.match(root.textContent, /Item 1/) }) await check('mounted add action retains keyboard focus', async () => { await click('Add item') assert.equal(root.querySelectorAll('li').length, 2) assert.equal(document.activeElement, button('Add item')) }) await check('invalid data shows alert without resetting; retry restores focus', async () => { await mount('broken') assert.ok(root.querySelector('[role="alert"]')) await click('Try again') assert.equal(window.localStorage.getItem(STORAGE_KEY), 'broken') assert.equal(document.activeElement, button('Try again')) window.localStorage.removeItem(STORAGE_KEY) await click('Try again') assert.match(root.textContent, /No items yet/) assert.equal(document.activeElement, button('Add item')) }) await check('capacity and exhausted ID refusals keep readable records visible', async () => { for (const records of [ Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: 'Keep', detail: '' })), [{ id: Number.MAX_SAFE_INTEGER, name: 'Last', detail: '' }], ]) { const raw = JSON.stringify(records) await mount(raw) await click('Add item') assert.equal(root.querySelectorAll('li').length, records.length) assert.match(root.querySelector('[role="alert"]').textContent, /1000|exhausted/) assert.equal(window.localStorage.getItem(STORAGE_KEY), raw) assert.equal(document.activeElement, button('Add item')) } }) await check('quota failure preserves empty/populated data and mounted action focus', async () => { for (const raw of [null, JSON.stringify([{ id: 7, name: 'Keep', detail: 'Existing data' }])]) { await mount(raw) const proto = Object.getPrototypeOf(window.localStorage) const original = proto.setItem proto.setItem = () => { throw Error('quota') } const action = raw === null ? 'Create first item' : 'Add item' try { await click(action) assert.match(root.querySelector('[role="alert"]').textContent, /quota/) assert.equal(root.querySelectorAll('li').length, raw === null ? 0 : 1) assert.equal(window.localStorage.getItem(STORAGE_KEY), raw) assert.equal(document.activeElement, button(action)) } finally { proto.setItem = original } } }) await check('unfocused activation does not steal focus', async () => { await mount() button('Refresh').focus() button('Create first item').click() await nextTick() await nextTick() assert.equal(document.activeElement, button('Refresh')) }) await check('focus moved before restoration is retained', async () => { await mount() const trigger = button('Create first item') trigger.focus() trigger.click() button('Refresh').focus() await nextTick() await nextTick() assert.equal(document.activeElement, button('Refresh')) }) await check('blocked storage renders error without uncaught lifecycle error', async () => { const descriptor = Object.getOwnPropertyDescriptor(window, 'localStorage') if (app) { app.unmount(); app = null } Object.defineProperty(window, 'localStorage', { configurable: true, get() { throw Error('blocked') } }) try { app = createApp(component.exports.default) app.mount(root) await nextTick() assert.ok(root.querySelector('[role="alert"]')) } finally { Object.defineProperty(window, 'localStorage', descriptor) } }) console.log(JSON.stringify({ tests: passed, app_sha256: createHash('sha256').update(input).digest('hex'), dependencies: Object.fromEntries(['vue', '@vue/compiler-sfc', 'typescript', 'jsdom'].map(name => [name, requireDep(`${name}/package.json`).version])), scope: 'SFC compilation and jsdom runtime only; no typecheck, browser/layout, package build or UAT acceptance' })) } main().catch(error => { console.error(error); process.exitCode = 1 }).finally(() => { if (app) app.unmount() dom.window.close() })