diff --git a/package-lock.json b/package-lock.json index 0b6739f..763f98f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 684f0b7..c1c5a4c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/GradebookFilters/AssignmentGradeFilter/__snapshots__/index.test.jsx.snap b/src/components/GradebookFilters/AssignmentGradeFilter/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 109d1d7..0000000 --- a/src/components/GradebookFilters/AssignmentGradeFilter/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AssignmentFilter component render with selected assignment snapshot 1`] = ` -
- - -
- -
-
-`; - -exports[`AssignmentFilter component render without selected assignment snapshot 1`] = ` -
- - -
- -
-
-`; diff --git a/src/components/GradebookFilters/AssignmentGradeFilter/index.test.jsx b/src/components/GradebookFilters/AssignmentGradeFilter/index.test.jsx index 18c5919..a929cc7 100644 --- a/src/components/GradebookFilters/AssignmentGradeFilter/index.test.jsx +++ b/src/components/GradebookFilters/AssignmentGradeFilter/index.test.jsx @@ -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(); - }); describe('behavior', () => { it('initializes hooks', () => { + render(); expect(useAssignmentGradeFilterData).toHaveBeenCalledWith({ updateQueryParams }); - expect(useIntl).toHaveBeenCalledWith(); }); }); describe('render', () => { describe('with selected assignment', () => { - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); + beforeEach(() => { + jest.clearAllMocks(); + render(); }); - 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(); - }); - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); + render(); }); 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'); }); }); }); diff --git a/src/components/GradebookFilters/PercentGroup.test.jsx b/src/components/GradebookFilters/PercentGroup.test.jsx index 1addab8..fa356d6 100644 --- a/src/components/GradebookFilters/PercentGroup.test.jsx +++ b/src/components/GradebookFilters/PercentGroup.test.jsx @@ -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'); diff --git a/src/components/GradesView/EditModal/ModalHeaders.test.jsx b/src/components/GradesView/EditModal/ModalHeaders.test.jsx index 10b3421..c6ff0a6 100644 --- a/src/components/GradesView/EditModal/ModalHeaders.test.jsx +++ b/src/components/GradesView/EditModal/ModalHeaders.test.jsx @@ -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(); - }); - describe('behavior', () => { - it('initializes intl', () => { - expect(useIntl).toHaveBeenCalled(); - }); - it('initializes redux hooks', () => { - expect(selectors.app.useModalData).toHaveBeenCalled(); - expect(selectors.grades.useGradeData).toHaveBeenCalled(); - }); + render(); }); 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(); }); }); }); diff --git a/src/components/GradesView/EditModal/__snapshots__/ModalHeaders.test.jsx.snap b/src/components/GradesView/EditModal/__snapshots__/ModalHeaders.test.jsx.snap deleted file mode 100644 index 22efe80..0000000 --- a/src/components/GradesView/EditModal/__snapshots__/ModalHeaders.test.jsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ModalHeaders render snapshot 1`] = ` -
- - - - -
-`; diff --git a/src/components/GradesView/ImportGradesButton/__snapshots__/index.test.jsx.snap b/src/components/GradesView/ImportGradesButton/__snapshots__/index.test.jsx.snap deleted file mode 100644 index ce266ad..0000000 --- a/src/components/GradesView/ImportGradesButton/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ImportGradesButton component render Form 1`] = ` -
- - - -
-`; - -exports[`ImportGradesButton component render snapshot 1`] = ` - -
- - - -
- -
-`; diff --git a/src/components/GradesView/ImportGradesButton/index.test.jsx b/src/components/GradesView/ImportGradesButton/index.test.jsx index ad4fd88..4766141 100644 --- a/src/components/GradesView/ImportGradesButton/index.test.jsx +++ b/src/components/GradesView/ImportGradesButton/index.test.jsx @@ -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(); - }); - describe('behavior', () => { - it('initializes hooks', () => { - expect(useImportGradesButtonData).toHaveBeenCalledWith(); - expect(useIntl).toHaveBeenCalledWith(); - }); + beforeEach(() => { + jest.clearAllMocks(); + render(); }); + 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(); }); }); }); diff --git a/src/components/GradesView/__snapshots__/index.test.jsx.snap b/src/components/GradesView/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 9960686..0000000 --- a/src/components/GradesView/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GradesView component render snapshot 1`] = ` - - - -

- filter-step-heading -

-
- - -
- - -

- gradebook-step-heading -

-
- - -
- - - -

- * - test-masters-hint -

- - -
-`; diff --git a/src/components/GradesView/index.test.jsx b/src/components/GradesView/index.test.jsx index c86a946..fafd550 100644 --- a/src/components/GradesView/index.test.jsx +++ b/src/components/GradesView/index.test.jsx @@ -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(); - }); - describe('behavior', () => { - it('initializes component hooks', () => { - expect(useGradesViewData).toHaveBeenCalled(); - }); + el = render(); }); 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(); }); }); });