From 897de134f7cf9c08e7a4ed534c2bbf8c87e8d6ab Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:37:12 -0800 Subject: [PATCH] Add tests and fix configuration --- src/shared/array.test.ts | 91 ++++++++++++++++++++++ src/utils/cost.test.ts | 67 ++++++++++++++++ src/utils/fs.test.ts | 71 +++++++++++++++++ tsconfig.test.json | 2 +- webview-ui/scripts/build-react-no-split.js | 7 +- 5 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 src/shared/array.test.ts create mode 100644 src/utils/cost.test.ts create mode 100644 src/utils/fs.test.ts diff --git a/src/shared/array.test.ts b/src/shared/array.test.ts new file mode 100644 index 0000000000..20f06a7539 --- /dev/null +++ b/src/shared/array.test.ts @@ -0,0 +1,91 @@ +import { describe, it } from "mocha" +import "should" +import { findLastIndex, findLast } from "./array" + +describe("Array Utilities", () => { + describe("findLastIndex", () => { + it("should find last matching element's index", () => { + const array = [1, 2, 3, 2, 1] + const index = findLastIndex(array, (x) => x === 2) + index.should.equal(3) // last '2' is at index 3 + }) + + it("should return -1 when no element matches", () => { + const array = [1, 2, 3] + const index = findLastIndex(array, (x) => x === 4) + index.should.equal(-1) + }) + + it("should handle empty arrays", () => { + const array: number[] = [] + const index = findLastIndex(array, (x) => x === 1) + index.should.equal(-1) + }) + + it("should work with different types", () => { + const array = ["a", "b", "c", "b", "a"] + const index = findLastIndex(array, (x) => x === "b") + index.should.equal(3) + }) + + it("should provide correct index in predicate", () => { + const array = [1, 2, 3] + const indices: number[] = [] + findLastIndex(array, (_, index) => { + indices.push(index) + return false + }) + indices.should.deepEqual([2, 1, 0]) // Should iterate in reverse + }) + + it("should provide array reference in predicate", () => { + const array = [1, 2, 3] + findLastIndex(array, (_, __, arr) => { + arr.should.equal(array) // Should pass original array + return false + }) + }) + }) + + describe("findLast", () => { + it("should find last matching element", () => { + const array = [1, 2, 3, 2, 1] + const element = findLast(array, (x) => x === 2) + should(element).not.be.undefined() + element!.should.equal(2) + }) + + it("should return undefined when no element matches", () => { + const array = [1, 2, 3] + const element = findLast(array, (x) => x === 4) + should(element).be.undefined() + }) + + it("should handle empty arrays", () => { + const array: number[] = [] + const element = findLast(array, (x) => x === 1) + should(element).be.undefined() + }) + + it("should work with object arrays", () => { + const array = [ + { id: 1, value: "a" }, + { id: 2, value: "b" }, + { id: 3, value: "a" }, + ] + const element = findLast(array, (x) => x.value === "a") + should(element).not.be.undefined() + element!.should.deepEqual({ id: 3, value: "a" }) + }) + + it("should provide correct index in predicate", () => { + const array = [1, 2, 3] + const indices: number[] = [] + findLast(array, (_, index) => { + indices.push(index) + return false + }) + indices.should.deepEqual([2, 1, 0]) // Should iterate in reverse + }) + }) +}) diff --git a/src/utils/cost.test.ts b/src/utils/cost.test.ts new file mode 100644 index 0000000000..ae7164553c --- /dev/null +++ b/src/utils/cost.test.ts @@ -0,0 +1,67 @@ +import { describe, it } from "mocha" +import "should" +import { calculateApiCost } from "./cost" +import { ModelInfo } from "../shared/api" + +describe("Cost Utilities", () => { + describe("calculateApiCost", () => { + it("should calculate basic input/output costs", () => { + const modelInfo: ModelInfo = { + supportsPromptCache: false, + inputPrice: 3.0, // $3 per million tokens + outputPrice: 15.0, // $15 per million tokens + } + + const cost = calculateApiCost(modelInfo, 1000, 500) + // Input: (3.0 / 1_000_000) * 1000 = 0.003 + // Output: (15.0 / 1_000_000) * 500 = 0.0075 + // Total: 0.003 + 0.0075 = 0.0105 + cost.should.equal(0.0105) + }) + + it("should handle missing prices", () => { + const modelInfo: ModelInfo = { + supportsPromptCache: true, + // No prices specified + } + + const cost = calculateApiCost(modelInfo, 1000, 500) + cost.should.equal(0) + }) + + it("should use real model configuration (Claude 3.5 Sonnet)", () => { + const modelInfo: ModelInfo = { + maxTokens: 8192, + contextWindow: 200_000, + supportsImages: true, + supportsComputerUse: true, + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + } + + const cost = calculateApiCost(modelInfo, 2000, 1000, 1500, 500) + // Cache writes: (3.75 / 1_000_000) * 1500 = 0.005625 + // Cache reads: (0.3 / 1_000_000) * 500 = 0.00015 + // Input: (3.0 / 1_000_000) * 2000 = 0.006 + // Output: (15.0 / 1_000_000) * 1000 = 0.015 + // Total: 0.005625 + 0.00015 + 0.006 + 0.015 = 0.026775 + cost.should.equal(0.026775) + }) + + it("should handle zero token counts", () => { + const modelInfo: ModelInfo = { + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + } + + const cost = calculateApiCost(modelInfo, 0, 0, 0, 0) + cost.should.equal(0) + }) + }) +}) diff --git a/src/utils/fs.test.ts b/src/utils/fs.test.ts new file mode 100644 index 0000000000..ea9f132d5b --- /dev/null +++ b/src/utils/fs.test.ts @@ -0,0 +1,71 @@ +import * as fs from "fs/promises" +import { after, describe, it } from "mocha" +import * as os from "os" +import * as path from "path" +import "should" +import { createDirectoriesForFile, fileExistsAtPath } from "./fs" + +describe("Filesystem Utilities", () => { + const tmpDir = path.join(os.tmpdir(), "cline-test-" + Math.random().toString(36).slice(2)) + + // Clean up after tests + after(async () => { + try { + await fs.rm(tmpDir, { recursive: true, force: true }) + } catch { + // Ignore cleanup errors + } + }) + + describe("fileExistsAtPath", () => { + it("should return true for existing paths", async () => { + await fs.mkdir(tmpDir, { recursive: true }) + const testFile = path.join(tmpDir, "test.txt") + await fs.writeFile(testFile, "test") + + const exists = await fileExistsAtPath(testFile) + exists.should.be.true() + }) + + it("should return false for non-existing paths", async () => { + const nonExistentPath = path.join(tmpDir, "does-not-exist.txt") + const exists = await fileExistsAtPath(nonExistentPath) + exists.should.be.false() + }) + }) + + describe("createDirectoriesForFile", () => { + it("should create all necessary directories", async () => { + const deepPath = path.join(tmpDir, "deep", "nested", "dir", "file.txt") + const createdDirs = await createDirectoriesForFile(deepPath) + + // Verify directories were created + createdDirs.length.should.be.greaterThan(0) + for (const dir of createdDirs) { + const exists = await fileExistsAtPath(dir) + exists.should.be.true() + } + }) + + it("should handle existing directories", async () => { + const existingDir = path.join(tmpDir, "existing") + await fs.mkdir(existingDir, { recursive: true }) + + const filePath = path.join(existingDir, "file.txt") + const createdDirs = await createDirectoriesForFile(filePath) + + // Should not create any new directories + createdDirs.length.should.equal(0) + }) + + it("should normalize paths", async () => { + const unnormalizedPath = path.join(tmpDir, "a", "..", "b", ".", "file.txt") + const createdDirs = await createDirectoriesForFile(unnormalizedPath) + + // Should create only the necessary directory + createdDirs.length.should.equal(1) + const exists = await fileExistsAtPath(path.join(tmpDir, "b")) + exists.should.be.true() + }) + }) +}) diff --git a/tsconfig.test.json b/tsconfig.test.json index 3063d7b5f5..40ca1269d2 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -8,7 +8,7 @@ "compilerOptions": { "module": "commonjs", "moduleResolution": "node", - "types": ["node", "mocha", "vscode"] + "types": ["node", "mocha", "should", "vscode"] }, "include": ["src/**/*.test.ts"] } diff --git a/webview-ui/scripts/build-react-no-split.js b/webview-ui/scripts/build-react-no-split.js index 43bfec964f..f021a12104 100644 --- a/webview-ui/scripts/build-react-no-split.js +++ b/webview-ui/scripts/build-react-no-split.js @@ -34,8 +34,11 @@ function getAllFiles(dir) { if (fs.statSync(filePath).isDirectory()) { files = files.concat(getAllFiles(filePath)) } else { - const withoutExtension = path.join(dir, path.parse(file).name) - files.push(withoutExtension) + // Skip test files + if (!file.endsWith(".test.ts")) { + const withoutExtension = path.join(dir, path.parse(file).name) + files.push(withoutExtension) + } } }) return files