Skip to content
This repository was archived by the owner on Jun 26, 2026. It is now read-only.

Commit a23cd8d

Browse files
authored
Add missing unit tests for FunctionEditPage (#42)
* SRVOCF-844: Add missing unit tests for FunctionEditPage * SRVOCF-844: fix lint
1 parent 70cb20f commit a23cd8d

1 file changed

Lines changed: 244 additions & 3 deletions

File tree

‎src/pages/function-edit/FunctionEditPage.test.tsx‎

Lines changed: 244 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,26 @@ let mockOnChange: ((value: string) => void) | undefined;
1616
vi.mock('@openshift-console/dynamic-plugin-sdk', () => ({
1717
DocumentTitle: ({ children }: { children: string }) => children,
1818
ListPageHeader: ({ title }: { title: string }) => title,
19-
CodeEditor: ({ onChange }: { onChange?: (value: string) => void }) => {
19+
CodeEditor: ({
20+
onChange,
21+
value,
22+
language,
23+
showEditor,
24+
emptyState,
25+
}: {
26+
onChange?: (value: string) => void;
27+
value?: string;
28+
language?: string;
29+
showEditor?: boolean;
30+
emptyState?: unknown;
31+
}) => {
2032
mockOnChange = onChange;
21-
return 'CodeEditor';
33+
if (!showEditor && emptyState) return emptyState;
34+
return (
35+
<div data-testid="code-editor" data-language={language ?? ''}>
36+
{value ?? ''}
37+
</div>
38+
);
2239
},
2340
}));
2441

@@ -147,6 +164,230 @@ describe('FunctionEditPage', () => {
147164
expect(screen.queryByText('Unsaved changes')).not.toBeInTheDocument();
148165
});
149166

167+
it('shows selected file content in editor when tree item is clicked', async () => {
168+
setupFetchHandlers();
169+
170+
renderEditPage('my-func');
171+
172+
await waitFor(() => {
173+
expect(screen.getByText('func.yaml')).toBeInTheDocument();
174+
});
175+
176+
await userEvent.setup().click(screen.getByText('func.yaml'));
177+
178+
await waitFor(() => {
179+
expect(screen.getByTestId('code-editor')).toHaveTextContent('name: my-func');
180+
});
181+
});
182+
183+
it('marks hasChanges true after editing a file', async () => {
184+
setupFetchHandlers();
185+
186+
renderEditPage('my-func');
187+
188+
await waitFor(() => {
189+
expect(screen.getByText('index.js')).toBeInTheDocument();
190+
});
191+
192+
expect(screen.getByRole('button', { name: /Save & Deploy/ })).toBeDisabled();
193+
194+
act(() => mockOnChange?.('const x = 1;'));
195+
196+
expect(screen.getByRole('button', { name: /Save & Deploy/ })).toBeEnabled();
197+
});
198+
199+
it('resets hasChanges after save', async () => {
200+
setupFetchHandlers();
201+
setupPushHandlers();
202+
203+
renderEditPage('my-func');
204+
205+
await waitFor(() => {
206+
expect(screen.getByText('index.js')).toBeInTheDocument();
207+
});
208+
209+
act(() => mockOnChange?.('const x = 1;'));
210+
expect(screen.getByRole('button', { name: /Save & Deploy/ })).toBeEnabled();
211+
212+
await userEvent.setup().click(screen.getByRole('button', { name: /Save & Deploy/ }));
213+
214+
await waitFor(() => {
215+
expect(screen.getByRole('button', { name: /Save & Deploy/ })).toBeDisabled();
216+
});
217+
});
218+
219+
it('persists edited content when switching files and back', async () => {
220+
setupFetchHandlers();
221+
222+
renderEditPage('my-func');
223+
224+
await waitFor(() => {
225+
expect(screen.getByText('index.js')).toBeInTheDocument();
226+
});
227+
228+
act(() => mockOnChange?.('edited module'));
229+
230+
const user = userEvent.setup();
231+
await user.click(screen.getByText('func.yaml'));
232+
233+
await waitFor(() => {
234+
expect(screen.getByTestId('code-editor')).toHaveTextContent('name: my-func');
235+
});
236+
237+
// After editing, dirty indicator appends ● to the filename
238+
await user.click(screen.getByText(/^index\.js/));
239+
240+
await waitFor(() => {
241+
expect(screen.getByTestId('code-editor')).toHaveTextContent('edited module');
242+
});
243+
});
244+
245+
it('updates editor language when selecting a different file type', async () => {
246+
setupFetchHandlers();
247+
248+
renderEditPage('my-func');
249+
250+
await waitFor(() => {
251+
expect(screen.getByText('index.js')).toBeInTheDocument();
252+
});
253+
254+
expect(screen.getByTestId('code-editor')).toHaveAttribute('data-language', 'javascript');
255+
256+
await userEvent.setup().click(screen.getByText('func.yaml'));
257+
258+
await waitFor(() => {
259+
expect(screen.getByTestId('code-editor')).toHaveAttribute('data-language', 'yaml');
260+
});
261+
});
262+
263+
it('calls GitHub push API when saving edited files', async () => {
264+
setupFetchHandlers();
265+
setupPushHandlers();
266+
267+
const createTree = vi.fn();
268+
server.use(
269+
http.post(`${GITHUB_API}/repos/twoGiants/my-func/git/trees`, async ({ request }) => {
270+
createTree(await request.json());
271+
return HttpResponse.json({ sha: 'tree-sha' });
272+
}),
273+
);
274+
275+
renderEditPage('my-func');
276+
277+
await waitFor(() => {
278+
expect(screen.getByText('index.js')).toBeInTheDocument();
279+
});
280+
281+
act(() => mockOnChange?.('edited'));
282+
283+
await userEvent.setup().click(screen.getByRole('button', { name: /Save & Deploy/ }));
284+
285+
await waitFor(() => {
286+
expect(createTree).toHaveBeenCalled();
287+
});
288+
});
289+
290+
it('shows danger alert when save fails', async () => {
291+
setupFetchHandlers();
292+
setupPushHandlers();
293+
server.use(
294+
http.post(`${GITHUB_API}/repos/twoGiants/my-func/git/blobs`, () =>
295+
HttpResponse.json({ message: 'Server Error' }, { status: 500 }),
296+
),
297+
);
298+
299+
renderEditPage('my-func');
300+
301+
await waitFor(() => {
302+
expect(screen.getByText('index.js')).toBeInTheDocument();
303+
});
304+
305+
act(() => mockOnChange?.('edited'));
306+
307+
await userEvent.setup().click(screen.getByRole('button', { name: /Save & Deploy/ }));
308+
309+
await waitFor(() => {
310+
expect(screen.getByText('Server Error')).toBeInTheDocument();
311+
});
312+
});
313+
314+
it('disables save button while saving is in progress', async () => {
315+
setupFetchHandlers();
316+
setupPushHandlers();
317+
server.use(
318+
http.post(`${GITHUB_API}/repos/twoGiants/my-func/git/blobs`, async () => {
319+
await delay('infinite');
320+
return HttpResponse.json({ sha: 'blob-sha' });
321+
}),
322+
);
323+
324+
renderEditPage('my-func');
325+
326+
await waitFor(() => {
327+
expect(screen.getByText('index.js')).toBeInTheDocument();
328+
});
329+
330+
act(() => mockOnChange?.('edited'));
331+
332+
await userEvent.setup().click(screen.getByRole('button', { name: /Save & Deploy/ }));
333+
334+
await waitFor(() => {
335+
expect(screen.getByRole('button', { name: /Save & Deploy/ })).toBeDisabled();
336+
});
337+
});
338+
339+
it('clears error alert when next save succeeds', async () => {
340+
setupFetchHandlers();
341+
setupPushHandlers();
342+
343+
server.use(
344+
http.post(`${GITHUB_API}/repos/twoGiants/my-func/git/blobs`, () =>
345+
HttpResponse.json({ message: 'Server Error' }, { status: 500 }),
346+
),
347+
);
348+
349+
renderEditPage('my-func');
350+
351+
await waitFor(() => {
352+
expect(screen.getByText('index.js')).toBeInTheDocument();
353+
});
354+
355+
act(() => mockOnChange?.('edited'));
356+
357+
const user = userEvent.setup();
358+
await user.click(screen.getByRole('button', { name: /Save & Deploy/ }));
359+
360+
await waitFor(() => {
361+
expect(screen.getByText('Server Error')).toBeInTheDocument();
362+
});
363+
364+
server.use(
365+
http.post(`${GITHUB_API}/repos/twoGiants/my-func/git/blobs`, () =>
366+
HttpResponse.json({ sha: 'blob-sha' }),
367+
),
368+
);
369+
370+
act(() => mockOnChange?.('edited again'));
371+
await user.click(screen.getByRole('button', { name: /Save & Deploy/ }));
372+
373+
await waitFor(() => {
374+
expect(screen.getByText('Pushed to GitHub. Deployment running...')).toBeInTheDocument();
375+
});
376+
});
377+
378+
it('shows empty state placeholder when no file is selected', async () => {
379+
renderEditPage('nonexistent');
380+
381+
await waitFor(() => {
382+
expect(screen.getByText('No files')).toBeInTheDocument();
383+
});
384+
385+
expect(screen.getByText('Start editing')).toBeInTheDocument();
386+
expect(
387+
screen.getByText('Select a file from the tree view to start editing.'),
388+
).toBeInTheDocument();
389+
});
390+
150391
it('shows success message after save and hides it after 2 seconds', async () => {
151392
vi.useFakeTimers({ shouldAdvanceTime: true });
152393
setupFetchHandlers();
@@ -182,7 +423,7 @@ function setupPushHandlers() {
182423
http.get(`${GITHUB_API}/repos/twoGiants/my-func/git/ref/:ref+`, () =>
183424
HttpResponse.json({ object: { sha: 'head-sha' } }),
184425
),
185-
http.get(`${GITHUB_API}/repos/twoGiants/my-func/git/commits/head-sha`, () =>
426+
http.get(`${GITHUB_API}/repos/twoGiants/my-func/git/commits/:sha`, () =>
186427
HttpResponse.json({ tree: { sha: 'parent-tree-sha' } }),
187428
),
188429
http.post(`${GITHUB_API}/repos/twoGiants/my-func/git/blobs`, () =>

0 commit comments

Comments
 (0)