Skip to content

Commit 9279276

Browse files
committed
test: improve coverage
1 parent c20852b commit 9279276

File tree

8 files changed

+407
-3
lines changed

8 files changed

+407
-3
lines changed

src/profile/forms/ExtendedProfileFields.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,5 @@ ExtendedProfileFields.defaultProps = {
117117
};
118118

119119
export default ExtendedProfileFields;
120+
121+
export const TestableExtendedProfileFields = ExtendedProfileFields;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import React from 'react';
2+
import {
3+
render, screen, fireEvent,
4+
} from '@testing-library/react';
5+
import { Provider } from 'react-redux';
6+
import configureStore from 'redux-mock-store';
7+
import { IntlProvider } from '@edx/frontend-platform/i18n';
8+
9+
import { TestableExtendedProfileFields as ExtendedProfileFields } from './ExtendedProfileFields';
10+
11+
jest.mock('./elements/SelectField', () => jest.fn((props) => (
12+
<form onSubmit={props.submitHandler} data-testid="test-form">
13+
<select data-testid="select-field" aria-label={props.label} value={props.value} onChange={(e) => props.changeHandler(props.formId, e.target.value)}>
14+
{props.options.map((option) => (
15+
<option key={option.value} value={option.value}>{option.label}</option>
16+
))}
17+
</select>
18+
</form>
19+
)));
20+
jest.mock('./elements/TextField', () => jest.fn((props) => (
21+
<form onSubmit={props.submitHandler} data-testid="test-form">
22+
<input data-testid="text-field" type="text" aria-label={props.label} value={props.value} onChange={(e) => props.changeHandler(props.formId, e.target.value)} />
23+
</form>
24+
)));
25+
jest.mock('./elements/CheckboxField', () => jest.fn((props) => (
26+
<form onSubmit={props.submitHandler} data-testid="test-form">
27+
<input data-testid="checkbox-field" type="checkbox" aria-label={props.label} checked={props.value} onChange={(e) => props.changeHandler(props.formId, e.target.checked)} />
28+
</form>
29+
)));
30+
31+
const mockStore = configureStore([]);
32+
33+
describe('ExtendedProfileFields', () => {
34+
const store = mockStore({
35+
profilePage: {
36+
drafts: {},
37+
account: {
38+
extendedProfile: [
39+
{ fieldName: 'first_name', fieldValue: 'John' },
40+
],
41+
},
42+
preferences: { visibilityExtendedProfile: {} },
43+
},
44+
});
45+
46+
const renderComponent = (fields, props = {}) => {
47+
render(
48+
<Provider store={store}>
49+
<IntlProvider locale="en">
50+
<ExtendedProfileFields
51+
formId="test-form"
52+
extendedProfileFields={fields}
53+
changeHandler={jest.fn()}
54+
submitHandler={jest.fn()}
55+
closeHandler={jest.fn()}
56+
openHandler={jest.fn()}
57+
isAuthenticatedUserProfile
58+
intl={{ formatMessage: jest.fn(() => 'Empty field') }}
59+
{...props}
60+
/>
61+
</IntlProvider>
62+
</Provider>,
63+
);
64+
};
65+
66+
it('renders SelectField when field type is select', () => {
67+
renderComponent([
68+
{
69+
name: 'country', type: 'select', label: 'Country', value: '', options: [{ value: 'us', label: 'USA' }],
70+
},
71+
]);
72+
expect(screen.getByTestId('select-field')).toBeInTheDocument();
73+
});
74+
75+
it('renders TextField when field type is text', () => {
76+
renderComponent([
77+
{
78+
name: 'first_name', type: 'text', label: 'First Name', value: 'John',
79+
},
80+
]);
81+
expect(screen.getByTestId('text-field')).toBeInTheDocument();
82+
});
83+
84+
it('renders CheckboxField when field type is checkbox', () => {
85+
renderComponent([
86+
{
87+
name: 'newsletter', type: 'checkbox', label: 'Subscribe', value: false,
88+
},
89+
]);
90+
expect(screen.getByTestId('checkbox-field')).toBeInTheDocument();
91+
});
92+
93+
it('handles change events', () => {
94+
const changeHandler = jest.fn();
95+
renderComponent([
96+
{
97+
name: 'first_name', type: 'text', label: 'First Name', value: 'John',
98+
},
99+
], { changeHandler });
100+
101+
const newValue = 'new value';
102+
const textField = screen.getByLabelText('First Name');
103+
fireEvent.change(textField, { target: { value: newValue } });
104+
store.getState().profilePage.account.extendedProfile[0].fieldValue = newValue;
105+
expect(changeHandler).toHaveBeenCalledWith('test-form', store.getState().profilePage.account.extendedProfile);
106+
});
107+
108+
it('handles form submission', () => {
109+
const submitHandler = jest.fn();
110+
renderComponent([
111+
{
112+
name: 'first_name', type: 'text', label: 'First Name', value: 'John',
113+
},
114+
], { submitHandler });
115+
116+
const form = screen.getByTestId('test-form');
117+
fireEvent.submit(form);
118+
expect(submitHandler).toHaveBeenCalledTimes(1);
119+
});
120+
});

src/profile/forms/elements/CheckboxField.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const CheckboxField = ({
5252
cases={{
5353
editing: (
5454
<div role="dialog" aria-labelledby={`${formId}-label`}>
55-
<form onSubmit={handleSubmit}>
55+
<form data-testid="test-form" onSubmit={handleSubmit}>
5656
<Form.Group
5757
controlId={formId}
5858
isInvalid={error !== null}
@@ -162,3 +162,5 @@ CheckboxField.defaultProps = {
162162
};
163163

164164
export default connect(editableFormSelector, {})(CheckboxField);
165+
166+
export const TestableCheckboxField = CheckboxField;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from 'react';
2+
import {
3+
render, screen, fireEvent,
4+
} from '@testing-library/react';
5+
import { Provider } from 'react-redux';
6+
import configureStore from 'redux-mock-store';
7+
import { IntlProvider } from '@edx/frontend-platform/i18n';
8+
import { TestableCheckboxField } from './CheckboxField';
9+
10+
const mockStore = configureStore([]);
11+
12+
describe('CheckboxField', () => {
13+
const defaultProps = {
14+
formId: 'test-checkbox',
15+
value: false,
16+
visibility: 'private',
17+
editMode: 'editing',
18+
saveState: null,
19+
error: null,
20+
label: 'Accept Terms',
21+
placeholder: 'Please accept',
22+
instructions: 'Check the box to continue',
23+
changeHandler: jest.fn(),
24+
submitHandler: jest.fn(),
25+
closeHandler: jest.fn(),
26+
openHandler: jest.fn(),
27+
};
28+
29+
let store;
30+
31+
beforeEach(() => {
32+
store = mockStore({});
33+
});
34+
35+
const renderComponent = (props = {}) => {
36+
render(
37+
<Provider store={store}>
38+
<IntlProvider locale="en">
39+
<TestableCheckboxField {...defaultProps} {...props} />
40+
</IntlProvider>
41+
</Provider>,
42+
);
43+
};
44+
45+
it('renders checkbox in editing mode', () => {
46+
renderComponent();
47+
expect(screen.getByRole('checkbox')).toBeInTheDocument();
48+
});
49+
50+
it('calls changeHandler when checkbox is clicked', () => {
51+
renderComponent();
52+
const checkbox = screen.getByRole('checkbox');
53+
fireEvent.click(checkbox);
54+
expect(defaultProps.changeHandler).toHaveBeenCalledWith('test-checkbox', true);
55+
});
56+
57+
it('calls submitHandler when form is submitted', () => {
58+
renderComponent();
59+
const form = screen.getByTestId('test-form');
60+
fireEvent.submit(form);
61+
expect(defaultProps.submitHandler).toHaveBeenCalledWith('test-checkbox');
62+
});
63+
64+
it('calls closeHandler when form is closed', () => {
65+
renderComponent();
66+
fireEvent.click(screen.getByText('Cancel'));
67+
expect(defaultProps.closeHandler).toHaveBeenCalledWith('test-checkbox');
68+
});
69+
70+
it('renders empty state when in empty mode', () => {
71+
renderComponent({ editMode: 'empty' });
72+
expect(screen.getByText('Check the box to continue')).toBeInTheDocument();
73+
});
74+
75+
it('renders static mode with checked value', () => {
76+
renderComponent({ editMode: 'static', value: true });
77+
expect(screen.getByLabelText('Accept Terms')).toBeChecked();
78+
});
79+
80+
it('renders editable mode with edit button', () => {
81+
renderComponent({ editMode: 'editable' });
82+
expect(screen.getByText('Accept Terms')).toBeInTheDocument();
83+
expect(screen.getByRole('button', { name: /edit/i })).toBeInTheDocument();
84+
});
85+
86+
it('calls openHandler when edit button is clicked', () => {
87+
renderComponent({ editMode: 'editable' });
88+
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
89+
expect(defaultProps.openHandler).toHaveBeenCalledWith('test-checkbox');
90+
});
91+
});

src/profile/forms/elements/SelectField.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const SelectField = ({
4848
cases={{
4949
editing: (
5050
<div role="dialog" aria-labelledby={`${formId}-label`}>
51-
<form onSubmit={handleSubmit}>
51+
<form data-testid="test-form" onSubmit={handleSubmit}>
5252
<Form.Group
5353
controlId={formId}
5454
isInvalid={error !== null}
@@ -151,3 +151,5 @@ SelectField.defaultProps = {
151151
};
152152

153153
export default connect(editableFormSelector, {})(SelectField);
154+
155+
export const TestableSelectField = SelectField;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import { Provider } from 'react-redux';
4+
import configureStore from 'redux-mock-store';
5+
import { IntlProvider } from '@edx/frontend-platform/i18n';
6+
import { TestableSelectField } from './SelectField';
7+
8+
const mockStore = configureStore([]);
9+
10+
describe('SelectField', () => {
11+
const defaultProps = {
12+
formId: 'test-select',
13+
value: '',
14+
visibility: 'private',
15+
editMode: 'editing',
16+
saveState: null,
17+
error: null,
18+
options: [
19+
['us', 'United States'],
20+
['ca', 'Canada'],
21+
['uk', 'United Kingdom'],
22+
],
23+
label: 'Country',
24+
emptyMessage: 'No country selected',
25+
changeHandler: jest.fn(),
26+
submitHandler: jest.fn(),
27+
closeHandler: jest.fn(),
28+
openHandler: jest.fn(),
29+
};
30+
31+
let store;
32+
33+
beforeEach(() => {
34+
store = mockStore({});
35+
});
36+
37+
const renderComponent = (props = {}) => {
38+
render(
39+
<Provider store={store}>
40+
<IntlProvider locale="en">
41+
<TestableSelectField {...defaultProps} {...props} />
42+
</IntlProvider>
43+
</Provider>,
44+
);
45+
};
46+
47+
it('renders select field in editing mode', () => {
48+
renderComponent();
49+
expect(screen.getByLabelText('Country')).toBeInTheDocument();
50+
});
51+
52+
it('calls changeHandler when an option is selected', () => {
53+
renderComponent();
54+
const select = screen.getByLabelText('Country');
55+
fireEvent.change(select, { target: { value: 'ca' } });
56+
expect(defaultProps.changeHandler).toHaveBeenCalledWith('test-select', 'ca');
57+
});
58+
59+
it('calls submitHandler when form is submitted', () => {
60+
renderComponent();
61+
const form = screen.getByTestId('test-form');
62+
fireEvent.submit(form);
63+
expect(defaultProps.submitHandler).toHaveBeenCalledWith('test-select');
64+
});
65+
66+
it('calls closeHandler when form is closed', () => {
67+
renderComponent();
68+
defaultProps.closeHandler.mockClear();
69+
fireEvent.click(screen.getByText('Cancel'));
70+
expect(defaultProps.closeHandler).toHaveBeenCalledWith('test-select');
71+
});
72+
73+
it('renders empty state when in empty mode', () => {
74+
renderComponent({ editMode: 'empty' });
75+
expect(screen.getByText('No country selected')).toBeInTheDocument();
76+
});
77+
78+
it('renders static mode with selected value', () => {
79+
renderComponent({ editMode: 'static', value: 'United States' });
80+
expect(screen.getByText('United States')).toBeInTheDocument();
81+
});
82+
83+
it('renders editable mode with edit button', () => {
84+
renderComponent({ editMode: 'editable' });
85+
expect(screen.getByText('Country')).toBeInTheDocument();
86+
expect(screen.getByRole('button', { name: /edit/i })).toBeInTheDocument();
87+
});
88+
89+
it('calls openHandler when edit button is clicked', () => {
90+
renderComponent({ editMode: 'editable' });
91+
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
92+
expect(defaultProps.openHandler).toHaveBeenCalledWith('test-select');
93+
});
94+
});

src/profile/forms/elements/TextField.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const TextField = ({
4949
cases={{
5050
editing: (
5151
<div role="dialog" aria-labelledby={`${formId}-label`}>
52-
<form onSubmit={handleSubmit}>
52+
<form data-testid="test-form" onSubmit={handleSubmit}>
5353
<Form.Group
5454
controlId={formId}
5555
isInvalid={error !== null}
@@ -142,3 +142,5 @@ TextField.defaultProps = {
142142
};
143143

144144
export default connect(editableFormSelector, {})(TextField);
145+
146+
export const TestableTextField = TextField;

0 commit comments

Comments
 (0)