| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { quoteGitPatchPath, unquoteGitPatchPath } from "./gitPatchPath.ts"; |
| 4 | |
| 5 | const BELL = "\u0007"; |
| 6 | const UNIT_SEPARATOR = "\u001f"; |
| 7 | const DELETE = "\u007f"; |
| 8 | |
| 9 | describe("quoteGitPatchPath", () => { |
| 10 | it("leaves a name a header can carry as itself", () => { |
| 11 | expect(quoteGitPatchPath("src/app.ts")).toBe("src/app.ts"); |
| 12 | expect(quoteGitPatchPath("with space.txt")).toBe("with space.txt"); |
| 13 | // Outside ASCII is only quoted for what a terminal can show, and a diff viewer is not one. |
| 14 | expect(quoteGitPatchPath("café/résumé.ts")).toBe("café/résumé.ts"); |
| 15 | expect(quoteGitPatchPath("🚀.ts")).toBe("🚀.ts"); |
| 16 | }); |
| 17 | |
| 18 | it("writes git's escape for a character the header would read as its own", () => { |
| 19 | expect(quoteGitPatchPath("tab\tfile.txt")).toBe('"tab\\tfile.txt"'); |
| 20 | expect(quoteGitPatchPath("line\nfile.txt")).toBe('"line\\nfile.txt"'); |
| 21 | expect(quoteGitPatchPath('quo"te.txt')).toBe('"quo\\"te.txt"'); |
| 22 | expect(quoteGitPatchPath("back\\slash.txt")).toBe('"back\\\\slash.txt"'); |
| 23 | expect(quoteGitPatchPath(`ring${BELL}.txt`)).toBe('"ring\\a.txt"'); |
| 24 | }); |
| 25 | |
| 26 | it("writes the octal of a control character git has no name for", () => { |
| 27 | expect(quoteGitPatchPath(`unit${UNIT_SEPARATOR}.txt`)).toBe('"unit\\037.txt"'); |
| 28 | expect(quoteGitPatchPath(`del${DELETE}.txt`)).toBe('"del\\177.txt"'); |
| 29 | }); |
| 30 | }); |
| 31 | |
| 32 | describe("a name written into a header and read back out", () => { |
| 33 | const names = [ |
| 34 | "src/app.ts", |
| 35 | "with space.txt", |
| 36 | "café/résumé.ts", |
| 37 | "🚀.ts", |
| 38 | "tab\tfile.txt", |
| 39 | "line\nfile.txt", |
| 40 | "carriage\rfile.txt", |
| 41 | 'quo"te.txt', |
| 42 | "back\\slash.txt", |
| 43 | `ring${BELL}.txt`, |
| 44 | `unit${UNIT_SEPARATOR}.txt`, |
| 45 | `del${DELETE}.txt`, |
| 46 | `every\t\n\r"\\${BELL}.txt`, |
| 47 | ]; |
| 48 | |
| 49 | it.each(names)("is the name that went in: %j", (name) => { |
| 50 | const written = quoteGitPatchPath(name); |
| 51 | expect(unquoteGitPatchPath(written)).toBe(name); |
| 52 | // A parser that takes the quotes off itself, as the clients' one does, gets there too. |
| 53 | const unwrapped = written.startsWith('"') ? written.slice(1, -1) : written; |
| 54 | expect(unquoteGitPatchPath(unwrapped)).toBe(name); |
| 55 | }); |
| 56 | |
| 57 | it("carries the whole name past the first thing a header stops at", () => { |
| 58 | const written = quoteGitPatchPath("tab\tfile.txt"); |
| 59 | |
| 60 | // The bug this guards: a header line ends at a tab and splits on a space, so the name written |
| 61 | // as itself would be read as `tab` and marked viewed under a path no host has. |
| 62 | expect(written).not.toContain("\t"); |
| 63 | expect(written).not.toContain("\n"); |
| 64 | expect(unquoteGitPatchPath(written)).not.toBe("tab"); |
| 65 | }); |
| 66 | }); |
| 67 | |
| 68 | describe("unquoteGitPatchPath", () => { |
| 69 | it("leaves a token git saw no need to quote alone", () => { |
| 70 | expect(unquoteGitPatchPath("src/app.ts")).toBe("src/app.ts"); |
| 71 | expect(unquoteGitPatchPath("with space.txt")).toBe("with space.txt"); |
| 72 | expect(unquoteGitPatchPath("")).toBe(""); |
| 73 | expect(unquoteGitPatchPath('"')).toBe('"'); |
| 74 | }); |
| 75 | |
| 76 | it("reads the octal escapes a host with core.quotePath on writes", () => { |
| 77 | expect(unquoteGitPatchPath('"caf\\303\\251/r\\303\\251sum\\303\\251.ts"')).toBe( |
| 78 | "café/résumé.ts", |
| 79 | ); |
| 80 | expect(unquoteGitPatchPath('"\\360\\237\\232\\200.ts"')).toBe("🚀.ts"); |
| 81 | }); |
| 82 | }); |
| 83 | |
| 84 | describe("a name a parser handed back with its quotes already off", () => { |
| 85 | it("is read for the escapes it still carries", () => { |
| 86 | expect(unquoteGitPatchPath("tab\\tfile.txt")).toBe("tab\tfile.txt"); |
| 87 | expect(unquoteGitPatchPath("caf\\303\\251.ts")).toBe("café.ts"); |
| 88 | }); |
| 89 | |
| 90 | it("reads an escape git would never write the way C reads it", () => { |
| 91 | expect(unquoteGitPatchPath("odd\\zname.txt")).toBe("oddzname.txt"); |
| 92 | expect(unquoteGitPatchPath("trailing\\")).toBe("trailing\\"); |
| 93 | expect(unquoteGitPatchPath("short\\12.txt")).toBe("short12.txt"); |
| 94 | }); |
| 95 | }); |