/* test-designer-host.mjs: the editor side of the designer and the extension's wiring. * There is no VS Code in this environment, so the real extension.js is loaded against a FAKE * `vscode` module. That is enough to catch the failures that actually break extensions: * a command declared in package.json but never registered (or the reverse), a webview that * points at a file that is not in the package, and an Insert that edits the wrong place. * It is not a substitute for opening the .vsix in VS Code once. */ import { createRequire } from 'node:module'; import { readFileSync, existsSync, readdirSync } from 'node:fs'; import { join, dirname, resolve as pathResolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import Module from 'node:module'; import { VERSION } from '../public/dssl/index.js'; import { designerHtml, handleMessage, DESIGNER_TABS } from '../dssl-vscode/lib/designer-host.js'; let pass = 0, fail = 0; const ok = (name, cond, extra) => { if (cond) { pass++; console.log(` ok ${name}`); } else { fail++; console.log(`FAIL ${name}${extra !== undefined ? '; ' + extra : ''}`); } }; const section = (t) => console.log(`\n== ${t}`); const ROOT = pathResolve(dirname(fileURLToPath(import.meta.url)), '..'); const EXT = join(ROOT, 'dssl-vscode'); const pkg = JSON.parse(readFileSync(join(EXT, 'package.json'), 'utf8')); section('the package manifest'); const mm = (v) => v.split('.').slice(0, 2).join('.'); ok('the version line (major.minor) matches the engine it bundles; patch may differ for extension-only releases', mm(pkg.version) === mm(VERSION), `${pkg.version} vs ${VERSION}`); ok('main points at a file that exists', existsSync(join(EXT, pkg.main))); ok('the icon exists', existsSync(join(EXT, pkg.icon))); const declared = pkg.contributes.commands.map((c) => c.command); ok('every command has a title and the DSSL category', pkg.contributes.commands.every((c) => c.title && c.category === 'DSSL')); ok('every command can activate the extension (onCommand or a language event)', declared.every((c) => pkg.activationEvents.includes(`onCommand:${c}`))); const menuCmds = Object.values(pkg.contributes.menus).flat().map((m) => m.command); ok('every menu entry points at a declared command', menuCmds.every((c) => declared.includes(c)), menuCmds.filter((c) => !declared.includes(c)).join()); ok('the extension ships the engine copies it needs (every module the resolver imports)', ['parser', 'resolver', 'utils', 'color', 'texture', 'aura', 'slider', 'lint', 'import'].every((f) => existsSync(join(EXT, 'lib', 'engine', f + '.js')))); const same = ['parser', 'resolver', 'utils', 'color', 'texture', 'aura', 'slider', 'lint', 'import'].filter((f) => readFileSync(join(EXT, 'lib', 'engine', f + '.js'), 'utf8') !== readFileSync(join(ROOT, 'public', 'dssl', f + '.js'), 'utf8')); ok('and they are byte-identical to the engine (run scripts/sync-extension-engine.mjs if not)', same.length === 0, same.join()); section('the panel HTML'); const html = designerHtml({ cspSource: 'vscode-webview://abc', uri: (p) => `vscode-webview://abc/${p}`, tab: 'texture' }); ok('it carries a strict content security policy', /default-src 'none'/.test(html) && /script-src vscode-webview:\/\/abc;/.test(html) && !/unsafe-eval/.test(html)); ok('scripts only run from the extension itself, with no inline script', !/]*src=)/.test(html)); ok('it opens on the requested tab, and falls back for nonsense', /data-tab="texture"/.test(html) && /data-tab="aura"/.test(designerHtml({ cspSource: 'x', uri: (p) => p, tab: '">