.archify files are Archify JSON diagram sources. They open as JSON, preview with the same commands, report validation problems, and get completion from a schema that dispatches on diagram_type. Adds a hello.archify example and tests for detection, the dispatching schema and the VS Code behaviour. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
163 lines
8.1 KiB
TypeScript
163 lines
8.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import path from 'node:path';
|
|
import * as vscode from 'vscode';
|
|
|
|
const EXTENSION_ID = 'vscode-extensions.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('.archify files open as JSON with the preview available', async () => {
|
|
const doc = await vscode.workspace.openTextDocument(workspaceFile('web-platform.archify'));
|
|
assert.equal(doc.languageId, 'json');
|
|
await vscode.window.showTextDocument(doc);
|
|
await vscode.commands.executeCommand('archify.showPreviewToSide');
|
|
const tabs = await waitFor('.archify preview tab', () => {
|
|
const found = webviewTabs('archify.preview').filter((tab) => /web-platform\.archify/.test(tab.label));
|
|
return found.length > 0 && found;
|
|
});
|
|
assert.equal(tabs.length, 1);
|
|
});
|
|
|
|
await check('.archify completion comes from the schema of its diagram_type', async () => {
|
|
const doc = await vscode.workspace.openTextDocument(workspaceFile('web-platform.archify'));
|
|
await vscode.window.showTextDocument(doc);
|
|
// Complete the value of the first component's "type".
|
|
const offset = doc.getText().indexOf('"type": "') + '"type": "'.length;
|
|
const position = doc.positionAt(offset);
|
|
let labels: string[] = [];
|
|
for (let attempt = 0; attempt < 50 && !labels.includes('backend'); attempt += 1) {
|
|
const list = await vscode.commands.executeCommand<vscode.CompletionList>('vscode.executeCompletionItemProvider', doc.uri, position);
|
|
labels = list.items.map((item) => (typeof item.label === 'string' ? item.label : item.label.label).replace(/"/g, ''));
|
|
if (!labels.includes('backend')) await new Promise((resolve) => setTimeout(resolve, 300));
|
|
}
|
|
assert.ok(labels.includes('backend') && labels.includes('database'), `offered: ${labels.join(', ')}`);
|
|
});
|
|
|
|
await check('.archify problems are reported against the file', async () => {
|
|
const good = await vscode.workspace.openTextDocument(workspaceFile('cache-miss.archify'));
|
|
const broken = good.getText().replace('"participants"', '"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('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',
|
|
);
|
|
});
|
|
}
|