diff --git a/src/api.test.ts b/src/api.test.ts index c46791b..62c70cf 100644 --- a/src/api.test.ts +++ b/src/api.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { buildFetchProgress, buildLineResolutionProgress, + deriveSegmentText, fetchAllResults, fetchRepoTeams, searchCode, @@ -28,6 +29,26 @@ describe("segmentLineCol (api)", () => { }); }); +// ─── deriveSegmentText (issue #151, restores PR #133) ───────────────────────────────────── + +describe("deriveSegmentText", () => { + it("returns seg.text unchanged when GitHub provides it", () => { + expect(deriveSegmentText("hello world", { text: "hello", indices: [0, 5] })).toBe("hello"); + }); + + it("derives the text from fragment + indices when seg.text is omitted", () => { + expect(deriveSegmentText("hello world", { indices: [6, 11] })).toBe("world"); + }); + + it("returns an empty string for out-of-range indices", () => { + expect(deriveSegmentText("hello", { indices: [10, 20] })).toBe(""); + }); + + it("returns an empty string when end is before start", () => { + expect(deriveSegmentText("hello", { indices: [4, 1] })).toBe(""); + }); +}); + // ─── searchCode ─────────────────────────────────────────────────────────────── describe("searchCode", () => { @@ -146,6 +167,28 @@ describe("fetchAllResults", () => { expect(results[0].textMatches[0].matches[0].col).toBe(1); }); + it("derives matchedText from fragment + indices when GitHub omits the segment's text field (issue #151)", async () => { + const fakeItem = { + path: "src/foo.ts", + html_url: "https://github.com/org/repo/blob/main/src/foo.ts", + repository: { full_name: "org/repo", archived: false }, + text_matches: [ + { + fragment: "hello world", + matches: [{ indices: [6, 11] }], + }, + ], + }; + globalThis.fetch = (async () => + new Response(JSON.stringify({ items: [fakeItem], total_count: 1 }), { + status: 200, + headers: { "content-type": "application/json" }, + })) as typeof fetch; + + const results = await fetchAllResults("world", "org", "tok"); + expect(results[0].textMatches[0].matches[0].text).toBe("world"); + }); + it("marks archived repos correctly", async () => { const fakeItem = { path: "lib/old.ts", diff --git a/src/api.ts b/src/api.ts index 693e435..134f138 100644 --- a/src/api.ts +++ b/src/api.ts @@ -123,6 +123,18 @@ export function segmentLineCol(fragment: string, offset: number): { line: number }; } +/** + * Derive a text-match segment's matched text from `fragment` and `indices` + * when GitHub omits the `text` field on the segment. Restores PR #133 — + * see issue #151. + */ +export function deriveSegmentText(fragment: string, seg: RawSearchSegment): string { + if (seg.text !== undefined) return seg.text; + const [start, end] = seg.indices; + if (start < 0 || end < start || end > fragment.length) return ""; + return fragment.slice(start, end); +} + export async function searchCode( q: string, org: string, @@ -296,7 +308,7 @@ export async function fetchAllResults( const indices = seg.indices; const { line: fragLine, col } = segmentLineCol(fragment, indices[0]); const line = fragmentStartLine + fragLine - 1; - return { text: seg.text ?? "", indices, line, col }; + return { text: deriveSegmentText(fragment, seg), indices, line, col }; }), }; }),