From bf5ff6921cf948fc5bc2e01ec3fbe2bdbaab4fd9 Mon Sep 17 00:00:00 2001
From: axel7083 <42176370+axel7083@users.noreply.github.com>
Date: Thu, 6 Jun 2024 15:07:38 +0200
Subject: [PATCH] chore: moving static webview logic to dedicated util file
 (#1167)

* chore: moving static webview logic to dedicated util file

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* fix: linter

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

---------

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
---
 packages/backend/src/studio.ts            | 61 ++----------------
 packages/backend/src/webviewUtils.spec.ts | 78 +++++++++++++++++++++++
 packages/backend/src/webviewUtils.ts      | 73 +++++++++++++++++++++
 3 files changed, 155 insertions(+), 57 deletions(-)
 create mode 100644 packages/backend/src/webviewUtils.spec.ts
 create mode 100644 packages/backend/src/webviewUtils.ts

diff --git a/packages/backend/src/studio.ts b/packages/backend/src/studio.ts
index f7950141b..80f941bf3 100644
--- a/packages/backend/src/studio.ts
+++ b/packages/backend/src/studio.ts
@@ -16,12 +16,11 @@
  * SPDX-License-Identifier: Apache-2.0
  ***********************************************************************/
 
-import { Uri, window, env, version } from '@podman-desktop/api';
+import { env, version } from '@podman-desktop/api';
 import { satisfies, minVersion, coerce } from 'semver';
 import type {
   ExtensionContext,
   TelemetryLogger,
-  WebviewOptions,
   WebviewPanel,
   WebviewPanelOnDidChangeViewStateEvent,
 } from '@podman-desktop/api';
@@ -32,7 +31,6 @@ import { GitManager } from './managers/gitManager';
 import { TaskRegistry } from './registries/TaskRegistry';
 import { CatalogManager } from './managers/catalogManager';
 import { ModelsManager } from './managers/modelsManager';
-import fs from 'node:fs';
 import { ContainerRegistry } from './registries/ContainerRegistry';
 import { PodmanConnection } from './managers/podmanConnection';
 import { LocalRepositoryRegistry } from './registries/LocalRepositoryRegistry';
@@ -43,6 +41,7 @@ import { CancellationTokenRegistry } from './registries/CancellationTokenRegistr
 import { engines } from '../package.json';
 import { BuilderManager } from './managers/recipes/BuilderManager';
 import { PodManager } from './managers/recipes/PodManager';
+import { initWebview } from './webviewUtils';
 
 export const AI_LAB_COLLECT_GPU_COMMAND = 'ai-lab.gpu.collect';
 
@@ -95,50 +94,8 @@ export class Studio {
 
     this.telemetry.logUsage('start');
 
-    const extensionUri = this.#extensionContext.extensionUri;
-
-    // register webview
-    this.#panel = window.createWebviewPanel('studio', 'AI Lab', this.getWebviewOptions(extensionUri));
-    this.#extensionContext.subscriptions.push(this.#panel);
-
-    // update html
-
-    const indexHtmlUri = Uri.joinPath(extensionUri, 'media', 'index.html');
-    const indexHtmlPath = indexHtmlUri.fsPath;
-
-    let indexHtml = await fs.promises.readFile(indexHtmlPath, 'utf8');
-
-    // replace links with webView Uri links
-    // in the content <script type="module" crossorigin src="./index-RKnfBG18.js"></script> replace src with webview.asWebviewUri
-    const scriptLink = indexHtml.match(/<script.*?src="(.*?)".*?>/g);
-    if (scriptLink) {
-      scriptLink.forEach(link => {
-        const src = link.match(/src="(.*?)"/);
-        if (src) {
-          const webviewSrc = this.#panel?.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', src[1]));
-          if (!webviewSrc) throw new Error('undefined webviewSrc');
-          indexHtml = indexHtml.replace(src[1], webviewSrc.toString());
-        }
-      });
-    }
-
-    // and now replace for css file as well
-    const cssLink = indexHtml.match(/<link.*?href="(.*?)".*?>/g);
-    if (cssLink) {
-      cssLink.forEach(link => {
-        const href = link.match(/href="(.*?)"/);
-        if (href) {
-          const webviewHref = this.#panel?.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', href[1]));
-          if (!webviewHref)
-            throw new Error('Something went wrong while replacing links with webView Uri links: undefined webviewHref');
-          indexHtml = indexHtml.replace(href[1], webviewHref.toString());
-        }
-      });
-    }
-
-    console.log('updated indexHtml to', indexHtml);
-
-    this.#panel.webview.html = indexHtml;
+    // init webview
+    this.#panel = await initWebview(this.#extensionContext.extensionUri);
 
     // Creating cancellation token registry
     const cancellationTokenRegistry = new CancellationTokenRegistry();
@@ -251,14 +208,4 @@ export class Studio {
     console.log('stopping AI Lab extension');
     this.telemetry?.logUsage('stop');
   }
-
-  getWebviewOptions(extensionUri: Uri): WebviewOptions {
-    return {
-      // Enable javascript in the webview
-      // enableScripts: true,
-
-      // And restrict the webview to only loading content from our extension's `media` directory.
-      localResourceRoots: [Uri.joinPath(extensionUri, 'media')],
-    };
-  }
 }
diff --git a/packages/backend/src/webviewUtils.spec.ts b/packages/backend/src/webviewUtils.spec.ts
new file mode 100644
index 000000000..b1e19d0bc
--- /dev/null
+++ b/packages/backend/src/webviewUtils.spec.ts
@@ -0,0 +1,78 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import { beforeEach, expect, test, vi } from 'vitest';
+import { initWebview } from './webviewUtils';
+import type { Uri } from '@podman-desktop/api';
+import { promises } from 'node:fs';
+
+vi.mock('@podman-desktop/api', async () => {
+  return {
+    Uri: class {
+      static joinPath = () => ({ fsPath: '.' });
+    },
+    window: {
+      createWebviewPanel: () => ({
+        webview: {
+          html: '',
+          onDidReceiveMessage: vi.fn(),
+          postMessage: vi.fn(),
+          asWebviewUri: () => 'dummy-src',
+        },
+        onDidChangeViewState: vi.fn(),
+      }),
+    },
+  };
+});
+
+vi.mock('node:fs', () => ({
+  promises: {
+    readFile: vi.fn(),
+  },
+}));
+
+beforeEach(() => {
+  vi.resetAllMocks();
+});
+
+test('panel should have file content as html', async () => {
+  vi.mocked(promises.readFile).mockImplementation(() => {
+    return Promise.resolve('<html></html>');
+  });
+
+  const panel = await initWebview({} as unknown as Uri);
+  expect(panel.webview.html).toBe('<html></html>');
+});
+
+test('script src should be replaced with asWebviewUri result', async () => {
+  vi.mocked(promises.readFile).mockImplementation(() => {
+    return Promise.resolve('<script type="module" crossorigin src="./index-RKnfBG18.js"></script>');
+  });
+
+  const panel = await initWebview({} as unknown as Uri);
+  expect(panel.webview.html).toBe('<script type="module" crossorigin src="dummy-src"></script>');
+});
+
+test('links src should be replaced with asWebviewUri result', async () => {
+  vi.mocked(promises.readFile).mockImplementation(() => {
+    return Promise.resolve('<link rel="stylesheet" href="./styles.css">');
+  });
+
+  const panel = await initWebview({} as unknown as Uri);
+  expect(panel.webview.html).toBe('<link rel="stylesheet" href="dummy-src">');
+});
diff --git a/packages/backend/src/webviewUtils.ts b/packages/backend/src/webviewUtils.ts
new file mode 100644
index 000000000..2aea9b848
--- /dev/null
+++ b/packages/backend/src/webviewUtils.ts
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ***********************************************************************/
+
+import { Uri, type WebviewOptions, type WebviewPanel, window } from '@podman-desktop/api';
+import { promises } from 'node:fs';
+
+function getWebviewOptions(extensionUri: Uri): WebviewOptions {
+  return {
+    // Enable javascript in the webview
+    // enableScripts: true,
+
+    // And restrict the webview to only loading content from our extension's `media` directory.
+    localResourceRoots: [Uri.joinPath(extensionUri, 'media')],
+  };
+}
+
+export async function initWebview(extensionUri: Uri): Promise<WebviewPanel> {
+  // register webview
+  const panel = window.createWebviewPanel('studio', 'AI Lab', getWebviewOptions(extensionUri));
+
+  // update html
+  const indexHtmlUri = Uri.joinPath(extensionUri, 'media', 'index.html');
+  const indexHtmlPath = indexHtmlUri.fsPath;
+
+  let indexHtml = await promises.readFile(indexHtmlPath, 'utf8');
+
+  // replace links with webView Uri links
+  // in the content <script type="module" crossorigin src="./index-RKnfBG18.js"></script> replace src with webview.asWebviewUri
+  const scriptLink = indexHtml.match(/<script.*?src="(.*?)".*?>/g);
+  if (scriptLink) {
+    scriptLink.forEach(link => {
+      const src = link.match(/src="(.*?)"/);
+      if (src) {
+        const webviewSrc = panel.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', src[1]));
+        if (!webviewSrc) throw new Error('undefined webviewSrc');
+        indexHtml = indexHtml.replace(src[1], webviewSrc.toString());
+      }
+    });
+  }
+
+  // and now replace for css file as well
+  const cssLink = indexHtml.match(/<link.*?href="(.*?)".*?>/g);
+  if (cssLink) {
+    cssLink.forEach(link => {
+      const href = link.match(/href="(.*?)"/);
+      if (href) {
+        const webviewHref = panel.webview.asWebviewUri(Uri.joinPath(extensionUri, 'media', href[1]));
+        if (!webviewHref)
+          throw new Error('Something went wrong while replacing links with webView Uri links: undefined webviewHref');
+        indexHtml = indexHtml.replace(href[1], webviewHref.toString());
+      }
+    });
+  }
+
+  panel.webview.html = indexHtml;
+
+  return panel;
+}