diff --git a/src/components/InfoPopover/index.test.jsx b/src/components/InfoPopover/index.test.jsx index ba408a4..2fab2a3 100644 --- a/src/components/InfoPopover/index.test.jsx +++ b/src/components/InfoPopover/index.test.jsx @@ -1,4 +1,5 @@ -import { render, fireEvent } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { InfoPopover } from '.'; jest.unmock('@openedx/paragon'); @@ -18,13 +19,14 @@ describe('Info Popover Component', () => { expect(getByTestId('esg-help-icon')).toBeInTheDocument(); }); - it('calls onClick when the help icon is clicked', () => { - const { getByTestId } = render( + it('calls onClick when the help icon is clicked', async () => { + render( {child} , ); - fireEvent.click(getByTestId('esg-help-icon')); + const user = userEvent.setup(); + await user.click(screen.getByTestId('esg-help-icon')); expect(onClick).toHaveBeenCalled(); }); }); diff --git a/src/containers/ReviewModal/ReviewErrors/LockErrors.test.jsx b/src/containers/ReviewModal/ReviewErrors/LockErrors.test.jsx index cf92f36..d5f82b8 100644 --- a/src/containers/ReviewModal/ReviewErrors/LockErrors.test.jsx +++ b/src/containers/ReviewModal/ReviewErrors/LockErrors.test.jsx @@ -1,6 +1,8 @@ import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { selectors } from 'data/redux'; import { ErrorStatuses, RequestKeys } from 'data/constants/requests'; @@ -9,53 +11,94 @@ import { mapStateToProps, } from './LockErrors'; +jest.unmock('react'); +jest.unmock('@openedx/paragon'); +jest.unmock('@edx/frontend-platform/i18n'); + jest.mock('data/redux', () => ({ selectors: { requests: { - errorStatus: (...args) => ({ errorStatus: args }), - isFailed: (...args) => ({ isFailed: args }), + errorStatus: jest.fn(), + isFailed: jest.fn(), }, }, })); -let el; -jest.mock('./ReviewError', () => 'ReviewError'); - -const requestKey = RequestKeys.setLock; +const renderWithIntl = (component) => render( + + {component} + , +); describe('LockErrors component', () => { - const props = { - isFailed: true, - }; - describe('component', () => { - beforeEach(() => { - el = shallow(); - el.instance.dismissError = jest.fn().mockName('this.dismissError'); - }); - describe('snapshots', () => { - test('no failure', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - test('snapshot: error with bad request', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - test('snapshot: error with conflicted lock', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); + describe('when not failed', () => { + it('renders nothing when isFailed is false', () => { + const { container } = renderWithIntl( + , + ); + expect(container.firstChild).toBeNull(); }); }); - describe('mapStateToProps', () => { - let mapped; - const testState = { some: 'test-state' }; - beforeEach(() => { - mapped = mapStateToProps(testState); - }); - test('errorStatus loads from requests.errorStatus(setLock)', () => { - expect(mapped.errorStatus).toEqual( - selectors.requests.errorStatus(testState, { requestKey }), + + describe('when failed', () => { + it('renders bad request error when errorStatus is badRequest', () => { + renderWithIntl( + , ); + + expect(screen.getByRole('alert')).toBeInTheDocument(); + const errorMessages = screen.getAllByText('Invalid request. Please check your input.'); + expect(errorMessages).toHaveLength(2); + }); + + it('renders conflict error when errorStatus is conflict', () => { + renderWithIntl( + , + ); + + expect(screen.getByRole('alert')).toBeInTheDocument(); + const errorMessages = screen.getAllByText('The lock owned by another user'); + expect(errorMessages).toHaveLength(2); + }); + + it('renders bad request error by default when no errorStatus provided', () => { + renderWithIntl( + , + ); + + expect(screen.getByRole('alert')).toBeInTheDocument(); + const errorMessages = screen.getAllByText('Invalid request. Please check your input.'); + expect(errorMessages).toHaveLength(2); + }); + }); + + describe('mapStateToProps', () => { + const testState = { some: 'test-state' }; + const requestKey = RequestKeys.setLock; + + beforeEach(() => { + selectors.requests.isFailed.mockReturnValue(true); + selectors.requests.errorStatus.mockReturnValue(ErrorStatuses.badRequest); + }); + + it('maps isFailed from requests selector', () => { + const mapped = mapStateToProps(testState); + + expect(selectors.requests.isFailed).toHaveBeenCalledWith(testState, { requestKey }); + expect(mapped.isFailed).toBe(true); + }); + + it('maps errorStatus from requests selector', () => { + const mapped = mapStateToProps(testState); + + expect(selectors.requests.errorStatus).toHaveBeenCalledWith(testState, { requestKey }); + expect(mapped.errorStatus).toBe(ErrorStatuses.badRequest); }); }); }); diff --git a/src/containers/ReviewModal/ReviewErrors/ReviewError.test.jsx b/src/containers/ReviewModal/ReviewErrors/ReviewError.test.jsx index a51d513..6a315ed 100644 --- a/src/containers/ReviewModal/ReviewErrors/ReviewError.test.jsx +++ b/src/containers/ReviewModal/ReviewErrors/ReviewError.test.jsx @@ -1,82 +1,112 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import { Button } from '@openedx/paragon'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; - +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import ReviewError from './ReviewError'; -let el; -const messages = { - heading: { - id: 'test-header-message', - defaultMessage: 'Test Header Message', - }, - cancel: { - id: 'test-cancel-message', - defaultMessage: 'Test Cancel Message', - }, - confirm: { - id: 'test-confirm-message', - defaultMessage: 'Test Confirm Message', - }, -}; -const cancel = { - onClick: jest.fn().mockName('this.props.cancel.onClick'), - message: messages.cancel, -}; -const confirm = { - onClick: jest.fn().mockName('this.props.confirm.onClick'), - message: messages.confirm, -}; +jest.unmock('@openedx/paragon'); +jest.unmock('react'); +jest.unmock('@edx/frontend-platform/i18n'); -const confirmBtn = ( - -); - -const cancelBtn = ( - +const renderWithIntl = (component) => render( + + {component} + , ); describe('ReviewError component', () => { - describe('component', () => { - const props = { - headingMessage: messages.heading, - }; - const children =
Test Children
; - describe('snapshots', () => { - test('no actions', () => { - el = shallow({children}); - expect(el.snapshot).toMatchSnapshot(); - const { actions } = el.instance.props; - expect(actions).toEqual([]); - }); - test('cancel only', () => { - el = shallow({children}); - expect(el.snapshot).toMatchSnapshot(); - const { actions } = el.instance.props; - expect(actions.length).toEqual(1); - expect(actions[0]).toEqual(cancelBtn); - }); - test('confirm only', () => { - el = shallow({children}); - expect(el.snapshot).toMatchSnapshot(); - const { actions } = el.instance.props; - expect(actions.length).toEqual(1); - expect(actions[0]).toEqual(confirmBtn); - }); - test('cancel and confirm', () => { - el = shallow({children}); - expect(el.snapshot).toMatchSnapshot(); - const { actions } = el.instance.props; - expect(actions.length).toEqual(2); - expect(actions[0]).toEqual(cancelBtn); - expect(actions[1]).toEqual(confirmBtn); - }); - }); + const messages = { + heading: { + id: 'test-header-message', + defaultMessage: 'Test Header Message', + }, + cancel: { + id: 'test-cancel-message', + defaultMessage: 'Test Cancel Message', + }, + confirm: { + id: 'test-confirm-message', + defaultMessage: 'Test Confirm Message', + }, + }; + + const cancel = { + onClick: jest.fn(), + message: messages.cancel, + }; + + const confirm = { + onClick: jest.fn(), + message: messages.confirm, + }; + + const defaultProps = { + headingMessage: messages.heading, + }; + + const children = 'Test error content'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders alert with heading and children', () => { + renderWithIntl({children}); + expect(screen.getByRole('alert')).toBeInTheDocument(); + expect(screen.getByText('Test Header Message')).toBeInTheDocument(); + expect(screen.getByText('Test error content')).toBeInTheDocument(); + }); + + it('renders with default danger variant', () => { + renderWithIntl({children}); + const alert = screen.getByRole('alert'); + expect(alert).toHaveClass('alert-danger'); + }); + + it('renders with custom variant', () => { + renderWithIntl({children}); + const alert = screen.getByRole('alert'); + expect(alert).toHaveClass('alert-warning'); + }); + + it('renders cancel button when cancel action provided', () => { + renderWithIntl({children}); + const cancelButton = screen.getByText('Test Cancel Message'); + expect(cancelButton).toBeInTheDocument(); + expect(cancelButton.closest('button')).toHaveClass('btn-outline-primary'); + }); + + it('renders confirm button when confirm action provided', () => { + renderWithIntl({children}); + const confirmButton = screen.getByText('Test Confirm Message'); + expect(confirmButton).toBeInTheDocument(); + expect(confirmButton.closest('button')).toHaveClass('btn-primary'); + }); + + it('renders both cancel and confirm buttons when both actions provided', () => { + renderWithIntl({children}); + expect(screen.getByText('Test Cancel Message')).toBeInTheDocument(); + expect(screen.getByText('Test Confirm Message')).toBeInTheDocument(); + }); + + it('calls cancel onClick when cancel button is clicked', async () => { + renderWithIntl({children}); + const user = userEvent.setup(); + const cancelButton = screen.getByText('Test Cancel Message'); + await user.click(cancelButton); + expect(cancel.onClick).toHaveBeenCalledTimes(1); + }); + + it('calls confirm onClick when confirm button is clicked', async () => { + renderWithIntl({children}); + const user = userEvent.setup(); + const confirmButton = screen.getByText('Test Confirm Message'); + await user.click(confirmButton); + expect(confirm.onClick).toHaveBeenCalledTimes(1); + }); + + it('applies custom className when provided', () => { + renderWithIntl({children}); + const alert = screen.getByRole('alert'); + expect(alert).toHaveClass('custom-class'); }); }); diff --git a/src/containers/ReviewModal/ReviewErrors/__snapshots__/LockErrors.test.jsx.snap b/src/containers/ReviewModal/ReviewErrors/__snapshots__/LockErrors.test.jsx.snap deleted file mode 100644 index fd1121a..0000000 --- a/src/containers/ReviewModal/ReviewErrors/__snapshots__/LockErrors.test.jsx.snap +++ /dev/null @@ -1,58 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LockErrors component component snapshots no failure 1`] = ` - - - -`; - -exports[`LockErrors component component snapshots snapshot: error with bad request 1`] = ` - - - -`; - -exports[`LockErrors component component snapshots snapshot: error with conflicted lock 1`] = ` - - - -`; diff --git a/src/containers/ReviewModal/ReviewErrors/__snapshots__/ReviewError.test.jsx.snap b/src/containers/ReviewModal/ReviewErrors/__snapshots__/ReviewError.test.jsx.snap deleted file mode 100644 index 479aac2..0000000 --- a/src/containers/ReviewModal/ReviewErrors/__snapshots__/ReviewError.test.jsx.snap +++ /dev/null @@ -1,124 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ReviewError component component snapshots cancel and confirm 1`] = ` - - - , - , - ] - } - className="" - variant="danger" -> - - - -

-

- Test Children -
-

-
-`; - -exports[`ReviewError component component snapshots cancel only 1`] = ` - - - , - ] - } - className="" - variant="danger" -> - - - -

-

- Test Children -
-

-
-`; - -exports[`ReviewError component component snapshots confirm only 1`] = ` - - - , - ] - } - className="" - variant="danger" -> - - - -

-

- Test Children -
-

-
-`; - -exports[`ReviewError component component snapshots no actions 1`] = ` - - - - -

-

- Test Children -
-

-
-`; diff --git a/src/containers/Rubric/RubricFeedback.test.jsx b/src/containers/Rubric/RubricFeedback.test.jsx index 0681577..038e377 100644 --- a/src/containers/Rubric/RubricFeedback.test.jsx +++ b/src/containers/Rubric/RubricFeedback.test.jsx @@ -1,162 +1,139 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { actions, selectors } from 'data/redux'; -import { - feedbackRequirement, - gradeStatuses, -} from 'data/services/lms/constants'; +import { feedbackRequirement, gradeStatuses } from 'data/services/lms/constants'; +import { RubricFeedback, mapDispatchToProps, mapStateToProps } from './RubricFeedback'; -import { - RubricFeedback, - mapDispatchToProps, - mapStateToProps, -} from './RubricFeedback'; +jest.unmock('@openedx/paragon'); +jest.unmock('react'); +jest.unmock('@edx/frontend-platform/i18n'); -jest.mock('components/InfoPopover', () => 'InfoPopover'); - -jest.mock('data/redux/app/selectors', () => ({ - rubric: { - feedbackConfig: jest.fn((...args) => ({ - rubricFeedbackConfig: args, - })), - feedbackPrompt: jest.fn((...args) => ({ - rubricFeedbackPrompt: args, - })), - }, -})); -jest.mock('data/redux/grading/selectors', () => ({ - selected: { - overallFeedback: jest.fn((...args) => ({ - selectedOverallFeedback: args, - })), - isGrading: jest.fn((...args) => ({ isGrading: args })), - }, - validation: { - overallFeedbackIsInvalid: jest.fn((...args) => ({ - selectedOverallFeedbackIsInvalid: args, - })), +jest.mock('data/redux', () => ({ + actions: { + grading: { setRubricFeedback: jest.fn() }, + }, + selectors: { + app: { + rubric: { + feedbackConfig: jest.fn((state) => state.config || 'config string'), + feedbackPrompt: jest.fn((state) => state.feedbackPrompt || 'feedback prompt'), + }, + }, + grading: { + selected: { + overallFeedback: jest.fn((state) => state.value || 'some value'), + isGrading: jest.fn((state) => (state.isGrading !== undefined ? state.isGrading : true)), + }, + validation: { + overallFeedbackIsInvalid: jest.fn((state) => state.isInvalid || false), + }, + }, }, })); +const renderWithIntl = (component) => render( + + {component} + , +); + describe('Rubric Feedback component', () => { - const props = { + const defaultProps = { config: 'config string', isGrading: true, value: 'some value', isInvalid: false, - feedbackPrompt: 'feedback prompt', gradeStatus: gradeStatuses.ungraded, - setValue: jest.fn().mockName('this.props.setValue'), + setValue: jest.fn(), + intl: { + formatMessage: jest.fn((message) => message.defaultMessage), + }, }; - let el; beforeEach(() => { - el = shallow(); - }); - describe('snapshot', () => { - test('is grading', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - test('is graded', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - - test('feedback value is invalid', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - - test('is configure to disabled', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); + jest.clearAllMocks(); }); - describe('component', () => { - describe('render', () => { - test('is grading (everything show up and the input is editable)', () => { - expect(el.isEmptyRender()).toEqual(false); - const input = el.instance.children[1]; - expect(input.props.disabled).toEqual(false); - expect(input.props.value).toEqual(props.value); - }); + it('should render feedback form when config is not disabled', () => { + const { getByText } = renderWithIntl(); + expect(getByText('Overall comments')).toBeInTheDocument(); + }); - test('is graded (the input are disabled)', () => { - el = shallow(); - expect(el.isEmptyRender()).toEqual(false); - const input = el.instance.children[1]; - expect(input.props.disabled).toEqual(true); - expect(input.props.value).toEqual(props.value); - }); + it('should not render when config is disabled', () => { + const props = { ...defaultProps, config: feedbackRequirement.disabled }; + const { container } = renderWithIntl(); + expect(container.firstChild).toBeNull(); + }); - test('is having invalid feedback (feedback get render)', () => { - el = shallow(); - const feedbackErrorEl = el.instance.children[2]; - expect(feedbackErrorEl.props.type).toBe('invalid'); - expect(feedbackErrorEl.props.className).toBe('feedback-error-msg'); - expect(feedbackErrorEl).toBeTruthy(); - }); + it('should display feedback prompt in info popover', async () => { + const { getByText, getByTestId } = renderWithIntl(); + const user = userEvent.setup(); + const infoIcon = getByTestId('esg-help-icon'); + await user.click(infoIcon); + expect(getByText(defaultProps.value)).toBeInTheDocument(); + }); - test('is configure to disabled (this input does not get render)', () => { - el = shallow(); - expect(el.isEmptyRender()).toEqual(true); - }); - }); - describe('behavior', () => { - test('onChange set value', () => { - el = shallow(); - el.instance.children[1].props.onChange({ - target: { - value: 'some value', - }, - }); - expect(props.setValue).toBeCalledTimes(1); - }); - }); + it('should render textarea with correct value', () => { + renderWithIntl(); + const textarea = screen.getByRole('textbox'); + expect(textarea).toHaveValue(defaultProps.value); + }); + + it('should enable textarea when isGrading is true', () => { + renderWithIntl(); + const textarea = screen.getByRole('textbox'); + expect(textarea).not.toBeDisabled(); + }); + + it('should disable textarea when isGrading is false', () => { + const props = { ...defaultProps, isGrading: false, gradeStatus: gradeStatuses.graded }; + renderWithIntl(); + const textarea = screen.getByRole('textbox'); + expect(textarea).toBeDisabled(); + }); + + it('should display error message when isInvalid is true', () => { + const props = { ...defaultProps, isInvalid: true }; + const { getByText } = renderWithIntl(); + expect(getByText('The overall feedback is required')).toBeInTheDocument(); + }); + + it('should not display error message when isInvalid is false', () => { + const { queryByText } = renderWithIntl(); + expect(queryByText('The overall feedback is required')).not.toBeInTheDocument(); + }); + + it('should call setValue when textarea value changes', async () => { + renderWithIntl(); + const user = userEvent.setup(); + const textarea = screen.getByRole('textbox'); + await user.clear(textarea); + expect(defaultProps.setValue).toHaveBeenCalledWith(''); }); describe('mapStateToProps', () => { - const testState = { arbitraryState: 'some data' }; - let mapped; - beforeEach(() => { - mapped = mapStateToProps(testState); - }); - test('selectors.grading.selected.isGrading', () => { + it('should map state properties correctly', () => { + const testState = { arbitraryState: 'some data' }; + const mapped = mapStateToProps(testState); + + expect(selectors.grading.selected.isGrading).toHaveBeenCalledWith(testState); + expect(selectors.app.rubric.feedbackConfig).toHaveBeenCalledWith(testState); + expect(selectors.grading.selected.overallFeedback).toHaveBeenCalledWith(testState); + expect(selectors.grading.validation.overallFeedbackIsInvalid).toHaveBeenCalledWith(testState); + expect(selectors.app.rubric.feedbackPrompt).toHaveBeenCalledWith(testState); + expect(mapped.isGrading).toEqual(selectors.grading.selected.isGrading(testState)); - }); - - test('selectors.app.rubricFeedbackConfig', () => { - expect(mapped.config).toEqual( - selectors.app.rubric.feedbackConfig(testState), - ); - }); - - test('selectors.grading.selected.overallFeedback', () => { - expect(mapped.value).toEqual( - selectors.grading.selected.overallFeedback(testState), - ); - }); - - test('selectors.grading.validation.overallFeedbackIsInvalid', () => { - expect(mapped.isInvalid).toEqual( - selectors.grading.validation.overallFeedbackIsInvalid(testState), - ); - }); - - test('selectors.app.rubric.feedbackPrompt', () => { - expect(mapped.feedbackPrompt).toEqual( - selectors.app.rubric.feedbackPrompt(testState), - ); + expect(mapped.config).toEqual(selectors.app.rubric.feedbackConfig(testState)); + expect(mapped.value).toEqual(selectors.grading.selected.overallFeedback(testState)); + expect(mapped.isInvalid).toEqual(selectors.grading.validation.overallFeedbackIsInvalid(testState)); + expect(mapped.feedbackPrompt).toEqual(selectors.app.rubric.feedbackPrompt(testState)); }); }); describe('mapDispatchToProps', () => { - test('maps actions.grading.setRubricFeedback to setValue prop', () => { - expect(mapDispatchToProps.setValue).toEqual( - actions.grading.setRubricFeedback, - ); + it('should map setValue to setRubricFeedback action', () => { + expect(mapDispatchToProps.setValue).toEqual(actions.grading.setRubricFeedback); }); }); }); diff --git a/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap b/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap deleted file mode 100644 index 71f8203..0000000 --- a/src/containers/Rubric/__snapshots__/RubricFeedback.test.jsx.snap +++ /dev/null @@ -1,106 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Rubric Feedback component snapshot feedback value is invalid 1`] = ` - - - - - - -
- feedback prompt -
-
-
- - - - -
-`; - -exports[`Rubric Feedback component snapshot is configure to disabled 1`] = `null`; - -exports[`Rubric Feedback component snapshot is graded 1`] = ` - - - - - - -
- feedback prompt -
-
-
- -
-`; - -exports[`Rubric Feedback component snapshot is grading 1`] = ` - - - - - - -
- feedback prompt -
-
-
- -
-`;