import assert from "node:assert/strict"; import * as vscode from "vscode"; import { control, spawnCounter, startImage } from "./server"; import { ImageSourceTree, RevisionDocuments } from "../src/sourceProvider"; import { actorFileUri } from "../src/actorFiles"; import type { activate } from "../src/extension"; export async function run(): Promise { const image = await startImage(); const folders = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.toString()); try { const extension = vscode.extensions.all.find( (candidate) => candidate.packageJSON.name === "runa", ); assert.ok(extension); const contributions = extension.packageJSON.contributes; assert.ok( contributions.viewsContainers.activitybar.some( (container: { id: string }) => container.id === "runa", ), ); assert.deepEqual( contributions.views.runa.map((view: { id: string }) => view.id), ["runa.sources", "runa.actors"], ); assert.equal(contributions.views.explorer, undefined); assert.ok( contributions.menus["view/title"].some( (item: { command: string; when: string }) => item.command === "runa.tick" && item.when.includes("runa.actors"), ), ); await vscode.commands.executeCommand("workbench.view.extension.runa"); await vscode.commands.executeCommand("runa.connect", image.address); assert.ok(!(await vscode.commands.getCommands(true)).includes("runa.addSourceFolder")); assert.deepEqual( vscode.workspace.workspaceFolders?.map((folder) => folder.uri.toString()), folders, ); await editSources(image.address); await editActorFiles(image.address); const spawned = await vscode.commands.executeCommand("runa.spawn", { path: "actors/counter", state: "5", }); assert.equal(typeof spawned, "string"); const selection = { actor_ref: String(spawned) }; await vscode.commands.executeCommand("runa.copyActorReference", selection); assert.equal(await vscode.env.clipboard.readText(), spawned); await vscode.commands.executeCommand("runa.inspect", selection); const controlled = vscode.window.activeTextEditor?.document; assert.ok(controlled); await vscode.commands.executeCommand("runa.send", selection, '{"op":"add","value":7}'); const controlPaused = documentChanged(controlled, (text) => text.includes('"status": "paused"'), ); await vscode.commands.executeCommand("runa.pause", selection); await vscode.commands.executeCommand("runa.tick", 100); await controlPaused; let snapshot = JSON.parse(controlled.getText()).actor; assert.equal(snapshot.status.status, "paused"); assert.equal(snapshot.state.data, 5); assert.equal(snapshot.queued.length, 1); const controlExecuted = documentChanged( controlled, (text) => JSON.parse(text).actor.state.data === 12, ); await vscode.commands.executeCommand("runa.resume", selection); await vscode.commands.executeCommand("runa.tick", 100); await controlExecuted; snapshot = JSON.parse(controlled.getText()).actor; assert.equal(snapshot.state.data, 12); assert.equal(snapshot.queued.length, 0); const controlRestarted = documentChanged( controlled, (text) => JSON.parse(text).actor.incarnation === 2, ); await vscode.commands.executeCommand("runa.restart", selection); await controlRestarted; snapshot = JSON.parse(controlled.getText()).actor; assert.equal(snapshot.incarnation, 2); assert.equal(snapshot.state.data, 12); await vscode.commands.executeCommand("runa.reload", selection); await vscode.commands.executeCommand("runa.tick", 10); await vscode.commands.executeCommand("runa.stop", selection); assert.equal(JSON.parse(controlled.getText()).removed, undefined); const controlStopped = documentChanged(controlled, (text) => JSON.parse(text).removed === true); await vscode.commands.executeCommand("runa.tick", 10); await controlStopped; assert.equal( (await vscode.workspace.fs.stat(controlled.uri)).permissions, vscode.FilePermission.Readonly, ); const doomed = await vscode.commands.executeCommand("runa.spawn", { path: "actors/counter", state: "0", }); await vscode.commands.executeCommand("runa.inspect", { actor_ref: doomed }); const killed = vscode.window.activeTextEditor?.document; assert.ok(killed); const controlKilled = documentChanged(killed, (text) => JSON.parse(text).removed === true); await vscode.commands.executeCommand("runa.kill", { actor_ref: doomed }); await controlKilled; assert.equal( (await vscode.workspace.fs.stat(killed.uri)).permissions, vscode.FilePermission.Readonly, ); const actorRef = await spawnCounter(image.address); await vscode.commands.executeCommand("runa.connect", image.address); await vscode.commands.executeCommand("runa.refresh"); await vscode.commands.executeCommand("runa.inspect", { actor_ref: actorRef }); let document = vscode.window.activeTextEditor?.document; assert.ok(document); assert.equal(document.uri.scheme, "runa"); assert.equal(document.languageId, "json"); assert.match(document.getText(), /9223372036854775807/); assert.match(document.getText(), /"connected": true/); const firstUri = document.uri.toString(); const refreshed = documentChanged(document, (text) => text.includes("queued from another client"), ); assert.deepEqual( await control(image.address, { operation: "send", parameters: { actor_ref: actorRef, message: { type: "string", data: "queued from another client" }, }, }), { version: 5, request_id: "integration-control", result: { Ok: { kind: "acknowledged" } } }, ); await refreshed; assert.match(document.getText(), /queued from another client/); const paused = documentChanged(document, (text) => text.includes('"status": "paused"')); await control(image.address, { operation: "system", parameters: { actor_ref: actorRef, message: { kind: "pause" } }, }); await paused; const failed = documentChanged(document, (text) => text.includes('"status": "failed"')); await control(image.address, { operation: "system", parameters: { actor_ref: actorRef, message: { kind: "resume" } }, }); await control(image.address, { operation: "tick", parameters: { ticks: 100, instruction_budget: 10 }, }); await failed; const disconnected = documentChanged(document, (text) => text.includes('"connected": false')); await vscode.commands.executeCommand("runa.disconnect"); await disconnected; assert.match(document.getText(), /"connected": false/); await vscode.commands.executeCommand("runa.connect", image.address); await vscode.commands.executeCommand("runa.inspect", { actor_ref: actorRef }); document = vscode.window.activeTextEditor?.document; assert.ok(document); assert.notEqual(document.uri.toString(), firstUri); assert.match(document.getText(), /queued from another client/); const old = await vscode.workspace.openTextDocument(vscode.Uri.parse(firstUri)); assert.match(old.getText(), /"connected": false/); assert.equal((await vscode.workspace.fs.stat(document.uri)).permissions, undefined); const updated = JSON.parse(document.getText()); updated.actor.state = { type: "integer", data: 77 }; updated.actor.reserved = null; updated.actor.queued = [ { type: "record", data: { op: { type: "string", data: "add" }, value: { type: "integer", data: 2 } }, }, ]; updated.actor.log_level = "warn"; updated.actor.incarnation = 999; updated.connection.endpoint = "not the actual endpoint"; const canonical = documentChanged(document, (text) => { const saved = JSON.parse(text); return ( saved.actor.state.data === 77 && saved.actor.incarnation !== 999 && saved.actor.log_level === "warn" && saved.connection.endpoint === image.address ); }); const edit = new vscode.WorkspaceEdit(); edit.replace( document.uri, new vscode.Range(document.positionAt(0), document.positionAt(document.getText().length)), JSON.stringify(updated, null, 2), ); assert.equal(await vscode.workspace.applyEdit(edit), true); const unsaved = document.getText(); await vscode.commands.executeCommand("runa.refresh"); assert.equal(document.getText(), unsaved, "refresh must preserve unsaved inspection edits"); const window = vscode.window as { showErrorMessage: (...args: unknown[]) => unknown }; const showError = window.showErrorMessage; const saveErrors: unknown[][] = []; window.showErrorMessage = async (...args: unknown[]) => { saveErrors.push(args); return undefined; }; try { assert.equal(await document.save(), true); await canonical; assert.deepEqual(saveErrors, [], "successful inspection save must not report refresh errors"); assert.equal(document.isDirty, false); } finally { window.showErrorMessage = showError; } const recovered = documentChanged(document, (text) => { const actor = JSON.parse(text).actor; return ( actor.state.data === 79 && actor.log_level === "warn" && actor.status.status === "idle" ); }); await vscode.commands.executeCommand("runa.logLevel", { actor_ref: actorRef }, "warn"); await vscode.commands.executeCommand("runa.restart", { actor_ref: actorRef }); await vscode.commands.executeCommand("runa.tick", 100); await recovered; await vscode.commands.executeCommand("runa.inspect", { actor_ref: actorRef }); const repaired = vscode.window.activeTextEditor?.document; assert.ok(repaired); assert.equal(JSON.parse(repaired.getText()).actor.state.data, 79, repaired.getText()); assert.equal(JSON.parse(repaired.getText()).actor.log_level, "warn"); assert.equal(JSON.parse(repaired.getText()).actor.status.status, "idle"); assert.equal( (await vscode.workspace.fs.stat(repaired.uri)).permissions, vscode.FilePermission.Readonly, ); await assert.rejects(async () => { await vscode.workspace.fs.writeFile(repaired.uri, Buffer.from(repaired.getText())); }, /paused or failed/); console.log( "Runa extension host: spawn, send, system messages, ticks, inspection, automatic refresh, reconnect passed", ); } finally { await vscode.commands.executeCommand("runa.disconnect"); await image.stop(); } } async function editSources(address: string): Promise { const extension = vscode.extensions.all.find( (candidate) => candidate.packageJSON.name === "runa", ); assert.ok(extension); const { sourceTree } = extension.exports as { sourceTree: ImageSourceTree }; const [root] = sourceTree.getChildren(); assert.ok(root); assert.equal(root.uri.toString(), `runa-image://${address}/source`); assert.deepEqual(await vscode.workspace.fs.readDirectory(root.uri), [ ["actors", vscode.FileType.Directory], ]); const [directory] = sourceTree.getChildren(root); assert.ok(directory); assert.equal(directory.directory, true); const [file] = sourceTree.getChildren(directory); assert.ok(file); assert.equal(sourceTree.getTreeItem(file).contextValue, "runa.sourceFile"); const actors = vscode.Uri.joinPath(root.uri, "actors"); assert.deepEqual(await vscode.workspace.fs.readDirectory(actors), [ ["counter.rn", vscode.FileType.File], ]); const uri = vscode.Uri.joinPath(actors, "counter.rn"); const document = await vscode.workspace.openTextDocument(uri); await vscode.window.showTextDocument(document); const original = document.getText(); assert.match(original, /pub fn handle\(ctx, state, sender, message\)/); const edited = "pub fn handle(ctx, state, sender, message) {\n state + 2\n}\n"; await replaceText(document, edited); const window = vscode.window as { showWarningMessage: (...args: unknown[]) => unknown }; const warning = window.showWarningMessage; const prompts: unknown[][] = []; let answer: string | undefined; window.showWarningMessage = async (...args: unknown[]) => { prompts.push(args); return answer; }; try { // Unsaved edits are never committed or spawned without an explicit save. assert.equal(await vscode.commands.executeCommand("runa.commitSource", uri), undefined); assert.equal(await vscode.commands.executeCommand("runa.spawnSource", uri), undefined); assert.equal(prompts.length, 2); assert.deepEqual(prompts[0]?.[1], { modal: true }); assert.equal(document.isDirty, true); const unsaved = JSON.stringify( await control(address, { operation: "read_source", parameters: { path: "actors/counter" } }), ); assert.ok(unsaved.includes(JSON.stringify(original))); assert.match( JSON.stringify(await control(address, { operation: "list_actors" })), /"data":\[\]/, ); answer = "Save and Spawn"; const saved = await vscode.commands.executeCommand("runa.spawnSource", uri); assert.match(String(saved), /^[A-Za-z0-9_-]{21}$/); assert.equal(prompts.length, 3); assert.equal(document.isDirty, false, "the explicit choice saves before spawning"); } finally { window.showWarningMessage = warning; } assert.equal(document.isDirty, false); assert.equal(document.isDirty, false); let reply = JSON.stringify( await control(address, { operation: "read_source", parameters: { path: "actors/counter" } }), ); assert.ok(reply.includes(JSON.stringify(edited)), reply); reply = JSON.stringify( await control(address, { operation: "source_history", parameters: { path: "actors/counter" } }), ); assert.match(reply, /"kind":"source_history","data":\[\]/); // Spawn context-menu actions receive the explorer URI and use the saved, uncommitted source. const nullActor = await vscode.commands.executeCommand("runa.spawnSource", uri); assert.match(String(nullActor), /^[A-Za-z0-9_-]{21}$/); reply = JSON.stringify( await control(address, { operation: "inspect_actor", parameters: { actor_ref: nullActor } }), ); assert.match(reply, /"state":\{"type":"null"\}/); const inputWindow = vscode.window as { showInputBox: (...args: unknown[]) => unknown }; const originalInput = inputWindow.showInputBox; const inputs: unknown[][] = []; let stateAnswer: string | undefined; inputWindow.showInputBox = async (...args: unknown[]) => { inputs.push(args); return stateAnswer; }; let actor: string | undefined; try { assert.equal( await vscode.commands.executeCommand("runa.spawnSourceWithState", file, [file]), undefined, ); stateAnswer = "40"; actor = await vscode.commands.executeCommand("runa.spawnSourceWithState", file, [file]); assert.equal(inputs.length, 2, "context menus always prompt for state"); assert.match(JSON.stringify(inputs[1]), /Initial state for actors\/counter/); } finally { inputWindow.showInputBox = originalInput; } assert.match(String(actor), /^[A-Za-z0-9_-]{21}$/); await vscode.commands.executeCommand("runa.send", { actor_ref: actor }, "null"); await vscode.commands.executeCommand("runa.tick", 10); reply = JSON.stringify( await control(address, { operation: "inspect_actor", parameters: { actor_ref: actor } }), ); assert.match(reply, /"state":\{"type":"integer","data":42\}/); const revision = await vscode.commands.executeCommand("runa.commitSource", uri); assert.match(String(revision), /^[A-Za-z0-9]{1,64}$/); const opened = await vscode.commands.executeCommand("runa.sourceHistory", uri, revision); const historical = vscode.window.activeTextEditor?.document; assert.ok(historical); assert.equal(historical.uri.toString(), opened); assert.equal(historical.uri.scheme, "runa-source"); assert.ok(historical.uri.path.endsWith(`/source/actors/counter@${revision}.rn`)); assert.equal(historical.getText(), edited); await vscode.commands.executeCommand("type", { text: "// cannot edit\n" }); assert.equal(historical.getText(), edited); // Saving again changes only the working source; history is unchanged. await vscode.window.showTextDocument(document); await replaceText(document, original); assert.ok(await document.save()); assert.equal(historical.getText(), edited); prompts.length = 0; answer = undefined; window.showWarningMessage = async (...args: unknown[]) => { prompts.push(args); return answer; }; try { await vscode.commands.executeCommand("runa.rollbackSource", uri, revision); assert.equal(prompts.length, 1); assert.deepEqual(prompts[0]?.[1], { modal: true }); assert.equal(document.getText(), original, "cancelled rollback changes nothing"); answer = "Roll Back"; const reloaded = documentChanged(document, (text) => text === edited); await vscode.commands.executeCommand("runa.rollbackSource", uri, revision); await reloaded; } finally { window.showWarningMessage = warning; } assert.equal(document.isDirty, false); reply = JSON.stringify( await control(address, { operation: "source_history", parameters: { path: "actors/counter" } }), ); assert.equal((reply.match(/"revision"/g) ?? []).length, 1); // Saves from another client reload clean editors through image notifications. const current = JSON.parse( JSON.stringify( await control(address, { operation: "read_source", parameters: { path: "actors/counter" } }), ), ).result.Ok.data; const external = documentChanged(document, (text) => text === original); await control(address, { operation: "write_source", parameters: { path: "actors/counter", expected_source_id: current.source_id, source: original }, }); await external; // Revision documents stay isolated per connection session. await vscode.commands.executeCommand("runa.disconnect"); assert.equal(historical.getText(), edited); await vscode.commands.executeCommand("runa.connect", address); assert.equal( vscode.workspace.workspaceFolders?.filter((candidate) => candidate.uri.scheme === "runa-image") .length ?? 0, 0, ); const reopened = await vscode.commands.executeCommand( "runa.sourceHistory", uri, revision, ); assert.ok(reopened); assert.notEqual(reopened, opened); assert.equal(vscode.window.activeTextEditor?.document.getText(), edited); const revisions = new RevisionDocuments(); try { const cached = revisions.set(1, "actors/counter", "abc", edited); assert.equal(revisions.provideTextDocumentContent(cached), edited); assert.notEqual( revisions.set(2, "actors/counter", "abc", original).toString(), cached.toString(), ); assert.throws(() => revisions.set(1, "actors/counter", "abc", original), /Immutable/); revisions.remove(cached); assert.throws(() => revisions.provideTextDocumentContent(cached), /no longer cached/); } finally { revisions.dispose(); } console.log( "Runa sources: automatic tree, state input, save, uncommitted spawn, commit, history, rollback, session isolation passed", ); } async function replaceText(document: vscode.TextDocument, text: string): Promise { const edit = new vscode.WorkspaceEdit(); edit.replace( document.uri, new vscode.Range(document.positionAt(0), document.positionAt(document.getText().length)), text, ); assert.ok(await vscode.workspace.applyEdit(edit)); } async function editActorFiles(address: string): Promise { const extension = vscode.extensions.all.find( (candidate) => candidate.packageJSON.name === "runa", ); assert.ok(extension); const { actorTree, sourceTree } = extension.exports as ReturnType; const inputWindow = vscode.window as { showInputBox: (...args: unknown[]) => unknown }; const input = inputWindow.showInputBox; const [root] = sourceTree.getChildren(); assert.ok(root); inputWindow.showInputBox = async () => "tests/files.rn"; try { await vscode.commands.executeCommand("runa.newSource", root, [root]); } finally { inputWindow.showInputBox = input; } const sourceDocument = vscode.window.activeTextEditor?.document; assert.ok(sourceDocument); const original = 'pub fn handle(ctx, state, sender, message) { ctx.info("original"); state + message }\n'; await replaceText(sourceDocument, original); assert.ok(await sourceDocument.save()); const ref = await vscode.commands.executeCommand("runa.spawn", { path: "tests/files", state: "1", }); assert.ok(ref); const selected = { actor_ref: ref }; await vscode.commands.executeCommand("runa.refresh"); const parent = actorTree.getChildren().find((node) => node.actor_ref === ref); assert.ok(parent); assert.equal( actorTree.getTreeItem(parent).collapsibleState, vscode.TreeItemCollapsibleState.Collapsed, ); assert.deepEqual( actorTree.getChildren(parent).map((node) => ("file" in node ? node.file : "")), ["meta.json", "config.json", "state.json", "mailbox.json", "log"], ); const session = actorTree.session; const uri = (name: string) => actorFileUri(session, ref, name); const meta = await vscode.workspace.openTextDocument(uri("meta.json")); await vscode.window.showTextDocument(meta, { preview: false }); assert.equal(JSON.parse(meta.getText()).behavior_path, "tests/files"); assert.equal(JSON.parse(meta.getText()).state, undefined); assert.equal( (await vscode.workspace.fs.stat(meta.uri)).permissions, vscode.FilePermission.Readonly, ); assert.equal( (await vscode.workspace.fs.stat(uri("state.json"))).permissions, vscode.FilePermission.Readonly, ); await assert.rejects( async () => vscode.workspace.fs.writeFile(meta.uri, Buffer.from("{}")), /read-only/, ); const config = await vscode.workspace.openTextDocument(uri("config.json")); await vscode.window.showTextDocument(config); await replaceText(config, '{"log_level":"trace"}'); assert.ok(await config.save(), "configuration is writable while running"); await vscode.commands.executeCommand("runa.pause", selected); await vscode.commands.executeCommand("runa.refresh"); assert.equal((await vscode.workspace.fs.stat(uri("state.json"))).permissions, undefined); const state = await vscode.workspace.openTextDocument(uri("state.json")); assert.equal(state.getText().trim(), "1"); await vscode.window.showTextDocument(state, { preview: false }); await replaceText(state, "40"); assert.ok(await state.save()); const mailbox = await vscode.workspace.openTextDocument(uri("mailbox.json")); await vscode.window.showTextDocument(mailbox); await replaceText(mailbox, '{"reserved":[2],"queued":[3]}'); assert.ok(await mailbox.save()); await assert.rejects( async () => vscode.workspace.fs.writeFile(mailbox.uri, Buffer.from('{"reserved":[1,2],"queued":[]}')), /zero or one/, ); const executed = documentChanged(state, (text) => text.trim() === "45"); await vscode.commands.executeCommand("runa.restart", selected); await vscode.commands.executeCommand("runa.tick", 20); await executed; const log = await vscode.workspace.openTextDocument(uri("log")); await vscode.window.showTextDocument(log, { preview: false }); assert.match(log.getText(), /INFO: original/); assert.equal( (await vscode.workspace.fs.stat(log.uri)).permissions, vscode.FilePermission.Readonly, ); const working = original.replace("state + message", "state + message * 10"); await vscode.workspace.fs.writeFile(sourceDocument.uri, Buffer.from(working)); await vscode.commands.executeCommand("runa.editBehavior", selected); const behavior = vscode.window.activeTextEditor?.document; assert.ok(behavior); assert.equal(behavior.uri.toString(), uri("behavior").toString()); assert.equal( behavior.getText(), original, "edit starts from loaded behavior, not current working source", ); const temporary = original .replace('"original"', '"temporary"') .replace("state + message", "state + message * 100"); await replaceText(behavior, temporary); assert.ok(await behavior.save()); assert.equal( Buffer.from(await vscode.workspace.fs.readFile(sourceDocument.uri)).toString(), working, ); await vscode.commands.executeCommand("runa.editBehavior", selected); assert.equal(vscode.window.activeTextEditor?.document.uri.toString(), behavior.uri.toString()); await vscode.commands.executeCommand("runa.refresh"); const withTemporary = actorTree.getChildren().find((node) => node.actor_ref === ref); assert.ok(withTemporary); assert.ok( actorTree.getChildren(withTemporary).some((node) => "file" in node && node.file === "behavior"), ); const tempExecuted = documentChanged(state, (text) => text.trim() === "145"); const logged = documentChanged(log, (text) => text.includes("INFO: temporary")); await vscode.commands.executeCommand("runa.send", selected, "1"); await vscode.commands.executeCommand("runa.tick", 20); await tempExecuted; await logged; const window = vscode.window as { showWarningMessage: (...args: unknown[]) => unknown }; const warning = window.showWarningMessage; let answer: string | undefined; window.showWarningMessage = async () => answer; const renamed = vscode.Uri.from({ scheme: "runa-image", authority: address, path: "/source/moved/files.rn", }); try { await vscode.commands.executeCommand("runa.updateBehavior", selected); assert.equal( Buffer.from(await vscode.workspace.fs.readFile(sourceDocument.uri)).toString(), working, ); answer = "Update Behavior"; await vscode.commands.executeCommand("runa.updateBehavior", selected); assert.equal( Buffer.from(await vscode.workspace.fs.readFile(sourceDocument.uri)).toString(), temporary, ); answer = undefined; await assert.rejects( async () => vscode.workspace.fs.rename(sourceDocument.uri, renamed), /[Cc]ancel/, ); assert.equal( Buffer.from(await vscode.workspace.fs.readFile(sourceDocument.uri)).toString(), temporary, ); answer = "Rename and Update Actors"; const renamedMeta = documentChanged( meta, (text) => JSON.parse(text).behavior_path === "moved/files", ); inputWindow.showInputBox = async () => "moved/files.rn"; try { await vscode.commands.executeCommand("runa.renameSource", { uri: sourceDocument.uri }, [ { uri: sourceDocument.uri }, ]); } finally { inputWindow.showInputBox = input; } await renamedMeta; await vscode.commands.executeCommand("runa.goToBehavior", selected); assert.equal(vscode.window.activeTextEditor?.document.uri.toString(), renamed.toString()); await assert.rejects(async () => vscode.workspace.fs.delete(renamed), /stop the actors first/); await vscode.commands.executeCommand("runa.reload", selected); await vscode.commands.executeCommand("runa.tick", 20); await vscode.commands.executeCommand("runa.refresh"); const reloaded = actorTree.getChildren().find((node) => node.actor_ref === ref); assert.ok(reloaded); assert.ok( !actorTree.getChildren(reloaded).some((node) => "file" in node && node.file === "behavior"), ); await vscode.commands.executeCommand("runa.kill", selected); await vscode.commands.executeCommand("runa.refresh"); assert.match( Buffer.from(await vscode.workspace.fs.readFile(log.uri)).toString(), /INFO: temporary/, ); assert.equal( JSON.parse(Buffer.from(await vscode.workspace.fs.readFile(meta.uri)).toString()).removed, true, ); await vscode.workspace.fs.delete(renamed); assert.equal( (await vscode.workspace.fs.stat(state.uri)).permissions, vscode.FilePermission.Readonly, ); } finally { window.showWarningMessage = warning; } console.log( "Runa actor directories, live config, natural state/mailbox edits, logs, temporary behaviors and source operations passed", ); } function documentChanged( document: vscode.TextDocument, expected: (text: string) => boolean, ): Promise { return new Promise((resolve, reject) => { const subscription = vscode.workspace.onDidChangeTextDocument((event) => { if ( event.document.uri.toString() === document.uri.toString() && expected(event.document.getText()) ) { clearTimeout(timeout); subscription.dispose(); resolve(); } }); const timeout = setTimeout(() => { subscription.dispose(); reject( new Error( `Document did not refresh: ${document.uri.toString()} (closed=${document.isClosed}): ${document.getText()}`, ), ); }, 5000); }); }