1import { describe, expect, it } from "vite-plus/test";
2import {
3 IsoDateTime,
4 ProviderInstanceId,
5 type PullRequestReviewGuideResult,
6} from "@t3tools/contracts";
7import { guideNoticeState, reviewGuideState } from "./reviewGuideState";
8
9const guide: NonNullable<PullRequestReviewGuideResult["guide"]> = {
10 headSha: "old-head",
11 generatedAt: IsoDateTime.make("2026-10-11T00:00:00.000Z"),
12 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5" },
13 stops: [],
14};
15
16describe("mobile review guide state", () => {
17 it("keeps a previous guide readable during a rewrite", () => {
18 const state = reviewGuideState({
19 guide,
20 currentHeadSha: "new-head",
21 run: { _tag: "Running", headSha: "new-head", startedAt: guide.generatedAt },
22 });
23 expect(state).toMatchObject({ guide, stale: true, running: true, failure: null });
24 });
25 it("does not label an unknown head stale", () => {
26 expect(reviewGuideState({ guide, currentHeadSha: null, run: null }).stale).toBe(false);
27 });
28 it("shows only failures for the current head", () => {
29 const run = {
30 _tag: "Failed" as const,
31 headSha: "old-head",
32 failedAt: guide.generatedAt,
33 detail: "The writer disconnected.",
34 };
35 expect(reviewGuideState({ guide: null, currentHeadSha: "old-head", run }).failure).toBe(
36 run.detail,
37 );
38 expect(reviewGuideState({ guide: null, currentHeadSha: "new-head", run }).failure).toBeNull();
39 });
40 it("starts empty without claiming a job is running", () => {
41 expect(reviewGuideState(null)).toEqual({
42 guide: null,
43 stale: false,
44 running: false,
45 failure: null,
46 });
47 });
48});
49
50const view = {
51 data: null,
52 isPending: false,
53 running: false,
54 startError: null,
55 error: null,
56};
57
58describe("guide notice transitions", () => {
59 it("does not flash an empty guide before the first response", () => {
60 expect(guideNoticeState({ ...view, isPending: true }).kind).toBe("loading");
61 expect(
62 guideNoticeState({ ...view, data: { guide: null, run: null, currentHeadSha: "head" } }).kind,
63 ).toBe("empty");
64 });
65
66 it("keeps cached guides visible while refreshing", () => {
67 expect(
68 guideNoticeState({
69 ...view,
70 isPending: true,
71 data: { guide, run: null, currentHeadSha: guide.headSha },
72 }).kind,
73 ).toBe("ready");
74 });
75
76 it("hides the previous failure as soon as a retry starts", () => {
77 const data = {
78 guide: null,
79 currentHeadSha: "head",
80 run: {
81 _tag: "Failed" as const,
82 headSha: "head",
83 failedAt: guide.generatedAt,
84 detail: "Writer disconnected",
85 },
86 };
87 expect(guideNoticeState({ ...view, data }).kind).toBe("failed");
88 expect(guideNoticeState({ ...view, data, running: true }).kind).toBe("writing");
89 });
90
91 it("keeps a stale guide readable through rewriting and completion", () => {
92 const data = { guide, currentHeadSha: "head", run: null };
93 expect(guideNoticeState({ ...view, data }).kind).toBe("stale");
94 expect(guideNoticeState({ ...view, data, running: true }).kind).toBe("writing");
95 expect(
96 guideNoticeState({ ...view, data: { ...data, guide: { ...guide, headSha: "head" } } }).kind,
97 ).toBe("ready");
98 });
99});
100
101it("distinguishes a failed read from a failed writer", () => {
102 expect(guideNoticeState({ ...view, error: "Connection lost" }).kind).toBe("unavailable");
103});