124 lines
4.7 KiB
TypeScript
124 lines
4.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { splitReadFiles, type ReadFileRef } from "./read-selector";
|
|
|
|
const ref = (path: string, startLine = 0, lineCount = 0): ReadFileRef => ({
|
|
path,
|
|
startLine,
|
|
lineCount,
|
|
});
|
|
|
|
describe("splitReadFiles", () => {
|
|
// Single-file inputs must behave exactly like the Go `Split`: one entry with
|
|
// the selector stripped and the range parsed.
|
|
it.each([
|
|
["no selector", "config.json", [ref("config.json")]],
|
|
["single line", "apps/web/lib/utils.ts:50", [ref("apps/web/lib/utils.ts", 50)]],
|
|
["open-ended range", "main.go:50-", [ref("main.go", 50)]],
|
|
[
|
|
"closed range",
|
|
"apps/backend/internal/sentry/handlers.go:43-94",
|
|
[ref("apps/backend/internal/sentry/handlers.go", 43, 52)],
|
|
],
|
|
["plus span", "main.go:50+150", [ref("main.go", 50, 150)]],
|
|
["multi-range stays one file", "main.go:5-16,960-973", [ref("main.go", 5, 12)]],
|
|
["raw mode", "main.go:raw", [ref("main.go")]],
|
|
["range then raw combo", "main.go:2-4:raw", [ref("main.go", 2, 3)]],
|
|
[
|
|
"windows absolute",
|
|
"C:\\Users\\me\\handlers.go:43-94",
|
|
[ref("C:\\Users\\me\\handlers.go", 43, 52)],
|
|
],
|
|
["no extension single line", "Makefile:10", [ref("Makefile", 10)]],
|
|
])("keeps %s as a single file", (_name, raw, want) => {
|
|
expect(splitReadFiles(raw as string)).toEqual(want);
|
|
});
|
|
|
|
it("splits two comma-joined files each with its own range", () => {
|
|
expect(
|
|
splitReadFiles(
|
|
"deployments/cluster-tools/values.backupprod.yaml:1-80,deployments/cluster-tools/values.au-backupprod.yaml:1-80",
|
|
),
|
|
).toEqual([
|
|
ref("deployments/cluster-tools/values.backupprod.yaml", 1, 80),
|
|
ref("deployments/cluster-tools/values.au-backupprod.yaml", 1, 80),
|
|
]);
|
|
});
|
|
|
|
it("splits two bare-extension files", () => {
|
|
expect(splitReadFiles("a.go:1,b.go:2")).toEqual([ref("a.go", 1), ref("b.go", 2)]);
|
|
});
|
|
|
|
it("attaches a trailing multi-range to the first file, not a new file", () => {
|
|
expect(splitReadFiles("a.go:5-16,40-80,b.go:10")).toEqual([
|
|
ref("a.go", 5, 12),
|
|
ref("b.go", 10),
|
|
]);
|
|
});
|
|
|
|
it("splits files carrying mode selectors", () => {
|
|
expect(splitReadFiles("a.go:2-4:raw,b.go:raw")).toEqual([ref("a.go", 2, 3), ref("b.go")]);
|
|
});
|
|
|
|
it("keeps a second file openable even without a range", () => {
|
|
expect(splitReadFiles("a/x.yaml:1-80,b/y.yaml")).toEqual([
|
|
ref("a/x.yaml", 1, 80),
|
|
ref("b/y.yaml"),
|
|
]);
|
|
});
|
|
|
|
it("treats a comma inside a directory name as a single file", () => {
|
|
expect(splitReadFiles("a,b/foo.go:1-80")).toEqual([ref("a,b/foo.go", 1, 80)]);
|
|
});
|
|
|
|
it("splits the hyphenated multi-file example each with its own range", () => {
|
|
// The hyphens in "tailscale-ingress-extras" must not be mistaken for a line
|
|
// range separator when deciding a segment is a new file.
|
|
expect(
|
|
splitReadFiles(
|
|
"deployments/tailscale-ingress-extras/values.prod.yaml:1-180,deployments/tailscale-ingress-extras/values.staging.yaml:1-180",
|
|
),
|
|
).toEqual([
|
|
ref("deployments/tailscale-ingress-extras/values.prod.yaml", 1, 180),
|
|
ref("deployments/tailscale-ingress-extras/values.staging.yaml", 1, 180),
|
|
]);
|
|
});
|
|
|
|
it("splits a legacy half-stripped multi-file path (trailing file's range moved to offset)", () => {
|
|
// Pre-fix backend stored the bundle with only the LAST file's range stripped;
|
|
// both files must still split into openable links.
|
|
expect(
|
|
splitReadFiles(
|
|
"deployments/tailscale-ingress-extras/values.prod.yaml:1-180,deployments/tailscale-ingress-extras/values.staging.yaml",
|
|
),
|
|
).toEqual([
|
|
ref("deployments/tailscale-ingress-extras/values.prod.yaml", 1, 180),
|
|
ref("deployments/tailscale-ingress-extras/values.staging.yaml"),
|
|
]);
|
|
});
|
|
|
|
it("falls back to a single entry when a segment is not file-like", () => {
|
|
// "5" alone is a line spec, not a file, so this is not a real multi-file read.
|
|
expect(splitReadFiles("main.go:1,5")).toEqual([ref("main.go", 1)]);
|
|
});
|
|
|
|
it("treats a comma inside a filename as a single file", () => {
|
|
// "src/foo" is file-like only via its separator (no selector, no extension),
|
|
// so it is not a file boundary and the whole path stays one entry.
|
|
expect(splitReadFiles("src/foo,bar.go:1-20")).toEqual([ref("src/foo,bar.go", 1, 20)]);
|
|
});
|
|
|
|
it("still splits two files when the first carries an extension", () => {
|
|
expect(splitReadFiles("src/a.go,src/b.go:1-20")).toEqual([
|
|
ref("src/a.go"),
|
|
ref("src/b.go", 1, 20),
|
|
]);
|
|
});
|
|
|
|
it("still splits an extensionless first file that carries a selector", () => {
|
|
expect(splitReadFiles("Makefile:10,foo.go:20")).toEqual([
|
|
ref("Makefile", 10),
|
|
ref("foo.go", 20),
|
|
]);
|
|
});
|
|
});
|