mirror of
https://github.com/cline/cline.git
synced 2025-06-03 03:59:07 +00:00
Add tests and fix configuration
This commit is contained in:
parent
06dab2c3df
commit
897de134f7
91
src/shared/array.test.ts
Normal file
91
src/shared/array.test.ts
Normal file
@ -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
|
||||
})
|
||||
})
|
||||
})
|
67
src/utils/cost.test.ts
Normal file
67
src/utils/cost.test.ts
Normal file
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
71
src/utils/fs.test.ts
Normal file
71
src/utils/fs.test.ts
Normal file
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@ -8,7 +8,7 @@
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"types": ["node", "mocha", "vscode"]
|
||||
"types": ["node", "mocha", "should", "vscode"]
|
||||
},
|
||||
"include": ["src/**/*.test.ts"]
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user