import { describe, expect, test } from "bun:test"import path from "path"import { GrepTool } from "../../src/tool/grep"import { Instance } from "../../src/project/instance"import { tmpdir } from "../fixture/fixture" const ctx = { sessionID: "test", messageID: "", callID: "", agent: "research", abort: AbortSignal.any([]), messages: [], metadata: () => {}, ask: async () => {},} const projectRoot = path.join(__dirname, "../..") describe("tool.grep", () => { test("basic search", async () => { await Instance.provide({ directory: projectRoot, fn: async () => { const grep = await GrepTool.init() const result = await grep.execute( { pattern: "export", path: path.join(projectRoot, "src/tool"), include: "*.ts", }, ctx, ) expect(result.metadata.matches).toBeGreaterThan(0) expect(result.output).toContain("Found") }, }) }) test("no matches returns correct output", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Bun.write(path.join(dir, "test.txt"), "hello world") }, }) await Instance.provide({ directory: tmp.path, fn: async () => { const grep = await GrepTool.init() const result = await grep.execute( { pattern: "xyznonexistentpatternxyz123", path: tmp.path, }, ctx, ) expect(result.metadata.matches).toBe(0) expect(result.output).toBe("No files found") }, }) }) test("handles CRLF line endings in output", async () => { // This test verifies the regex split handles both \n and \r\n await using tmp = await tmpdir({ init: async (dir) => { // Create a test file with content await Bun.write(path.join(dir, "test.txt"), "line1\nline2\nline3") }, }) await Instance.provide({ directory: tmp.path, fn: async () => { const grep = await GrepTool.init() const result = await grep.execute( { pattern: "line", path: tmp.path, }, ctx, ) expect(result.metadata.matches).toBeGreaterThan(0) }, }) })}) describe("CRLF regex handling", () => { test("regex correctly splits Unix line endings", () => { const unixOutput = "file1.txt|1|content1\nfile2.txt|2|content2\nfile3.txt|3|content3" const lines = unixOutput.trim().split(/\r?\n/) expect(lines.length).toBe(3) expect(lines[0]).toBe("file1.txt|1|content1") expect(lines[2]).toBe("file3.txt|3|content3") }) test("regex correctly splits Windows CRLF line endings", () => { const windowsOutput = "file1.txt|1|content1\r\nfile2.txt|2|content2\r\nfile3.txt|3|content3" const lines = windowsOutput.trim().split(/\r?\n/) expect(lines.length).toBe(3) expect(lines[0]).toBe("file1.txt|1|content1") expect(lines[2]).toBe("file3.txt|3|content3") }) test("regex handles mixed line endings", () => { const mixedOutput = "file1.txt|1|content1\nfile2.txt|2|content2\r\nfile3.txt|3|content3" const lines = mixedOutput.trim().split(/\r?\n/) expect(lines.length).toBe(3) })})
import { describe, expect, test } from "bun:test"
import path from "path"
import { GrepTool } from "../../src/tool/grep"
import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
const ctx = {
sessionID: "test",
messageID: "",
callID: "",
agent: "research",
abort: AbortSignal.any([]),
messages: [],
metadata: () => {},
ask: async () => {},
}
const projectRoot = path.join(__dirname, "../..")
describe("tool.grep", () => {
test("basic search", async () => {
await Instance.provide({
directory: projectRoot,
fn: async () => {
const grep = await GrepTool.init()
const result = await grep.execute(
{
pattern: "export",
path: path.join(projectRoot, "src/tool"),
include: "*.ts",
},
ctx,
)
expect(result.metadata.matches).toBeGreaterThan(0)
expect(result.output).toContain("Found")
},
})
})
test("no matches returns correct output", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "test.txt"), "hello world")
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const grep = await GrepTool.init()
const result = await grep.execute(
{
pattern: "xyznonexistentpatternxyz123",
path: tmp.path,
},
ctx,
)
expect(result.metadata.matches).toBe(0)
expect(result.output).toBe("No files found")
},
})
})
test("handles CRLF line endings in output", async () => {
// This test verifies the regex split handles both \n and \r\n
await using tmp = await tmpdir({
init: async (dir) => {
// Create a test file with content
await Bun.write(path.join(dir, "test.txt"), "line1\nline2\nline3")
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const grep = await GrepTool.init()
const result = await grep.execute(
{
pattern: "line",
path: tmp.path,
},
ctx,
)
expect(result.metadata.matches).toBeGreaterThan(0)
},
})
})
})
describe("CRLF regex handling", () => {
test("regex correctly splits Unix line endings", () => {
const unixOutput = "file1.txt|1|content1\nfile2.txt|2|content2\nfile3.txt|3|content3"
const lines = unixOutput.trim().split(/\r?\n/)
expect(lines.length).toBe(3)
expect(lines[0]).toBe("file1.txt|1|content1")
expect(lines[2]).toBe("file3.txt|3|content3")
})
test("regex correctly splits Windows CRLF line endings", () => {
const windowsOutput = "file1.txt|1|content1\r\nfile2.txt|2|content2\r\nfile3.txt|3|content3"
const lines = windowsOutput.trim().split(/\r?\n/)
expect(lines.length).toBe(3)
expect(lines[0]).toBe("file1.txt|1|content1")
expect(lines[2]).toBe("file3.txt|3|content3")
})
test("regex handles mixed line endings", () => {
const mixedOutput = "file1.txt|1|content1\nfile2.txt|2|content2\r\nfile3.txt|3|content3"
const lines = mixedOutput.trim().split(/\r?\n/)
expect(lines.length).toBe(3)
})
})