From d5c7d6daca3fb49f0790c8cc2898cddab061d11b Mon Sep 17 00:00:00 2001 From: Felix Haeberle Date: Tue, 10 Sep 2024 16:26:31 +0200 Subject: [PATCH] update recommendation --- .../recommendation/recommendation.test.ts | 72 +++++++++++++++---- .../recommendation/recommendation.ts | 57 +++++++++++---- 2 files changed, 99 insertions(+), 30 deletions(-) diff --git a/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.test.ts b/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.test.ts index b00e730d57..379e64067a 100644 --- a/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.test.ts +++ b/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.test.ts @@ -7,6 +7,7 @@ import { } from "./recommendation.js" import * as fs from "node:fs/promises" +// Mocking VSCode APIs vi.mock("vscode", () => ({ resolveWebviewView: vi.fn(), window: { @@ -15,13 +16,14 @@ vi.mock("vscode", () => ({ }, WebviewView: class { webview = { - asWebviewUri: (uri: vscode.Uri) => uri.toString(), + asWebviewUri: (uri: vscode.Uri) => (uri ? uri.toString() : "invalid-uri"), options: {}, html: "", onDidReceiveMessage: vi.fn(), postMessage: vi.fn(), cspSource: "", } + onDidDispose = vi.fn() }, commands: { executeCommand: vi.fn(), @@ -33,6 +35,7 @@ vi.mock("vscode", () => ({ Uri: { file: vi.fn(), joinPath: vi.fn((...args: string[]) => args.join("/")), + parse: vi.fn((uri: string) => ({ toString: () => uri })), }, })) vi.mock("node:path", () => ({ @@ -42,6 +45,16 @@ vi.mock("node:fs", () => ({ existsSync: vi.fn(), readFileSync: vi.fn(), })) +vi.mock("../../configuration.js", () => ({ + CONFIGURATION: { + EVENTS: { + ON_DID_RECOMMENDATION_VIEW_CHANGE: { + event: vi.fn(), + fire: vi.fn(), + }, + }, + }, +})) describe("recommendationBannerView", () => { beforeEach(() => { @@ -68,7 +81,7 @@ describe("recommendationBannerView", () => { }) }) -describe("createRecommendationBanner", () => { +describe("createRecommendationView", () => { const fakeWorkspaceFolder: vscode.WorkspaceFolder = { uri: { fsPath: "/path/to/workspace" } as vscode.Uri, name: "test-workspace", @@ -83,26 +96,33 @@ describe("createRecommendationBanner", () => { }) expect(typeof result.resolveWebviewView).toBe("function") }) -}) -describe("createRecommendationBanner", () => { - const fakeWorkspaceFolder: vscode.WorkspaceFolder = { - uri: { fsPath: "/path/to/workspace" } as vscode.Uri, - name: "test-workspace", - index: 0, - } + it("should set up webview with scripts enabled and handle messages", async () => { + // @ts-expect-error + const webviewView = new vscode.WebviewView() + const mockContext = {} as vscode.ExtensionContext - it("should return an object with a resolveWebviewView function", () => { - const result = createRecommendationView({ + const recommendationView = createRecommendationView({ fs: fs, workspaceFolder: fakeWorkspaceFolder, - context: {} as vscode.ExtensionContext, + context: mockContext, + }) + + await recommendationView.resolveWebviewView(webviewView) + + expect(webviewView.webview.options.enableScripts).toBe(true) + + webviewView.webview.onDidReceiveMessage({ + command: "addSherlockToWorkspace", + }) + + expect(webviewView.webview.onDidReceiveMessage).toHaveBeenCalledWith({ + command: "addSherlockToWorkspace", }) - expect(typeof result.resolveWebviewView).toBe("function") }) }) -describe("getRecommendationBannerHtml", () => { +describe("getRecommendationViewHtml", () => { const fakeWorkspaceFolder: vscode.WorkspaceFolder = { uri: { fsPath: "/path/to/workspace" } as vscode.Uri, name: "test-workspace", @@ -116,9 +136,10 @@ describe("getRecommendationBannerHtml", () => { it("should return html", async () => { const args = { webview: { - asWebviewUri: (uri: vscode.Uri) => (uri ? uri.toString() : ""), + asWebviewUri: (uri: vscode.Uri) => (uri ? uri.toString() : "invalid-uri"), options: {}, html: "", + onDidDispose: vi.fn(), onDidReceiveMessage: vi.fn(), postMessage: vi.fn(), cspSource: "self", @@ -130,4 +151,25 @@ describe("getRecommendationBannerHtml", () => { const result = await getRecommendationViewHtml(args) expect(result).toContain(``) }) + + it("should generate valid HTML structure with content", async () => { + const args = { + webview: { + asWebviewUri: (uri: vscode.Uri) => (uri ? uri.toString() : "invalid-uri"), + options: {}, + html: "", + onDidReceiveMessage: vi.fn(), + postMessage: vi.fn(), + cspSource: "self", + } as unknown as vscode.Webview, + fs: {} as typeof fs, + workspaceFolder: fakeWorkspaceFolder, + context: {} as vscode.ExtensionContext, + } + + const html = await getRecommendationViewHtml(args) + expect(html).toContain("To improve your i18n workflow:") + expect(html).toContain('
') + expect(html).toContain('') + }) }) diff --git a/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.ts b/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.ts index 179433a624..4dcccc27ac 100644 --- a/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.ts +++ b/inlang/source-code/ide-extension/src/utilities/recommendation/recommendation.ts @@ -19,7 +19,7 @@ export function createRecommendationView(args: { switch (message.command) { case "addSherlockToWorkspace": if (args.workspaceFolder) { - Sherlock.add({ + await Sherlock.add({ fs: args.fs, workingDirectory: args.workspaceFolder.uri.fsPath, }) @@ -35,26 +35,42 @@ export function createRecommendationView(args: { } }) - webviewView.webview.html = await getRecommendationViewHtml({ - webview: webviewView.webview, - workspaceFolder: args.workspaceFolder, - context: args.context, - fs: args.fs, - }) + // Load the webview content initially + await updateRecommendationViewHtml(webviewView, args) + + // Listen for updates and debounce rapid events + const debouncedUpdate = debounce(() => updateRecommendationViewHtml(webviewView, args), 300) + + const disposableEvent = CONFIGURATION.EVENTS.ON_DID_RECOMMENDATION_VIEW_CHANGE.event( + async () => { + debouncedUpdate() + } + ) - // Listen for updates - CONFIGURATION.EVENTS.ON_DID_RECOMMENDATION_VIEW_CHANGE.event(async () => { - webviewView.webview.html = await getRecommendationViewHtml({ - webview: webviewView.webview, - workspaceFolder: args.workspaceFolder, - context: args.context, - fs: args.fs, - }) + // Dispose the listener when the view is disposed + webviewView.onDidDispose(() => { + disposableEvent.dispose() }) }, } } +async function updateRecommendationViewHtml( + webviewView: vscode.WebviewView, + args: { + workspaceFolder: vscode.WorkspaceFolder + context: vscode.ExtensionContext + fs: FileSystem + } +) { + webviewView.webview.html = await getRecommendationViewHtml({ + webview: webviewView.webview, + workspaceFolder: args.workspaceFolder, + context: args.context, + fs: args.fs, + }) +} + export async function getRecommendationViewHtml(args: { webview: vscode.Webview workspaceFolder: vscode.WorkspaceFolder @@ -215,3 +231,14 @@ export async function recommendationBannerView(args: { }) ) } + +const debounce = void>( + fn: T, + delay: number +): ((...args: Parameters) => void) => { + let timeout: NodeJS.Timeout + return (...args: Parameters) => { + clearTimeout(timeout) + timeout = setTimeout(() => fn(...args), delay) + } +}