diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0587c47dcc..191977c516 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -6,6 +6,12 @@ on:
branches:
- main
+# Set default permissions for all jobs
+permissions:
+ contents: read # Needed to check out code
+ checks: write # Needed to report test results
+ pull-requests: write # Needed to add comments/annotations to PRs
+
jobs:
test:
runs-on: ubuntu-latest
@@ -51,6 +57,5 @@ jobs:
- name: Prettier / Format Check
run: npm run format
- - name: Tests
+ - name: Extension Tests
run: xvfb-run -a npm run test
- if: runner.os == 'Linux'
diff --git a/.gitignore b/.gitignore
index a380392074..e6899656c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
out
dist
node_modules
+tmp
.vscode-test/
*.vsix
diff --git a/.vscode-test.mjs b/.vscode-test.mjs
index da0108114e..ce9992d0d1 100644
--- a/.vscode-test.mjs
+++ b/.vscode-test.mjs
@@ -1,8 +1,14 @@
import { defineConfig } from "@vscode/test-cli"
+import path from "path"
export default defineConfig({
- files: "out/**/*.test.js",
+ files: "out/test/**/*.test.js",
mocha: {
+ ui: "bdd",
timeout: 20000, // Maximum time (in ms) that a test can run before failing
},
+ workspaceFolder: "test-workspace",
+ version: "stable",
+ extensionDevelopmentPath: path.resolve("./"),
+ launchArgs: ["--disable-extensions"],
})
diff --git a/package-lock.json b/package-lock.json
index 96a127d497..4d042f8b28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -60,6 +60,7 @@
"@vscode/test-electron": "^2.4.0",
"esbuild": "^0.21.5",
"eslint": "^8.57.0",
+ "glob": "^10.3.10",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts
index 440a353ec6..063cf3bfc7 100644
--- a/src/test/extension.test.ts
+++ b/src/test/extension.test.ts
@@ -4,7 +4,7 @@ import path from "path"
import "should"
import * as vscode from "vscode"
-const packagePath = path.join(__dirname, "..", "..", "..", "package.json")
+const packagePath = path.join(__dirname, "..", "..", "package.json")
describe("Cline Extension", () => {
after(() => {
@@ -23,4 +23,69 @@ describe("Cline Extension", () => {
await new Promise((resolve) => setTimeout(resolve, 400))
await vscode.commands.executeCommand("cline.plusButtonClicked")
})
+
+ // New test to verify xvfb and webview functionality
+ it("should create and display a webview panel", async () => {
+ // Create a webview panel
+ const panel = vscode.window.createWebviewPanel("testWebview", "CI/CD Test", vscode.ViewColumn.One, {
+ enableScripts: true,
+ })
+
+ // Set some HTML content
+ panel.webview.html = `
+
+
+
+
+ xvfb Test
+
+
+ Testing xvfb display server
+
+
+ `
+
+ // Verify panel exists
+ should.exist(panel)
+ panel.visible.should.be.true()
+
+ // Clean up
+ panel.dispose()
+ })
+
+ // Test webview message passing
+ it("should handle webview messages", async () => {
+ const panel = vscode.window.createWebviewPanel("testWebview", "Message Test", vscode.ViewColumn.One, {
+ enableScripts: true,
+ })
+
+ // Set up message handling
+ const messagePromise = new Promise((resolve) => {
+ panel.webview.onDidReceiveMessage((message) => resolve(message.text), undefined)
+ })
+
+ // Add message sending script
+ panel.webview.html = `
+
+
+
+
+ Message Test
+
+
+
+
+
+ `
+
+ // Wait for message
+ const message = await messagePromise
+ message.should.equal("test-message")
+
+ // Clean up
+ panel.dispose()
+ })
})
diff --git a/src/test/webview/chat-native.test.ts b/src/test/webview/chat-native.test.ts
new file mode 100644
index 0000000000..775af4b7ae
--- /dev/null
+++ b/src/test/webview/chat-native.test.ts
@@ -0,0 +1,116 @@
+import * as vscode from "vscode"
+import { describe, it, beforeEach, afterEach } from "mocha"
+import { strict as assert } from "assert"
+import { join } from "path"
+describe("Chat Integration Tests", () => {
+ let panel: vscode.WebviewPanel
+ let disposables: vscode.Disposable[] = []
+
+ beforeEach(async () => {
+ // Create VSCode webview panel
+ panel = vscode.window.createWebviewPanel("testWebview", "Chat Test", vscode.ViewColumn.One, {
+ enableScripts: true,
+ retainContextWhenHidden: true,
+ })
+
+ // Set up minimal test webview
+ panel.webview.html = `
+
+
+
+
+
+
+
+
+
+
+ `
+ })
+
+ afterEach(() => {
+ panel.dispose()
+ disposables.forEach((d) => d.dispose())
+ disposables = []
+ })
+
+ it("should send chat messages", async () => {
+ // Set up message listener
+ const messagePromise = new Promise((resolve) => {
+ panel.webview.onDidReceiveMessage((message) => {
+ if (message.type === "newTask") {
+ resolve(message)
+ }
+ })
+ })
+
+ // Trigger send message
+ await panel.webview.postMessage({
+ type: "sendMessage",
+ text: "Create a hello world app",
+ })
+
+ // Verify message was sent
+ const message = await messagePromise
+ assert.equal(message.type, "newTask")
+ assert.equal(message.text, "Create a hello world app")
+ })
+
+ it("should toggle between plan and act modes", async () => {
+ // Set up state change listener
+ const stateChangePromise = new Promise((resolve) => {
+ panel.webview.onDidReceiveMessage((message) => {
+ if (message.type === "chatSettings") {
+ resolve(message)
+ }
+ })
+ })
+
+ // Trigger mode toggle
+ await panel.webview.postMessage({ type: "toggleMode" })
+
+ // Verify mode changed
+ const stateChange = await stateChangePromise
+ assert.equal(stateChange.chatSettings.mode, "act")
+ })
+
+ it("should handle tool approval flow", async () => {
+ // Set up approval listener
+ const approvalPromise = new Promise((resolve) => {
+ panel.webview.onDidReceiveMessage((message) => {
+ if (message.type === "askResponse") {
+ resolve(message)
+ }
+ })
+ })
+
+ // Trigger tool approval
+ await panel.webview.postMessage({
+ type: "invoke",
+ invoke: "primaryButtonClick",
+ })
+
+ // Verify approval was sent
+ const response = await approvalPromise
+ assert.equal(response.type, "askResponse")
+ assert.equal(response.askResponse, "yesButtonClicked")
+ })
+})
diff --git a/tsconfig.test.json b/tsconfig.test.json
index 40ca1269d2..45dd541b8b 100644
--- a/tsconfig.test.json
+++ b/tsconfig.test.json
@@ -8,7 +8,9 @@
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
- "types": ["node", "mocha", "should", "vscode"]
+ "types": ["node", "mocha", "should", "vscode"],
+ "outDir": "out",
+ "rootDir": "src"
},
"include": ["src/**/*.test.ts"]
}
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index ce0a4180ca..adbd59ae31 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -803,6 +803,7 @@ const ChatTextArea = forwardRef(
}}
/>
{
if (typeof ref === "function") {
ref(el)
@@ -909,6 +910,7 @@ const ChatTextArea = forwardRef(
}}
/> */}
{
if (!textAreaDisabled) {
@@ -923,6 +925,7 @@ const ChatTextArea = forwardRef(
(
(
-
+
Plan
Act