import assert from 'node:assert/strict'; import path from 'node:path'; import * as vscode from 'vscode'; const EXTENSION_ID = 'root-at-skic.archify-vscode-ext'; function workspaceFile(name: string): vscode.Uri { const folder = vscode.workspace.workspaceFolders?.[0]; assert.ok(folder, 'a workspace folder is open'); return vscode.Uri.joinPath(folder.uri, name); } async function waitFor(what: string, probe: () => T | undefined | false, timeoutMs = 20_000): Promise { const start = Date.now(); for (;;) { const value = probe(); if (value) return value; if (Date.now() - start > timeoutMs) throw new Error(`Timed out waiting for ${what}`); await new Promise((resolve) => setTimeout(resolve, 200)); } } function webviewTabs(viewTypeSuffix: string): vscode.Tab[] { return vscode.window.tabGroups.all .flatMap((group) => group.tabs) .filter((tab) => (tab.input instanceof vscode.TabInputWebview && tab.input.viewType.endsWith(viewTypeSuffix)) || (tab.input instanceof vscode.TabInputCustom && tab.input.viewType === viewTypeSuffix)); } async function check(name: string, body: () => Promise): Promise { try { await body(); console.log(` ✔ ${name}`); } catch (error) { console.log(` ✖ ${name}\n ${error instanceof Error ? error.stack ?? error.message : String(error)}`); throw error; } } export async function run(): Promise { console.log('Archify extension integration suite'); await check('activates on a diagram source and sets the context key', async () => { const doc = await vscode.workspace.openTextDocument(workspaceFile('production-deployment.architecture.json')); await vscode.window.showTextDocument(doc); const extension = vscode.extensions.getExtension(EXTENSION_ID); assert.ok(extension, 'extension is installed'); await waitFor('activation', () => extension.isActive); }); await check('clicking a .archify file opens the diagram, not the text', async () => { const uri = workspaceFile('web-platform.archify'); await vscode.commands.executeCommand('vscode.open', uri); const tab = await waitFor('diagram editor tab', () => vscode.window.tabGroups.all .flatMap((group) => group.tabs) .find((t) => t.input instanceof vscode.TabInputCustom && t.input.viewType === 'archify.diagram' && t.input.uri.toString() === uri.toString()), ); assert.ok(tab.isActive, 'the diagram tab is active'); assert.equal(vscode.window.activeTextEditor?.document.uri.toString() === uri.toString(), false, 'no text editor was opened for it'); }); await check('.archify files are YAML and Show Source opens the text beside the diagram', async () => { const uri = workspaceFile('web-platform.archify'); await vscode.commands.executeCommand('archify.showSource'); const editor = await waitFor('source text editor', () => vscode.window.visibleTextEditors.find((e) => e.document.uri.toString() === uri.toString()), ); assert.equal(editor.document.languageId, 'yaml'); assert.match(editor.document.getText(), /^schema_version: 1/m); }); await check('.archify validation problems are reported against the YAML', async () => { const good = await vscode.workspace.openTextDocument(workspaceFile('cache-miss.archify')); const broken = good.getText().replace(/^participants:/m, 'actors:'); const target = workspaceFile('broken.archify'); await vscode.workspace.fs.writeFile(target, new TextEncoder().encode(broken)); const doc = await vscode.workspace.openTextDocument(target); await vscode.window.showTextDocument(doc); const diagnostics = await waitFor('archify diagnostics on broken.archify', () => { const found = vscode.languages.getDiagnostics(doc.uri).filter((d) => d.source === 'archify'); return found.length > 0 && found; }); assert.ok(diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error)); }); await check('.archify YAML syntax errors are reported at their line', async () => { const target = workspaceFile('syntax.archify'); await vscode.workspace.fs.writeFile(target, new TextEncoder().encode('schema_version: 1\ndiagram_type: architecture\nmeta: [unclosed\n')); const doc = await vscode.workspace.openTextDocument(target); await vscode.window.showTextDocument(doc); const diagnostics = await waitFor('yaml syntax diagnostics', () => { const found = vscode.languages.getDiagnostics(doc.uri).filter((d) => d.source === 'archify' && d.message.startsWith('YAML:')); return found.length > 0 && found; }); assert.ok(diagnostics[0].range.start.line >= 2, `reported on line ${diagnostics[0].range.start.line + 1}`); }); await check('opens a live preview beside the source', async () => { await vscode.commands.executeCommand('archify.showPreviewToSide', workspaceFile('production-deployment.architecture.json')); const tabs = await waitFor('preview tab', () => { const found = webviewTabs('archify.preview').filter((tab) => /production-deployment/.test(tab.label)); return found.length > 0 && found; }); assert.equal(tabs.length, 1); assert.match(tabs[0].label, /Preview production-deployment\.architecture\.json/); }); await check('reports validation problems in the Problems panel with JSON ranges', async () => { const doc = await vscode.workspace.openTextDocument({ language: 'json', content: JSON.stringify({ schema_version: 1, diagram_type: 'architecture', meta: { title: 'x' }, components: [{ id: 'a' }] }, null, 2), }); await vscode.window.showTextDocument(doc); const diagnostics = await waitFor('archify diagnostics', () => { const found = vscode.languages.getDiagnostics(doc.uri).filter((d) => d.source === 'archify'); return found.length > 0 && found; }); const component = diagnostics.find((d) => /components\/0/.test(d.message)); assert.ok(component, 'a diagnostic for /components/0'); assert.equal(doc.getText(component.range), '{', 'range points at the component object'); assert.equal(component.severity, vscode.DiagnosticSeverity.Error); }); await check('clears problems once the document is fixed', async () => { const good = await vscode.workspace.openTextDocument(workspaceFile('event-stream.dataflow.json')); const doc = await vscode.workspace.openTextDocument({ language: 'json', content: good.getText() }); await vscode.window.showTextDocument(doc); await new Promise((resolve) => setTimeout(resolve, 3000)); const errors = vscode.languages.getDiagnostics(doc.uri).filter((d) => d.source === 'archify' && d.severity === vscode.DiagnosticSeverity.Error); assert.deepEqual(errors.map((d) => d.message), []); }); await check('renders to an HTML file and opens it in the Archify viewer', async () => { const source = workspaceFile('cache-miss-request.sequence.json'); const output = vscode.Uri.file(path.join(path.dirname(source.fsPath), 'cache-miss-request.sequence.html')); // Stub the save dialog and the follow-up notification. const window = vscode.window as unknown as Record; const originalSave = window.showSaveDialog; const originalInfo = window.showInformationMessage; window.showSaveDialog = async () => output; window.showInformationMessage = async () => 'Open in Viewer'; try { await vscode.commands.executeCommand('archify.renderToHtml', source); } finally { window.showSaveDialog = originalSave; window.showInformationMessage = originalInfo; } const html = new TextDecoder().decode(await vscode.workspace.fs.readFile(output)); assert.match(html, /