The Explorer folder menu shows a command's title without its category, so it read just "New Diagram…". The title now names Archify itself and the category is dropped, so the Command Palette shows the same text. Integration test asserts the title.
204 lines
10 KiB
TypeScript
204 lines
10 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('New Diagram Archify creates a .archify file from a template and opens the diagram', async () => {
|
|
// Menus show the bare title, so it must name Archify by itself.
|
|
const commands = vscode.extensions.getExtension(EXTENSION_ID)?.packageJSON.contributes.commands as { command: string; title: string; category?: string }[];
|
|
const newDiagram = commands.find((item) => item.command === 'archify.newDiagram');
|
|
assert.equal(newDiagram?.title, 'New Diagram Archify…');
|
|
assert.equal(newDiagram?.category, undefined);
|
|
const folder = workspaceFile('.');
|
|
// Stub the template pick and the name prompt; check the name validation on the way.
|
|
const window = vscode.window as unknown as Record<string, unknown>;
|
|
const originalPick = window.showQuickPick;
|
|
const originalInput = window.showInputBox;
|
|
let existingNameMessage: unknown;
|
|
window.showQuickPick = async (items: readonly { label: string }[]) => (await items).find((item) => item.label === 'Everyday Plan');
|
|
window.showInputBox = async (options: vscode.InputBoxOptions) => {
|
|
existingNameMessage = await options.validateInput?.('web-platform');
|
|
return 'summer-plan';
|
|
};
|
|
try {
|
|
await vscode.commands.executeCommand('archify.newDiagram', folder);
|
|
} finally {
|
|
window.showQuickPick = originalPick;
|
|
window.showInputBox = originalInput;
|
|
}
|
|
assert.match(String(existingNameMessage), /already exists/);
|
|
const uri = vscode.Uri.joinPath(folder, 'summer-plan.archify');
|
|
const text = new TextDecoder().decode(await vscode.workspace.fs.readFile(uri));
|
|
assert.match(text, /^diagram_type: workflow$/m);
|
|
assert.match(text, /^ {2}title: Summer plan$/m);
|
|
assert.match(text, /^ {2}output: summer-plan\.html$/m);
|
|
await waitFor('diagram editor for the new file', () =>
|
|
webviewTabs('archify.diagram').some((tab) => tab.input instanceof vscode.TabInputCustom && tab.input.uri.toString() === uri.toString()),
|
|
);
|
|
});
|
|
|
|
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',
|
|
);
|
|
});
|
|
}
|