Skip to content

Commit f1beac9

Browse files
committed
Extend RTL Test Coverage for Components Migrated from Enzyme
1 parent 62dd642 commit f1beac9

11 files changed

Lines changed: 934 additions & 409 deletions

File tree

Lines changed: 181 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,104 @@
1-
import type { ComponentProps } from 'react';
2-
import { screen, fireEvent } from '@testing-library/react';
3-
import type { FormikProps, FormikValues } from 'formik';
4-
import { formikFormProps } from '@console/shared/src/test-utils/formik-props-utils';
1+
import { screen, waitFor } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { Formik } from 'formik';
4+
import * as yup from 'yup';
5+
import { limitsValidationSchema } from '@console/dev-console/src/components/import/validation-schema';
6+
import type { K8sResourceKind } from '@console/internal/module/k8s';
57
import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils';
8+
import { getLimitsDataFromResource } from '@console/shared/src/utils/resource-utils';
9+
import { t } from '../../../../../../../__mocks__/i18next';
610
import ResourceLimitsModal from '../ResourceLimitsModal';
711

8-
jest.mock('@console/dev-console/src/components/import/advanced/ResourceLimitSection', () => ({
9-
default: () => null,
10-
}));
12+
jest.mock('@patternfly/react-topology', () => ({}));
1113

12-
type ResourceLimitsModalProps = ComponentProps<typeof ResourceLimitsModal>;
14+
const emptyLimits = {
15+
cpu: {
16+
request: '',
17+
requestUnit: '',
18+
defaultRequestUnit: '',
19+
limit: '',
20+
limitUnit: '',
21+
defaultLimitUnit: '',
22+
},
23+
memory: {
24+
request: '',
25+
requestUnit: 'Mi',
26+
defaultRequestUnit: 'Mi',
27+
limit: '',
28+
limitUnit: 'Mi',
29+
defaultLimitUnit: 'Mi',
30+
},
31+
};
1332

14-
describe('ResourceLimitsModal Form', () => {
15-
let formProps: ResourceLimitsModalProps;
16-
17-
type Props = FormikProps<FormikValues> & ResourceLimitsModalProps;
33+
const resourceLimitsSchema = yup.object().shape({
34+
limits: limitsValidationSchema(t),
35+
});
1836

19-
beforeEach(() => {
20-
jest.clearAllMocks();
21-
formProps = {
22-
...formikFormProps,
23-
isSubmitting: false,
24-
cancel: jest.fn(),
25-
resource: {
26-
apiVersion: 'apps/v1',
27-
kind: 'Deployment',
37+
const baseDeployment = (): K8sResourceKind =>
38+
({
39+
apiVersion: 'apps/v1',
40+
kind: 'Deployment',
41+
metadata: {
42+
name: 'xyz-deployment',
43+
},
44+
spec: {
45+
selector: {
46+
matchLabels: {
47+
app: 'hello-openshift',
48+
},
49+
},
50+
replicas: 1,
51+
template: {
2852
metadata: {
29-
name: 'xyz-deployment',
53+
labels: {
54+
app: 'hello-openshift',
55+
},
3056
},
3157
spec: {
32-
selector: {
33-
matchLabels: {
34-
app: 'hello-openshift',
35-
},
36-
},
37-
replicas: 1,
38-
template: {
39-
metadata: {
40-
labels: {
41-
app: 'hello-openshift',
42-
},
43-
},
44-
spec: {
45-
containers: [
58+
containers: [
59+
{
60+
name: 'hello-openshift',
61+
image: 'openshift/hello-openshift',
62+
ports: [
4663
{
47-
name: 'hello-openshift',
48-
image: 'openshift/hello-openshift',
49-
ports: [
50-
{
51-
containerPort: 8080,
52-
},
53-
],
64+
containerPort: 8080,
5465
},
5566
],
5667
},
57-
},
68+
],
5869
},
5970
},
60-
} as Props;
71+
},
72+
} as K8sResourceKind);
73+
74+
describe('ResourceLimitsModal Form', () => {
75+
const limitsFormValues = {
76+
limits: emptyLimits,
77+
container: 'hello-openshift',
78+
};
79+
80+
const renderModalWithFormikContext = (options?: { onSubmit?: jest.Mock; cancel?: jest.Mock }) => {
81+
const onSubmit = options?.onSubmit ?? jest.fn();
82+
const cancel = options?.cancel ?? jest.fn();
83+
return {
84+
onSubmit,
85+
cancel,
86+
...renderWithProviders(
87+
<Formik initialValues={limitsFormValues} onSubmit={onSubmit}>
88+
{(formikProps) => (
89+
<ResourceLimitsModal {...formikProps} cancel={cancel} isSubmitting={false} />
90+
)}
91+
</Formik>,
92+
),
93+
};
94+
};
95+
96+
beforeEach(() => {
97+
jest.clearAllMocks();
6198
});
6299

63100
it('renders the modal with the correct title and initial elements', () => {
64-
renderWithProviders(<ResourceLimitsModal {...formProps} />);
101+
renderModalWithFormikContext();
65102

66103
expect(screen.getByText('Edit resource limits')).toBeVisible();
67104
expect(screen.getByRole('form')).toBeVisible();
@@ -70,16 +107,107 @@ describe('ResourceLimitsModal Form', () => {
70107
});
71108

72109
it('calls the cancel function when the Cancel button is clicked', async () => {
73-
renderWithProviders(<ResourceLimitsModal {...formProps} />);
110+
const user = userEvent.setup();
111+
const cancel = jest.fn();
112+
renderModalWithFormikContext({ cancel });
74113

75-
await fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
76-
expect(formProps.cancel).toHaveBeenCalledTimes(1);
114+
await user.click(screen.getByRole('button', { name: 'Cancel' }));
115+
expect(cancel).toHaveBeenCalledTimes(1);
77116
});
78117

79-
it('calls the handleSubmit function when the form is submitted', async () => {
80-
renderWithProviders(<ResourceLimitsModal {...formProps} />);
118+
it('submits the form when Save is clicked', async () => {
119+
const user = userEvent.setup();
120+
const onSubmit = jest.fn();
121+
renderModalWithFormikContext({ onSubmit });
122+
123+
await user.click(screen.getByRole('button', { name: 'Save' }));
124+
expect(onSubmit).toHaveBeenCalledTimes(1);
125+
});
126+
});
127+
128+
describe('ResourceLimitsModal with validation (resource limits schema)', () => {
129+
const renderModalWithFormik = (resource: K8sResourceKind) => {
130+
const initialValues = {
131+
limits: getLimitsDataFromResource(resource),
132+
container: resource.spec.template.spec.containers[0].name,
133+
};
134+
const onSubmit = jest.fn();
135+
136+
return {
137+
onSubmit,
138+
...renderWithProviders(
139+
<Formik
140+
initialValues={initialValues}
141+
validationSchema={resourceLimitsSchema}
142+
onSubmit={onSubmit}
143+
>
144+
{(formikProps) => (
145+
<ResourceLimitsModal {...formikProps} cancel={jest.fn()} isSubmitting={false} />
146+
)}
147+
</Formik>,
148+
),
149+
};
150+
};
151+
152+
it('populates CPU and Memory request/limit fields from the workload resource', () => {
153+
const resource = baseDeployment();
154+
resource.spec.template.spec.containers[0].resources = {
155+
requests: { cpu: '100m', memory: '128Mi' },
156+
limits: { cpu: '500m', memory: '256Mi' },
157+
};
158+
159+
renderModalWithFormik(resource);
160+
161+
expect(screen.getByDisplayValue('100')).toBeVisible();
162+
expect(screen.getByDisplayValue('500')).toBeVisible();
163+
expect(screen.getByDisplayValue('128')).toBeVisible();
164+
expect(screen.getByDisplayValue('256')).toBeVisible();
165+
});
166+
167+
it('disables Save when CPU request is greater than CPU limit', async () => {
168+
const user = userEvent.setup();
169+
const resource = baseDeployment();
170+
resource.spec.template.spec.containers[0].resources = {
171+
requests: { cpu: '100m', memory: '128Mi' },
172+
limits: { cpu: '200m', memory: '256Mi' },
173+
};
174+
175+
renderModalWithFormik(resource);
176+
177+
const save = screen.getByRole('button', { name: 'Save' });
178+
expect(save).not.toBeDisabled();
179+
180+
const spinbuttons = screen.getAllByRole('spinbutton');
181+
await user.click(spinbuttons[0]);
182+
await user.keyboard('{Control>}a{/Control}');
183+
await user.keyboard('300');
184+
185+
await waitFor(() => {
186+
expect(save).toBeDisabled();
187+
});
188+
expect(screen.getByText('CPU request must be less than or equal to limit.')).toBeVisible();
189+
});
190+
191+
it('disables Save when Memory request is greater than Memory limit', async () => {
192+
const user = userEvent.setup();
193+
const resource = baseDeployment();
194+
resource.spec.template.spec.containers[0].resources = {
195+
requests: { cpu: '100m', memory: '128Mi' },
196+
limits: { cpu: '500m', memory: '256Mi' },
197+
};
198+
199+
renderModalWithFormik(resource);
200+
201+
const save = screen.getByRole('button', { name: 'Save' });
202+
const spinbuttons = screen.getAllByRole('spinbutton');
203+
204+
await user.click(spinbuttons[2]);
205+
await user.keyboard('{Control>}a{/Control}');
206+
await user.keyboard('512');
81207

82-
await fireEvent.submit(screen.getByRole('form'));
83-
expect(formProps.handleSubmit).toHaveBeenCalledTimes(1);
208+
await waitFor(() => {
209+
expect(save).toBeDisabled();
210+
});
211+
expect(screen.getByText('Memory request must be less than or equal to limit.')).toBeVisible();
84212
});
85213
});
Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,83 @@
1-
import { render } from '@testing-library/react';
1+
import { Button } from '@patternfly/react-core';
2+
import { screen } from '@testing-library/react';
3+
import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils';
24
import { AccessDenied, EmptyBox, ConsoleEmptyState } from '..';
35

6+
const TestIcon = () => 'TestIcon';
7+
48
describe('EmptyBox', () => {
5-
it('should render without label', () => {
6-
const { getByText } = render(<EmptyBox />);
7-
getByText('Not found');
9+
it('renders default "Not found" message without label', () => {
10+
renderWithProviders(<EmptyBox />);
11+
expect(screen.getByText('Not found')).toBeVisible();
812
});
913

10-
it('should render with label', () => {
11-
const { getByText } = render(<EmptyBox label="test-label" />);
12-
getByText('No test-label found');
14+
it('renders message with label when provided', () => {
15+
renderWithProviders(<EmptyBox label="resources" />);
16+
expect(screen.getByText('No resources found')).toBeVisible();
1317
});
1418
});
1519

16-
describe('MsgBox', () => {
17-
it('should render title', () => {
18-
const { getByText } = render(<ConsoleEmptyState title="test-title" />);
19-
getByText('test-title');
20+
describe('ConsoleEmptyState', () => {
21+
it('renders title and children in body', () => {
22+
renderWithProviders(
23+
<ConsoleEmptyState title="Empty State Title">Body content</ConsoleEmptyState>,
24+
);
25+
expect(screen.getByText('Empty State Title')).toBeVisible();
26+
expect(screen.getByText('Body content')).toBeVisible();
27+
});
28+
29+
it('renders Icon when provided', () => {
30+
renderWithProviders(<ConsoleEmptyState Icon={TestIcon} title="With Icon" />);
31+
expect(screen.getByText('TestIcon')).toBeVisible();
2032
});
2133

22-
it('should render children', () => {
23-
const { getByText } = render(<ConsoleEmptyState>test-child</ConsoleEmptyState>);
24-
getByText('test-child');
34+
it('renders primary and secondary actions when provided', () => {
35+
const primaryActions = [<Button key="create">Create Resource</Button>];
36+
const secondaryActions = [
37+
<Button key="learn" variant="link">
38+
Learn more
39+
</Button>,
40+
];
41+
renderWithProviders(
42+
<ConsoleEmptyState
43+
title="Test"
44+
primaryActions={primaryActions}
45+
secondaryActions={secondaryActions}
46+
/>,
47+
);
48+
expect(screen.getByRole('button', { name: 'Create Resource' })).toBeVisible();
49+
expect(screen.getByRole('button', { name: 'Learn more' })).toBeVisible();
50+
});
51+
52+
it('does not render body or footer when not provided', () => {
53+
renderWithProviders(<ConsoleEmptyState title="No Body" />);
54+
expect(screen.queryByTestId('console-empty-state-body')).not.toBeInTheDocument();
55+
expect(screen.queryByTestId('console-empty-state-footer')).not.toBeInTheDocument();
2556
});
2657
});
2758

2859
describe('AccessDenied', () => {
29-
it('should render message', () => {
30-
const { getByText } = render(<AccessDenied>test-message</AccessDenied>);
31-
getByText('test-message');
60+
it('renders restricted access title and message', () => {
61+
renderWithProviders(<AccessDenied />);
62+
expect(screen.getByText('Restricted access')).toBeVisible();
63+
expect(
64+
screen.getByText("You don't have access to this section due to cluster policy"),
65+
).toBeVisible();
66+
});
67+
68+
it('renders error details alert when children provided', () => {
69+
renderWithProviders(<AccessDenied>Permission denied for resource xyz</AccessDenied>);
70+
expect(screen.getByText('Error details')).toBeVisible();
71+
expect(screen.getByText('Permission denied for resource xyz')).toBeVisible();
72+
});
73+
74+
it('does not render error alert when no children provided', () => {
75+
renderWithProviders(<AccessDenied />);
76+
expect(screen.queryByText('Error details')).not.toBeInTheDocument();
77+
});
78+
79+
it('renders restricted sign icon', () => {
80+
renderWithProviders(<AccessDenied />);
81+
expect(screen.getByAltText('Restricted access')).toBeVisible();
3282
});
3383
});

0 commit comments

Comments
 (0)