| 1 | import type { CodeViewItem } from "@pierre/diffs"; |
| 2 | import { parsePatchFiles } from "@pierre/diffs/utils/parsePatchFiles"; |
| 3 | import { describe, expect, it } from "vite-plus/test"; |
| 4 | |
| 5 | // Find-in-diff lives in our @pierre/diffs patch and is not a package export. |
| 6 | const searchUrl = new URL("./components/CodeViewSearch.js", import.meta.resolve("@pierre/diffs")); |
| 7 | const { collectMatches } = (await import(/* @vite-ignore */ searchUrl.href)) as { |
| 8 | collectMatches( |
| 9 | items: ReadonlyArray<CodeViewItem<undefined>>, |
| 10 | params: { text: string; caseSensitive: boolean; wholeWord: boolean; regex: boolean }, |
| 11 | ): Array<{ |
| 12 | id: string; |
| 13 | side: string; |
| 14 | isContext: boolean; |
| 15 | lineNumber: number; |
| 16 | start: number; |
| 17 | end: number; |
| 18 | }>; |
| 19 | }; |
| 20 | |
| 21 | const PATCH = `diff --git a/src/a.ts b/src/a.ts |
| 22 | index 1111111..2222222 100644 |
| 23 | --- a/src/a.ts |
| 24 | +++ b/src/a.ts |
| 25 | @@ -1,4 +1,5 @@ |
| 26 | const keep = 1; |
| 27 | -const oldName = 2; |
| 28 | +const newName = 2; |
| 29 | +const added = oldName; |
| 30 | const tail = 4; |
| 31 | @@ -20,3 +21,3 @@ function x() { |
| 32 | ctx20 name |
| 33 | -del21 Name |
| 34 | +add22 names |
| 35 | ctx22 |
| 36 | diff --git a/src/b.ts b/src/b.ts |
| 37 | index 3333333..4444444 100644 |
| 38 | --- a/src/b.ts |
| 39 | +++ b/src/b.ts |
| 40 | @@ -1,1 +1,1 @@ |
| 41 | -nil |
| 42 | +: nil |
| 43 | `; |
| 44 | |
| 45 | const items: CodeViewItem<undefined>[] = parsePatchFiles(PATCH)[0]!.files.map( |
| 46 | (fileDiff, index) => ({ id: `file-${index}`, type: "diff", fileDiff, collapsed: index === 1 }), |
| 47 | ); |
| 48 | |
| 49 | function find( |
| 50 | text: string, |
| 51 | flags: { caseSensitive?: boolean; wholeWord?: boolean; regex?: boolean } = {}, |
| 52 | ) { |
| 53 | return collectMatches(items, { |
| 54 | text, |
| 55 | caseSensitive: flags.caseSensitive ?? false, |
| 56 | wholeWord: flags.wholeWord ?? false, |
| 57 | regex: flags.regex ?? false, |
| 58 | }).map(({ id, side, lineNumber, start, end }) => `${id}:${side}:${lineNumber}:${start}-${end}`); |
| 59 | } |
| 60 | |
| 61 | describe("collectMatches", () => { |
| 62 | it("finds removed, added, and context lines in render order, folded files included", () => { |
| 63 | expect(find("oldName")).toEqual(["file-0:deletions:2:6-13", "file-0:additions:3:14-21"]); |
| 64 | expect(find("nil")).toEqual(["file-1:deletions:1:0-3", "file-1:additions:1:2-5"]); |
| 65 | }); |
| 66 | |
| 67 | it("honours case, whole-word, and regex toggles", () => { |
| 68 | expect(find("Name", { caseSensitive: true })).toEqual([ |
| 69 | "file-0:deletions:2:9-13", |
| 70 | "file-0:additions:2:9-13", |
| 71 | "file-0:additions:3:17-21", |
| 72 | "file-0:deletions:21:6-10", |
| 73 | ]); |
| 74 | expect(find("name", { wholeWord: true })).toEqual([ |
| 75 | "file-0:additions:21:6-10", |
| 76 | "file-0:deletions:21:6-10", |
| 77 | ]); |
| 78 | expect(find("n[a-z]+e", { regex: true })).toHaveLength(6); |
| 79 | }); |
| 80 | |
| 81 | it("marks unchanged lines, which split view draws in both columns", () => { |
| 82 | const flags = collectMatches(items, { |
| 83 | text: "ctx", |
| 84 | caseSensitive: false, |
| 85 | wholeWord: false, |
| 86 | regex: false, |
| 87 | }).map(({ lineNumber, isContext }) => [lineNumber, isContext]); |
| 88 | expect(flags).toEqual([ |
| 89 | [21, true], |
| 90 | [23, true], |
| 91 | ]); |
| 92 | expect( |
| 93 | collectMatches(items, { |
| 94 | text: "oldName", |
| 95 | caseSensitive: false, |
| 96 | wholeWord: false, |
| 97 | regex: false, |
| 98 | }).map(({ isContext }) => isContext), |
| 99 | ).toEqual([false, false]); |
| 100 | }); |
| 101 | |
| 102 | it("returns nothing for an empty query or an invalid pattern", () => { |
| 103 | expect(find("")).toEqual([]); |
| 104 | expect(find("(", { regex: true })).toEqual([]); |
| 105 | }); |
| 106 | }); |