test: deprecate react-unit-test-utils part-3 (#430)

This commit is contained in:
Victor Navarro
2025-06-13 05:31:43 -06:00
committed by GitHub
parent b54e8ffc85
commit ec3c25f54a
17 changed files with 326 additions and 886 deletions

View File

@@ -1,4 +1,4 @@
import { render } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import { ConfirmModal } from './ConfirmModal';
jest.unmock('@openedx/paragon');
@@ -14,12 +14,30 @@ describe('ConfirmModal', () => {
onCancel: jest.fn().mockName('this.props.onCancel'),
onConfirm: jest.fn().mockName('this.props.onConfirm'),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should not render content when modal is closed', () => {
const { queryByText } = render(<ConfirmModal {...props} />);
expect(queryByText(props.content)).toBeNull();
});
it('should display content when modal is open', () => {
const { getByText } = render(<ConfirmModal {...props} isOpen />);
expect(getByText(props.content)).toBeInTheDocument();
});
it('should call onCancel when cancel button is clicked', () => {
render(<ConfirmModal {...props} isOpen />);
fireEvent.click(screen.getByText(props.cancelText));
expect(props.onCancel).toHaveBeenCalledTimes(1);
});
it('should call onConfirm when confirm button is clicked', () => {
render(<ConfirmModal {...props} isOpen />);
fireEvent.click(screen.getByText(props.confirmText));
expect(props.onConfirm).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,20 +1,29 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { Document, Page } from 'react-pdf';
import { render } from '@testing-library/react';
import PropTypes from 'prop-types';
import PDFRenderer from './PDFRenderer';
import * as hooks from './pdfHooks';
jest.mock('react-pdf', () => ({
pdfjs: { GlobalWorkerOptions: {} },
Document: () => 'Document',
Page: () => 'Page',
Document: jest.fn(),
Page: jest.fn(),
}));
Document.mockImplementation((props) => <div data-testid="pdf-document">{props.children}</div>);
Document.propTypes = {
children: PropTypes.node,
};
Page.mockImplementation(() => <div data-testid="pdf-page">Page Content</div>);
jest.mock('./pdfHooks', () => ({
rendererHooks: jest.fn(),
}));
jest.unmock('@openedx/paragon');
jest.unmock('react');
describe('PDF Renderer Component', () => {
const props = {
url: 'some_url.pdf',
@@ -33,25 +42,45 @@ describe('PDF Renderer Component', () => {
onNextPageButtonClick: jest.fn().mockName('hooks.onNextPageButtonClick'),
onPrevPageButtonClick: jest.fn().mockName('hooks.onPrevPageButtonClick'),
hasNext: true,
hasPref: false,
hasPrev: false,
};
beforeEach(() => {
jest.clearAllMocks();
});
describe('snapshots', () => {
test('first page, prev is disabled', () => {
describe('rendering', () => {
it('should render the PDF document with navigation controls', () => {
hooks.rendererHooks.mockReturnValue(hookProps);
expect(shallow(<PDFRenderer {...props} />).snapshot).toMatchSnapshot();
const { getByTestId, getAllByText, container } = render(<PDFRenderer {...props} />);
expect(getByTestId('pdf-document')).toBeInTheDocument();
expect(getByTestId('pdf-page')).toBeInTheDocument();
expect(container.querySelector('input[type="number"]')).toBeInTheDocument();
expect(getAllByText(/Page/).length).toBeGreaterThan(0);
expect(getAllByText(`of ${hookProps.numPages}`).length).toBeGreaterThan(0);
});
test('on last page, next is disabled', () => {
it('should have disabled previous button when on the first page', () => {
hooks.rendererHooks.mockReturnValue({
...hookProps,
hasPrev: false,
});
const { container } = render(<PDFRenderer {...props} />);
const prevButton = container.querySelector('button[aria-label="previous pdf page"]');
expect(prevButton).toBeDisabled();
});
it('should have disabled next button when on the last page', () => {
hooks.rendererHooks.mockReturnValue({
...hookProps,
pageNumber: hookProps.numPages,
hasNext: false,
hasPrev: true,
});
expect(shallow(<PDFRenderer {...props} />).snapshot).toMatchSnapshot();
const { container } = render(<PDFRenderer {...props} />);
const nextButton = container.querySelector('button[aria-label="next pdf page"]');
expect(nextButton).toBeDisabled();
});
});
});

View File

@@ -1,23 +1,41 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { render } from '@testing-library/react';
import TXTRenderer from './TXTRenderer';
jest.mock('./textHooks', () => {
const content = 'test-content';
const mockRendererHooks = jest.fn().mockReturnValue({ content: 'test-content' });
return {
content,
rendererHooks: (args) => ({ content, rendererHooks: args }),
rendererHooks: mockRendererHooks,
};
});
jest.unmock('@openedx/paragon');
jest.unmock('react');
const textHooks = require('./textHooks');
describe('TXT Renderer Component', () => {
const props = {
url: 'some_url.txt',
onError: jest.fn().mockName('this.props.onError'),
onSuccess: jest.fn().mockName('this.props.onSuccess'),
};
test('snapshot', () => {
expect(shallow(<TXTRenderer {...props} />).snapshot).toMatchSnapshot();
beforeEach(() => {
textHooks.rendererHooks.mockClear();
});
it('renders the text content in a pre element', () => {
const { getByText, container } = render(<TXTRenderer {...props} />);
expect(getByText('test-content')).toBeInTheDocument();
expect(container.querySelector('pre')).toHaveClass('txt-renderer');
});
it('passes the correct props to rendererHooks', () => {
render(<TXTRenderer {...props} />);
expect(textHooks.rendererHooks).toHaveBeenCalledWith({
url: props.url,
onError: props.onError,
onSuccess: props.onSuccess,
});
});
});

View File

@@ -1,137 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PDF Renderer Component snapshots first page, prev is disabled 1`] = `
<div
className="pdf-renderer"
>
<Document
file="some_url.pdf"
onLoadError={[MockFunction hooks.onDocumentLoadError]}
onLoadSuccess={[MockFunction hooks.onDocumentLoadSuccess]}
>
<div
className="page-wrapper"
style={
{
"height": 200,
}
}
>
<Page
onLoadSuccess={[MockFunction hooks.onLoadPageSuccess]}
pageNumber={1}
/>
</div>
</Document>
<ActionRow
className="d-flex justify-content-center m-0"
>
<IconButton
alt="previous pdf page"
disabled={true}
iconAs="Icon"
onClick={[MockFunction hooks.onPrevPageButtonClick]}
size="inline"
src={[MockFunction icons.ChevronLeft]}
/>
<Form.Group
className="d-flex align-items-center m-0"
>
<Form.Label
isInline={true}
>
Page
</Form.Label>
<Form.Control
max={10}
min={0}
onChange={[MockFunction hooks.onInputPageChange]}
type="number"
value={1}
/>
<Form.Label
isInline={true}
>
of
10
</Form.Label>
</Form.Group>
<IconButton
alt="next pdf page"
disabled={false}
iconAs="Icon"
onClick={[MockFunction hooks.onNextPageButtonClick]}
size="inline"
src={[MockFunction icons.ChevronRight]}
/>
</ActionRow>
</div>
`;
exports[`PDF Renderer Component snapshots on last page, next is disabled 1`] = `
<div
className="pdf-renderer"
>
<Document
file="some_url.pdf"
onLoadError={[MockFunction hooks.onDocumentLoadError]}
onLoadSuccess={[MockFunction hooks.onDocumentLoadSuccess]}
>
<div
className="page-wrapper"
style={
{
"height": 200,
}
}
>
<Page
onLoadSuccess={[MockFunction hooks.onLoadPageSuccess]}
pageNumber={10}
/>
</div>
</Document>
<ActionRow
className="d-flex justify-content-center m-0"
>
<IconButton
alt="previous pdf page"
disabled={false}
iconAs="Icon"
onClick={[MockFunction hooks.onPrevPageButtonClick]}
size="inline"
src={[MockFunction icons.ChevronLeft]}
/>
<Form.Group
className="d-flex align-items-center m-0"
>
<Form.Label
isInline={true}
>
Page
</Form.Label>
<Form.Control
max={10}
min={0}
onChange={[MockFunction hooks.onInputPageChange]}
type="number"
value={10}
/>
<Form.Label
isInline={true}
>
of
10
</Form.Label>
</Form.Group>
<IconButton
alt="next pdf page"
disabled={true}
iconAs="Icon"
onClick={[MockFunction hooks.onNextPageButtonClick]}
size="inline"
src={[MockFunction icons.ChevronRight]}
/>
</ActionRow>
</div>
`;

View File

@@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TXT Renderer Component snapshot 1`] = `
<pre
className="txt-renderer"
>
test-content
</pre>
`;

View File

@@ -1,14 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Head snapshot 1`] = `
<Helmet>
<title>
ORA staff grading | site-name
</title>
<link
href="favicon-url"
rel="shortcut icon"
type="image/x-icon"
/>
</Helmet>
`;

View File

@@ -1,25 +1,48 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { shallow } from '@edx/react-unit-test-utils';
import { render } from '@testing-library/react';
import { Helmet } from 'react-helmet';
import Head from '.';
jest.mock('react-helmet', () => ({
Helmet: 'Helmet',
jest.mock('@edx/frontend-platform/i18n', () => ({
useIntl: () => ({
formatMessage: (message, values) => {
if (message.defaultMessage && values) {
return message.defaultMessage.replace('{siteName}', values.siteName);
}
return message.defaultMessage || message.id;
},
}),
defineMessages: (messages) => messages,
}));
jest.mock('react-helmet', () => ({
Helmet: jest.fn(),
}));
Helmet.mockImplementation(({ children }) => <div data-testid="helmet-mock">{children}</div>);
jest.mock('@edx/frontend-platform', () => ({
getConfig: () => ({
getConfig: jest.fn().mockReturnValue({
SITE_NAME: 'site-name',
FAVICON_URL: 'favicon-url',
}),
}));
describe('Head', () => {
it('snapshot', () => {
const el = shallow(<Head />);
expect(el.snapshot).toMatchSnapshot();
jest.unmock('@openedx/paragon');
jest.unmock('react');
expect(el.instance.findByType('title')[0].el.children[0]).toContain(getConfig().SITE_NAME);
expect(el.instance.findByType('link')[0].props.href).toEqual(getConfig().FAVICON_URL);
describe('Head', () => {
it('should render page title with site name from config', () => {
const { container } = render(<Head />);
const titleElement = container.querySelector('title');
expect(titleElement).toBeInTheDocument();
expect(titleElement.textContent).toContain('ORA staff grading | site-name');
});
it('should render favicon link with URL from config', () => {
const { container } = render(<Head />);
const faviconLink = container.querySelector('link[rel="shortcut icon"]');
expect(faviconLink).toBeInTheDocument();
expect(faviconLink.getAttribute('href')).toEqual('favicon-url');
expect(faviconLink.getAttribute('type')).toEqual('image/x-icon');
});
});

View File

@@ -1,30 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Info Popover Component snapshot 1`] = `
<OverlayTrigger
flip={true}
overlay={
<Popover
className="overlay-help-popover"
id="info-popover"
>
<Popover.Content>
<div>
Children component
</div>
</Popover.Content>
</Popover>
}
placement="right-end"
trigger="focus"
>
<IconButton
alt="Display more info"
className="esg-help-icon"
data-testid="esg-help-icon"
iconAs="Icon"
onClick={[MockFunction this.props.onClick]}
src={[MockFunction icons.InfoOutline]}
/>
</OverlayTrigger>
`;

View File

@@ -1,23 +1,32 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { render, fireEvent } from '@testing-library/react';
import { formatMessage } from 'testUtils';
import { InfoPopover } from '.';
jest.unmock('@openedx/paragon');
jest.unmock('react');
describe('Info Popover Component', () => {
const child = <div>Children component</div>;
const onClick = jest.fn().mockName('this.props.onClick');
let el;
beforeEach(() => {
el = shallow(<InfoPopover onClick={onClick} intl={{ formatMessage }}>{child}</InfoPopover>);
});
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
});
describe('Component', () => {
test('Test component render', () => {
expect(el.instance.children.length).toEqual(1);
expect(el.instance.findByTestId('esg-help-icon').length).toEqual(1);
it('renders the help icon button', () => {
const { getByTestId } = render(
<InfoPopover onClick={onClick} intl={{ formatMessage }}>
{child}
</InfoPopover>,
);
expect(getByTestId('esg-help-icon')).toBeInTheDocument();
});
it('calls onClick when the help icon is clicked', () => {
const { getByTestId } = render(
<InfoPopover onClick={onClick} intl={{ formatMessage }}>
{child}
</InfoPopover>,
);
fireEvent.click(getByTestId('esg-help-icon'));
expect(onClick).toHaveBeenCalled();
});
});
});

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { render, fireEvent } from '@testing-library/react';
import { actions, selectors } from 'data/redux';
import {
@@ -12,7 +12,6 @@ import {
mapStateToProps,
mapDispatchToProps,
} from './CriterionFeedback';
import messages from './messages';
jest.mock('data/redux/app/selectors', () => ({
rubric: {
@@ -34,6 +33,9 @@ jest.mock('data/redux/grading/selectors', () => ({
},
}));
jest.unmock('@openedx/paragon');
jest.unmock('react');
describe('Criterion Feedback', () => {
const props = {
intl: { formatMessage },
@@ -45,110 +47,49 @@ describe('Criterion Feedback', () => {
setValue: jest.fn().mockName('this.props.setValue'),
isInvalid: false,
};
let el;
beforeEach(() => {
el = shallow(<CriterionFeedback {...props} />);
el.instance.onChange = jest.fn().mockName('this.onChange');
});
describe('snapshot', () => {
test('is grading', () => {
expect(el.snapshot).toMatchSnapshot();
});
test('is graded', () => {
el = shallow(<CriterionFeedback {...props} isGrading={false} gradeStatus={gradeStatuses.graded} />);
expect(el.snapshot).toMatchSnapshot();
});
test('feedback value is invalid', () => {
el = shallow(<CriterionFeedback {...props} isInvalid />);
expect(el.snapshot).toMatchSnapshot();
});
Object.values(feedbackRequirement).forEach((requirement) => {
test(`feedback is configured to ${requirement}`, () => {
el = shallow(<CriterionFeedback {...props} config={requirement} />);
expect(el.snapshot).toMatchSnapshot();
});
});
});
describe('component', () => {
describe('render', () => {
test('is grading (the feedback input is not disabled)', () => {
expect(el.isEmptyRender()).toEqual(false);
const controlEl = el.instance.findByTestId('criterion-feedback-input')[0];
expect(controlEl.props.disabled).toEqual(false);
expect(controlEl.props.value).toEqual(props.value);
it('shows a non-disabled input when grading', () => {
const { getByTestId } = render(<CriterionFeedback {...props} />);
const input = getByTestId('criterion-feedback-input');
expect(input).toBeInTheDocument();
expect(input).not.toBeDisabled();
expect(input).toHaveValue(props.value);
});
test('is graded (the input is disabled)', () => {
el = shallow(<CriterionFeedback {...props} isGrading={false} gradeStatus={gradeStatuses.graded} />);
const controlEl = el.instance.findByTestId('criterion-feedback-input')[0];
expect(controlEl.props.disabled).toEqual(true);
expect(controlEl.props.value).toEqual(props.value);
it('shows a disabled input when not grading', () => {
const { getByTestId } = render(
<CriterionFeedback {...props} isGrading={false} gradeStatus={gradeStatuses.graded} />,
);
const input = getByTestId('criterion-feedback-input');
expect(input).toBeInTheDocument();
expect(input).toBeDisabled();
expect(input).toHaveValue(props.value);
});
test('is having invalid feedback (feedback get render)', () => {
el = shallow(<CriterionFeedback {...props} isInvalid />);
const feedbackErrorEl = el.instance.findByTestId('criterion-feedback-error-msg');
expect(feedbackErrorEl).toBeDefined();
it('displays an error message when feedback is invalid', () => {
const { getByTestId } = render(<CriterionFeedback {...props} isInvalid />);
expect(getByTestId('criterion-feedback-error-msg')).toBeInTheDocument();
});
test('is configure to disabled (the input does not get render)', () => {
el = shallow(<CriterionFeedback {...props} config={feedbackRequirement.disabled} />);
expect(el.isEmptyRender()).toEqual(true);
it('does not render anything when config is set to disabled', () => {
const { container } = render(
<CriterionFeedback {...props} config={feedbackRequirement.disabled} />,
);
expect(container.firstChild).toBeNull();
});
});
describe('behavior', () => {
test('onChange call set value', () => {
el = shallow(<CriterionFeedback {...props} />);
el.instance.findByTestId('criterion-feedback-input')[0].props.onChange({
target: {
value: 'some value',
},
it('calls setValue when input value changes', () => {
const { getByTestId } = render(<CriterionFeedback {...props} />);
const input = getByTestId('criterion-feedback-input');
fireEvent.change(input, { target: { value: 'some value' } });
expect(props.setValue).toHaveBeenCalledWith({
value: 'some value',
orderNum: props.orderNum,
});
expect(props.setValue).toBeCalledTimes(1);
});
});
describe('getter commentMessage', () => {
test('is grading', () => {
let commentMessage;
el = shallow(<CriterionFeedback {...props} isGrading config={feedbackRequirement.optional} />);
commentMessage = el.instance.findByTestId('criterion-feedback-input')[0].props.floatingLabel;
expect(commentMessage).toContain(
messages.optional.defaultMessage,
);
el = shallow(<CriterionFeedback {...props} config={feedbackRequirement.required} />);
commentMessage = el.instance.findByTestId('criterion-feedback-input')[0].props.floatingLabel;
expect(commentMessage).not.toContain(
messages.optional.defaultMessage,
);
expect(commentMessage).toContain(
messages.addComments.defaultMessage,
);
});
test('is not grading', () => {
let commentMessage;
el = shallow(<CriterionFeedback {...props} isGrading={false} config={feedbackRequirement.optional} />);
commentMessage = el.instance.findByTestId('criterion-feedback-input')[0].props.floatingLabel;
expect(commentMessage).toContain(
messages.optional.defaultMessage,
);
el = shallow(<CriterionFeedback {...props} isGrading={false} config={feedbackRequirement.required} />);
commentMessage = el.instance.findByTestId('criterion-feedback-input')[0].props.floatingLabel;
expect(commentMessage).not.toContain(
messages.optional.defaultMessage,
);
expect(commentMessage).toContain(
messages.comments.defaultMessage,
);
});
});
});
@@ -160,17 +101,17 @@ describe('Criterion Feedback', () => {
beforeEach(() => {
mapped = mapStateToProps(testState, ownProps);
});
test('selectors.app.rubric.criterionFeedbackConfig', () => {
it('gets config from selectors.app.rubric.criterionFeedbackConfig', () => {
expect(mapped.config).toEqual(
selectors.app.rubric.criterionFeedbackConfig(testState, ownProps),
);
});
test('selector.grading.selected.criterionFeedback', () => {
it('gets value from selectors.grading.selected.criterionFeedback', () => {
expect(mapped.value).toEqual(
selectors.grading.selected.criterionFeedback(testState, ownProps),
);
});
test('selector.grading.validation.criterionFeedbackIsInvalid', () => {
it('gets isInvalid from selectors.grading.validation.criterionFeedbackIsInvalid', () => {
expect(mapped.isInvalid).toEqual(
selectors.grading.validation.criterionFeedbackIsInvalid(
testState,
@@ -181,7 +122,7 @@ describe('Criterion Feedback', () => {
});
describe('mapDispatchToProps', () => {
test('maps actions.grading.setCriterionFeedback to setValue prop', () => {
it('maps actions.grading.setCriterionFeedback to setValue prop', () => {
expect(mapDispatchToProps.setValue).toEqual(
actions.grading.setCriterionFeedback,
);

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { render } from '@testing-library/react';
import { actions, selectors } from 'data/redux';
import { formatMessage } from 'testUtils';
@@ -29,6 +28,9 @@ jest.mock('data/redux/grading/selectors', () => ({
},
}));
jest.unmock('@openedx/paragon');
jest.unmock('react');
describe('Radio Criterion Container', () => {
const props = {
intl: { formatMessage },
@@ -55,70 +57,47 @@ describe('Radio Criterion Container', () => {
},
],
},
data: 'selected radio option',
data: 'option name',
setCriterionOption: jest.fn().mockName('this.props.setCriterionOption'),
isInvalid: false,
};
let el;
beforeEach(() => {
el = shallow(<RadioCriterion {...props} />);
el.instance.onChange = jest.fn().mockName('this.onChange');
});
describe('snapshot', () => {
test('is grading', () => {
expect(el.snapshot).toMatchSnapshot();
});
describe('component rendering', () => {
it('should render radio buttons that are enabled when in grading mode', () => {
const { container } = render(<RadioCriterion {...props} />);
test('is not grading', () => {
el = shallow(<RadioCriterion {...props} isGrading={false} />);
expect(el.snapshot).toMatchSnapshot();
});
const radioButtons = container.querySelectorAll('input[type="radio"]');
expect(radioButtons.length).toEqual(props.config.options.length);
test('radio contain invalid response', () => {
el = shallow(<RadioCriterion {...props} isInvalid />);
expect(el.snapshot).toMatchSnapshot();
});
});
describe('component', () => {
describe('rendering', () => {
test('is grading (all options are not disabled)', () => {
expect(el.isEmptyRender()).toEqual(false);
const optionsEl = el.instance.children;
expect(optionsEl.length).toEqual(props.config.options.length);
optionsEl.forEach((optionEl) => expect(optionEl.props.disabled).toEqual(false));
});
test('is not grading (all options are disabled)', () => {
el = shallow(<RadioCriterion {...props} isGrading={false} />);
expect(el.isEmptyRender()).toEqual(false);
const optionsEl = el.instance.children;
expect(optionsEl.length).toEqual(props.config.options.length);
optionsEl.forEach((optionEl) => expect(optionEl.props.disabled).toEqual(true));
});
test('radio contain invalid response (error response get render)', () => {
el = shallow(<RadioCriterion {...props} isInvalid />);
expect(el.isEmptyRender()).toEqual(false);
const radioErrorEl = el.instance.children[2];
expect(radioErrorEl.props.type).toBe('invalid');
expect(radioErrorEl.props.className).toBe('feedback-error-msg');
expect(radioErrorEl).toBeTruthy();
radioButtons.forEach(button => {
expect(button).not.toBeDisabled();
});
});
describe('behavior', () => {
test('onChange call set crition option', () => {
el = shallow(<RadioCriterion {...props} />);
el.instance.children[0].props.onChange({
target: {
value: 'some value',
},
});
expect(props.setCriterionOption).toBeCalledTimes(1);
it('should render radio buttons that are disabled when not in grading mode', () => {
const { container } = render(<RadioCriterion {...props} isGrading={false} />);
const radioButtons = container.querySelectorAll('input[type="radio"]');
expect(radioButtons.length).toEqual(props.config.options.length);
radioButtons.forEach(button => {
expect(button).toBeDisabled();
});
});
it('should render an error message when the criterion is invalid', () => {
const { container } = render(<RadioCriterion {...props} isInvalid />);
const errorMessage = container.querySelector('.feedback-error-msg');
expect(errorMessage).toBeInTheDocument();
});
it('should not render an error message when the criterion is valid', () => {
const { container } = render(<RadioCriterion {...props} />);
const errorMessage = container.querySelector('.feedback-error-msg');
expect(errorMessage).not.toBeInTheDocument();
});
});
describe('mapStateToProps', () => {
@@ -128,18 +107,20 @@ describe('Radio Criterion Container', () => {
beforeEach(() => {
mapped = mapStateToProps(testState, ownProps);
});
test('selectors.app.rubric.criterionConfig', () => {
it('should properly map config from rubric criterion config selector', () => {
expect(mapped.config).toEqual(
selectors.app.rubric.criterionConfig(testState, ownProps),
);
});
test('selectors.grading.selected.criterionSelectedOption', () => {
it('should properly map data from selected criterion option selector', () => {
expect(mapped.data).toEqual(
selectors.grading.selected.criterionSelectedOption(testState, ownProps),
);
});
test('selectors.grading.validation.criterionSelectedOptionIsInvalid', () => {
it('should properly map isInvalid from criterion validation selector', () => {
expect(mapped.isInvalid).toEqual(
selectors.grading.validation.criterionSelectedOptionIsInvalid(testState, ownProps),
);
@@ -147,7 +128,7 @@ describe('Radio Criterion Container', () => {
});
describe('mapDispatchToProps', () => {
test('maps actions.grading.setCriterionFeedback to setValue prop', () => {
it('should map setCriterionOption action to props', () => {
expect(mapDispatchToProps.setCriterionOption).toEqual(
actions.grading.setCriterionOption,
);

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { render } from '@testing-library/react';
import { selectors } from 'data/redux';
import { ReviewCriterion, mapStateToProps } from './ReviewCriterion';
import messages from './messages';
jest.mock('data/redux/app/selectors', () => ({
rubric: {
@@ -20,7 +19,10 @@ jest.mock('data/redux/grading/selectors', () => ({
},
}));
describe('Review Crition Container', () => {
jest.unmock('@openedx/paragon');
jest.unmock('react');
describe('Review Criterion Container', () => {
const props = {
orderNum: 1,
config: {
@@ -50,29 +52,20 @@ describe('Review Crition Container', () => {
},
};
let el;
beforeEach(() => {
el = shallow(<ReviewCriterion {...props} />);
});
test('snapshot', () => {
expect(el.snapshot).toMatchSnapshot();
});
describe('component', () => {
test('rendering (everything show up)', () => {
expect(el.isEmptyRender()).toEqual(false);
const optionsEl = el.instance.findByTestId('criteria-option');
expect(optionsEl.length).toEqual(props.config.options.length);
optionsEl.forEach((optionEl, i) => {
const option = props.config.options[i];
expect(optionEl.props.key).toEqual(option.name);
expect(optionEl.findByTestId('option-label')[0].children[0].el).toEqual(
option.label,
);
expect(optionEl.findByTestId('option-points')[0].children[0].props).toEqual({
...messages.optionPoints,
values: { points: option.points },
});
it('renders all criteria options with correct labels and points', () => {
const { getAllByTestId } = render(<ReviewCriterion {...props} />);
const optionsElements = getAllByTestId('criteria-option');
expect(optionsElements.length).toEqual(props.config.options.length);
props.config.options.forEach((option, index) => {
const optionElement = optionsElements[index];
const labelElement = optionElement.querySelector('[data-testid="option-label"]');
const pointsElement = optionElement.querySelector('[data-testid="option-points"]');
expect(labelElement.textContent).toEqual(option.label);
expect(pointsElement.textContent).toEqual('FormattedMessage');
});
});
});
@@ -81,16 +74,18 @@ describe('Review Crition Container', () => {
const testState = { arbitrary: 'some data' };
const ownProps = { orderNum: props.orderNum };
let mapped;
beforeEach(() => {
mapped = mapStateToProps(testState, ownProps);
});
test('selectors.app.rubric.criterionConfig', () => {
it('should map criterion config from state', () => {
expect(mapped.config).toEqual(
selectors.app.rubric.criterionConfig(testState, ownProps),
);
});
test('selectors.grading.selected.criterionGradeData', () => {
it('should map criterion grade data from state', () => {
expect(mapped.data).toEqual(
selectors.grading.selected.criterionGradeData(testState, ownProps),
);

View File

@@ -1,80 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Criterion Feedback snapshot feedback is configured to disabled 1`] = `null`;
exports[`Criterion Feedback snapshot feedback is configured to optional 1`] = `
<Form.Group>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
disabled={false}
floatingLabel="Add comments (Optional)"
onChange={[Function]}
value="criterion value"
/>
</Form.Group>
`;
exports[`Criterion Feedback snapshot feedback is configured to required 1`] = `
<Form.Group>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
disabled={false}
floatingLabel="Add comments"
onChange={[Function]}
value="criterion value"
/>
</Form.Group>
`;
exports[`Criterion Feedback snapshot feedback value is invalid 1`] = `
<Form.Group>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
disabled={false}
floatingLabel="Add comments"
onChange={[Function]}
value="criterion value"
/>
<Form.Control.Feedback
className="feedback-error-msg"
data-testid="criterion-feedback-error-msg"
type="invalid"
>
The feedback is required
</Form.Control.Feedback>
</Form.Group>
`;
exports[`Criterion Feedback snapshot is graded 1`] = `
<Form.Group>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
disabled={true}
floatingLabel="Comments"
onChange={[Function]}
value="criterion value"
/>
</Form.Group>
`;
exports[`Criterion Feedback snapshot is grading 1`] = `
<Form.Group>
<Form.Control
as="textarea"
className="criterion-feedback feedback-input"
data-testid="criterion-feedback-input"
disabled={false}
floatingLabel="Add comments"
onChange={[Function]}
value="criterion value"
/>
</Form.Group>
`;

View File

@@ -1,121 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Radio Criterion Container snapshot is grading 1`] = `
<Form.RadioSet
name="random name"
value="selected radio option"
>
<Form.Radio
className="criteria-option align-items-center"
description="1 points"
disabled={false}
key="option name"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name"
>
this label
</Form.Radio>
<Form.Radio
className="criteria-option align-items-center"
description="2 points"
disabled={false}
key="option name 2"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name 2"
>
this label 2
</Form.Radio>
</Form.RadioSet>
`;
exports[`Radio Criterion Container snapshot is not grading 1`] = `
<Form.RadioSet
name="random name"
value="selected radio option"
>
<Form.Radio
className="criteria-option align-items-center"
description="1 points"
disabled={true}
key="option name"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name"
>
this label
</Form.Radio>
<Form.Radio
className="criteria-option align-items-center"
description="2 points"
disabled={true}
key="option name 2"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name 2"
>
this label 2
</Form.Radio>
</Form.RadioSet>
`;
exports[`Radio Criterion Container snapshot radio contain invalid response 1`] = `
<Form.RadioSet
name="random name"
value="selected radio option"
>
<Form.Radio
className="criteria-option align-items-center"
description="1 points"
disabled={false}
key="option name"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name"
>
this label
</Form.Radio>
<Form.Radio
className="criteria-option align-items-center"
description="2 points"
disabled={false}
key="option name 2"
onChange={[Function]}
style={
{
"flexShrink": 0,
}
}
value="option name 2"
>
this label 2
</Form.Radio>
<Form.Control.Feedback
className="feedback-error-msg"
type="invalid"
>
Rubric selection is required
</Form.Control.Feedback>
</Form.RadioSet>
`;

View File

@@ -1,66 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Review Crition Container snapshot 1`] = `
<div
className="review-criterion"
>
<div
className="criteria-option"
data-testid="criteria-option"
key="option name"
>
<div>
<Form.Label
className="option-label"
data-testid="option-label"
>
this label
</Form.Label>
<FormControlFeedback
className="option-points"
data-testid="option-points"
>
<FormattedMessage
defaultMessage="{points} points"
description="criterion option point value display"
id="ora-grading.RadioCriterion.optionPoints"
values={
{
"points": 1,
}
}
/>
</FormControlFeedback>
</div>
</div>
<div
className="criteria-option"
data-testid="criteria-option"
key="option name 2"
>
<div>
<Form.Label
className="option-label"
data-testid="option-label"
>
this label 2
</Form.Label>
<FormControlFeedback
className="option-points"
data-testid="option-points"
>
<FormattedMessage
defaultMessage="{points} points"
description="criterion option point value display"
id="ora-grading.RadioCriterion.optionPoints"
values={
{
"points": 2,
}
}
/>
</FormControlFeedback>
</div>
</div>
</div>
`;

View File

@@ -1,153 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Criterion Container snapshot is graded and is not grading 1`] = `
<Form.Group>
<Form.Label
className="criteria-label"
>
<span
className="criteria-title"
>
prompt
</span>
<InfoPopover>
<div
className="help-popover-option"
data-testid="help-popover-option"
key="option name"
>
<strong>
this label
</strong>
<br />
explanation
</div>
<div
className="help-popover-option"
data-testid="help-popover-option"
key="option name 2"
>
<strong>
this label 2
</strong>
<br />
explanation 2
</div>
</InfoPopover>
</Form.Label>
<div
className="rubric-criteria"
data-testid="rubric-criteria"
>
<RadioCriterion
isGrading={false}
orderNum={1}
/>
</div>
<CriterionFeedback
isGrading={false}
orderNum={1}
/>
</Form.Group>
`;
exports[`Criterion Container snapshot is ungraded and is grading 1`] = `
<Form.Group>
<Form.Label
className="criteria-label"
>
<span
className="criteria-title"
>
prompt
</span>
<InfoPopover>
<div
className="help-popover-option"
data-testid="help-popover-option"
key="option name"
>
<strong>
this label
</strong>
<br />
explanation
</div>
<div
className="help-popover-option"
data-testid="help-popover-option"
key="option name 2"
>
<strong>
this label 2
</strong>
<br />
explanation 2
</div>
</InfoPopover>
</Form.Label>
<div
className="rubric-criteria"
data-testid="rubric-criteria"
>
<RadioCriterion
isGrading={true}
orderNum={1}
/>
</div>
<CriterionFeedback
isGrading={true}
orderNum={1}
/>
</Form.Group>
`;
exports[`Criterion Container snapshot is ungraded and is not grading 1`] = `
<Form.Group>
<Form.Label
className="criteria-label"
>
<span
className="criteria-title"
>
prompt
</span>
<InfoPopover>
<div
className="help-popover-option"
data-testid="help-popover-option"
key="option name"
>
<strong>
this label
</strong>
<br />
explanation
</div>
<div
className="help-popover-option"
data-testid="help-popover-option"
key="option name 2"
>
<strong>
this label 2
</strong>
<br />
explanation 2
</div>
</InfoPopover>
</Form.Label>
<div
className="rubric-criteria"
data-testid="rubric-criteria"
>
<ReviewCriterion
orderNum={1}
/>
</div>
<CriterionFeedback
isGrading={false}
orderNum={1}
/>
</Form.Group>
`;

View File

@@ -1,15 +1,53 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { render, screen } from '@testing-library/react';
import PropTypes from 'prop-types';
import { selectors } from 'data/redux';
import { gradeStatuses } from 'data/services/lms/constants';
import { CriterionContainer, mapStateToProps } from '.';
jest.mock('components/InfoPopover', () => 'InfoPopover');
jest.mock('./RadioCriterion', () => 'RadioCriterion');
jest.mock('./CriterionFeedback', () => 'CriterionFeedback');
jest.mock('./ReviewCriterion', () => 'ReviewCriterion');
jest.unmock('@openedx/paragon');
jest.unmock('react');
const MockRadioCriterion = ({ orderNum, isGrading }) => (
<div data-testid="radio-criterion-component">
RadioCriterion Component (orderNum={orderNum}, isGrading={String(isGrading)})
</div>
);
MockRadioCriterion.propTypes = {
orderNum: PropTypes.number.isRequired,
isGrading: PropTypes.bool.isRequired,
};
const MockReviewCriterion = ({ orderNum }) => (
<div data-testid="review-criterion-component">
ReviewCriterion Component (orderNum={orderNum})
</div>
);
MockReviewCriterion.propTypes = {
orderNum: PropTypes.number.isRequired,
};
const MockCriterionFeedback = ({ orderNum, isGrading }) => (
<div data-testid="criterion-feedback-component">
CriterionFeedback Component (orderNum={orderNum}, isGrading={String(isGrading)})
</div>
);
MockCriterionFeedback.propTypes = {
orderNum: PropTypes.number.isRequired,
isGrading: PropTypes.bool.isRequired,
};
const MockInfoPopover = ({ children }) => (
<div data-testid="info-popover">{children}</div>
);
MockInfoPopover.propTypes = {
children: PropTypes.node.isRequired,
};
jest.mock('data/redux/app/selectors', () => ({
rubric: {
@@ -18,12 +56,18 @@ jest.mock('data/redux/app/selectors', () => ({
})),
},
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
gradeStatus: jest.fn((...args) => ({ selectedGradeStatus: args })),
},
}));
jest.mock('./RadioCriterion', () => jest.fn((props) => MockRadioCriterion(props)));
jest.mock('./ReviewCriterion', () => jest.fn((props) => MockReviewCriterion(props)));
jest.mock('./CriterionFeedback', () => jest.fn((props) => MockCriterionFeedback(props)));
jest.mock('components/InfoPopover', () => jest.fn((props) => MockInfoPopover(props)));
describe('Criterion Container', () => {
const props = {
isGrading: true,
@@ -51,53 +95,43 @@ describe('Criterion Container', () => {
},
gradeStatus: gradeStatuses.ungraded,
};
let el;
beforeEach(() => {
el = shallow(<CriterionContainer {...props} />);
});
describe('snapshot', () => {
test('is ungraded and is grading', () => {
expect(el.snapshot).toMatchSnapshot();
describe('component rendering', () => {
it('displays the criterion prompt', () => {
render(<CriterionContainer {...props} />);
expect(screen.getByText('prompt')).toBeInTheDocument();
});
test('is ungraded and is not grading', () => {
el = shallow(<CriterionContainer {...props} isGrading={false} />);
expect(el.snapshot).toMatchSnapshot();
it('displays all option explanations in the info popover', () => {
render(<CriterionContainer {...props} />);
const infoPopover = screen.getByTestId('info-popover');
expect(infoPopover).toHaveTextContent('explanation');
expect(infoPopover).toHaveTextContent('explanation 2');
expect(infoPopover).toHaveTextContent('this label');
expect(infoPopover).toHaveTextContent('this label 2');
});
test('is graded and is not grading', () => {
el = shallow(<CriterionContainer {...props} isGrading={false} gradeStatus={gradeStatuses.graded} />);
expect(el.snapshot).toMatchSnapshot();
});
});
describe('component', () => {
test('rendering and all of the option show up', () => {
expect(el.isEmptyRender()).toEqual(false);
const optionsEl = el.instance.findByTestId('help-popover-option');
expect(optionsEl.length).toEqual(props.config.options.length);
optionsEl.forEach((optionEl, i) => {
expect(optionEl.props.key).toEqual(props.config.options[i].name);
expect(optionEl.children[2].el).toContain(props.config.options[i].explanation);
});
it('renders RadioCriterion when is ungraded and is grading', () => {
render(<CriterionContainer {...props} />);
expect(screen.getByTestId('radio-criterion-component')).toBeInTheDocument();
expect(screen.queryByTestId('review-criterion-component')).not.toBeInTheDocument();
});
test('is ungraded and is grading (Radio criterion get render)', () => {
const rubricCriteria = el.instance.findByTestId('rubric-criteria')[0];
expect(rubricCriteria.children[0].el.type).toEqual('RadioCriterion');
it('renders ReviewCriterion when is ungraded and is not grading', () => {
render(<CriterionContainer {...props} isGrading={false} />);
expect(screen.getByTestId('review-criterion-component')).toBeInTheDocument();
expect(screen.queryByTestId('radio-criterion-component')).not.toBeInTheDocument();
});
test('is ungraded and is not grading (Review criterion get render)', () => {
el = shallow(<CriterionContainer {...props} isGrading={false} />);
const rubricCriteria = el.instance.findByTestId('rubric-criteria')[0];
expect(rubricCriteria.children[0].el.type).toEqual('ReviewCriterion');
it('renders RadioCriterion when is graded and is not grading', () => {
render(<CriterionContainer {...props} isGrading={false} gradeStatus={gradeStatuses.graded} />);
expect(screen.getByTestId('radio-criterion-component')).toBeInTheDocument();
expect(screen.queryByTestId('review-criterion-component')).not.toBeInTheDocument();
});
test('is graded and is not grading (Radio criterion get render)', () => {
el = shallow(<CriterionContainer {...props} isGrading={false} gradeStatus={gradeStatuses.graded} />);
const rubricCriteria = el.instance.findByTestId('rubric-criteria')[0];
expect(rubricCriteria.children[0].el.type).toEqual('RadioCriterion');
it('renders CriterionFeedback component', () => {
render(<CriterionContainer {...props} />);
expect(screen.getByTestId('criterion-feedback-component')).toBeInTheDocument();
});
});
@@ -105,16 +139,18 @@ describe('Criterion Container', () => {
const testState = { arbitraryState: 'some data' };
const ownProps = { orderNum: props.orderNum };
let mapped;
beforeEach(() => {
mapped = mapStateToProps(testState, ownProps);
});
test('selectors.app.rubric.criterionConfig', () => {
it('maps rubric criterion config to props', () => {
expect(mapped.config).toEqual(
selectors.app.rubric.criterionConfig(testState, ownProps),
);
});
test('selectors.grading.selected.gradeStatus', () => {
it('maps grading status to props', () => {
expect(mapped.gradeStatus).toEqual(
selectors.grading.selected.gradeStatus(testState),
);