feat: Deprecate react-unit-test-utils 4/9 (#488)

* feat: deprecte shallow on unit tests

* test: use userEvent instead of fireEvent
This commit is contained in:
Diana Villalvazo
2025-08-26 10:58:08 -06:00
committed by GitHub
parent afd688d198
commit 86ede70c41
11 changed files with 96 additions and 333 deletions

15
package-lock.json generated
View File

@@ -52,6 +52,7 @@
"@openedx/frontend-build": "^14.3.3",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"es-check": "^2.3.0",
"fetch-mock": "^12.2.0",
"identity-obj-proxy": "^3.0.0",
@@ -5223,6 +5224,20 @@
}
}
},
"node_modules/@testing-library/user-event": {
"version": "14.6.1",
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
"integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12",
"npm": ">=6"
},
"peerDependencies": {
"@testing-library/dom": ">=7.21.4"
}
},
"node_modules/@tokens-studio/sd-transforms": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@tokens-studio/sd-transforms/-/sd-transforms-1.3.0.tgz",

View File

@@ -72,6 +72,7 @@
"@openedx/frontend-build": "^14.3.3",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"es-check": "^2.3.0",
"fetch-mock": "^12.2.0",
"identity-obj-proxy": "^3.0.0",

View File

@@ -1,67 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AssignmentFilter component render with selected assignment snapshot 1`] = `
<div
className="grade-filter-inputs"
>
<PercentGroup
disabled={false}
id="assignmentGradeMin"
label="Min Grade"
onChange={[MockFunction]}
value={23}
/>
<PercentGroup
disabled={false}
id="assignmentGradeMax"
label="Max Grade"
onChange={[MockFunction]}
value={300}
/>
<div
className="grade-filter-action"
>
<Button
disabled={false}
name="assignmentGradeMinMax"
type="submit"
variant="outline-secondary"
>
Apply
</Button>
</div>
</div>
`;
exports[`AssignmentFilter component render without selected assignment snapshot 1`] = `
<div
className="grade-filter-inputs"
>
<PercentGroup
disabled={true}
id="assignmentGradeMin"
label="Min Grade"
onChange={[MockFunction]}
value={23}
/>
<PercentGroup
disabled={true}
id="assignmentGradeMax"
label="Max Grade"
onChange={[MockFunction]}
value={300}
/>
<div
className="grade-filter-action"
>
<Button
disabled={true}
name="assignmentGradeMinMax"
type="submit"
variant="outline-secondary"
>
Apply
</Button>
</div>
</div>
`;

View File

@@ -1,17 +1,18 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
/* eslint-disable import/no-extraneous-dependencies */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import PercentGroup from '../PercentGroup';
import useAssignmentGradeFilterData from './hooks';
import AssignmentFilter from '.';
jest.mock('../PercentGroup', () => 'PercentGroup');
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
jest.unmock('@openedx/paragon');
jest.unmock('react');
jest.unmock('@edx/frontend-platform/i18n');
const hookData = {
handleChange: jest.fn(),
handleSubmit: jest.fn(),
handleSetMax: jest.fn(),
handleSetMin: jest.fn(),
selectedAssignment: 'test-assignment',
@@ -22,37 +23,39 @@ useAssignmentGradeFilterData.mockReturnValue(hookData);
const updateQueryParams = jest.fn();
let el;
describe('AssignmentFilter component', () => {
beforeEach(() => {
jest.clearAllMocks();
el = shallow(<AssignmentFilter updateQueryParams={updateQueryParams} />);
});
describe('behavior', () => {
it('initializes hooks', () => {
render(<IntlProvider locale="en" messages={{}}><AssignmentFilter updateQueryParams={updateQueryParams} /></IntlProvider>);
expect(useAssignmentGradeFilterData).toHaveBeenCalledWith({ updateQueryParams });
expect(useIntl).toHaveBeenCalledWith();
});
});
describe('render', () => {
describe('with selected assignment', () => {
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
beforeEach(() => {
jest.clearAllMocks();
render(<IntlProvider locale="en" messages={{}}><AssignmentFilter updateQueryParams={updateQueryParams} /></IntlProvider>);
});
it('renders a PercentGroup for both Max and Min filters', () => {
let { props } = el.instance.findByType(PercentGroup)[0];
expect(props.value).toEqual(hookData.assignmentGradeMin);
expect(props.disabled).toEqual(false);
expect(props.onChange).toEqual(hookData.handleSetMin);
props = el.instance.findByType(PercentGroup)[1].props;
expect(props.value).toEqual(hookData.assignmentGradeMax);
expect(props.disabled).toEqual(false);
expect(props.onChange).toEqual(hookData.handleSetMax);
it('renders a PercentGroup for both Max and Min filters', async () => {
const user = userEvent.setup();
const minGradeInput = screen.getByRole('spinbutton', { name: /Min Grade/i });
const maxGradeInput = screen.getByRole('spinbutton', { name: /Max Grade/i });
expect(minGradeInput).toBeInTheDocument();
expect(maxGradeInput).toBeInTheDocument();
expect(minGradeInput).toBeEnabled();
expect(maxGradeInput).toBeEnabled();
await user.type(minGradeInput, '25');
expect(hookData.handleSetMin).toHaveBeenCalled();
await user.type(maxGradeInput, '50');
expect(hookData.handleSetMax).toHaveBeenCalled();
});
it('renders a submit button', () => {
const { props } = el.instance.findByType(Button)[0];
expect(props.disabled).toEqual(false);
expect(props.onClick).toEqual(hookData.handleSubmit);
it('renders a submit button', async () => {
const user = userEvent.setup();
const submitButton = screen.getByRole('button', { name: /Apply/ });
expect(submitButton).toBeInTheDocument();
expect(submitButton).not.toHaveAttribute('disabled');
await user.click(submitButton);
expect(hookData.handleSubmit).toHaveBeenCalled();
});
});
describe('without selected assignment', () => {
@@ -61,16 +64,13 @@ describe('AssignmentFilter component', () => {
...hookData,
selectedAssignment: null,
});
el = shallow(<AssignmentFilter updateQueryParams={updateQueryParams} />);
});
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
render(<IntlProvider locale="en" messages={{}}><AssignmentFilter updateQueryParams={updateQueryParams} /></IntlProvider>);
});
it('disables controls', () => {
let { props } = el.instance.findByType(PercentGroup)[0];
expect(props.disabled).toEqual(true);
props = el.instance.findByType(PercentGroup)[1].props;
expect(props.disabled).toEqual(true);
const minGrade = screen.getByRole('spinbutton', { name: /Min Grade/ });
const maxGrade = screen.getByRole('spinbutton', { name: /Max Grade/ });
expect(minGrade).toHaveAttribute('disabled');
expect(maxGrade).toHaveAttribute('disabled');
});
});
});

View File

@@ -1,8 +1,6 @@
import React from 'react';
import { render, screen } from 'testUtilsExtra';
import { render, screen, initializeMocks } from 'testUtilsExtra';
import PercentGroup from './PercentGroup';
import { initializeMocks } from '../../testUtilsExtra';
jest.unmock('@openedx/paragon');
jest.unmock('react');

View File

@@ -1,16 +1,12 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { useIntl } from '@edx/frontend-platform/i18n';
import { selectors } from 'data/redux/hooks';
import { formatMessage } from 'testUtils';
import { render, screen, initializeMocks } from 'testUtilsExtra';
import ModalHeaders from './ModalHeaders';
import HistoryHeader from './HistoryHeader';
import ModalHeaders, { HistoryKeys } from './ModalHeaders';
import messages from './messages';
jest.mock('./HistoryHeader', () => 'HistoryHeader');
jest.unmock('@openedx/paragon');
jest.unmock('react');
jest.unmock('@edx/frontend-platform/i18n');
jest.mock('data/redux/hooks', () => ({
selectors: {
@@ -29,57 +25,25 @@ const gradeData = {
gradeOriginalEarnedGraded: 'test-original-grade',
};
selectors.grades.useGradeData.mockReturnValue(gradeData);
initializeMocks();
let el;
describe('ModalHeaders', () => {
beforeEach(() => {
jest.clearAllMocks();
el = shallow(<ModalHeaders />);
});
describe('behavior', () => {
it('initializes intl', () => {
expect(useIntl).toHaveBeenCalled();
});
it('initializes redux hooks', () => {
expect(selectors.app.useModalData).toHaveBeenCalled();
expect(selectors.grades.useGradeData).toHaveBeenCalled();
});
render(<ModalHeaders />);
});
describe('render', () => {
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
});
test('assignment header', () => {
const headerProps = el.instance.findByType(HistoryHeader)[0].props;
expect(headerProps).toMatchObject({
id: HistoryKeys.assignment,
label: formatMessage(messages.assignmentHeader),
value: modalData.assignmentName,
});
expect(screen.getByText(modalData.assignmentName)).toBeInTheDocument();
});
test('student header', () => {
const headerProps = el.instance.findByType(HistoryHeader)[1].props;
expect(headerProps).toMatchObject({
id: HistoryKeys.student,
label: formatMessage(messages.studentHeader),
value: modalData.updateUserName,
});
expect(screen.getByText(modalData.updateUserName)).toBeInTheDocument();
});
test('originalGrade header', () => {
const headerProps = el.instance.findByType(HistoryHeader)[2].props;
expect(headerProps).toMatchObject({
id: HistoryKeys.originalGrade,
label: formatMessage(messages.originalGradeHeader),
value: gradeData.gradeOriginalEarnedGraded,
});
expect(screen.getByText(gradeData.gradeOriginalEarnedGraded)).toBeInTheDocument();
});
test('currentGrade header', () => {
const headerProps = el.instance.findByType(HistoryHeader)[3].props;
expect(headerProps).toMatchObject({
id: HistoryKeys.currentGrade,
label: formatMessage(messages.currentGradeHeader),
value: gradeData.gradeOverrideCurrentEarnedGradedOverride,
});
expect(screen.getByText(gradeData.gradeOverrideCurrentEarnedGradedOverride)).toBeInTheDocument();
});
});
});

View File

@@ -1,26 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ModalHeaders render snapshot 1`] = `
<div>
<HistoryHeader
id="assignment"
label="Assignment"
value="test-assignment-name"
/>
<HistoryHeader
id="student"
label="Student"
value="test-user-name"
/>
<HistoryHeader
id="original-grade"
label="Original Grade"
value="test-original-grade"
/>
<HistoryHeader
id="current-grade"
label="Current Grade"
value="test-current-grade"
/>
</div>
`;

View File

@@ -1,53 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ImportGradesButton component render Form 1`] = `
<Form
action="test-grade-export-url"
method="post"
>
<Form.Group
controlId="csv"
>
<Form.Control
className="d-none"
data-testid="file-control"
label="Upload Grade CSV"
onChange={[MockFunction props.handleFileInputChange]}
type="file"
/>
</Form.Group>
</Form>
`;
exports[`ImportGradesButton component render snapshot 1`] = `
<Fragment>
<Form
action="test-grade-export-url"
method="post"
>
<Form.Group
controlId="csv"
>
<Form.Control
className="d-none"
data-testid="file-control"
label="Upload Grade CSV"
onChange={[MockFunction props.handleFileInputChange]}
type="file"
/>
</Form.Group>
</Form>
<NetworkButton
className="import-grades-btn"
import={true}
label={
{
"defaultMessage": "Import Grades",
"description": "A labeled button to import grades in the BulkManagement Tab File Upload Form",
"id": "gradebook.GradesView.importGradesBtnText",
}
}
onClick={[MockFunction props.handleClickImportGrades]}
/>
</Fragment>
`;

View File

@@ -1,46 +1,30 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Form } from '@openedx/paragon';
import {
render, screen, initializeMocks,
} from 'testUtilsExtra';
import NetworkButton from 'components/NetworkButton';
import useImportGradesButtonData from './hooks';
import ImportGradesButton from '.';
jest.mock('components/NetworkButton', () => 'NetworkButton');
jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
jest.unmock('@openedx/paragon');
jest.unmock('react');
jest.unmock('@edx/frontend-platform/i18n');
initializeMocks();
let el;
let props;
describe('ImportGradesButton component', () => {
beforeAll(() => {
props = {
fileInputRef: { current: null },
gradeExportUrl: 'test-grade-export-url',
handleClickImportGrades: jest.fn().mockName('props.handleClickImportGrades'),
handleFileInputChange: jest.fn().mockName('props.handleFileInputChange'),
};
useImportGradesButtonData.mockReturnValue(props);
el = shallow(<ImportGradesButton />);
});
describe('behavior', () => {
it('initializes hooks', () => {
expect(useImportGradesButtonData).toHaveBeenCalledWith();
expect(useIntl).toHaveBeenCalledWith();
});
beforeEach(() => {
jest.clearAllMocks();
render(<ImportGradesButton />);
});
describe('render', () => {
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
});
test('Form', () => {
expect(el.instance.findByType(Form)[0].snapshot).toMatchSnapshot();
expect(el.instance.findByType(Form)[0].props.action).toEqual(props.gradeExportUrl);
expect(el.instance.findByType(Form.Control)[0].props.onChange).toEqual(props.handleFileInputChange);
test('Form', async () => {
const uploader = screen.getByTestId('file-control');
expect(uploader).toBeInTheDocument();
});
test('import button', () => {
expect(el.instance.findByType(NetworkButton)[0].props.onClick).toEqual(props.handleClickImportGrades);
expect(screen.getByRole('button', { name: 'Import Grades' })).toBeInTheDocument();
});
});
});

View File

@@ -1,41 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GradesView component render snapshot 1`] = `
<Fragment>
<SpinnerIcon />
<InterventionsReport />
<h3
className="step-message-1"
>
filter-step-heading
</h3>
<div
className="d-flex justify-content-between flex-wrap"
>
<FilterMenuToggle />
<SearchControls />
</div>
<FilterBadges
handleClose={[MockFunction hooks.handleFilterBadgeClose]}
/>
<StatusAlerts />
<h3>
gradebook-step-heading
</h3>
<div
className="d-flex justify-content-between align-items-center mb-2"
>
<ScoreViewInput />
<BulkManagementControls />
</div>
<FilteredUsersLabel />
<GradebookTable />
<PageButtons />
<p>
*
test-masters-hint
</p>
<EditModal />
<ImportSuccessToast />
</Fragment>
`;

View File

@@ -1,24 +1,14 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import FilterBadges from './FilterBadges';
// eslint-disable-next-line import/no-extraneous-dependencies
import userEvent from '@testing-library/user-event';
import { render, initializeMocks } from 'testUtilsExtra';
import useGradesViewData from './hooks';
import GradesView from '.';
jest.mock('./BulkManagementControls', () => 'BulkManagementControls');
jest.mock('./EditModal', () => 'EditModal');
jest.mock('./FilterBadges', () => 'FilterBadges');
jest.mock('./FilteredUsersLabel', () => 'FilteredUsersLabel');
jest.mock('./FilterMenuToggle', () => 'FilterMenuToggle');
jest.mock('./GradebookTable', () => 'GradebookTable');
jest.mock('./ImportSuccessToast', () => 'ImportSuccessToast');
jest.mock('./InterventionsReport', () => 'InterventionsReport');
jest.mock('./PageButtons', () => 'PageButtons');
jest.mock('./ScoreViewInput', () => 'ScoreViewInput');
jest.mock('./SearchControls', () => 'SearchControls');
jest.mock('./SpinnerIcon', () => 'SpinnerIcon');
jest.mock('./StatusAlerts', () => 'StatusAlerts');
jest.unmock('@openedx/paragon');
jest.unmock('react');
jest.unmock('@edx/frontend-platform/i18n');
jest.mock('./hooks', () => jest.fn());
const hookProps = {
@@ -35,23 +25,21 @@ const updateQueryParams = jest.fn().mockName('props.updateQueryParams');
let el;
describe('GradesView component', () => {
beforeAll(() => {
initializeMocks();
});
beforeEach(() => {
jest.clearAllMocks();
el = shallow(<GradesView updateQueryParams={updateQueryParams} />);
});
describe('behavior', () => {
it('initializes component hooks', () => {
expect(useGradesViewData).toHaveBeenCalled();
});
el = render(<GradesView updateQueryParams={updateQueryParams} />);
});
describe('render', () => {
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
test('component to be rendered', () => {
expect(el.container).toBeInTheDocument();
});
test('filterBadges load close behavior from hook', () => {
expect(el.instance.findByType(FilterBadges)[0].props.handleClose).toEqual(
hookProps.handleFilterBadgeClose,
);
test('filterBadges load close behavior from hook', async () => {
const user = userEvent.setup();
await user.click(el.getAllByRole('button', { name: 'close' })[0]); // All the buttons use the same handler
expect(hookProps.handleFilterBadgeClose).toHaveBeenCalled();
});
});
});