| 1 | import { describe, expect, it } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; |
| 5 | import { ChildProcessSpawner } from "effect/process"; |
| 6 | |
| 7 | import { gitDiffFileStats, githubRepository } from "./JjStackService.ts"; |
| 8 | import * as JjStackService from "./JjStackService.ts"; |
| 9 | import { VcsProcess } from "./VcsProcess.ts"; |
| 10 | |
| 11 | describe("gitDiffFileStats", () => { |
| 12 | it("counts lines per file and keeps rename sources", () => { |
| 13 | const diff = [ |
| 14 | "diff --git a/src/old.ts b/src/new.ts", |
| 15 | "rename from src/old.ts", |
| 16 | "rename to src/new.ts", |
| 17 | "--- a/src/old.ts", |
| 18 | "+++ b/src/new.ts", |
| 19 | "@@ -1,2 +1,2 @@", |
| 20 | "-const a = 1;", |
| 21 | "+const a = 2;", |
| 22 | " export { a };", |
| 23 | "diff --git a/README.md b/README.md", |
| 24 | "new file mode 100644", |
| 25 | "--- /dev/null", |
| 26 | "+++ b/README.md", |
| 27 | "@@ -0,0 +1,2 @@", |
| 28 | "+# Title", |
| 29 | "+--- a line that looks like a header", |
| 30 | ].join("\n"); |
| 31 | expect(gitDiffFileStats(diff)).toEqual([ |
| 32 | { path: "src/new.ts", previousPath: "src/old.ts", additions: 1, deletions: 1 }, |
| 33 | { path: "README.md", previousPath: null, additions: 2, deletions: 0 }, |
| 34 | ]); |
| 35 | }); |
| 36 | }); |
| 37 | |
| 38 | describe("githubRepository", () => { |
| 39 | const remotes = [ |
| 40 | "origin git@github.com:acme/app.git", |
| 41 | "upstream https://github.com/acme/upstream", |
| 42 | "mirror https://git.example.com/acme/app.git", |
| 43 | "lookalike https://notgithub.com/acme/app.git", |
| 44 | ].join("\n"); |
| 45 | |
| 46 | it("reads owner/name from the push remote's GitHub URL", () => { |
| 47 | expect(githubRepository(remotes, "origin")).toBe("acme/app"); |
| 48 | expect(githubRepository(remotes, "upstream")).toBe("acme/upstream"); |
| 49 | }); |
| 50 | |
| 51 | it("returns null for other hosts and unknown remotes", () => { |
| 52 | expect(githubRepository(remotes, "mirror")).toBeNull(); |
| 53 | expect(githubRepository(remotes, "lookalike")).toBeNull(); |
| 54 | expect(githubRepository(remotes, "fork")).toBeNull(); |
| 55 | }); |
| 56 | }); |
| 57 | |
| 58 | const pushLayer = (bookmarks: string) => |
| 59 | JjStackService.layer.pipe( |
| 60 | Layer.provide( |
| 61 | Layer.succeed( |
| 62 | VcsProcess, |
| 63 | VcsProcess.of({ |
| 64 | run: (input) => |
| 65 | Effect.succeed({ |
| 66 | exitCode: ChildProcessSpawner.ExitCode(0), |
| 67 | stdout: |
| 68 | input.operation === "JjStackService.push-template" |
| 69 | ? '"review-" ++ change_id.short()' |
| 70 | : input.operation === "JjStackService.bookmark-name" |
| 71 | ? "review-selected" |
| 72 | : input.operation === "JjStackService.pushed-bookmark" |
| 73 | ? bookmarks |
| 74 | : "", |
| 75 | stderr: "", |
| 76 | stdoutTruncated: false, |
| 77 | stderrTruncated: false, |
| 78 | }), |
| 79 | }), |
| 80 | ), |
| 81 | ), |
| 82 | Layer.provide(NodeServices.layer), |
| 83 | ); |
| 84 | |
| 85 | describe("JjStackService.push", () => { |
| 86 | it.effect("returns the bookmark it pushed when other bookmarks point at the same commit", () => |
| 87 | Effect.gen(function* () { |
| 88 | const service = yield* JjStackService.JjStackService; |
| 89 | const result = yield* service.push({ cwd: "/workspace", changeId: "selected" }); |
| 90 | expect(result).toEqual({ bookmark: "review-selected", remote: "origin" }); |
| 91 | }).pipe(Effect.provide(pushLayer("another-pr\torigin\nreview-selected\torigin"))), |
| 92 | ); |
| 93 | |
| 94 | it.effect("refuses to report success when only another bookmark is pushed", () => |
| 95 | Effect.gen(function* () { |
| 96 | const service = yield* JjStackService.JjStackService; |
| 97 | const result = yield* service |
| 98 | .push({ cwd: "/workspace", changeId: "selected" }) |
| 99 | .pipe(Effect.flip); |
| 100 | expect(result).toMatchObject({ _tag: "JjError", operation: "push" }); |
| 101 | }).pipe(Effect.provide(pushLayer("another-pr\torigin"))), |
| 102 | ); |
| 103 | }); |