History

feat(prs): navigate, merge and rebase GitHub stacks (#10875)

Julius Marminge committed 2 months ago

Commit de379641 parent f0401c653 files · +3104 −464Browse files

Showing the changes to apps/web/src/components/pullRequest/pullRequestStackSnapshot.test.ts (1 of 53 files). Show all

Unified · Split

apps/web/src/components/pullRequest/pullRequestStackSnapshot.test.ts+93−0
@@ -0,0 +1,93 @@
1+import type { ThreadPullRequestLink } from "@t3tools/contracts";
2+import { ProjectId } from "@t3tools/contracts";
3+import { describe, expect, it } from "vite-plus/test";
4+import { savedPullRequestStack, pullRequestStackView } from "./pullRequestStackSnapshot";
5+
6+const reference = {
7+ projectId: ProjectId.make("project"),
8+ host: "github.com",
9+ repository: "acme/web",
10+ number: 2,
11+};
12+const link: ThreadPullRequestLink = {
13+ host: "github.com",
14+ repository: "acme/web",
15+ number: 2,
16+ url: "https://github.com/acme/web/pull/2",
17+ source: "manual",
18+ linkedAt: "2026-09-09T10:00:00Z",
19+ snapshot: {
20+ title: "Top layer",
21+ headBranch: "top",
22+ baseBranch: "bottom",
23+ state: "open",
24+ isDraft: false,
25+ updatedAt: null,
26+ syncedAt: "2026-09-09T10:00:00Z",
27+ },
28+ stack: {
29+ kind: "native",
30+ id: "stack",
31+ number: 3,
32+ url: "https://github.com/acme/web/pull/3",
33+ base: "main",
34+ layers: [
35+ { number: 1, headBranch: "bottom", state: "open" },
36+ { number: 2, headBranch: "top", state: "open" },
37+ ],
38+ },
39+};
40+
41+describe("saved stack navigation", () => {
42+ it("preserves all native layers even when only one has a linked snapshot, without action SHAs", () => {
43+ const saved = savedPullRequestStack([link], reference);
44+ expect(saved?.layers).toEqual([
45+ { number: 1, headBranch: "bottom", state: "open" },
46+ { number: 2, headBranch: "top", state: "open", title: "Top layer", isDraft: false },
47+ ]);
48+ expect(savedPullRequestStack([link], { ...reference, number: 1 })?.number).toBe(3);
49+ });
50+ it("does not borrow stacks across hosts or repositories", () => {
51+ expect(savedPullRequestStack([link], { ...reference, host: "enterprise.example" })).toBeNull();
52+ expect(savedPullRequestStack([link], { ...reference, repository: "other/web" })).toBeNull();
53+ expect(savedPullRequestStack([link], { ...reference, host: undefined })).toBeNull();
54+ });
55+ it("honors a newer saved removal across linked threads", () => {
56+ expect(
57+ savedPullRequestStack(
58+ [
59+ link,
60+ {
61+ ...link,
62+ stack: null,
63+ snapshot: { ...link.snapshot!, syncedAt: "2026-09-09T11:00:00Z" },
64+ },
65+ ],
66+ reference,
67+ ),
68+ ).toBeNull();
69+ });
70+ it("shows saved data during loading and marks a failed refresh stale", () => {
71+ const saved = savedPullRequestStack([link], reference);
72+ const query = { data: null, isSuccess: false, isPending: true, error: null };
73+ expect(pullRequestStackView(query, saved)).toMatchObject({
74+ data: saved,
75+ isFresh: false,
76+ notice: expect.stringContaining("Refreshing"),
77+ });
78+ expect(
79+ pullRequestStackView({ ...query, isPending: false, error: "Rate limited" }, saved),
80+ ).toMatchObject({ data: saved, isFresh: false, notice: expect.stringContaining("stale") });
81+ });
82+ it("prefers refreshed data and honors a successful absence", () => {
83+ const saved = savedPullRequestStack([link], reference);
84+ const query = { data: saved, isSuccess: true, isPending: false, error: null };
85+ expect(pullRequestStackView(query, null)).toEqual({ data: saved, isFresh: true, notice: null });
86+ expect(pullRequestStackView({ ...query, data: null }, saved)).toEqual({
87+ data: null,
88+ isFresh: true,
89+ notice: null,
90+ });
91+ expect(pullRequestStackView({ ...query, isPending: true }, saved).isFresh).toBe(false);
92+ });
93+});