diff --git a/.changeset/calm-mirrors-switch.md b/.changeset/calm-mirrors-switch.md new file mode 100644 index 0000000000..6a00963a66 --- /dev/null +++ b/.changeset/calm-mirrors-switch.md @@ -0,0 +1,5 @@ +--- +"claude-dev": patch +--- + +Add orchestrator script and improve standalone service for local testing diff --git a/package.json b/package.json index e44a375767..2dcb4c2111 100644 --- a/package.json +++ b/package.json @@ -358,6 +358,7 @@ "test:unit": "TS_NODE_PROJECT='./tsconfig.unit-test.json' mocha", "test:coverage": "vscode-test --coverage", "test:sca-server": "npx tsx watch scripts/test-standalone-core-api-server.ts", + "test:tp-orchestrator": "npx tsx scripts/testing-platform-orchestrator.ts", "e2e": "playwright test -c playwright.config.ts", "test:e2e": "playwright install && vsce package --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.mjs && playwright test", "test:e2e:optimal": "vsce package --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.mjs && playwright test", diff --git a/scripts/test-hostbridge-server.ts b/scripts/test-hostbridge-server.ts index 73c05c80b9..63ce4f3a25 100755 --- a/scripts/test-hostbridge-server.ts +++ b/scripts/test-hostbridge-server.ts @@ -3,7 +3,11 @@ import * as grpc from "@grpc/grpc-js" import { ReflectionService } from "@grpc/reflection" import * as health from "grpc-health-check" import * as os from "os" -import { host } from "src/generated/grpc-js/index" +import { type DiffServiceServer, DiffServiceService } from "../src/generated/grpc-js/host/diff" +import { type EnvServiceServer, EnvServiceService } from "../src/generated/grpc-js/host/env" +import { type TestingServiceServer, TestingServiceService } from "../src/generated/grpc-js/host/testing" +import { type WindowServiceServer, WindowServiceService } from "../src/generated/grpc-js/host/window" +import { type WorkspaceServiceServer, WorkspaceServiceService } from "../src/generated/grpc-js/host/workspace" import { getPackageDefinition } from "./proto-utils.mjs" export async function startTestHostBridgeServer() { @@ -14,11 +18,11 @@ export async function startTestHostBridgeServer() { healthImpl.addToServer(server) // Add host bridge services using the mock implementations - server.addService(host.WorkspaceServiceService, createMockService("WorkspaceService")) - server.addService(host.WindowServiceService, createMockService("WindowService")) - server.addService(host.EnvServiceService, createMockService("EnvService")) - server.addService(host.DiffServiceService, createMockService("DiffService")) - server.addService(host.WatchServiceService, createMockService("WatchService")) + server.addService(WorkspaceServiceService, createMockService("WorkspaceService")) + server.addService(WindowServiceService, createMockService("WindowService")) + server.addService(EnvServiceService, createMockService("EnvService")) + server.addService(DiffServiceService, createMockService("DiffService")) + server.addService(TestingServiceService, createMockService("TestingService")) // Load package definition for reflection service const packageDefinition = await getPackageDefinition() @@ -58,8 +62,9 @@ function createMockService(serviceN // Special cases that need specific return values switch (prop) { case "getWorkspacePaths": + const workspaceDir = process.env.TEST_HOSTBRIDGE_WORKSPACE_DIR || "/test-workspace" callback(null, { - paths: ["/test-workspace"], + paths: [workspaceDir], }) return @@ -69,6 +74,12 @@ function createMockService(serviceN }) return + case "getTelemetrySettings": + callback(null, { + isEnabled: 2, // Setting.DISABLED + }) + return + case "clipboardReadText": callback(null, { value: "", diff --git a/scripts/test-standalone-core-api-server.ts b/scripts/test-standalone-core-api-server.ts index 29f0c3d471..0355e2db73 100644 --- a/scripts/test-standalone-core-api-server.ts +++ b/scripts/test-standalone-core-api-server.ts @@ -28,7 +28,9 @@ * Ideal for local development, testing, or lightweight E2E scenarios. */ -import { ChildProcess, spawn } from "child_process" +import { mkdtempSync, rmSync } from "node:fs" +import * as os from "node:os" +import { ChildProcess, execSync, spawn } from "child_process" import * as fs from "fs" import * as path from "path" import { ClineApiServerMock } from "../src/test/e2e/fixtures/server/index" @@ -66,21 +68,49 @@ async function main(): Promise { } try { - const apiServer = await ClineApiServerMock.startGlobalServer() + await ClineApiServerMock.startGlobalServer() console.log("Cline API Server started in-process") } catch (error) { console.error("Failed to start Cline API Server:", error) process.exit(1) } + // Create temporary directories like e2e tests + const userDataDir = mkdtempSync(path.join(os.tmpdir(), "vsce")) + const extensionsDir = mkdtempSync(path.join(os.tmpdir(), "vsce")) + const clineTestWorkspace = mkdtempSync(path.join(os.tmpdir(), "cline-test-workspace-")) + // Start hostbridge test server in background. // We run it as a child process to emulate how the extension currently operates console.log("Starting HostBridge test server...") const hostbridge: ChildProcess = spawn("npx", ["tsx", path.join(__dirname, "test-hostbridge-server.ts")], { stdio: "pipe", detached: false, + env: { + ...process.env, + TEST_HOSTBRIDGE_WORKSPACE_DIR: clineTestWorkspace, + }, }) + console.log(`Temp user data dir: ${userDataDir}`) + console.log(`Temp extensions dir: ${extensionsDir}`) + + // Extract standalone.zip to the extensions directory + const standaloneZipPath = path.join(distDir, "standalone.zip") + if (!fs.existsSync(standaloneZipPath)) { + console.error(`standalone.zip not found at: ${standaloneZipPath}`) + process.exit(1) + } + + console.log("Extracting standalone.zip to extensions directory...") + try { + execSync(`unzip -q "${standaloneZipPath}" -d "${extensionsDir}"`, { stdio: "inherit" }) + console.log(`Successfully extracted standalone.zip to: ${extensionsDir}`) + } catch (error) { + console.error("Failed to extract standalone.zip:", error) + process.exit(1) + } + // Start the core service // We run it as a child process to emulate how the extension currently operates console.log("Starting Cline Core Service...") @@ -94,16 +124,29 @@ async function main(): Promise { HOST_BRIDGE_ADDRESS: `localhost:${HOSTBRIDGE_PORT}`, E2E_TEST: E2E_TEST, CLINE_ENVIRONMENT: CLINE_ENVIRONMENT, + CLINE_DIR: userDataDir, + INSTALL_DIR: extensionsDir, }, stdio: "inherit", }) // Handle graceful shutdown const shutdown = async (): Promise => { - console.log("\n Shutting down services...") + console.log(`\n Shutting down services...\n${userDataDir}\n${extensionsDir}\n${clineTestWorkspace}\n`) hostbridge.kill() coreService.kill() await ClineApiServerMock.stopGlobalServer() + + // Cleanup temp directories + try { + rmSync(userDataDir, { recursive: true, force: true }) + rmSync(extensionsDir, { recursive: true, force: true }) + rmSync(clineTestWorkspace, { recursive: true, force: true }) + console.log("Cleaned up temporary directories") + } catch (error) { + console.warn("Failed to cleanup temp directories:", error) + } + process.exit(0) } diff --git a/scripts/testing-platform-orchestrator.ts b/scripts/testing-platform-orchestrator.ts new file mode 100644 index 0000000000..92c3bf9d0c --- /dev/null +++ b/scripts/testing-platform-orchestrator.ts @@ -0,0 +1,159 @@ +#!/usr/bin/env npx tsx +/** + * Test Orchestrator + * + * Automates server lifecycle for running spec files against the standalone server. + * + * Prerequisites: + * Build standalone first: `npm run compile-standalone` + * + * Usage: + * - Single file: `npm run test:tp-orchestrator path/to/spec.json` + * - All specs dir: `npm run test:tp-orchestrator tests/specs` + * + * Flags: + * --server-logs Show server logs (hidden by default) + * --count= Repeat execution N times (default: 1) + * + * Environment Variables: + * HOSTBRIDGE_PORT gRPC server port (default: 26040) + * SERVER_BOOT_DELAY Server startup delay in ms (default: 3000) + */ + +import { ChildProcess, spawn } from "child_process" +import fs from "fs" +import minimist from "minimist" +import path from "path" + +const STANDALONE_GRPC_SERVER_PORT = process.env.STANDALONE_GRPC_SERVER_PORT || "26040" +const SERVER_BOOT_DELAY = Number(process.env.SERVER_BOOT_DELAY) || 3000 + +let showServerLogs = false + +function startServer(): Promise { + return new Promise((resolve, reject) => { + const server = spawn("npx", ["tsx", "scripts/test-standalone-core-api-server.ts"], { + stdio: showServerLogs ? "inherit" : "ignore", + }) + + server.once("error", reject) + + setTimeout(() => { + if (server.killed) { + reject(new Error("Server died during startup")) + } else { + resolve(server) + } + }, SERVER_BOOT_DELAY) + }) +} + +function stopServer(server: ChildProcess): Promise { + return new Promise((resolve) => { + server.once("exit", () => resolve()) + server.kill("SIGINT") + setTimeout(() => { + if (!server.killed) { + server.kill("SIGKILL") + resolve() + } + }, 5000) + }) +} + +function runTestingPlatform(specFile: string): Promise { + return new Promise((resolve, reject) => { + const testProcess = spawn("npx", ["ts-node", "index.ts", specFile], { + cwd: path.join(process.cwd(), "testing-platform"), + stdio: "inherit", + env: { + ...process.env, + HOSTBRIDGE_PORT: STANDALONE_GRPC_SERVER_PORT, + }, + }) + + testProcess.once("error", reject) + testProcess.once("exit", (code) => { + code === 0 ? resolve() : reject(new Error(`Exit code ${code}`)) + }) + }) +} + +async function runSpec(specFile: string): Promise { + const server = await startServer() + try { + await runTestingPlatform(specFile) + console.log(`āœ… ${path.basename(specFile)} passed`) + } finally { + await stopServer(server) + } +} + +function collectSpecFiles(inputPath: string): string[] { + const fullPath = path.resolve(inputPath) + if (!fs.existsSync(fullPath)) throw new Error(`Path does not exist: ${fullPath}`) + + const stat = fs.statSync(fullPath) + if (stat.isDirectory()) { + return fs + .readdirSync(fullPath) + .filter((f) => f.endsWith(".json")) + .map((f) => path.join(fullPath, f)) + } + if (fullPath.endsWith(".json")) return [fullPath] + throw new Error("Spec path must be a JSON file or a folder containing JSON files") +} + +async function runAll(inputPath: string, count: number) { + const specFiles = collectSpecFiles(inputPath) + if (specFiles.length === 0) { + console.warn(`āš ļø No spec files found in ${inputPath}`) + return + } + + let success = 0 + let failure = 0 + const totalStart = Date.now() + + for (let i = 0; i < count; i++) { + console.log(`\nšŸ” Run #${i + 1} of ${count}`) + for (const specFile of specFiles) { + try { + await runSpec(specFile) + success++ + } catch (err) { + console.error(`āŒ run #${i + 1}: ${path.basename(specFile)} failed:`, (err as Error).message) + failure++ + } + } + + if (failure > 0) process.exitCode = 1 + } + + console.log(`āœ… Passed: ${success}`) + if (failure > 0) console.log(`āŒ Failed: ${failure}`) + console.log(`šŸ“‹ Total specs: ${specFiles.length} Total runs: ${specFiles.length * count}`) + const totalElapsed = ((Date.now() - totalStart) / 1000).toFixed(2) + console.log(`\nšŸ All runs completed in ${totalElapsed}s`) +} + +async function main() { + const args = minimist(process.argv.slice(2), { default: { count: 1 } }) + const inputPath = args._[0] + const count = Number(args.count) + showServerLogs = Boolean(args["server-logs"]) + + if (!inputPath) { + console.error("Usage: npx tsx scripts/testing-platform-orchestrator.ts [--count=N] [--server-logs]") + process.exit(1) + } + + await runAll(inputPath, count) +} + +if (require.main === module) { + main().catch((err) => { + console.error("āŒ Fatal error:", err) + process.exit(1) + }) +} diff --git a/src/test/e2e/utils/helpers.ts b/src/test/e2e/utils/helpers.ts index 681df174b6..6a9c651276 100644 --- a/src/test/e2e/utils/helpers.ts +++ b/src/test/e2e/utils/helpers.ts @@ -247,6 +247,7 @@ export const e2e = test CLINE_ENVIRONMENT: "local", GRPC_RECORDER_FILE_NAME: E2ETestHelper.generateTestFileName(testInfo.title, testInfo.project.name), // GRPC_RECORDER_ENABLED: "true", + // GRPC_RECORDER_TESTS_FILTERS_ENABLED: "true" // IS_DEV: "true", // DEV_WORKSPACE_FOLDER: E2ETestHelper.CODEBASE_ROOT_DIR, }, diff --git a/testing-platform/harness/config.ts b/testing-platform/harness/config.ts new file mode 100644 index 0000000000..4313fbd21c --- /dev/null +++ b/testing-platform/harness/config.ts @@ -0,0 +1,28 @@ +// Note: This list is temporary. Some items may remain in the long run, +// but our goal is to reduce it over time by continuously improving +// how we prepare the testing-platform environment. +export const NON_DETERMINISTIC_FIELDS = [ + "stateJson.distinctId", + "stateJson.shouldShowAnnouncement", + "stateJson.platform", + "stateJson.focusChainFeatureFlagEnabled", + "stateJson.clineMessages.ts", + "stateJson.taskHistory.cwdOnTaskInitialization", + "stateJson.taskHistory.id", + "stateJson.taskHistory.size", + "stateJson.taskHistory.ts", + "stateJson.taskHistory.ulid", + "stateJson.taskHistory.cacheWrites", + "stateJson.taskHistory.cacheReads", + "stateJson.taskHistory.tokensIn", + "stateJson.taskHistory.tokensOut", + "stateJson.taskHistory.totalCost", + "stateJson.currentTaskItem.cwdOnTaskInitialization", + "stateJson.currentTaskItem.id", + "stateJson.currentTaskItem.ts", + "stateJson.currentTaskItem.ulid", + "stateJson.workspaceRoots.commitHash", + "stateJson.workspaceRoots.name", + "stateJson.workspaceRoots.path", + "stateJson.workspaceRoots.vcs", +] diff --git a/testing-platform/harness/utils.ts b/testing-platform/harness/utils.ts index 332f6c0ec8..44ef9fb3dd 100644 --- a/testing-platform/harness/utils.ts +++ b/testing-platform/harness/utils.ts @@ -1,4 +1,5 @@ import fs from "fs" +import { diff } from "jest-diff" import path from "path" export function loadJson(filePath: string): any { @@ -9,13 +10,42 @@ export function pretty(obj: any): string { return JSON.stringify(obj, null, 2) } -export function compareResponse(actual: any, expected: any): { success: boolean; diffs: string[] } { - const diffs: string[] = [] - const expectedStr = JSON.stringify(expected, null, 2) - const actualStr = JSON.stringify(actual, null, 2) +// Normalize object and ignore specified fields +function normalize(obj: any, ignoreFields: string[] = [], parentPath = ""): any { + if (Array.isArray(obj)) { + return obj.map((item, idx) => normalize(item, ignoreFields, parentPath)) // do not include index + } + if (obj && typeof obj === "object") { + const result: Record = {} + for (const [k, v] of Object.entries(obj)) { + const currentPath = parentPath ? `${parentPath}.${k}` : k + if (ignoreFields.includes(currentPath) || ignoreFields.includes(k)) continue + result[k] = normalize(v, ignoreFields, currentPath) + } + return result + } + if (typeof obj === "string") { + try { + return normalize(JSON.parse(obj), ignoreFields, parentPath) + } catch { + return obj + } + } + return obj +} - if (expectedStr !== actualStr) { - diffs.push(`Expected:\n${expectedStr}\n\nActual:\n${actualStr}`) +// Compare two objects, ignoring specified fields +export function compareResponse(actual: any, expected: any, ignoreFields: string[] = []): { success: boolean; diffs: string[] } { + const diffs: string[] = [] + + const normalizedActual = normalize(actual, ignoreFields) + const normalizedExpected = normalize(expected, ignoreFields) + + if (JSON.stringify(normalizedActual) !== JSON.stringify(normalizedExpected)) { + const difference = diff(normalizedExpected, normalizedActual, { + expand: false, // compact diff + }) + diffs.push(difference || "Objects differ but no diff generated.") } return { success: diffs.length === 0, diffs } diff --git a/testing-platform/index.ts b/testing-platform/index.ts index 28e34dac88..493c94cb78 100644 --- a/testing-platform/index.ts +++ b/testing-platform/index.ts @@ -2,21 +2,23 @@ import "tsconfig-paths/register" import { GrpcAdapter } from "@adapters/grpcAdapter" +import { NON_DETERMINISTIC_FIELDS } from "@harness/config" import { SpecFile } from "@harness/types" import { compareResponse, loadJson } from "@harness/utils" import fs from "fs" import path from "path" -const STANDALONE_GRPC_SERVER_PORT = process.env.HOSTBRIDGE_PORT || "26040" +const STANDALONE_GRPC_SERVER_PORT = process.env.STANDALONE_GRPC_SERVER_PORT || "26040" async function runSpec(specPath: string, grpcAdapter: GrpcAdapter) { const spec: SpecFile = loadJson(specPath) for (const entry of spec.entries) { console.log(`ā–¶ļø ${entry.service}.${entry.method}`) + await new Promise((resolve) => setTimeout(resolve, 50)) const response = await grpcAdapter.call(entry.service, entry.method, entry.request) - const { success, diffs } = compareResponse(response, entry?.response?.message) + const { success, diffs } = compareResponse(response, entry?.response?.message, NON_DETERMINISTIC_FIELDS) if (!success) { console.error("āŒ Response mismatch! RequestID: %s", entry.requestId) console.error(diffs.join("\n")) diff --git a/testing-platform/package-lock.json b/testing-platform/package-lock.json index 539fdce959..ff1e0dcc06 100644 --- a/testing-platform/package-lock.json +++ b/testing-platform/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "testing-infra", "version": "0.1.0", + "dependencies": { + "jest-diff": "^30.1.2" + }, "devDependencies": { "ts-node": "^10.9.1", "typescript": "^5.3.3" @@ -25,6 +28,36 @@ "node": ">=12" } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -53,6 +86,12 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -118,6 +157,21 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -125,6 +179,40 @@ "dev": true, "license": "MIT" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -142,6 +230,30 @@ "node": ">=0.3.1" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "30.1.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.2.tgz", + "integrity": "sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==", + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -149,6 +261,50 @@ "dev": true, "license": "ISC" }, + "node_modules/pretty-format": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/testing-platform/package.json b/testing-platform/package.json index a3a1248ae8..70ee49cf85 100644 --- a/testing-platform/package.json +++ b/testing-platform/package.json @@ -7,7 +7,9 @@ "start:dev": "ts-node index.ts", "start": "npm run build && node dist/runner.js" }, - "dependencies": {}, + "dependencies": { + "jest-diff": "^30.1.2" + }, "devDependencies": { "ts-node": "^10.9.1", "typescript": "^5.3.3"