浏览代码

Set up vscode integration test

ColemanRoo 1 年之前
父节点
当前提交
66c5485534
共有 7 个文件被更改,包括 387 次插入15 次删除
  1. 3 0
      .gitignore
  2. 12 3
      .vscode-test.mjs
  3. 4 0
      package.json
  4. 5 0
      src/exports/cline.d.ts
  5. 2 0
      src/exports/index.ts
  6. 342 12
      src/test/extension.test.ts
  7. 19 0
      src/test/tsconfig.json

+ 3 - 0
.gitignore

@@ -11,3 +11,6 @@ roo-cline-*.vsix
 
 # Local prompts and rules
 /local-prompts
+
+# Test environment
+.test_env

+ 12 - 3
.vscode-test.mjs

@@ -1,5 +1,14 @@
-import { defineConfig } from "@vscode/test-cli"
+import { defineConfig } from '@vscode/test-cli';
 
 export default defineConfig({
-	files: "out/test/**/*.test.js",
-})
+	files: 'src/test/extension.test.ts',
+	workspaceFolder: '.',
+	mocha: {
+		timeout: 60000,
+		ui: 'tdd'
+	},
+	launchArgs: [
+		'--enable-proposed-api=RooVeterinaryInc.roo-cline',
+		'--disable-extensions'
+	]
+});

+ 4 - 0
package.json

@@ -20,6 +20,9 @@
     "url": "https://github.com/RooVetGit/Roo-Cline"
   },
   "homepage": "https://github.com/RooVetGit/Roo-Cline",
+  "enabledApiProposals": [
+    "extensionRuntime"
+  ],
   "categories": [
     "AI",
     "Chat",
@@ -159,6 +162,7 @@
     "start:webview": "cd webview-ui && npm run start",
     "test": "jest && npm run test:webview",
     "test:webview": "cd webview-ui && npm run test",
+    "test:extension": "vscode-test",
     "prepare": "husky",
     "publish:marketplace": "vsce publish",
     "publish": "npm run build && changeset publish && npm install --package-lock-only",

+ 5 - 0
src/exports/cline.d.ts

@@ -34,4 +34,9 @@ export interface ClineAPI {
 	 * Simulates pressing the secondary button in the chat interface.
 	 */
 	pressSecondaryButton(): Promise<void>
+
+	/**
+	 * The sidebar provider instance.
+	 */
+	sidebarProvider: ClineSidebarProvider
 }

+ 2 - 0
src/exports/index.ts

@@ -56,6 +56,8 @@ export function createClineAPI(outputChannel: vscode.OutputChannel, sidebarProvi
 				invoke: "secondaryButtonClick",
 			})
 		},
+
+		sidebarProvider: sidebarProvider,
 	}
 
 	return api

+ 342 - 12
src/test/extension.test.ts

@@ -1,15 +1,345 @@
-import * as assert from "assert"
+const assert = require('assert');
+const vscode = require('vscode');
+const path = require('path');
+const fs = require('fs');
 
-// You can import and use all API from the 'vscode' module
-// as well as import your extension to test it
-import * as vscode from "vscode"
-// import * as myExtension from '../../extension';
+suite('Roo Cline Extension Test Suite', () => {
+	vscode.window.showInformationMessage('Starting Roo Cline extension tests.');
 
-suite("Extension Test Suite", () => {
-	vscode.window.showInformationMessage("Start all tests.")
+	test('Extension should be present', () => {
+		const extension = vscode.extensions.getExtension('RooVeterinaryInc.roo-cline');
+		assert.notStrictEqual(extension, undefined);
+	});
 
-	test("Sample test", () => {
-		assert.strictEqual(-1, [1, 2, 3].indexOf(5))
-		assert.strictEqual(-1, [1, 2, 3].indexOf(0))
-	})
-})
+	test('Extension should activate', async () => {
+		const extension = vscode.extensions.getExtension('RooVeterinaryInc.roo-cline');
+		if (!extension) {
+			assert.fail('Extension not found');
+		}
+		await extension.activate();
+		assert.strictEqual(extension.isActive, true);
+	});
+
+	test('OpenRouter API key and models should be configured correctly', function(done) {
+		// @ts-ignore
+		this.timeout(60000); // Increase timeout to 60s for network requests
+
+		(async () => {
+			try {
+				// Get extension instance
+				const extension = vscode.extensions.getExtension('RooVeterinaryInc.roo-cline');
+				if (!extension) {
+					done(new Error('Extension not found'));
+					return;
+				}
+
+				// Verify API key is set and valid
+				const testEnvPath = path.join(__dirname, '.test_env');
+				const envContent = fs.readFileSync(testEnvPath, 'utf8');
+				const match = envContent.match(/OPEN_ROUTER_API_KEY=(.+)/);
+				if (!match) {
+					done(new Error('OpenRouter API key should be present in .test_env'));
+					return;
+				}
+				const apiKey = match[1];
+				if (!apiKey.startsWith('sk-or-v1-')) {
+					done(new Error('OpenRouter API key should have correct format'));
+					return;
+				}
+
+				// Activate extension and get provider
+				const api = await extension.activate();
+				if (!api) {
+					done(new Error('Extension API not found'));
+					return;
+				}
+
+				// Get the provider from the extension's exports
+				const provider = api.sidebarProvider;
+				if (!provider) {
+					done(new Error('Provider not found'));
+					return;
+				}
+
+				// Set up the API configuration
+				await provider.updateGlobalState('apiProvider', 'openrouter');
+				await provider.storeSecret('openRouterApiKey', apiKey);
+
+				// Set up timeout to fail test if models don't load
+				const timeout = setTimeout(() => {
+					done(new Error('Timeout waiting for models to load'));
+				}, 30000);
+
+				// Wait for models to be loaded
+				const checkModels = setInterval(async () => {
+					try {
+						const models = await provider.readOpenRouterModels();
+						if (!models) {
+							return;
+						}
+
+						clearInterval(checkModels);
+						clearTimeout(timeout);
+
+						// Verify expected Claude models are available
+						const expectedModels = [
+							'anthropic/claude-3.5-sonnet:beta',
+							'anthropic/claude-3-sonnet:beta',
+							'anthropic/claude-3.5-sonnet',
+							'anthropic/claude-3.5-sonnet-20240620',
+							'anthropic/claude-3.5-sonnet-20240620:beta',
+							'anthropic/claude-3.5-haiku:beta'
+						];
+
+						for (const modelId of expectedModels) {
+							assert.strictEqual(
+								modelId in models,
+								true,
+								`Model ${modelId} should be available`
+							);
+						}
+
+						done();
+					} catch (error) {
+						clearInterval(checkModels);
+						clearTimeout(timeout);
+						done(error);
+					}
+				}, 1000);
+
+				// Trigger model loading
+				await provider.refreshOpenRouterModels();
+
+			} catch (error) {
+				done(error);
+			}
+		})();
+	});
+
+	test('Commands should be registered', async () => {
+		const commands = await vscode.commands.getCommands(true);
+		
+		// Test core commands are registered
+		const expectedCommands = [
+			'roo-cline.plusButtonClicked',
+			'roo-cline.mcpButtonClicked', 
+			'roo-cline.historyButtonClicked',
+			'roo-cline.popoutButtonClicked',
+			'roo-cline.settingsButtonClicked',
+			'roo-cline.openInNewTab'
+		];
+
+		for (const cmd of expectedCommands) {
+			assert.strictEqual(
+				commands.includes(cmd),
+				true,
+				`Command ${cmd} should be registered`
+			);
+		}
+	});
+
+	test('Views should be registered', () => {
+		const view = vscode.window.createWebviewPanel(
+			'roo-cline.SidebarProvider',
+			'Roo Cline',
+			vscode.ViewColumn.One,
+			{}
+		);
+		assert.notStrictEqual(view, undefined);
+		view.dispose();
+	});
+
+	test('Should handle prompt and response correctly', async function() {
+		// @ts-ignore
+		this.timeout(60000); // Increase timeout for API request
+
+		const timeout = 30000;
+		const interval = 1000;
+
+		// Get extension instance
+		const extension = vscode.extensions.getExtension('RooVeterinaryInc.roo-cline');
+		if (!extension) {
+			assert.fail('Extension not found');
+			return;
+		}
+
+		// Activate extension and get API
+		const api = await extension.activate();
+		if (!api) {
+			assert.fail('Extension API not found');
+			return;
+		}
+
+		// Get provider
+		const provider = api.sidebarProvider;
+		if (!provider) {
+			assert.fail('Provider not found');
+			return;
+		}
+
+		// Set up API configuration
+		await provider.updateGlobalState('apiProvider', 'openrouter');
+		await provider.updateGlobalState('openRouterModelId', 'anthropic/claude-3.5-sonnet');
+		const testEnvPath = path.join(__dirname, '.test_env');
+		const envContent = fs.readFileSync(testEnvPath, 'utf8');
+		const match = envContent.match(/OPEN_ROUTER_API_KEY=(.+)/);
+		if (!match) {
+			assert.fail('OpenRouter API key should be present in .test_env');
+			return;
+		}
+		await provider.storeSecret('openRouterApiKey', match[1]);
+
+		// Create webview panel with development options
+		const extensionUri = extension.extensionUri;
+		const panel = vscode.window.createWebviewPanel(
+			'roo-cline.SidebarProvider',
+			'Roo Cline',
+			vscode.ViewColumn.One,
+			{
+				enableScripts: true,
+				enableCommandUris: true,
+				retainContextWhenHidden: true,
+				localResourceRoots: [extensionUri]
+			}
+		);
+
+		try {
+			// Initialize webview with development context
+			panel.webview.options = {
+				enableScripts: true,
+				enableCommandUris: true,
+				localResourceRoots: [extensionUri]
+			};
+
+			// Initialize provider with panel
+			provider.resolveWebviewView(panel);
+
+			// Set up message tracking
+			let webviewReady = false;
+			let messagesReceived = false;
+			const originalPostMessage = provider.postMessageToWebview.bind(provider);
+			// @ts-ignore
+			provider.postMessageToWebview = async (message) => {
+				if (message.type === 'state') {
+					webviewReady = true;
+					console.log('Webview state received:', message);
+					if (message.state?.clineMessages?.length > 0) {
+						messagesReceived = true;
+						console.log('Messages in state:', message.state.clineMessages);
+					}
+				}
+				await originalPostMessage(message);
+			};
+
+			// Wait for webview to launch and receive initial state
+			let startTime = Date.now();
+			while (Date.now() - startTime < timeout) {
+				if (webviewReady) {
+					// Wait an additional second for webview to fully initialize
+					await new Promise(resolve => setTimeout(resolve, 1000));
+					break;
+				}
+				await new Promise(resolve => setTimeout(resolve, interval));
+			}
+
+			if (!webviewReady) {
+				throw new Error('Timeout waiting for webview to be ready');
+			}
+
+			// Send webviewDidLaunch to initialize chat
+			await provider.postMessageToWebview({ type: 'webviewDidLaunch' });
+			console.log('Sent webviewDidLaunch');
+
+			// Wait for webview to fully initialize
+			await new Promise(resolve => setTimeout(resolve, 2000));
+
+			// Restore original postMessage
+			provider.postMessageToWebview = originalPostMessage;
+
+			// Wait for OpenRouter models to be fully loaded
+			startTime = Date.now();
+			while (Date.now() - startTime < timeout) {
+				const models = await provider.readOpenRouterModels();
+				if (models && Object.keys(models).length > 0) {
+					console.log('OpenRouter models loaded');
+					break;
+				}
+				await new Promise(resolve => setTimeout(resolve, interval));
+			}
+
+			// Send prompt
+			const prompt = "Hello world, what is your name?";
+			console.log('Sending prompt:', prompt);
+
+			// Start task
+			try {
+				await api.startNewTask(prompt);
+				console.log('Task started');
+			} catch (error) {
+				console.error('Error starting task:', error);
+				throw error;
+			}
+
+			// Wait for task to appear in history with tokens
+			startTime = Date.now();
+			while (Date.now() - startTime < timeout) {
+				const state = await provider.getState();
+				const task = state.taskHistory?.[0];
+				if (task && task.tokensOut > 0) {
+					console.log('Task completed with tokens:', task);
+					break;
+				}
+				await new Promise(resolve => setTimeout(resolve, interval));
+			}
+
+			// Wait for messages to be processed
+			startTime = Date.now();
+			let responseReceived = false;
+			while (Date.now() - startTime < timeout) {
+				// Check provider.clineMessages
+				const messages = provider.clineMessages;
+				if (messages && messages.length > 0) {
+					console.log('Provider messages:', JSON.stringify(messages, null, 2));
+					// @ts-ignore
+					const hasResponse = messages.some(m =>
+						m.type === 'say' &&
+						m.text &&
+						m.text.toLowerCase().includes('cline')
+					);
+					if (hasResponse) {
+						console.log('Found response containing "Cline" in provider messages');
+						responseReceived = true;
+						break;
+					}
+				}
+
+				// Check provider.cline.clineMessages
+				const clineMessages = provider.cline?.clineMessages;
+				if (clineMessages && clineMessages.length > 0) {
+					console.log('Cline messages:', JSON.stringify(clineMessages, null, 2));
+					// @ts-ignore
+					const hasResponse = clineMessages.some(m =>
+						m.type === 'say' &&
+						m.text &&
+						m.text.toLowerCase().includes('cline')
+					);
+					if (hasResponse) {
+						console.log('Found response containing "Cline" in cline messages');
+						responseReceived = true;
+						break;
+					}
+				}
+
+				await new Promise(resolve => setTimeout(resolve, interval));
+			}
+
+			if (!responseReceived) {
+				console.log('Final provider state:', await provider.getState());
+				console.log('Final cline messages:', provider.cline?.clineMessages);
+				throw new Error('Did not receive expected response containing "Cline"');
+			}
+		} finally {
+			panel.dispose();
+		}
+	});
+});

+ 19 - 0
src/test/tsconfig.json

@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+    "module": "commonjs",
+    "target": "ES2020",
+    "lib": ["ES2020"],
+    "sourceMap": true,
+    "rootDir": "../..",
+    "strict": false,
+    "noImplicitAny": false,
+    "noImplicitThis": false,
+    "alwaysStrict": false,
+    "skipLibCheck": true,
+    "baseUrl": "../..",
+    "paths": {
+      "*": ["*", "src/*"]
+    }
+  },
+  "exclude": ["node_modules", ".vscode-test"]
+}