Skip to content

Commit 018f2c7

Browse files
authored
Merge pull request #51 from bartoval/linkform_tests
increase code coverage
2 parents c1106d9 + 5a12f65 commit 018f2c7

File tree

6 files changed

+416
-148
lines changed

6 files changed

+416
-148
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
4+
import DeploymentNetworkConsoleButton from '../src/console/pages/components/DeploymentNetworkConsoleButton';
5+
6+
const mockUseK8sWatchResource = vi.hoisted(() => vi.fn());
7+
const mockUseMutationImpl = vi.hoisted(() => vi.fn());
8+
const mockRESTApiCreateDeployment = vi.hoisted(() => vi.fn());
9+
const mockRESTApiDeleteDeployment = vi.hoisted(() => vi.fn());
10+
const mockNamespaceManagerGetNamespace = vi.hoisted(() => vi.fn());
11+
12+
vi.mock('@openshift-console/dynamic-plugin-sdk', () => ({
13+
useK8sWatchResource: mockUseK8sWatchResource
14+
}));
15+
16+
vi.mock('../src/console/API/REST.api', () => ({
17+
RESTApi: {
18+
createDeployment: mockRESTApiCreateDeployment,
19+
deleteDeployment: mockRESTApiDeleteDeployment
20+
}
21+
}));
22+
23+
vi.mock('../src/console/config/db', () => ({
24+
NamespaceManager: {
25+
getNamespace: mockNamespaceManagerGetNamespace
26+
}
27+
}));
28+
29+
vi.mock('@tanstack/react-query', () => ({
30+
useMutation: mockUseMutationImpl
31+
}));
32+
33+
const defaultMutationMock = {
34+
mutate: vi.fn(),
35+
isLoading: false,
36+
isError: false,
37+
error: null
38+
};
39+
40+
type ResourceOptions = { name?: string; selector?: { matchLabels?: Record<string, string> } };
41+
42+
const loadedResourceMock = (resourceName: string) => (options: ResourceOptions) => {
43+
if (options.name === resourceName) {
44+
return [{ spec: { host: 'example.com', port: { targetPort: '8080' } } }, false, undefined];
45+
}
46+
if (options.selector?.matchLabels?.['app.kubernetes.io/name'] === 'network-observer') {
47+
return [{ status: { phase: 'Running' } }, false, undefined];
48+
}
49+
50+
return [undefined, false, undefined];
51+
};
52+
53+
const BUTTON_LABELS = {
54+
deploy: 'Deploy the Network Console',
55+
open: 'Open the Network Console',
56+
delete: 'Delete the Network Console'
57+
};
58+
59+
const setupMocks = (namespace = 'test-namespace') => {
60+
vi.clearAllMocks();
61+
mockNamespaceManagerGetNamespace.mockReturnValue(namespace);
62+
};
63+
64+
describe('DeploymentNetworkConsoleButton', () => {
65+
beforeEach(() => setupMocks());
66+
67+
it('renders the deploy button when not loaded', () => {
68+
mockUseK8sWatchResource.mockReturnValue([undefined, false, undefined]);
69+
mockUseMutationImpl.mockReturnValue(defaultMutationMock);
70+
71+
render(<DeploymentNetworkConsoleButton />);
72+
expect(screen.getByText(BUTTON_LABELS.deploy)).toBeInTheDocument();
73+
});
74+
75+
it('renders the open and delete buttons when loaded', () => {
76+
mockUseK8sWatchResource.mockImplementation(loadedResourceMock('network-observer'));
77+
mockUseMutationImpl.mockReturnValue(defaultMutationMock);
78+
79+
render(<DeploymentNetworkConsoleButton />);
80+
expect(screen.getByText(BUTTON_LABELS.open)).toBeInTheDocument();
81+
expect(screen.getByText(BUTTON_LABELS.delete)).toBeInTheDocument();
82+
});
83+
84+
it('calls the create mutation when the deploy button is clicked', () => {
85+
const mutateMock = vi.fn();
86+
mockUseK8sWatchResource.mockReturnValue([undefined, false, undefined]);
87+
mockUseMutationImpl.mockReturnValue({ ...defaultMutationMock, mutate: mutateMock });
88+
89+
render(<DeploymentNetworkConsoleButton />);
90+
fireEvent.click(screen.getByText(BUTTON_LABELS.deploy));
91+
expect(mutateMock).toHaveBeenCalled();
92+
});
93+
94+
it('calls the delete mutation when the delete button is clicked and calls onSuccess', () => {
95+
const mutateMock = vi.fn();
96+
let onSuccessCallback: (() => void) | undefined;
97+
98+
mockUseK8sWatchResource.mockImplementation(loadedResourceMock('network-observer'));
99+
mockUseMutationImpl.mockImplementation(({ onSuccess }) => {
100+
onSuccessCallback = onSuccess;
101+
102+
return { ...defaultMutationMock, mutate: mutateMock };
103+
});
104+
105+
render(<DeploymentNetworkConsoleButton />);
106+
fireEvent.click(screen.getByText(BUTTON_LABELS.delete));
107+
expect(mutateMock).toHaveBeenCalled();
108+
109+
onSuccessCallback?.();
110+
});
111+
112+
it('updates URL when route data changes', () => {
113+
mockUseK8sWatchResource.mockImplementation(loadedResourceMock('network-observer'));
114+
mockUseMutationImpl.mockReturnValue(defaultMutationMock);
115+
116+
render(<DeploymentNetworkConsoleButton />);
117+
expect(screen.getByText(BUTTON_LABELS.open)).toBeInTheDocument();
118+
});
119+
});

__tests__/LinkFormHowTo.spec.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { describe, it, expect, vi } from 'vitest';
3+
4+
import { HowToPage } from '../src/console/pages/components/forms/LinkForm/HowToPage';
5+
6+
vi.mock('../src/console/assets/step1.png', () => ({ default: 'step1.png' }));
7+
vi.mock('../src/console/assets/step2.png', () => ({ default: 'step2.png' }));
8+
vi.mock('../src/console/assets/step3.png', () => ({ default: 'step3.png' }));
9+
vi.mock('../src/console/assets/step4.png', () => ({ default: 'step4.png' }));
10+
11+
vi.mock('../src/console/core/components/InstructionBlock', () => ({
12+
default: ({
13+
title,
14+
description,
15+
link1,
16+
link1Text,
17+
link2,
18+
link2Text
19+
}: {
20+
title: string;
21+
description: string;
22+
link1?: string;
23+
link1Text?: string;
24+
link2?: string;
25+
link2Text?: string;
26+
}) => (
27+
<div data-testid="instruction-block">
28+
<h2>{title}</h2>
29+
<p>{description}</p>
30+
{link1 && <a href={link1}>{link1Text}</a>}
31+
{link2 && <a href={link2}>{link2Text}</a>}
32+
</div>
33+
)
34+
}));
35+
36+
describe('HowToPage', () => {
37+
it('renders all instruction blocks', () => {
38+
render(<HowToPage />);
39+
const blocks = screen.getAllByTestId('instruction-block');
40+
expect(blocks).toHaveLength(4);
41+
});
42+
43+
it('displays correct step titles', () => {
44+
render(<HowToPage />);
45+
46+
expect(screen.getByText('Step 1 - Visit a remote site')).toBeInTheDocument();
47+
expect(screen.getByText('Step 2 - Generate a grant from the remote site')).toBeInTheDocument();
48+
expect(screen.getByText('Step 3 - Download the grant file')).toBeInTheDocument();
49+
expect(screen.getByText('Step 4 - Use the grant to create a link')).toBeInTheDocument();
50+
});
51+
52+
it('displays step descriptions', () => {
53+
render(<HowToPage />);
54+
55+
expect(screen.getByText('Open a new browser window or tab and visit the remote site.')).toBeInTheDocument();
56+
expect(screen.getByText('Generate the grant with the web console or the CLI.')).toBeInTheDocument();
57+
expect(screen.getByText('Download the grant file from the remote site after generating it.')).toBeInTheDocument();
58+
expect(
59+
screen.getByText('Use the grant to create a link from the local site to the remote site.')
60+
).toBeInTheDocument();
61+
});
62+
63+
it('includes documentation links in step 2', () => {
64+
render(<HowToPage />);
65+
66+
const cliLink = screen.getByText('More information CLI');
67+
const tokenLink = screen.getByText('More information on token creation');
68+
69+
expect(cliLink).toHaveAttribute('href', 'https://skupper.io/docs/cli/index.html');
70+
expect(tokenLink).toHaveAttribute('href', 'https://skupper.io/docs/cli/tokens.html');
71+
});
72+
});

__tests__/LinkFormPage.spec.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { describe, it, expect, vi, beforeEach } from 'vitest';
4+
5+
import { FormPage } from '../src/console/pages/components/forms/LinkForm/FormPage';
6+
7+
const mockDispatch = vi.hoisted(() => vi.fn());
8+
9+
vi.mock('../src/console/pages/components/forms/LinkForm/hooks/useLinkForm', () => ({
10+
useLinkForm: () => ({
11+
state: {
12+
name: '',
13+
fileName: '',
14+
cost: '',
15+
file: ''
16+
},
17+
dispatch: mockDispatch
18+
})
19+
}));
20+
21+
describe('FormPage', () => {
22+
beforeEach(() => {
23+
vi.clearAllMocks();
24+
});
25+
26+
it('renders all form fields', () => {
27+
render(<FormPage />);
28+
expect(screen.getByText('Token')).toBeInTheDocument();
29+
expect(screen.getByText('Name')).toBeInTheDocument();
30+
expect(screen.getByText('Cost')).toBeInTheDocument();
31+
expect(screen.getByText('Upload')).toBeInTheDocument();
32+
});
33+
34+
it('handles file upload selection', async () => {
35+
render(<FormPage />);
36+
37+
const file = new File(['content'], 'test.yaml', { type: 'text/yaml' });
38+
const fileUpload = screen.getByTestId('access-token-file');
39+
40+
const fileInputChangeHandler = fileUpload.querySelector('input[type="file"]') as HTMLElement;
41+
if (fileInputChangeHandler) {
42+
await userEvent.upload(fileInputChangeHandler, file);
43+
}
44+
45+
expect(mockDispatch).toHaveBeenCalledWith({
46+
type: 'SET_FILE_NAME',
47+
payload: 'test'
48+
});
49+
});
50+
51+
it('handles name input change', () => {
52+
render(<FormPage />);
53+
const nameInput = screen.getByTestId('simple-form-name-01');
54+
55+
fireEvent.change(nameInput, { target: { value: 'test-name' } });
56+
57+
expect(mockDispatch).toHaveBeenCalledWith({
58+
type: 'SET_NAME',
59+
payload: 'test-name'
60+
});
61+
});
62+
63+
it('handles cost input change', () => {
64+
render(<FormPage />);
65+
const costInput = screen.getByTestId('form-cost');
66+
67+
fireEvent.change(costInput, { target: { value: '100' } });
68+
69+
expect(mockDispatch).toHaveBeenCalledWith({
70+
type: 'SET_COST',
71+
payload: '100'
72+
});
73+
});
74+
75+
it('shows file upload placeholder text', () => {
76+
render(<FormPage />);
77+
expect(screen.getByPlaceholderText('Drag and drop a file or upload one')).toBeInTheDocument();
78+
});
79+
80+
it('validates required fields', () => {
81+
render(<FormPage />);
82+
const nameInput = screen.getByTestId('simple-form-name-01');
83+
const costInput = screen.getByTestId('form-cost');
84+
85+
expect(nameInput).toBeRequired();
86+
expect(costInput).toBeRequired();
87+
});
88+
});

0 commit comments

Comments
 (0)