Skip to content

Commit 4aa43d0

Browse files
authored
Merge pull request RooCodeInc#926 from RooVetGit/feature/enhanceFullIntegrationTests
Modify Full Integration Test Structure to allow for multiple tests th…
2 parents 7ebf3fa + 4170479 commit 4aa43d0

File tree

9 files changed

+692
-228
lines changed

9 files changed

+692
-228
lines changed

package-lock.json

Lines changed: 416 additions & 136 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@
278278
"dev": "cd webview-ui && npm run dev",
279279
"test": "jest && npm run test:webview",
280280
"test:webview": "cd webview-ui && npm run test",
281-
"test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- vscode-test",
281+
"test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- node ./out-integration/test/runTest.js",
282282
"prepare": "husky",
283283
"publish:marketplace": "vsce publish && ovsx publish",
284284
"publish": "npm run build && changeset publish && npm install --package-lock-only",
@@ -342,8 +342,9 @@
342342
"@types/debug": "^4.1.12",
343343
"@types/diff": "^5.2.1",
344344
"@types/diff-match-patch": "^1.0.36",
345+
"@types/glob": "^8.1.0",
345346
"@types/jest": "^29.5.14",
346-
"@types/mocha": "^10.0.7",
347+
"@types/mocha": "^10.0.10",
347348
"@types/node": "20.x",
348349
"@types/string-similarity": "^4.0.2",
349350
"@typescript-eslint/eslint-plugin": "^7.14.1",
@@ -354,10 +355,12 @@
354355
"mkdirp": "^3.0.1",
355356
"rimraf": "^6.0.1",
356357
"eslint": "^8.57.0",
358+
"glob": "^11.0.1",
357359
"husky": "^9.1.7",
358360
"jest": "^29.7.0",
359361
"jest-simple-dot-reporter": "^1.0.5",
360362
"lint-staged": "^15.2.11",
363+
"mocha": "^11.1.0",
361364
"npm-run-all": "^4.1.5",
362365
"prettier": "^3.4.2",
363366
"ts-jest": "^29.2.5",

src/test/runTest.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as path from "path"
2+
3+
import { runTests } from "@vscode/test-electron"
4+
5+
async function main() {
6+
try {
7+
// The folder containing the Extension Manifest package.json
8+
// Passed to `--extensionDevelopmentPath`
9+
const extensionDevelopmentPath = path.resolve(__dirname, "../../")
10+
11+
// The path to the extension test script
12+
// Passed to --extensionTestsPath
13+
const extensionTestsPath = path.resolve(__dirname, "./suite/index")
14+
15+
// Download VS Code, unzip it and run the integration test
16+
await runTests({ extensionDevelopmentPath, extensionTestsPath })
17+
} catch {
18+
console.error("Failed to run tests")
19+
process.exit(1)
20+
}
21+
}
22+
23+
main()

src/test/extension.test.ts renamed to src/test/suite/extension.test.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,4 @@ suite("Roo Code Extension", () => {
4848
assert.ok(commands.includes(cmd), `Command ${cmd} should be registered`)
4949
}
5050
})
51-
52-
test("Webview panel can be created", () => {
53-
const view = vscode.window.createWebviewPanel(
54-
"roo-cline.SidebarProvider",
55-
"Roo Code",
56-
vscode.ViewColumn.One,
57-
{},
58-
)
59-
60-
assert.ok(view, "Failed to create webview panel")
61-
view.dispose()
62-
})
6351
})

src/test/suite/index.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as path from "path"
2+
import Mocha from "mocha"
3+
import { glob } from "glob"
4+
import { ClineAPI } from "../../exports/cline"
5+
import { ClineProvider } from "../../core/webview/ClineProvider"
6+
import * as vscode from "vscode"
7+
8+
declare global {
9+
var api: ClineAPI
10+
var provider: ClineProvider
11+
var extension: vscode.Extension<ClineAPI> | undefined
12+
var panel: vscode.WebviewPanel | undefined
13+
}
14+
15+
export async function run(): Promise<void> {
16+
// Create the mocha test
17+
const mocha = new Mocha({
18+
ui: "tdd",
19+
timeout: 600000, // 10 minutes to compensate for time communicating with LLM while running in GHA
20+
})
21+
22+
const testsRoot = path.resolve(__dirname, "..")
23+
24+
try {
25+
// Find all test files
26+
const files = await glob("**/**.test.js", { cwd: testsRoot })
27+
28+
// Add files to the test suite
29+
files.forEach((f: string) => mocha.addFile(path.resolve(testsRoot, f)))
30+
31+
//Set up global extension, api, provider, and panel
32+
globalThis.extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
33+
if (!globalThis.extension) {
34+
throw new Error("Extension not found")
35+
}
36+
37+
globalThis.api = globalThis.extension.isActive
38+
? globalThis.extension.exports
39+
: await globalThis.extension.activate()
40+
globalThis.provider = globalThis.api.sidebarProvider
41+
await globalThis.provider.updateGlobalState("apiProvider", "openrouter")
42+
await globalThis.provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
43+
await globalThis.provider.storeSecret(
44+
"openRouterApiKey",
45+
process.env.OPENROUTER_API_KEY || "sk-or-v1-fake-api-key",
46+
)
47+
48+
globalThis.panel = vscode.window.createWebviewPanel(
49+
"roo-cline.SidebarProvider",
50+
"Roo Code",
51+
vscode.ViewColumn.One,
52+
{
53+
enableScripts: true,
54+
enableCommandUris: true,
55+
retainContextWhenHidden: true,
56+
localResourceRoots: [globalThis.extension?.extensionUri],
57+
},
58+
)
59+
60+
await globalThis.provider.resolveWebviewView(globalThis.panel)
61+
62+
let startTime = Date.now()
63+
const timeout = 60000
64+
const interval = 1000
65+
66+
while (Date.now() - startTime < timeout) {
67+
if (globalThis.provider.viewLaunched) {
68+
break
69+
}
70+
71+
await new Promise((resolve) => setTimeout(resolve, interval))
72+
}
73+
74+
// Run the mocha test
75+
return new Promise((resolve, reject) => {
76+
try {
77+
mocha.run((failures: number) => {
78+
if (failures > 0) {
79+
reject(new Error(`${failures} tests failed.`))
80+
} else {
81+
resolve()
82+
}
83+
})
84+
} catch (err) {
85+
console.error(err)
86+
reject(err)
87+
}
88+
})
89+
} catch (err) {
90+
console.error("Error while running tests:")
91+
console.error(err)
92+
throw err
93+
}
94+
}

src/test/suite/modes.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as assert from "assert"
2+
import * as vscode from "vscode"
3+
4+
suite("Roo Code Modes", () => {
5+
test("Should handle switching modes correctly", async function () {
6+
const timeout = 30000
7+
const interval = 1000
8+
9+
if (!globalThis.extension) {
10+
assert.fail("Extension not found")
11+
}
12+
13+
try {
14+
let startTime = Date.now()
15+
16+
// Ensure the webview is launched.
17+
while (Date.now() - startTime < timeout) {
18+
if (globalThis.provider.viewLaunched) {
19+
break
20+
}
21+
22+
await new Promise((resolve) => setTimeout(resolve, interval))
23+
}
24+
25+
await globalThis.provider.updateGlobalState("mode", "Ask")
26+
await globalThis.provider.updateGlobalState("alwaysAllowModeSwitch", true)
27+
await globalThis.provider.updateGlobalState("autoApprovalEnabled", true)
28+
29+
// Start a new task.
30+
await globalThis.api.startNewTask(
31+
"For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode, do not start with the current mode, be sure to say 'I AM DONE' after the task is complete",
32+
)
33+
34+
// Wait for task to appear in history with tokens.
35+
startTime = Date.now()
36+
37+
while (Date.now() - startTime < timeout) {
38+
const messages = globalThis.provider.messages
39+
40+
if (
41+
messages.some(
42+
({ type, text }) =>
43+
type === "say" && text?.includes("I AM DONE") && !text?.includes("be sure to say"),
44+
)
45+
) {
46+
break
47+
}
48+
49+
await new Promise((resolve) => setTimeout(resolve, interval))
50+
}
51+
if (globalThis.provider.messages.length === 0) {
52+
assert.fail("No messages received")
53+
}
54+
55+
assert.ok(
56+
globalThis.provider.messages.some(
57+
({ type, text }) => type === "say" && text?.includes(`"request":"[switch_mode to 'code' because:`),
58+
),
59+
"Did not receive expected response containing 'Roo wants to switch to code mode'",
60+
)
61+
assert.ok(
62+
globalThis.provider.messages.some(
63+
({ type, text }) => type === "say" && text?.includes("software engineer"),
64+
),
65+
"Did not receive expected response containing 'I am Roo in Code mode, specializing in software engineering'",
66+
)
67+
68+
assert.ok(
69+
globalThis.provider.messages.some(
70+
({ type, text }) =>
71+
type === "say" && text?.includes(`"request":"[switch_mode to 'architect' because:`),
72+
),
73+
"Did not receive expected response containing 'Roo wants to switch to architect mode'",
74+
)
75+
assert.ok(
76+
globalThis.provider.messages.some(
77+
({ type, text }) =>
78+
type === "say" && (text?.includes("technical planning") || text?.includes("technical leader")),
79+
),
80+
"Did not receive expected response containing 'I am Roo in Architect mode, specializing in analyzing codebases'",
81+
)
82+
83+
assert.ok(
84+
globalThis.provider.messages.some(
85+
({ type, text }) => type === "say" && text?.includes(`"request":"[switch_mode to 'ask' because:`),
86+
),
87+
"Did not receive expected response containing 'Roo wants to switch to ask mode'",
88+
)
89+
assert.ok(
90+
globalThis.provider.messages.some(
91+
({ type, text }) =>
92+
type === "say" && (text?.includes("technical knowledge") || text?.includes("technical assist")),
93+
),
94+
"Did not receive expected response containing 'I am Roo in Ask mode, specializing in answering questions'",
95+
)
96+
} finally {
97+
}
98+
})
99+
})

src/test/suite/task.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as assert from "assert"
2+
import * as vscode from "vscode"
3+
4+
suite("Roo Code Task", () => {
5+
test("Should handle prompt and response correctly", async function () {
6+
const timeout = 30000
7+
const interval = 1000
8+
9+
if (!globalThis.extension) {
10+
assert.fail("Extension not found")
11+
}
12+
13+
try {
14+
// Ensure the webview is launched.
15+
let startTime = Date.now()
16+
17+
while (Date.now() - startTime < timeout) {
18+
if (globalThis.provider.viewLaunched) {
19+
break
20+
}
21+
22+
await new Promise((resolve) => setTimeout(resolve, interval))
23+
}
24+
25+
await globalThis.api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'")
26+
27+
// Wait for task to appear in history with tokens.
28+
startTime = Date.now()
29+
30+
while (Date.now() - startTime < timeout) {
31+
const state = await globalThis.provider.getState()
32+
const task = state.taskHistory?.[0]
33+
34+
if (task && task.tokensOut > 0) {
35+
break
36+
}
37+
38+
await new Promise((resolve) => setTimeout(resolve, interval))
39+
}
40+
41+
if (globalThis.provider.messages.length === 0) {
42+
assert.fail("No messages received")
43+
}
44+
45+
assert.ok(
46+
globalThis.provider.messages.some(
47+
({ type, text }) => type === "say" && text?.includes("My name is Roo"),
48+
),
49+
"Did not receive expected response containing 'My name is Roo'",
50+
)
51+
} finally {
52+
}
53+
})
54+
})

0 commit comments

Comments
 (0)