From f75437af48cd7c63f76e68640dab3f188c156e78 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 2 Oct 2026 16:59:08 +0100 Subject: [PATCH] Expose running extension API for integration tests --- client/src/extension.ts | 20 +++++++++++++++++++- client/src/services/diagnostics.ts | 1 + client/src/services/webview.ts | 1 + client/src/state.ts | 1 + client/src/types/test-api.ts | 15 +++++++++++++++ client/src/webview/provider.ts | 15 +++++++++++++-- 6 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 client/src/types/test-api.ts diff --git a/client/src/extension.ts b/client/src/extension.ts index dce1496f..b3e52e0e 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -12,12 +12,14 @@ import { registerAutocomplete } from "./services/autocomplete"; import { refreshCodeLenses, registerCodeLens } from "./services/codelens"; import { runLanguageServer, stopLanguageServer } from "./lsp/server"; import { runClient, stopClient } from "./lsp/client"; +import type { LiquidJavaTestApi } from "./types/test-api"; /** * Activates the LiquidJava extension * @param context The extension context */ -export async function activate(context: vscode.ExtensionContext) { +export async function activate(context: vscode.ExtensionContext): Promise { + context.subscriptions.push(extension.diagnosticsEmitter); registerLogger(context); extension.logger!.client.info("Activating LiquidJava extension..."); @@ -30,6 +32,22 @@ export async function activate(context: vscode.ExtensionContext) { registerHover(); await applyItalicOverlay(); await startExtension(context); + + const ready = extension.client?.isRunning() + ? Promise.resolve() + : Promise.reject(new Error("LiquidJava language client did not start")); + // keep activation behavior while callers observe startup failures through ready + void ready.catch(() => {}); + + return { + ready, + onDiagnostics: extension.diagnosticsEmitter.event, + getState: () => ({ + status: extension.status, + diagnostics: [...(extension.diagnostics ?? [])], + }), + onWebviewMessage: extension.webview!.onWebviewMessage, + }; } /** diff --git a/client/src/services/diagnostics.ts b/client/src/services/diagnostics.ts index 57422227..4e022154 100644 --- a/client/src/services/diagnostics.ts +++ b/client/src/services/diagnostics.ts @@ -19,6 +19,7 @@ export function handleLJDiagnostics(diagnostics: LJDiagnostic[]) { extension.webview?.sendMessage({ type: "diagnostics", diagnostics }); if (extension.context) extension.webview?.sendMessage({ type: "context", context: extension.context, errorAtCursor: extension.errorAtCursor }); + extension.diagnosticsEmitter.fire(diagnostics); } /** diff --git a/client/src/services/webview.ts b/client/src/services/webview.ts index 23a57403..c3425953 100644 --- a/client/src/services/webview.ts +++ b/client/src/services/webview.ts @@ -13,6 +13,7 @@ export function registerWebview(context: vscode.ExtensionContext) { // webview provider context.subscriptions.push( + extension.webview, vscode.window.registerWebviewViewProvider(LiquidJavaWebviewProvider.viewType, extension.webview) ); // show view command diff --git a/client/src/state.ts b/client/src/state.ts index 2d8cdb07..56f6593f 100644 --- a/client/src/state.ts +++ b/client/src/state.ts @@ -25,6 +25,7 @@ export class ExtensionState { // application state file?: string; diagnostics?: LJDiagnostic[]; + readonly diagnosticsEmitter = new vscode.EventEmitter(); stateMachine?: LJStateMachine | null; context?: LJContext; currentSelection?: Range; diff --git a/client/src/types/test-api.ts b/client/src/types/test-api.ts new file mode 100644 index 00000000..07a8aa71 --- /dev/null +++ b/client/src/types/test-api.ts @@ -0,0 +1,15 @@ +import type * as vscode from "vscode"; +import type { ExtensionStatus } from "../state"; +import type { LJDiagnostic } from "./diagnostics"; + +export interface WebviewMessage { + direction: "toWebview" | "fromWebview"; + message: any; +} + +export interface LiquidJavaTestApi { + readonly ready: Promise; + readonly onDiagnostics: vscode.Event; + getState(): { status: ExtensionStatus | undefined; diagnostics: LJDiagnostic[] }; + readonly onWebviewMessage: vscode.Event; +} diff --git a/client/src/webview/provider.ts b/client/src/webview/provider.ts index 1cacfe93..0f861a78 100644 --- a/client/src/webview/provider.ts +++ b/client/src/webview/provider.ts @@ -1,16 +1,19 @@ import * as vscode from 'vscode'; import { getHtml } from './html'; import { highlightRange, openFile } from '../services/editor'; +import type { WebviewMessage } from '../types/test-api'; /** * Webview provider for the LiquidJava extension * Provides an interactive user interface for the LiquidJava diagnostics */ -export class LiquidJavaWebviewProvider implements vscode.WebviewViewProvider { +export class LiquidJavaWebviewProvider implements vscode.WebviewViewProvider, vscode.Disposable { public static readonly viewType = "liquidJavaView"; private view?: vscode.WebviewView; private messageEmitter = new vscode.EventEmitter(); public readonly onDidReceiveMessage = this.messageEmitter.event; + private webviewMessageEmitter = new vscode.EventEmitter(); + public readonly onWebviewMessage = this.webviewMessageEmitter.event; constructor(private readonly extensionUri: vscode.Uri) {} @@ -29,6 +32,7 @@ export class LiquidJavaWebviewProvider implements vscode.WebviewViewProvider { // listen for messages coming from webview webviewView.webview.onDidReceiveMessage(message => { // emit the message to any external listeners + this.webviewMessageEmitter.fire({ direction: "fromWebview", message }); this.messageEmitter.fire(message); // handle message @@ -46,7 +50,14 @@ export class LiquidJavaWebviewProvider implements vscode.WebviewViewProvider { * @param message */ public sendMessage(message: any) { - this.view?.webview.postMessage(message); + if (!this.view) return; + this.webviewMessageEmitter.fire({ direction: "toWebview", message }); + this.view.webview.postMessage(message); + } + + public dispose() { + this.messageEmitter.dispose(); + this.webviewMessageEmitter.dispose(); } /**