import { describe, expect, test } from "bun:test";
import { compileQuery, matchLines, requiredLiteral } from "./text-search";
describe("workspace text search", () => {
test("literal patterns are escaped and case-insensitive unless asked", () => {
const content = "const a = foo.bar();\nFOO.BAR\nfooXbar";
expect(matchLines(content, compileQuery({ pattern: "foo.bar" }), 10)).toEqual([
{ line: 0, text: "const a = foo.bar();", ranges: [[10, 17]] },
{ line: 1, text: "FOO.BAR", ranges: [[0, 7]] },
]);
expect(matchLines(content, compileQuery({ pattern: "foo.bar", isCaseSensitive: true }), 10)).toHaveLength(1);
});
test("regular expressions, whole words, every match on a line, and the limit", () => {
const content = "cat concat cat\ndog\ncat";
expect(matchLines(content, compileQuery({ pattern: "cat", isWordMatch: true }), 10)[0].ranges).toEqual([
[0, 3],
[11, 14],
]);
expect(matchLines(content, compileQuery({ pattern: "^d.g$", isRegExp: true }), 10)).toEqual([
{ line: 1, text: "dog", ranges: [[0, 3]] },
]);
expect(matchLines(content, compileQuery({ pattern: "cat" }), 1)).toHaveLength(1);
expect(matchLines("abc", compileQuery({ pattern: "x*", isRegExp: true }), 10)).toEqual([]);
expect(() => compileQuery({ pattern: "(", isRegExp: true })).toThrow();
});
});
describe("index prefilter literal", () => {
test("literal patterns are their own prefilter once long enough", () => {
expect(requiredLiteral({ pattern: "foo.bar" })).toBe("foo.bar");
expect(requiredLiteral({ pattern: "ab" })).toBeNull();
});
test("regexes yield only substrings every match must contain", () => {
expect(requiredLiteral({ pattern: "function\\s+handle\\w*Request", isRegExp: true })).toBe("function");
expect(requiredLiteral({ pattern: "colou?r_name", isRegExp: true })).toBe("r_name");
expect(requiredLiteral({ pattern: "import\\.meta", isRegExp: true })).toBe("import.meta");
expect(requiredLiteral({ pattern: "[abc]+xyz(opt)?tail", isRegExp: true })).toBe("tail");
expect(requiredLiteral({ pattern: "foo|barbaz", isRegExp: true })).toBeNull();