Files
T
root-at-skic d2f52d43ac 0.3.0: .archify files are YAML and open straight into the diagram
- .archify content is YAML (JSON still accepted); converted to JSON for the renderer
- Archify Diagram custom editor is the default for *.archify; Show Source opens the YAML beside it
- YAML syntax errors and validation problems mapped to YAML lines
- yamlValidation for schema completion with redhat.vscode-yaml
- scripts/to-yaml.mjs converts existing .archify files
2026-09-30 15:21:55 +03:00

170 lines
8.5 KiB
TypeScript

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<T>(what: string, probe: () => T | undefined | false, timeoutMs = 20_000): Promise<T> {
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<void>): Promise<void> {
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<void> {
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<string, unknown>;
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, /<meta name="generator" content="archify/);
await waitFor('custom editor tab', () => webviewTabs('archify.htmlViewer').length > 0);
});
await check('source-backed diagram previews with the evidence fallback', async () => {
const uri = workspaceFile('mco-runtime.architecture.json');
await vscode.commands.executeCommand('archify.showPreview', uri);
await waitFor('mco preview tab', () => webviewTabs('archify.preview').some((tab) => /mco-runtime/.test(tab.label)));
const doc = await vscode.workspace.openTextDocument(uri);
const diagnostics = await waitFor('evidence diagnostics', () => {
const found = vscode.languages.getDiagnostics(doc.uri).filter((d) => d.source === 'archify');
return found.length > 0 && found;
});
assert.ok(
diagnostics.every((d) => d.severity !== vscode.DiagnosticSeverity.Error),
'unverifiable evidence is a warning in fallback mode',
);
});
}