Skip to content

Commit df46062

Browse files
authored
Implement streaming (#59)
2 parents 36e51ce + 2df2fc0 commit df46062

File tree

4 files changed

+284
-164
lines changed

4 files changed

+284
-164
lines changed
Lines changed: 114 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import React from 'react';
2-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3-
import { PullRequestItem } from './pull-request';
4-
import { vi, describe, it, expect, beforeEach } from 'vitest';
5-
import { PullRequest } from './types';
6-
import useSWR from 'swr';
7-
8-
vi.mock('@/lib/github', async () => {
9-
const actual = await vi.importActual('@/lib/github');
1+
import React from "react";
2+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
3+
import { PullRequestItem } from "./pull-request";
4+
import { vi, describe, it, expect, beforeEach } from "vitest";
5+
import { PullRequest } from "./types";
6+
import useSWR from "swr";
7+
import { fetchBuildStatus } from "@/lib/github";
8+
import { experimental_useObject as useObject } from "ai/react";
9+
import { act } from "react";
10+
11+
vi.mock("@/lib/github", async () => {
12+
const actual = await vi.importActual("@/lib/github");
1013
return {
1114
...(actual as object),
1215
getPullRequestInfo: vi.fn(),
@@ -17,44 +20,56 @@ vi.mock('@/lib/github', async () => {
1720
};
1821
});
1922

20-
vi.mock('@/hooks/use-toast', () => ({
23+
vi.mock("@/hooks/use-toast", () => ({
2124
useToast: vi.fn(() => ({
2225
toast: vi.fn(),
2326
})),
2427
}));
2528

26-
vi.mock('next/link', () => ({
27-
default: ({ children, href }: { children: React.ReactNode; href: string }) => (
28-
<a href={href}>{children}</a>
29-
),
29+
vi.mock("next/link", () => ({
30+
default: ({
31+
children,
32+
href,
33+
}: {
34+
children: React.ReactNode;
35+
href: string;
36+
}) => <a href={href}>{children}</a>,
3037
}));
3138

32-
vi.mock('react-diff-viewer', () => ({
39+
vi.mock("react-diff-viewer", () => ({
3340
default: () => <div data-testid="react-diff-viewer">Mocked Diff Viewer</div>,
3441
}));
3542

36-
vi.mock('swr', () => ({
43+
vi.mock("swr", () => ({
3744
default: vi.fn(),
3845
}));
3946

40-
vi.mock('./log-view', () => ({
47+
vi.mock("./log-view", () => ({
4148
LogView: () => <div data-testid="log-view">Mocked Log View</div>,
4249
}));
4350

44-
describe('PullRequestItem', () => {
51+
vi.mock("./log-view", () => ({
52+
LogView: () => <div data-testid="log-view">Mocked Log View</div>,
53+
}));
54+
55+
vi.mock("ai/react", () => ({
56+
experimental_useObject: vi.fn(),
57+
}));
58+
59+
describe("PullRequestItem", () => {
4560
const mockPullRequest: PullRequest = {
4661
id: 1,
47-
title: 'Test PR',
62+
title: "Test PR",
4863
number: 123,
49-
buildStatus: 'success',
64+
buildStatus: "success",
5065
isDraft: false,
51-
branchName: 'feature-branch',
66+
branchName: "feature-branch",
5267
repository: {
5368
id: 1,
54-
name: 'test-repo',
55-
full_name: 'owner/test-repo',
69+
name: "test-repo",
70+
full_name: "owner/test-repo",
5671
owner: {
57-
login: 'owner',
72+
login: "owner",
5873
},
5974
},
6075
};
@@ -69,17 +84,22 @@ describe('PullRequestItem', () => {
6984
isValidating: false,
7085
isLoading: false,
7186
});
87+
vi.mocked(useObject).mockReturnValue({
88+
object: null,
89+
submit: vi.fn(),
90+
isLoading: false,
91+
});
7292
});
7393

74-
it('renders the pull request information correctly', () => {
94+
it("renders the pull request information correctly", () => {
7595
render(<PullRequestItem pullRequest={mockPullRequest} />);
76-
expect(screen.getByText('Test PR')).toBeInTheDocument();
77-
expect(screen.getByText('#123')).toBeInTheDocument();
78-
expect(screen.getByText('Build: success')).toBeInTheDocument();
96+
expect(screen.getByText("Test PR")).toBeInTheDocument();
97+
expect(screen.getByText("#123")).toBeInTheDocument();
98+
expect(screen.getByText("Build: success")).toBeInTheDocument();
7999
});
80100

81-
it('displays running build status', () => {
82-
const runningPR = { ...mockPullRequest, buildStatus: 'running' };
101+
it("displays running build status", () => {
102+
const runningPR = { ...mockPullRequest, buildStatus: "running" };
83103
vi.mocked(useSWR).mockReturnValue({
84104
data: runningPR,
85105
mutate: vi.fn(),
@@ -88,12 +108,12 @@ describe('PullRequestItem', () => {
88108
isLoading: false,
89109
});
90110
render(<PullRequestItem pullRequest={runningPR} />);
91-
expect(screen.getByText('Build: Running')).toBeInTheDocument();
92-
expect(screen.getByText('Running...')).toBeInTheDocument();
111+
expect(screen.getByText("Build: Running")).toBeInTheDocument();
112+
expect(screen.getByText("Running...")).toBeInTheDocument();
93113
});
94114

95-
it('disables buttons when build is running', () => {
96-
const runningPR = { ...mockPullRequest, buildStatus: 'running' };
115+
it("disables buttons when build is running", () => {
116+
const runningPR = { ...mockPullRequest, buildStatus: "running" };
97117
vi.mocked(useSWR).mockReturnValue({
98118
data: runningPR,
99119
mutate: vi.fn(),
@@ -102,14 +122,16 @@ describe('PullRequestItem', () => {
102122
isLoading: false,
103123
});
104124
render(<PullRequestItem pullRequest={runningPR} />);
105-
expect(screen.getByText('Running...')).toBeDisabled();
125+
expect(screen.getByText("Running...")).toBeDisabled();
106126
});
107127

108-
it('updates build status periodically', async () => {
128+
it("updates build status periodically", async () => {
109129
const mutate = vi.fn();
110130
const fetchBuildStatusMock = vi.fn().mockResolvedValue(mockPullRequest);
131+
vi.mocked(fetchBuildStatus).mockImplementation(fetchBuildStatusMock);
132+
111133
vi.mocked(useSWR).mockImplementation((key, fetcher, options) => {
112-
if (typeof fetcher === 'function') {
134+
if (typeof fetcher === "function") {
113135
fetcher();
114136
}
115137
return {
@@ -122,7 +144,7 @@ describe('PullRequestItem', () => {
122144
});
123145

124146
render(<PullRequestItem pullRequest={mockPullRequest} />);
125-
147+
126148
await waitFor(() => {
127149
expect(useSWR).toHaveBeenCalledWith(
128150
`pullRequest-${mockPullRequest.id}`,
@@ -134,20 +156,37 @@ describe('PullRequestItem', () => {
134156
})
135157
);
136158
});
159+
160+
// Verify that fetchBuildStatus is called with the correct parameters
161+
162+
expect(fetchBuildStatusMock).toHaveBeenCalledWith(
163+
mockPullRequest.repository.owner.login,
164+
mockPullRequest.repository.name,
165+
mockPullRequest.number
166+
);
137167
});
138168

139-
it('triggers revalidation after committing changes', async () => {
140-
const { getPullRequestInfo, commitChangesToPullRequest } = await import('@/lib/github');
169+
it("triggers revalidation after committing changes", async () => {
170+
const { getPullRequestInfo, commitChangesToPullRequest } = await import(
171+
"@/lib/github"
172+
);
141173
vi.mocked(getPullRequestInfo).mockResolvedValue({
142-
diff: 'mock diff',
143-
testFiles: [{ name: 'existing_test.ts', content: 'existing content' }],
174+
diff: "mock diff",
175+
testFiles: [{ name: "existing_test.ts", content: "existing content" }],
144176
});
145-
vi.mocked(commitChangesToPullRequest).mockResolvedValue('https://github.com/commit/123');
177+
vi.mocked(commitChangesToPullRequest).mockResolvedValue(
178+
"https://github.com/commit/123"
179+
);
146180

147-
vi.mocked(global.fetch).mockResolvedValue({
148-
ok: true,
149-
json: () => Promise.resolve([{ name: 'generated_test.ts', content: 'generated content' }]),
150-
} as Response);
181+
const mockSubmit = vi.fn();
182+
vi.mocked(useObject).mockReturnValue({
183+
object: null,
184+
submit: mockSubmit,
185+
isLoading: false,
186+
setInput: vi.fn(),
187+
error: null,
188+
stop: vi.fn(),
189+
});
151190

152191
const mutate = vi.fn();
153192
vi.mocked(useSWR).mockReturnValue({
@@ -159,14 +198,28 @@ describe('PullRequestItem', () => {
159198
});
160199

161200
render(<PullRequestItem pullRequest={mockPullRequest} />);
162-
const writeTestsButton = screen.getByText('Write new tests');
201+
202+
const writeTestsButton = screen.getByText("Write new tests");
163203
fireEvent.click(writeTestsButton);
164204

165205
await waitFor(() => {
166-
expect(screen.getByText('generated_test.ts')).toBeInTheDocument();
206+
expect(screen.getByText("Analyzing PR diff...")).toBeInTheDocument();
207+
});
208+
209+
await act(async () => {
210+
const { onFinish } = vi.mocked(useObject).mock.calls[0][0];
211+
await onFinish({
212+
object: {
213+
tests: [{ name: "generated_test.ts", content: "generated content" }],
214+
},
215+
});
216+
});
217+
218+
await waitFor(() => {
219+
expect(screen.getByText("generated_test.ts")).toBeInTheDocument();
167220
});
168221

169-
const commitButton = screen.getByText('Commit changes');
222+
const commitButton = screen.getByText("Commit changes");
170223
fireEvent.click(commitButton);
171224

172225
await waitFor(() => {
@@ -175,36 +228,36 @@ describe('PullRequestItem', () => {
175228
});
176229
});
177230

178-
it('shows and hides logs when toggle is clicked', async () => {
231+
it("shows and hides logs when toggle is clicked", async () => {
179232
vi.mocked(useSWR).mockReturnValue({
180-
data: { ...mockPullRequest, buildStatus: 'success' },
233+
data: { ...mockPullRequest, buildStatus: "success" },
181234
mutate: vi.fn(),
182235
error: undefined,
183236
isValidating: false,
184237
isLoading: false,
185238
});
186239

187-
const { getLatestRunId } = await import('@/lib/github');
188-
vi.mocked(getLatestRunId).mockResolvedValue('123');
240+
const { getLatestRunId } = await import("@/lib/github");
241+
vi.mocked(getLatestRunId).mockResolvedValue("123");
189242

190243
render(<PullRequestItem pullRequest={mockPullRequest} />);
191244

192245
await waitFor(() => {
193-
expect(screen.getByText('Show Logs')).toBeInTheDocument();
246+
expect(screen.getByText("Show Logs")).toBeInTheDocument();
194247
});
195248

196-
fireEvent.click(screen.getByText('Show Logs'));
249+
fireEvent.click(screen.getByText("Show Logs"));
197250

198251
await waitFor(() => {
199-
expect(screen.getByTestId('log-view')).toBeInTheDocument();
200-
expect(screen.getByText('Hide Logs')).toBeInTheDocument();
252+
expect(screen.getByTestId("log-view")).toBeInTheDocument();
253+
expect(screen.getByText("Hide Logs")).toBeInTheDocument();
201254
});
202255

203-
fireEvent.click(screen.getByText('Hide Logs'));
256+
fireEvent.click(screen.getByText("Hide Logs"));
204257

205258
await waitFor(() => {
206-
expect(screen.queryByTestId('log-view')).not.toBeInTheDocument();
207-
expect(screen.getByText('Show Logs')).toBeInTheDocument();
259+
expect(screen.queryByTestId("log-view")).not.toBeInTheDocument();
260+
expect(screen.getByText("Show Logs")).toBeInTheDocument();
208261
});
209262
});
210263
});

0 commit comments

Comments
 (0)