mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
- Rename packages/opencode → packages/tfcode (directory only) - Rename bin/opencode → bin/tfcode (CLI binary) - Rename .opencode → .tfcode (config directory) - Update package.json name and bin field - Update config directory path references (.tfcode) - Keep internal code references as 'opencode' for easy upstream sync - Keep @opencode-ai/* workspace package names This minimal branding approach allows clean merges from upstream opencode repository while providing tfcode branding for users.
320 lines
9.8 KiB
TypeScript
320 lines
9.8 KiB
TypeScript
import { describe, test, expect } from "bun:test"
|
|
import { Effect, Layer } from "effect"
|
|
import { NodeFileSystem } from "@effect/platform-node"
|
|
import { AppFileSystem } from "../../src/filesystem"
|
|
import { testEffect } from "../lib/effect"
|
|
import path from "path"
|
|
|
|
const live = AppFileSystem.layer.pipe(Layer.provide(NodeFileSystem.layer))
|
|
const { effect: it } = testEffect(live)
|
|
|
|
describe("AppFileSystem", () => {
|
|
describe("isDir", () => {
|
|
it(
|
|
"returns true for directories",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
expect(yield* fs.isDir(tmp)).toBe(true)
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"returns false for files",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "test.txt")
|
|
yield* fs.writeFileString(file, "hello")
|
|
expect(yield* fs.isDir(file)).toBe(false)
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"returns false for non-existent paths",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
expect(yield* fs.isDir("/tmp/nonexistent-" + Math.random())).toBe(false)
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("isFile", () => {
|
|
it(
|
|
"returns true for files",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "test.txt")
|
|
yield* fs.writeFileString(file, "hello")
|
|
expect(yield* fs.isFile(file)).toBe(true)
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"returns false for directories",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
expect(yield* fs.isFile(tmp)).toBe(false)
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("readJson / writeJson", () => {
|
|
it(
|
|
"round-trips JSON data",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "data.json")
|
|
const data = { name: "test", count: 42, nested: { ok: true } }
|
|
|
|
yield* fs.writeJson(file, data)
|
|
const result = yield* fs.readJson(file)
|
|
|
|
expect(result).toEqual(data)
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("ensureDir", () => {
|
|
it(
|
|
"creates nested directories",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const nested = path.join(tmp, "a", "b", "c")
|
|
|
|
yield* fs.ensureDir(nested)
|
|
|
|
const info = yield* fs.stat(nested)
|
|
expect(info.type).toBe("Directory")
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"is idempotent",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const dir = path.join(tmp, "existing")
|
|
yield* fs.makeDirectory(dir)
|
|
|
|
yield* fs.ensureDir(dir)
|
|
|
|
const info = yield* fs.stat(dir)
|
|
expect(info.type).toBe("Directory")
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("writeWithDirs", () => {
|
|
it(
|
|
"creates parent directories if missing",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "deep", "nested", "file.txt")
|
|
|
|
yield* fs.writeWithDirs(file, "hello")
|
|
|
|
expect(yield* fs.readFileString(file)).toBe("hello")
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"writes directly when parent exists",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "direct.txt")
|
|
|
|
yield* fs.writeWithDirs(file, "world")
|
|
|
|
expect(yield* fs.readFileString(file)).toBe("world")
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"writes Uint8Array content",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "binary.bin")
|
|
const content = new Uint8Array([0x00, 0x01, 0x02, 0x03])
|
|
|
|
yield* fs.writeWithDirs(file, content)
|
|
|
|
const result = yield* fs.readFile(file)
|
|
expect(new Uint8Array(result)).toEqual(content)
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("findUp", () => {
|
|
it(
|
|
"finds target in start directory",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
yield* fs.writeFileString(path.join(tmp, "target.txt"), "found")
|
|
|
|
const result = yield* fs.findUp("target.txt", tmp)
|
|
expect(result).toEqual([path.join(tmp, "target.txt")])
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"finds target in parent directories",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
yield* fs.writeFileString(path.join(tmp, "marker"), "root")
|
|
const child = path.join(tmp, "a", "b")
|
|
yield* fs.makeDirectory(child, { recursive: true })
|
|
|
|
const result = yield* fs.findUp("marker", child, tmp)
|
|
expect(result).toEqual([path.join(tmp, "marker")])
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"returns empty array when not found",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const result = yield* fs.findUp("nonexistent", tmp, tmp)
|
|
expect(result).toEqual([])
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("up", () => {
|
|
it(
|
|
"finds multiple targets walking up",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
yield* fs.writeFileString(path.join(tmp, "a.txt"), "a")
|
|
yield* fs.writeFileString(path.join(tmp, "b.txt"), "b")
|
|
const child = path.join(tmp, "sub")
|
|
yield* fs.makeDirectory(child)
|
|
yield* fs.writeFileString(path.join(child, "a.txt"), "a-child")
|
|
|
|
const result = yield* fs.up({ targets: ["a.txt", "b.txt"], start: child, stop: tmp })
|
|
|
|
expect(result).toContain(path.join(child, "a.txt"))
|
|
expect(result).toContain(path.join(tmp, "a.txt"))
|
|
expect(result).toContain(path.join(tmp, "b.txt"))
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("glob", () => {
|
|
it(
|
|
"finds files matching pattern",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
yield* fs.writeFileString(path.join(tmp, "a.ts"), "a")
|
|
yield* fs.writeFileString(path.join(tmp, "b.ts"), "b")
|
|
yield* fs.writeFileString(path.join(tmp, "c.json"), "c")
|
|
|
|
const result = yield* fs.glob("*.ts", { cwd: tmp })
|
|
expect(result.sort()).toEqual(["a.ts", "b.ts"])
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"supports absolute paths",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
yield* fs.writeFileString(path.join(tmp, "file.txt"), "hello")
|
|
|
|
const result = yield* fs.glob("*.txt", { cwd: tmp, absolute: true })
|
|
expect(result).toEqual([path.join(tmp, "file.txt")])
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("globMatch", () => {
|
|
it(
|
|
"matches patterns",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
expect(fs.globMatch("*.ts", "foo.ts")).toBe(true)
|
|
expect(fs.globMatch("*.ts", "foo.json")).toBe(false)
|
|
expect(fs.globMatch("src/**", "src/a/b.ts")).toBe(true)
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("globUp", () => {
|
|
it(
|
|
"finds files walking up directories",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
yield* fs.writeFileString(path.join(tmp, "root.md"), "root")
|
|
const child = path.join(tmp, "a", "b")
|
|
yield* fs.makeDirectory(child, { recursive: true })
|
|
yield* fs.writeFileString(path.join(child, "leaf.md"), "leaf")
|
|
|
|
const result = yield* fs.globUp("*.md", child, tmp)
|
|
expect(result).toContain(path.join(child, "leaf.md"))
|
|
expect(result).toContain(path.join(tmp, "root.md"))
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("built-in passthrough", () => {
|
|
it(
|
|
"exists works",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "exists.txt")
|
|
yield* fs.writeFileString(file, "yes")
|
|
|
|
expect(yield* fs.exists(file)).toBe(true)
|
|
expect(yield* fs.exists(file + ".nope")).toBe(false)
|
|
}),
|
|
)
|
|
|
|
it(
|
|
"remove works",
|
|
Effect.gen(function* () {
|
|
const fs = yield* AppFileSystem.Service
|
|
const tmp = yield* fs.makeTempDirectoryScoped()
|
|
const file = path.join(tmp, "delete-me.txt")
|
|
yield* fs.writeFileString(file, "bye")
|
|
|
|
yield* fs.remove(file)
|
|
|
|
expect(yield* fs.exists(file)).toBe(false)
|
|
}),
|
|
)
|
|
})
|
|
|
|
describe("pure helpers", () => {
|
|
test("mimeType returns correct types", () => {
|
|
expect(AppFileSystem.mimeType("file.json")).toBe("application/json")
|
|
expect(AppFileSystem.mimeType("image.png")).toBe("image/png")
|
|
expect(AppFileSystem.mimeType("unknown.qzx")).toBe("application/octet-stream")
|
|
})
|
|
|
|
test("contains checks path containment", () => {
|
|
expect(AppFileSystem.contains("/a/b", "/a/b/c")).toBe(true)
|
|
expect(AppFileSystem.contains("/a/b", "/a/c")).toBe(false)
|
|
})
|
|
|
|
test("overlaps detects overlapping paths", () => {
|
|
expect(AppFileSystem.overlaps("/a/b", "/a/b/c")).toBe(true)
|
|
expect(AppFileSystem.overlaps("/a/b/c", "/a/b")).toBe(true)
|
|
expect(AppFileSystem.overlaps("/a", "/b")).toBe(false)
|
|
})
|
|
})
|
|
})
|