Skip to content

Commit fcd52f9

Browse files
committed
add SourceMenu unit tests
Signed-off-by: Colorado, Camilo <[email protected]>
1 parent 49d8b62 commit fcd52f9

File tree

6 files changed

+171
-29
lines changed

6 files changed

+171
-29
lines changed

application/ui/src/features/inspect/toolbar/sources/add-source/add-source.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('add-source', () => {
3030

3131
it('calls connectToPipelineMutation after successful submit', async () => {
3232
const mockOnSaved = vi.fn();
33-
const mockSourceMutation = vi.fn().mockResolvedValue(newConfig);
33+
const mockSourceMutation = vi.fn().mockResolvedValue(newConfig.id);
3434
const mockConnectToPipeline = vi.fn().mockResolvedValue(undefined);
3535

3636
vi.mocked(useConnectSourceToPipeline).mockReturnValue(mockConnectToPipeline);
@@ -58,7 +58,7 @@ describe('add-source', () => {
5858

5959
await waitFor(() => {
6060
expect(mockSourceMutation).toHaveBeenCalledWith(expect.objectContaining({ ...newConfig, id: '' }));
61-
expect(mockConnectToPipeline).toHaveBeenCalledWith(newConfig);
61+
expect(mockConnectToPipeline).toHaveBeenCalledWith(newConfig.id);
6262
expect(mockOnSaved).toHaveBeenCalled();
6363
});
6464
});

application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { SourceConfig } from '../../util';
22

33
import classes from './settings-list.module.scss';
44

5-
interface SettingsListPops {
5+
interface SettingsListProps {
66
source: SourceConfig;
77
}
88

9-
export const SettingsList = ({ source }: SettingsListPops) => {
9+
export const SettingsList = ({ source }: SettingsListProps) => {
1010
if (source.source_type === 'images_folder') {
1111
return (
1212
<ul className={classes.list}>

application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.component.tsx

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useProjectIdentifier } from '@geti-inspect/hooks';
33
import { MoreMenu } from '@geti/ui/icons';
44
import { ActionButton, Item, Key, Menu, MenuTrigger, toast } from 'packages/ui';
55

6-
interface SourceMenuProps {
6+
export interface SourceMenuProps {
77
id: string;
88
name: string;
99
isConnected: boolean;
@@ -23,9 +23,7 @@ export const SourceMenu = ({ id, name, isConnected, onEdit }: SourceMenuProps) =
2323
});
2424

2525
const removeSource = $api.useMutation('delete', '/api/sources/{source_id}', {
26-
meta: {
27-
invalidates: [['get', '/api/sources']],
28-
},
26+
meta: { invalidates: [['get', '/api/sources']] },
2927
});
3028

3129
const handleOnAction = (option: Key) => {
@@ -43,36 +41,50 @@ export const SourceMenu = ({ id, name, isConnected, onEdit }: SourceMenuProps) =
4341
};
4442

4543
const handleConnect = async () => {
46-
await updatePipeline.mutateAsync({
47-
params: { path: { project_id: projectId } },
48-
body: { source_id: id },
49-
});
50-
51-
toast({
52-
type: 'success',
53-
message: `Successfully connected to "${name}"`,
54-
});
55-
};
56-
57-
const handleDelete = async () => {
58-
if (isConnected) {
44+
try {
5945
await updatePipeline.mutateAsync({
6046
params: { path: { project_id: projectId } },
61-
body: { source_id: null },
47+
body: { source_id: id },
48+
});
49+
50+
toast({
51+
type: 'success',
52+
message: `Successfully connected to "${name}"`,
53+
});
54+
} catch (_error) {
55+
toast({
56+
type: 'error',
57+
message: `Failed to connect to "${name}".`,
6258
});
6359
}
60+
};
61+
62+
const handleDelete = async () => {
63+
try {
64+
if (isConnected) {
65+
await updatePipeline.mutateAsync({
66+
params: { path: { project_id: projectId } },
67+
body: { source_id: null },
68+
});
69+
}
6470

65-
await removeSource.mutateAsync({ params: { path: { source_id: id } } });
71+
await removeSource.mutateAsync({ params: { path: { source_id: id } } });
6672

67-
toast({
68-
type: 'success',
69-
message: `${name} has been removed successfully!`,
70-
});
73+
toast({
74+
type: 'success',
75+
message: `${name} has been removed successfully!`,
76+
});
77+
} catch (_error) {
78+
toast({
79+
type: 'error',
80+
message: `Failed to remove "${name}".`,
81+
});
82+
}
7183
};
7284

7385
return (
7486
<MenuTrigger>
75-
<ActionButton isQuiet>
87+
<ActionButton isQuiet aria-label='source menu'>
7688
<MoreMenu />
7789
</ActionButton>
7890
<Menu onAction={handleOnAction}>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { HttpResponse } from 'msw';
4+
import { http } from 'src/api/utils';
5+
import { server } from 'src/msw-node-setup';
6+
import { TestProviders } from 'src/providers';
7+
8+
import { SourceMenu, SourceMenuProps } from './source-menu.component';
9+
10+
vi.mock('@geti-inspect/hooks', () => ({ useProjectIdentifier: () => ({ projectId: '123' }) }));
11+
12+
describe('SourceMenu', () => {
13+
const renderApp = ({
14+
id = 'id-test',
15+
name = 'name test',
16+
isConnected = false,
17+
onEdit = vi.fn(),
18+
}: Partial<SourceMenuProps>) => {
19+
render(
20+
<TestProviders>
21+
<SourceMenu id={id} name={name} isConnected={isConnected} onEdit={onEdit} />
22+
</TestProviders>
23+
);
24+
};
25+
26+
it('edit', async () => {
27+
const mockedOnEdit = vi.fn();
28+
29+
renderApp({ onEdit: mockedOnEdit });
30+
31+
await userEvent.click(screen.getByRole('button', { name: /source menu/i }));
32+
await userEvent.click(screen.getByRole('menuitem', { name: /Edit/i }));
33+
34+
expect(mockedOnEdit).toHaveBeenCalled();
35+
});
36+
37+
describe('remove', () => {
38+
const name = 'test-name';
39+
const configRequests = (status = 200) => {
40+
const pipelinePatchSpy = vi.fn();
41+
42+
server.use(
43+
http.patch('/api/projects/{project_id}/pipeline', () => {
44+
pipelinePatchSpy();
45+
return HttpResponse.json({}, { status });
46+
}),
47+
http.delete('/api/sources/{source_id}', () => HttpResponse.json(null, { status: 204 }))
48+
);
49+
50+
return pipelinePatchSpy;
51+
};
52+
53+
it('success', async () => {
54+
const pipelinePatchSpy = configRequests();
55+
56+
renderApp({ name, isConnected: false });
57+
58+
await userEvent.click(screen.getByRole('button', { name: /source menu/i }));
59+
await userEvent.click(screen.getByRole('menuitem', { name: /Remove/i }));
60+
61+
await expect(await screen.findByLabelText('toast')).toHaveTextContent(
62+
`${name} has been removed successfully!`
63+
);
64+
expect(pipelinePatchSpy).not.toHaveBeenCalled();
65+
});
66+
67+
it('success with isConnected true - calls pipeline patch', async () => {
68+
const pipelinePatchSpy = configRequests();
69+
70+
renderApp({ name, isConnected: true });
71+
72+
await userEvent.click(screen.getByRole('button', { name: /source menu/i }));
73+
await userEvent.click(screen.getByRole('menuitem', { name: /Remove/i }));
74+
75+
await expect(await screen.findByLabelText('toast')).toHaveTextContent(
76+
`${name} has been removed successfully!`
77+
);
78+
79+
expect(pipelinePatchSpy).toHaveBeenCalled();
80+
});
81+
82+
it('error', async () => {
83+
configRequests(500);
84+
85+
renderApp({ name, isConnected: true });
86+
87+
await userEvent.click(screen.getByRole('button', { name: /source menu/i }));
88+
await userEvent.click(screen.getByRole('menuitem', { name: /Remove/i }));
89+
90+
expect(await screen.findByLabelText('toast')).toHaveTextContent(`Failed to remove "${name}".`);
91+
});
92+
});
93+
94+
describe('connect', () => {
95+
const name = 'test-name';
96+
const configRequests = (status = 200) => {
97+
server.use(http.patch('/api/projects/{project_id}/pipeline', () => HttpResponse.json({}, { status })));
98+
};
99+
100+
it('success', async () => {
101+
configRequests();
102+
103+
renderApp({ name });
104+
105+
await userEvent.click(screen.getByRole('button', { name: /source menu/i }));
106+
await userEvent.click(screen.getByRole('menuitem', { name: /Connect/i }));
107+
108+
await expect(await screen.findByLabelText('toast')).toHaveTextContent(
109+
`Successfully connected to "${name}"`
110+
);
111+
});
112+
113+
it('error', async () => {
114+
configRequests(500);
115+
116+
renderApp({ name });
117+
118+
await userEvent.click(screen.getByRole('button', { name: /source menu/i }));
119+
await userEvent.click(screen.getByRole('menuitem', { name: /Connect/i }));
120+
121+
await expect(await screen.findByLabelText('toast')).toHaveTextContent(`Failed to connect to "${name}"`);
122+
});
123+
});
124+
});

application/ui/src/features/inspect/train-model/train-model-dialog.component.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export const TrainModelDialog = ({ close }: { close: () => void }) => {
2424
}
2525

2626
await startTrainingMutation.mutateAsync({
27-
body: { project_id: projectId, model_name: selectedModel },
27+
body: {
28+
project_id: projectId,
29+
model_name: selectedModel,
30+
device: null,
31+
},
2832
});
2933

3034
close();

application/ui/src/providers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ReactNode } from 'react';
22

3+
import { Toast } from '@geti/ui';
34
import { ThemeProvider } from '@geti/ui/theme';
45
import { QueryClientProvider } from '@tanstack/react-query';
56
import { MemoryRouterProps, RouterProvider } from 'react-router';
@@ -31,6 +32,7 @@ export const TestProviders = ({ children, routerProps }: { children: ReactNode;
3132
<Router {...routerProps}>
3233
<WebRTCConnectionProvider>{children}</WebRTCConnectionProvider>
3334
</Router>
35+
<Toast />
3436
</ThemeProvider>
3537
</QueryClientProvider>
3638
);

0 commit comments

Comments
 (0)