test: replacing snapshot tests with RTL tests part 6 (#2173)

* test: replacing snapshot tests with rtl tests part 6

* fix: removing testing purposed text

* test: fixing test mocking issues
This commit is contained in:
jacobo-dominguez-wgu
2025-06-17 13:29:34 -06:00
committed by GitHub
parent fa9d66c5e5
commit 19f81cc05d
17 changed files with 287 additions and 825 deletions

View File

@@ -11,10 +11,6 @@ import { useMessageHandlers } from '..';
jest.useFakeTimers();
jest.mock('@edx/react-unit-test-utils', () => ({
useKeyedState: jest.fn(),
}));
jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
}));

View File

@@ -8,7 +8,7 @@ import {
Form,
} from '@openedx/paragon';
import { FeedbackOutline, DeleteOutline } from '@openedx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
import { selectors } from '../../../../../data/redux';
@@ -22,8 +22,6 @@ import ExpandableTextArea from '../../../../../sharedComponents/ExpandableTextAr
const AnswerOption = ({
answer,
hasSingleAnswer,
// injected
intl,
// redux
problemType,
images,
@@ -31,6 +29,7 @@ const AnswerOption = ({
blockId,
learningContextId,
}) => {
const intl = useIntl();
const dispatch = useDispatch();
const removeAnswer = hooks.removeAnswer({ answer, dispatch });
const setAnswer = hooks.setAnswer({ answer, hasSingleAnswer, dispatch });
@@ -151,8 +150,6 @@ const AnswerOption = ({
AnswerOption.propTypes = {
answer: answerOptionProps.isRequired,
hasSingleAnswer: PropTypes.bool.isRequired,
// injected
intl: intlShape.isRequired,
// redux
problemType: PropTypes.string.isRequired,
images: PropTypes.shape({}).isRequired,
@@ -171,4 +168,4 @@ export const mapStateToProps = (state) => ({
export const mapDispatchToProps = {};
export const AnswerOptionInternal = AnswerOption; // For testing only
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(memo(AnswerOption)));
export default connect(mapStateToProps, mapDispatchToProps)(memo(AnswerOption));

View File

@@ -1,101 +0,0 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { formatMessage } from '../../../../../testUtils';
import { selectors } from '../../../../../data/redux';
import { AnswerOptionInternal as AnswerOption, mapStateToProps } from './AnswerOption';
jest.mock('../../../../../data/redux', () => ({
__esModule: true,
default: jest.fn(),
selectors: {
problem: {
problemType: jest.fn(state => ({ problemType: state })),
},
app: {
images: jest.fn(state => ({ images: state })),
isLibrary: jest.fn(state => ({ isLibrary: state })),
learningContextId: jest.fn(state => ({ learningContextId: state })),
blockId: jest.fn(state => ({ blockId: state })),
},
},
thunkActions: {
video: {
importTranscripts: jest.fn(),
},
},
}));
describe('AnswerOption', () => {
const answerWithOnlyFeedback = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'some feedback',
};
const answerWithSelectedUnselectedFeedback = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
};
const answerRange = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: true,
};
const props = {
hasSingleAnswer: false,
answer: answerWithOnlyFeedback,
// inject
intl: { formatMessage },
// redux
problemType: 'multiplechoiceresponse',
images: {},
isLibrary: false,
learningContextId: 'course+org+run',
};
describe('render', () => {
test('snapshot: renders correct option with feedback', () => {
expect(shallow(<AnswerOption {...props} />).snapshot).toMatchSnapshot();
});
test('snapshot: renders correct option with selected unselected feedback', () => {
expect(shallow(<AnswerOption {...props} problemType="choiceresponse" answer={answerWithSelectedUnselectedFeedback} />).snapshot).toMatchSnapshot();
});
test('snapshot: renders correct option with numeric input problem', () => {
expect(shallow(<AnswerOption {...props} problemType="numericalresponse" />).snapshot).toMatchSnapshot();
});
test('snapshot: renders correct option with numeric input problem and answer range', () => {
expect(shallow(<AnswerOption {...props} problemType="numericalresponse" answer={answerRange} />).snapshot).toMatchSnapshot();
});
});
describe('mapStateToProps', () => {
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
test('problemType from problem.problemType', () => {
expect(
mapStateToProps(testState).problemType,
).toEqual(selectors.problem.problemType(testState));
});
test('images from app.images', () => {
expect(
mapStateToProps(testState).images,
).toEqual(selectors.app.images(testState));
});
test('learningContextId from app.learningContextId', () => {
expect(
mapStateToProps(testState).learningContextId,
).toEqual(selectors.app.learningContextId(testState));
});
test('isLibrary from app.isLibrary', () => {
expect(
mapStateToProps(testState).isLibrary,
).toEqual(selectors.app.isLibrary(testState));
});
});
});

View File

@@ -0,0 +1,110 @@
import React from 'react';
import { render, screen, initializeMocks } from '@src/testUtils';
import AnswerOption from './AnswerOption';
import * as hooks from './hooks';
import { selectors } from '../../../../../data/redux';
const { problem } = selectors;
jest.mock('../../../../../data/redux', () => ({
__esModule: true,
default: jest.fn(),
selectors: {
problem: {
problemType: jest.fn().mockReturnValue(''),
},
app: {
images: jest.fn(state => ({ images: state })),
isLibrary: jest.fn().mockReturnValue(true),
learningContextId: jest.fn(state => ({ learningContextId: state })),
blockId: jest.fn(state => ({ blockId: state })),
},
},
thunkActions: {
video: {
importTranscripts: jest.fn(),
},
},
}));
jest.mock('../../../../../sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea');
describe('AnswerOption', () => {
const answerWithOnlyFeedback = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'some feedback',
isAnswerRange: true,
};
const answerWithSelectedUnselectedFeedback = {
id: 'B',
title: 'Answer 2',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: false,
};
const answerRange = {
id: 'A',
title: 'Answer Range 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: true,
};
const props = {
hasSingleAnswer: false,
answer: answerWithOnlyFeedback,
// redux
problemType: 'multiplechoiceresponse',
images: {},
isLibrary: false,
learningContextId: 'course+org+run',
blockId: 'block-id',
};
beforeEach(() => {
jest.spyOn(hooks, 'removeAnswer').mockReturnValue(jest.fn());
jest.spyOn(hooks, 'setAnswer').mockReturnValue(jest.fn());
jest.spyOn(hooks, 'setAnswerTitle').mockReturnValue(jest.fn());
jest.spyOn(hooks, 'setSelectedFeedback').mockReturnValue(jest.fn());
jest.spyOn(hooks, 'setUnselectedFeedback').mockReturnValue(jest.fn());
jest.spyOn(hooks, 'useFeedback').mockReturnValue({
isFeedbackVisible: false,
toggleFeedback: jest.fn(),
});
initializeMocks();
});
test('renders correct option with feedback', () => {
jest.spyOn(problem, 'problemType').mockReturnValue('multiplechoiceresponse');
render(<AnswerOption {...props} />);
expect(screen.getByPlaceholderText('Enter an answer')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Delete answer' })).toBeInTheDocument();
});
test('renders correct option with selected unselected feedback', () => {
jest.spyOn(problem, 'problemType').mockReturnValue('choiceresponse');
const myProps = { ...props, answer: answerWithSelectedUnselectedFeedback };
render(<AnswerOption {...myProps} />);
expect(screen.getByText(answerWithSelectedUnselectedFeedback.id)).toBeInTheDocument();
});
test('renders correct option with optionresponse input problem', () => {
jest.spyOn(problem, 'problemType').mockReturnValue('optionresponse');
const myProps = { ...props };
render(<AnswerOption {...myProps} />);
expect(screen.getByRole('textbox')).toBeInTheDocument();
expect(screen.getByText(answerWithOnlyFeedback.title)).toBeInTheDocument();
});
test('renders correct option with numeric input problem and answer range', () => {
jest.spyOn(problem, 'problemType').mockReturnValue('numericalresponse');
const myProps = { ...props };
render(<AnswerOption {...myProps} answer={answerRange} />);
expect(screen.getByText(answerRange.title)).toBeInTheDocument();
expect(screen.getByRole('textbox')).toBeInTheDocument();
});
});

View File

@@ -1,342 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AnswerOption render snapshot: renders correct option with feedback 1`] = `
<Advanced
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<div
className="mr-1 d-flex"
>
<Checker
answer={
{
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
}
}
disabled={false}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="ml-1 flex-grow-1"
>
<ExpandableTextArea
error={false}
errorMessage={null}
id="answer-A"
images={{}}
isLibrary={false}
learningContextId="course+org+run"
placeholder="Enter an answer"
setContent={[Function]}
value="Answer 1"
/>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
{
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
}
}
images={{}}
intl={
{
"formatMessage": [Function],
}
}
isLibrary={false}
learningContextId="course+org+run"
problemType="multiplechoiceresponse"
setSelectedFeedback={[Function]}
setUnselectedFeedback={[Function]}
/>
</Body>
</div>
<div
className="d-flex flex-row flex-nowrap"
>
<Trigger
aria-label="Toggle feedback"
className="btn-icon btn-icon-primary btn-icon-md align-items-center"
>
<Icon
alt="Toggle feedback"
/>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;
exports[`AnswerOption render snapshot: renders correct option with numeric input problem 1`] = `
<Advanced
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<div
className="mr-1 d-flex"
>
<Checker
answer={
{
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
}
}
disabled={true}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="ml-1 flex-grow-1"
>
<Form.Control
as="textarea"
autoResize={true}
className="answer-option-textarea text-gray-500 small"
onChange={[Function]}
placeholder="Enter an answer"
rows={1}
value="Answer 1"
/>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
{
"correct": true,
"id": "A",
"selectedFeedback": "some feedback",
"title": "Answer 1",
}
}
images={{}}
intl={
{
"formatMessage": [Function],
}
}
isLibrary={false}
learningContextId="course+org+run"
problemType="numericalresponse"
setSelectedFeedback={[Function]}
setUnselectedFeedback={[Function]}
/>
</Body>
</div>
<div
className="d-flex flex-row flex-nowrap"
>
<Trigger
aria-label="Toggle feedback"
className="btn-icon btn-icon-primary btn-icon-md align-items-center"
>
<Icon
alt="Toggle feedback"
/>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;
exports[`AnswerOption render snapshot: renders correct option with numeric input problem and answer range 1`] = `
<Advanced
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<div
className="mr-1 d-flex"
>
<Checker
answer={
{
"correct": true,
"id": "A",
"isAnswerRange": true,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
disabled={true}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="ml-1 flex-grow-1"
>
<div>
<Form.Control
as="textarea"
autoResize={true}
className="answer-option-textarea text-gray-500 small"
onChange={[Function]}
placeholder="Enter an answer range"
rows={1}
value="Answer 1"
/>
<div
className="pgn__form-switch-helper-text"
>
<FormattedMessage
defaultMessage="Enter min and max values separated by a comma. Use a bracket to include the number next to it in the range, or a parenthesis to exclude the number. For example, to identify the correct answers as 5, 6, or 7, but not 8, specify [5,8)."
description="Helper text describing usage of answer ranges"
id="authoring.answerwidget.answer.answerRangeHelperText"
/>
</div>
</div>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
{
"correct": true,
"id": "A",
"isAnswerRange": true,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
images={{}}
intl={
{
"formatMessage": [Function],
}
}
isLibrary={false}
learningContextId="course+org+run"
problemType="numericalresponse"
setSelectedFeedback={[Function]}
setUnselectedFeedback={[Function]}
/>
</Body>
</div>
<div
className="d-flex flex-row flex-nowrap"
>
<Trigger
aria-label="Toggle feedback"
className="btn-icon btn-icon-primary btn-icon-md align-items-center"
>
<Icon
alt="Toggle feedback"
/>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;
exports[`AnswerOption render snapshot: renders correct option with selected unselected feedback 1`] = `
<Advanced
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<div
className="mr-1 d-flex"
>
<Checker
answer={
{
"correct": true,
"id": "A",
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
disabled={false}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="ml-1 flex-grow-1"
>
<ExpandableTextArea
error={false}
errorMessage={null}
id="answer-A"
images={{}}
isLibrary={false}
learningContextId="course+org+run"
placeholder="Enter an answer"
setContent={[Function]}
value="Answer 1"
/>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
{
"correct": true,
"id": "A",
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
images={{}}
intl={
{
"formatMessage": [Function],
}
}
isLibrary={false}
learningContextId="course+org+run"
problemType="choiceresponse"
setSelectedFeedback={[Function]}
setUnselectedFeedback={[Function]}
/>
</Body>
</div>
<div
className="d-flex flex-row flex-nowrap"
>
<Trigger
aria-label="Toggle feedback"
className="btn-icon btn-icon-primary btn-icon-md align-items-center"
>
<Icon
alt="Toggle feedback"
/>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;

View File

@@ -1,24 +0,0 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import SettingsOption from './SettingsOption';
describe('SettingsOption', () => {
describe('default with children', () => {
const children = (<h1>My test content</h1>);
test('snapshot: renders correct', () => {
expect(shallow(<SettingsOption title="Settings Option Title" summary="Settings Option Summary">{children}</SettingsOption>).snapshot).toMatchSnapshot();
});
});
describe('with additional sections', () => {
const children = (<h1>First Section</h1>);
const sections = [<h1>Second Section</h1>, <h1>Third Section</h1>];
test('snapshot: renders correct', () => {
expect(shallow(
<SettingsOption title="Settings Option Title" summary="Settings Option Summary" extraSections={sections}>
{children}
</SettingsOption>,
).snapshot).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,44 @@
import React from 'react';
import {
render, screen, initializeMocks,
} from '@src/testUtils';
import SettingsOption from './SettingsOption';
describe('SettingsOption', () => {
const defaultProps = {
title: 'Settings Option Title',
summary: 'Settings Option Summary',
hasExpandableTextArea: true,
className: 'test-classname',
extraSections: [],
};
beforeEach(() => {
initializeMocks();
});
test('renders correct expanded area', () => {
const children = <h1>My test content</h1>;
render(<SettingsOption {...defaultProps}>{children}</SettingsOption>);
expect(screen.getByText('Settings Option Title')).toBeInTheDocument();
expect(screen.getByText('My test content')).toBeInTheDocument();
expect(document.querySelector('.test-classname')).toBeInTheDocument();
});
test('renders summary when not expanded', () => {
const children = <h1>My test content</h1>;
render((<SettingsOption {...defaultProps} hasExpandableTextArea={false}>{children}</SettingsOption>));
expect(screen.getByText('Settings Option Title')).toBeInTheDocument();
expect(screen.queryByText('My test content')).not.toBeInTheDocument();
expect(screen.getByText('Settings Option Summary')).toBeInTheDocument();
});
test('renders sections when expanded', () => {
const children = (<h1>First Section</h1>);
const sections = [{ children: <h1>Second Section</h1> }, { children: <h1>Third Section</h1> }];
render(<SettingsOption {...defaultProps} extraSections={sections}>{children}</SettingsOption>);
expect(screen.getByText('First Section')).toBeInTheDocument();
expect(screen.getByText('Second Section')).toBeInTheDocument();
expect(screen.getByText('Third Section')).toBeInTheDocument();
});
});

View File

@@ -1,109 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SettingsOption default with children snapshot: renders correct 1`] = `
<Card
className=" settingsOption border border-light-700 shadow-none"
>
<Card.Section
className="settingsCardTitleSection"
key="settingsOption-Settings Option Title-header"
>
<Advanced
onToggle={[Function]}
open={false}
>
<Trigger
className="collapsible-trigger d-flex"
>
<span
className="flex-grow-1 text-primary-500 x-small"
>
Settings Option Title
</span>
<Visible
whenClosed={true}
>
<Icon />
</Visible>
<Visible
whenOpen={true}
>
<Icon />
</Visible>
</Trigger>
</Advanced>
</Card.Section>
<CardSection
isCardCollapsibleOpen={false}
key="settingsOption-Settings Option Title-children"
none={false}
summary="Settings Option Summary"
>
<h1>
My test content
</h1>
</CardSection>
</Card>
`;
exports[`SettingsOption with additional sections snapshot: renders correct 1`] = `
<Card
className=" settingsOption border border-light-700 shadow-none"
>
<Card.Section
className="settingsCardTitleSection"
key="settingsOption-Settings Option Title-header"
>
<Advanced
onToggle={[Function]}
open={false}
>
<Trigger
className="collapsible-trigger d-flex"
>
<span
className="flex-grow-1 text-primary-500 x-small"
>
Settings Option Title
</span>
<Visible
whenClosed={true}
>
<Icon />
</Visible>
<Visible
whenOpen={true}
>
<Icon />
</Visible>
</Trigger>
</Advanced>
</Card.Section>
<CardSection
isCardCollapsibleOpen={false}
key="settingsOption-Settings Option Title-children"
none={false}
summary="Settings Option Summary"
>
<h1>
First Section
</h1>
</CardSection>
<Fragment>
<CardSection
isCardCollapsibleOpen={false}
key="settingsOption-Settings Option Title-0"
none={false}
summary={null}
/>
</Fragment>
<Fragment>
<CardSection
isCardCollapsibleOpen={false}
key="settingsOption-Settings Option Title-1"
none={false}
summary={null}
/>
</Fragment>
</Card>
`;

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
ActionRow, Form, Icon, IconButton, Row,
} from '@openedx/paragon';
import { DeleteOutline } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../../messages';
const GroupFeedbackRow = ({
@@ -13,48 +13,47 @@ const GroupFeedbackRow = ({
handleFeedbackChange,
handleDelete,
answers,
// injected
intl,
}) => (
<div className="mb-4">
<ActionRow className="mb-2">
<Form.Control
value={value.feedback}
onChange={handleFeedbackChange}
/>
<div className="d-flex flex-row flex-nowrap">
<IconButton
src={DeleteOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.settingsDeleteIconAltText)}
onClick={handleDelete}
variant="primary"
}) => {
const intl = useIntl();
return (
<div className="mb-4">
<ActionRow className="mb-2">
<Form.Control
value={value.feedback}
onChange={handleFeedbackChange}
/>
</div>
</ActionRow>
<Form.CheckboxSet
onChange={handleAnswersSelectedChange}
value={value.answers}
>
<Row className="mx-0">
{answers.map((letter) => (
<Form.Checkbox
className="mr-4 mt-1"
value={letter.id}
checked={value.answers.indexOf(letter.id)}
isValid={value.answers.indexOf(letter.id) >= 0}
>
<div className="x-small">
{letter.id}
</div>
</Form.Checkbox>
))}
</Row>
</Form.CheckboxSet>
</div>
);
<div className="d-flex flex-row flex-nowrap">
<IconButton
src={DeleteOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.settingsDeleteIconAltText)}
onClick={handleDelete}
variant="primary"
/>
</div>
</ActionRow>
<Form.CheckboxSet
onChange={handleAnswersSelectedChange}
value={value.answers}
>
<Row className="mx-0">
{answers.map((letter) => (
<Form.Checkbox
className="mr-4 mt-1"
value={letter.id}
checked={value.answers.indexOf(letter.id)}
isValid={value.answers.indexOf(letter.id) >= 0}
>
<div className="x-small">
{letter.id}
</div>
</Form.Checkbox>
))}
</Row>
</Form.CheckboxSet>
</div>
);
};
GroupFeedbackRow.propTypes = {
answers: PropTypes.arrayOf(PropTypes.shape({
@@ -72,9 +71,6 @@ GroupFeedbackRow.propTypes = {
answers: PropTypes.arrayOf(PropTypes.string),
feedback: PropTypes.string,
}).isRequired,
// injected
intl: intlShape.isRequired,
};
export const GroupFeedbackRowInternal = GroupFeedbackRow; // For testing only
export default injectIntl(GroupFeedbackRow);
export default GroupFeedbackRow;

View File

@@ -1,38 +0,0 @@
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { formatMessage } from '../../../../../../../testUtils';
import { GroupFeedbackRowInternal as GroupFeedbackRow } from './GroupFeedbackRow';
jest.mock('@openedx/paragon', () => ({
...jest.requireActual('@openedx/paragon'),
Row: 'Row',
IconButton: 'IconButton',
Icon: 'Icon',
Form: {
CheckboxSet: 'Form.CheckboxSet',
Checkbox: 'Form.CheckboxSet',
Control: 'Form.Control',
},
ActionRow: 'ActionRow',
}));
jest.mock('@openedx/paragon/icons', () => ({
...jest.requireActual('@openedx/paragon/icons'),
DeleteOutline: 'DeleteOutline',
}));
describe('GroupFeedbackRow', () => {
const props = {
value: { answers: ['A', 'C'], feedback: 'sOmE FeEDBACK' },
answers: ['A', 'B', 'C', 'D'],
handleAnswersSelectedChange: jest.fn().mockName('handleAnswersSelectedChange'),
handleFeedbackChange: jest.fn().mockName('handleFeedbackChange'),
handleDelete: jest.fn().mockName('handleDelete'),
intl: { formatMessage },
};
describe('snapshot', () => {
test('snapshot: renders hints row', () => {
expect(shallow(<GroupFeedbackRow {...props} />).snapshot).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,59 @@
import React from 'react';
import {
render,
screen,
initializeMocks,
fireEvent,
} from '@src/testUtils';
import GroupFeedbackRow from './GroupFeedbackRow';
describe('GroupFeedbackRow', () => {
const answers = [
{ id: '1', text: 'Answer 1', isCorrect: false },
{ id: '2', text: 'Answer 2', isCorrect: true },
{ id: '3', text: 'Answer 3', isCorrect: false },
{ id: '4', text: 'Answer 4', isCorrect: false },
];
const props = {
value: { id: 1, answers: answers.map(a => a.id), feedback: 'sOmE FeEDBACK' },
answers,
handleAnswersSelectedChange: jest.fn(),
handleFeedbackChange: jest.fn(),
handleDelete: jest.fn(),
};
beforeEach(() => {
initializeMocks();
});
test('renders component and anwers', () => {
render(<GroupFeedbackRow {...props} />);
expect(screen.getByRole('button', { name: 'Delete answer' })).toBeInTheDocument();
const options = screen.getAllByRole('checkbox');
expect(options).toHaveLength(answers.length);
});
test('handles delete answear correctly', () => {
const mockDelete = jest.fn();
render(<GroupFeedbackRow {...props} handleDelete={mockDelete} />);
const button = screen.getByRole('button', { name: 'Delete answer' });
fireEvent.click(button);
expect(mockDelete).toHaveBeenCalled();
});
test('handles selected answer change correctly', () => {
const handleAnswersSelectedChangeMock = jest.fn();
render(<GroupFeedbackRow {...props} handleAnswersSelectedChange={handleAnswersSelectedChangeMock} />);
const checkbox2 = screen.getByRole('checkbox', { name: '2' });
fireEvent.click(checkbox2);
expect(handleAnswersSelectedChangeMock).toHaveBeenCalled();
});
test('handles feedback change correctly', () => {
const handleFeedbackChangeMock = jest.fn();
render(<GroupFeedbackRow {...props} handleFeedbackChange={handleFeedbackChangeMock} />);
const feedbackInput = screen.getByRole('textbox');
fireEvent.change(feedbackInput, { target: { value: 'New feedback' } });
expect(handleFeedbackChangeMock).toHaveBeenCalled();
});
});

View File

@@ -1,77 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GroupFeedbackRow snapshot snapshot: renders hints row 1`] = `
<div
className="mb-4"
>
<ActionRow
className="mb-2"
>
<Form.Control
onChange={[MockFunction handleFeedbackChange]}
value="sOmE FeEDBACK"
/>
<div
className="d-flex flex-row flex-nowrap"
>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[MockFunction handleDelete]}
src="DeleteOutline"
variant="primary"
/>
</div>
</ActionRow>
<Form.CheckboxSet
onChange={[MockFunction handleAnswersSelectedChange]}
value={
[
"A",
"C",
]
}
>
<Row
className="mx-0"
>
<Form.CheckboxSet
checked={-1}
className="mr-4 mt-1"
isValid={false}
>
<div
className="x-small"
/>
</Form.CheckboxSet>
<Form.CheckboxSet
checked={-1}
className="mr-4 mt-1"
isValid={false}
>
<div
className="x-small"
/>
</Form.CheckboxSet>
<Form.CheckboxSet
checked={-1}
className="mr-4 mt-1"
isValid={false}
>
<div
className="x-small"
/>
</Form.CheckboxSet>
<Form.CheckboxSet
checked={-1}
className="mr-4 mt-1"
isValid={false}
>
<div
className="x-small"
/>
</Form.CheckboxSet>
</Row>
</Form.CheckboxSet>
</div>
`;

View File

@@ -18,7 +18,7 @@ exports[`HintsCard snapshot snapshot: renders groupFeedbacks setting card multip
id="authoring.problemeditor.settings.GroupFeedbackInputLabel"
/>
</div>
<injectIntl(ShimmedIntlComponent)
<GroupFeedbackRow
answers={
[
"A",
@@ -41,7 +41,7 @@ exports[`HintsCard snapshot snapshot: renders groupFeedbacks setting card multip
}
}
/>
<injectIntl(ShimmedIntlComponent)
<GroupFeedbackRow
answers={
[
"A",
@@ -131,7 +131,7 @@ exports[`HintsCard snapshot snapshot: renders groupFeedbacks setting card one gr
id="authoring.problemeditor.settings.GroupFeedbackInputLabel"
/>
</div>
<injectIntl(ShimmedIntlComponent)
<GroupFeedbackRow
answers={
[
"A",

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import { Form } from '@openedx/paragon';
import PropTypes from 'prop-types';
import SettingsOption from '../SettingsOption';
@@ -9,9 +9,8 @@ import { timerCardHooks } from '../hooks';
const TimerCard = ({
timeBetween,
updateSettings,
// inject
intl,
}) => {
const intl = useIntl();
const { handleChange } = timerCardHooks(updateSettings);
return (
@@ -40,8 +39,6 @@ const TimerCard = ({
TimerCard.propTypes = {
timeBetween: PropTypes.number.isRequired,
updateSettings: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};
export const TimerCardInternal = TimerCard; // For testing only
export default injectIntl(TimerCard);
export default TimerCard;

View File

@@ -1,37 +0,0 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { formatMessage } from '../../../../../../testUtils';
import { TimerCardInternal as TimerCard } from './TimerCard';
import { timerCardHooks } from '../hooks';
jest.mock('../hooks', () => ({
timerCardHooks: jest.fn(),
}));
describe('TimerCard', () => {
const props = {
timeBetween: 5,
updateSettings: jest.fn().mockName('args.updateSettings'),
intl: { formatMessage },
};
const timerCardHooksProps = {
handleChange: jest.fn().mockName('timerCardHooks.handleChange'),
};
timerCardHooks.mockReturnValue(timerCardHooksProps);
describe('behavior', () => {
it(' calls timerCardHooks when initialized', () => {
shallow(<TimerCard {...props} />);
expect(timerCardHooks).toHaveBeenCalledWith(props.updateSettings);
});
});
describe('snapshot', () => {
test('snapshot: renders reset true setting card', () => {
expect(shallow(<TimerCard {...props} />).snapshot).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,23 @@
import React from 'react';
import {
render, screen, initializeMocks,
} from '@src/testUtils';
import TimerCard from './TimerCard';
describe('TimerCard', () => {
const updateSettingsMock = jest.fn().mockName('updateSettingsMock');
const props = {
timeBetween: 5,
updateSettings: updateSettingsMock,
};
beforeEach(() => {
initializeMocks();
});
test('renders component', () => {
render(<TimerCard {...props} />);
expect(screen.getByText('Time between attempts')).toBeInTheDocument();
expect(screen.getByText(`${props.timeBetween} seconds`)).toBeInTheDocument();
});
});

View File

@@ -1,32 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TimerCard snapshot snapshot: renders reset true setting card 1`] = `
<SettingsOption
className=""
extraSections={[]}
hasExpandableTextArea={false}
summary="5 seconds"
title="Time between attempts"
>
<div
className="spacedMessage"
>
<span>
<FormattedMessage
defaultMessage="Seconds a student must wait between submissions for a problem with multiple attempts."
description="Timer settings card text"
id="authoring.problemeditor.settings.timer.text"
/>
</span>
</div>
<Form.Group>
<Form.Control
floatingLabel="Seconds"
min={0}
onChange={[MockFunction timerCardHooks.handleChange]}
type="number"
value={5}
/>
</Form.Group>
</SettingsOption>
`;