test: replacing snapshot tests with RTL tests part 7 (#2181)
This commit is contained in:
committed by
GitHub
parent
920f4a54e1
commit
08c3d123d8
@@ -47,5 +47,4 @@ EditableHeader.propTypes = {
|
||||
cancelEdit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const EditableHeaderInternal = EditableHeader; // For testing only
|
||||
export default EditableHeader;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import 'CourseAuthoring/editors/setupEditorTest';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { Form } from '@openedx/paragon';
|
||||
import { EditableHeaderInternal as EditableHeader } from './EditableHeader';
|
||||
import EditConfirmationButtons from './EditConfirmationButtons';
|
||||
|
||||
describe('EditableHeader', () => {
|
||||
const props = {
|
||||
handleChange: jest.fn().mockName('args.handleChange'),
|
||||
updateTitle: jest.fn().mockName('args.updateTitle'),
|
||||
handleKeyDown: jest.fn().mockName('args.handleKeyDown'),
|
||||
inputRef: jest.fn().mockName('args.inputRef'),
|
||||
localTitle: 'test-title-text',
|
||||
cancelEdit: jest.fn().mockName('args.cancelEdit'),
|
||||
};
|
||||
let el;
|
||||
beforeEach(() => {
|
||||
el = shallow(<EditableHeader {...props} />);
|
||||
});
|
||||
|
||||
describe('snapshot', () => {
|
||||
test('snapshot', () => {
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('displays Edit Icon', () => {
|
||||
const formControl = el.instance.findByType(Form.Control)[0];
|
||||
expect(formControl.props.trailingElement).toMatchObject(
|
||||
<EditConfirmationButtons updateTitle={props.updateTitle} cancelEdit={props.cancelEdit} />,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
render, screen, initializeMocks, fireEvent,
|
||||
} from '@src/testUtils';
|
||||
import EditableHeader from './EditableHeader';
|
||||
|
||||
describe('EditableHeader', () => {
|
||||
const props = {
|
||||
handleChange: jest.fn().mockName('args.handleChange'),
|
||||
updateTitle: jest.fn().mockName('args.updateTitle'),
|
||||
handleKeyDown: jest.fn().mockName('args.handleKeyDown'),
|
||||
inputRef: jest.fn().mockName('args.inputRef'),
|
||||
localTitle: 'test-title-text',
|
||||
cancelEdit: jest.fn().mockName('args.cancelEdit'),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
test('renders input with correct value and placeholder', () => {
|
||||
render(<EditableHeader {...props} />);
|
||||
const input = screen.getByPlaceholderText('Title');
|
||||
expect(input).toBeInTheDocument();
|
||||
expect((input as HTMLInputElement).value).toBe(props.localTitle);
|
||||
});
|
||||
|
||||
test('calls handleChange when input changes', () => {
|
||||
render(<EditableHeader {...props} />);
|
||||
const input = screen.getByPlaceholderText('Title');
|
||||
fireEvent.change(input, { target: { value: 'New title' } });
|
||||
expect(props.handleChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls handleKeyDown on keydown', () => {
|
||||
render(<EditableHeader {...props} />);
|
||||
const input = screen.getByPlaceholderText('Title');
|
||||
fireEvent.keyDown(input, { target: { value: 'New title' } });
|
||||
expect(props.handleKeyDown).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls updateTitle on blur', () => {
|
||||
render(<EditableHeader {...props} />);
|
||||
const input = screen.getByPlaceholderText('Title');
|
||||
fireEvent.blur(input);
|
||||
expect(props.updateTitle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls inputRef if provided', () => {
|
||||
const inputRef = jest.fn();
|
||||
render(<EditableHeader {...props} inputRef={inputRef} />);
|
||||
expect(inputRef).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('renders buttons from trailing element EditConfirmationButtons', () => {
|
||||
render(<EditableHeader {...props} />);
|
||||
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
||||
expect(cancelButton).toBeInTheDocument();
|
||||
const saveButton = screen.getByRole('button', { name: /save/i });
|
||||
expect(saveButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EditableHeader snapshot snapshot 1`] = `
|
||||
<Form.Group
|
||||
onBlur={[Function]}
|
||||
>
|
||||
<Form.Control
|
||||
autoFocus={true}
|
||||
onChange={[MockFunction args.handleChange]}
|
||||
onKeyDown={[MockFunction args.handleKeyDown]}
|
||||
placeholder="Title"
|
||||
style={
|
||||
{
|
||||
"paddingInlineEnd": "calc(1rem + 84px)",
|
||||
}
|
||||
}
|
||||
trailingElement={
|
||||
<EditConfirmationButtons
|
||||
cancelEdit={[MockFunction args.cancelEdit]}
|
||||
updateTitle={[MockFunction args.updateTitle]}
|
||||
/>
|
||||
}
|
||||
value="test-title-text"
|
||||
/>
|
||||
</Form.Group>
|
||||
`;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import SettingsOption from '../SettingsOption';
|
||||
import { ProblemTypeKeys } from '../../../../../../data/constants/problem';
|
||||
import messages from '../messages';
|
||||
@@ -15,9 +15,8 @@ const HintsCard = ({
|
||||
images,
|
||||
isLibrary,
|
||||
learningContextId,
|
||||
// inject
|
||||
intl,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { summary, handleAdd } = hintsCardHooks(hints, updateSettings);
|
||||
|
||||
if (problemType === ProblemTypeKeys.ADVANCED) { return null; }
|
||||
@@ -55,7 +54,6 @@ const HintsCard = ({
|
||||
};
|
||||
|
||||
HintsCard.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
hints: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
@@ -67,5 +65,4 @@ HintsCard.propTypes = {
|
||||
isLibrary: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export const HintsCardInternal = HintsCard; // For testing only
|
||||
export default injectIntl(HintsCard);
|
||||
export default HintsCard;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import 'CourseAuthoring/editors/setupEditorTest';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { formatMessage } from '../../../../../../testUtils';
|
||||
import { HintsCardInternal as HintsCard } from './HintsCard';
|
||||
import { hintsCardHooks, hintsRowHooks } from '../hooks';
|
||||
import messages from '../messages';
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
hintsCardHooks: jest.fn(),
|
||||
hintsRowHooks: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('HintsCard', () => {
|
||||
const hint1 = { id: 1, value: 'hint1' };
|
||||
const hint2 = { id: 2, value: '' };
|
||||
const hints0 = [];
|
||||
const hints1 = [hint1];
|
||||
const hints2 = [hint1, hint2];
|
||||
const props = {
|
||||
intl: { formatMessage },
|
||||
hints: hints0,
|
||||
updateSettings: jest.fn().mockName('args.updateSettings'),
|
||||
};
|
||||
|
||||
const hintsRowHooksProps = {
|
||||
handleChange: jest.fn().mockName('hintsRowHooks.handleChange'),
|
||||
handleDelete: jest.fn().mockName('hintsRowHooks.handleDelete'),
|
||||
images: {},
|
||||
isLibrary: false,
|
||||
learningContextId: 'course+org+run',
|
||||
};
|
||||
hintsRowHooks.mockReturnValue(hintsRowHooksProps);
|
||||
|
||||
describe('behavior', () => {
|
||||
it(' calls hintsCardHooks when initialized', () => {
|
||||
const hintsCardHooksProps = {
|
||||
summary: { message: messages.noHintSummary, values: {} },
|
||||
handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'),
|
||||
};
|
||||
|
||||
hintsCardHooks.mockReturnValue(hintsCardHooksProps);
|
||||
shallow(<HintsCard {...props} />);
|
||||
expect(hintsCardHooks).toHaveBeenCalledWith(hints0, props.updateSettings);
|
||||
});
|
||||
});
|
||||
|
||||
describe('snapshot', () => {
|
||||
test('snapshot: renders hints setting card no hints', () => {
|
||||
const hintsCardHooksProps = {
|
||||
summary: { message: messages.noHintSummary, values: {} },
|
||||
handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'),
|
||||
};
|
||||
|
||||
hintsCardHooks.mockReturnValue(hintsCardHooksProps);
|
||||
expect(shallow(<HintsCard {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: renders hints setting card one hint', () => {
|
||||
const hintsCardHooksProps = {
|
||||
summary: {
|
||||
message: messages.hintSummary,
|
||||
values: { hint: hint1.value, count: 1 },
|
||||
},
|
||||
handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'),
|
||||
};
|
||||
|
||||
hintsCardHooks.mockReturnValue(hintsCardHooksProps);
|
||||
expect(shallow(<HintsCard {...props} hints={hints1} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: renders hints setting card multiple hints', () => {
|
||||
const hintsCardHooksProps = {
|
||||
summary: {
|
||||
message: messages.hintSummary,
|
||||
values: { hint: hint2.value, count: 2 },
|
||||
},
|
||||
handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'),
|
||||
};
|
||||
|
||||
hintsCardHooks.mockReturnValue(hintsCardHooksProps);
|
||||
expect(shallow(<HintsCard {...props} hints={hints2} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { render, screen, initializeMocks } from '@src/testUtils';
|
||||
import HintsCard from './HintsCard';
|
||||
|
||||
jest.mock('./HintRow', () => 'HintRow');
|
||||
|
||||
describe('HintsCard', () => {
|
||||
const hint1 = { id: '1', value: 'hint-1' };
|
||||
const hint2 = { id: '2', value: 'hint-2' };
|
||||
const hints0 = [];
|
||||
const hints1 = [hint1];
|
||||
const hints2 = [hint1, hint2];
|
||||
|
||||
const props = {
|
||||
hints: hints0,
|
||||
updateSettings: jest.fn().mockName('args.updateSettings'),
|
||||
problemType: 'multiplechoiceresponse',
|
||||
images: {},
|
||||
isLibrary: false,
|
||||
learningContextId: 'ID+',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
describe('HintsCard', () => {
|
||||
test('renders component', () => {
|
||||
render(<HintsCard {...props} />);
|
||||
expect(screen.getByText('Hints')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add hint' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not render component when problemType is advanced', () => {
|
||||
render(<HintsCard {...props} problemType="advanced" />);
|
||||
expect(screen.queryByText('Hints')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders hints setting card one hint', () => {
|
||||
render(<HintsCard {...props} hints={hints1} />);
|
||||
expect(document.querySelector('hintrow[value="hint-1"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('snapshot: renders hints setting card multiple hints', () => {
|
||||
render(<HintsCard {...props} hints={hints2} />);
|
||||
expect(document.querySelector('hintrow[value="hint-1"]')).toBeInTheDocument();
|
||||
expect(document.querySelector('hintrow[value="hint-2"]')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import isNil from 'lodash/isNil';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Form, Hyperlink } from '@openedx/paragon';
|
||||
import { selectors } from '../../../../../../data/redux';
|
||||
import SettingsOption from '../SettingsOption';
|
||||
@@ -13,13 +13,12 @@ const ScoringCard = ({
|
||||
scoring,
|
||||
defaultValue,
|
||||
updateSettings,
|
||||
// inject
|
||||
intl,
|
||||
// redux
|
||||
studioEndpointUrl,
|
||||
learningContextId,
|
||||
isLibrary,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
handleUnlimitedChange,
|
||||
handleMaxAttemptChange,
|
||||
@@ -93,7 +92,6 @@ const ScoringCard = ({
|
||||
};
|
||||
|
||||
ScoringCard.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
// eslint-disable-next-line
|
||||
scoring: PropTypes.any.isRequired,
|
||||
updateSettings: PropTypes.func.isRequired,
|
||||
@@ -117,5 +115,4 @@ export const mapStateToProps = (state) => ({
|
||||
|
||||
export const mapDispatchToProps = {};
|
||||
|
||||
export const ScoringCardInternal = ScoringCard; // For testing only
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ScoringCard));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ScoringCard);
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import 'CourseAuthoring/editors/setupEditorTest';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import { formatMessage } from '../../../../../../testUtils';
|
||||
import { scoringCardHooks } from '../hooks';
|
||||
import { ScoringCardInternal as ScoringCard } from './ScoringCard';
|
||||
import {
|
||||
render, screen, initializeMocks, fireEvent,
|
||||
} from '@src/testUtils';
|
||||
import ScoringCard from './ScoringCard';
|
||||
import { selectors } from '../../../../../../data/redux';
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
scoringCardHooks: jest.fn(),
|
||||
}));
|
||||
const { app } = selectors;
|
||||
|
||||
describe('ScoringCard', () => {
|
||||
const scoring = {
|
||||
@@ -17,55 +15,69 @@ describe('ScoringCard', () => {
|
||||
number: 5,
|
||||
},
|
||||
updateSettings: jest.fn().mockName('args.updateSettings'),
|
||||
intl: { formatMessage },
|
||||
};
|
||||
|
||||
const props = {
|
||||
scoring,
|
||||
intl: { formatMessage },
|
||||
defaultValue: 1,
|
||||
updateSettings: jest.fn(),
|
||||
};
|
||||
|
||||
const scoringCardHooksProps = {
|
||||
handleMaxAttemptChange: jest.fn().mockName('scoringCardHooks.handleMaxAttemptChange'),
|
||||
handleWeightChange: jest.fn().mockName('scoringCardHooks.handleWeightChange'),
|
||||
handleOnChange: jest.fn().mockName('scoringCardHooks.handleOnChange'),
|
||||
local: 5,
|
||||
};
|
||||
|
||||
scoringCardHooks.mockReturnValue(scoringCardHooksProps);
|
||||
|
||||
describe('behavior', () => {
|
||||
it(' calls scoringCardHooks when initialized', () => {
|
||||
shallow(<ScoringCard {...props} />);
|
||||
expect(scoringCardHooks).toHaveBeenCalledWith(scoring, props.updateSettings, props.defaultValue);
|
||||
});
|
||||
beforeEach(() => {
|
||||
jest.spyOn(app, 'studioEndpointUrl').mockReturnValue('studioEndpointUrl');
|
||||
jest.spyOn(app, 'learningContextId').mockReturnValue('learningContextId');
|
||||
jest.spyOn(app, 'isLibrary').mockReturnValue(false);
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
describe('snapshot', () => {
|
||||
test('snapshot: scoring setting card', () => {
|
||||
expect(shallow(<ScoringCard {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: scoring setting card zero zero weight', () => {
|
||||
expect(shallow(<ScoringCard
|
||||
{...props}
|
||||
scoring={{
|
||||
...scoring,
|
||||
weight: 0,
|
||||
}}
|
||||
/>).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: scoring setting card max attempts', () => {
|
||||
expect(shallow(<ScoringCard
|
||||
{...props}
|
||||
scoring={{
|
||||
...scoring,
|
||||
attempts: {
|
||||
unlimited: true,
|
||||
number: 0,
|
||||
},
|
||||
}}
|
||||
/>).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('render the component', () => {
|
||||
render(<ScoringCard {...props} />);
|
||||
expect(screen.getByText('Scoring')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render advance settings link when isLibrary is true', () => {
|
||||
jest.spyOn(app, 'isLibrary').mockReturnValue(true);
|
||||
render(<ScoringCard {...props} />);
|
||||
fireEvent.click(screen.getByText('Scoring'));
|
||||
expect(screen.queryByText('Set a default value in advanced settings')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render advance settings link when isLibrary is false', () => {
|
||||
jest.spyOn(app, 'isLibrary').mockReturnValue(false);
|
||||
render(<ScoringCard {...props} />);
|
||||
fireEvent.click(screen.getByText('Scoring'));
|
||||
expect(screen.getByText('Set a default value in advanced settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call updateSettings when clicking points button', () => {
|
||||
render(<ScoringCard {...props} scoring={{ ...scoring, weight: 0 }} />);
|
||||
fireEvent.click(screen.getByText('Scoring'));
|
||||
const pointsButton = screen.getByRole('spinbutton', { name: 'Points' });
|
||||
expect(pointsButton).toBeInTheDocument();
|
||||
expect(pointsButton.value).toBe('0');
|
||||
fireEvent.change(pointsButton, { target: { value: '0.1' } });
|
||||
expect(props.updateSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateSettings when clicking attempts button', () => {
|
||||
const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } };
|
||||
render(<ScoringCard {...props} scoring={scoringUnlimited} />);
|
||||
fireEvent.click(screen.getByText('Scoring'));
|
||||
fireEvent.click(screen.getByText('Attempts'));
|
||||
const attemptsButton = screen.getByRole('spinbutton', { name: 'Points' });
|
||||
expect(attemptsButton).toBeInTheDocument();
|
||||
expect(attemptsButton.value).toBe('1.5');
|
||||
fireEvent.change(attemptsButton, { target: { value: '2' } });
|
||||
expect(props.updateSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should display checked checkbox when unlimited is true', () => {
|
||||
const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } };
|
||||
render(<ScoringCard {...props} scoring={scoringUnlimited} />);
|
||||
fireEvent.click(screen.getByText('Scoring'));
|
||||
const checkbox = screen.getByRole('checkbox', { name: 'Unlimited attempts' });
|
||||
expect(checkbox).toBeChecked();
|
||||
fireEvent.click(checkbox);
|
||||
expect(props.updateSettings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HintsCard snapshot snapshot: renders hints setting card multiple hints 1`] = `
|
||||
<SettingsOption
|
||||
className=""
|
||||
extraSections={[]}
|
||||
hasExpandableTextArea={true}
|
||||
none={false}
|
||||
summary=" {count, plural, =0 {} other {(+# more)}}"
|
||||
title="Hints"
|
||||
>
|
||||
<HintRow
|
||||
handleChange={[MockFunction hintsRowHooks.handleChange]}
|
||||
handleDelete={[MockFunction hintsRowHooks.handleDelete]}
|
||||
id={1}
|
||||
key="1"
|
||||
value="hint1"
|
||||
/>
|
||||
<HintRow
|
||||
handleChange={[MockFunction hintsRowHooks.handleChange]}
|
||||
handleDelete={[MockFunction hintsRowHooks.handleDelete]}
|
||||
id={2}
|
||||
key="2"
|
||||
value=""
|
||||
/>
|
||||
<Button
|
||||
className="m-0 p-0 font-weight-bold"
|
||||
onClick={[MockFunction hintsCardHooks.handleAdd]}
|
||||
size="sm"
|
||||
text={null}
|
||||
variant="add"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add hint"
|
||||
description="Add hint button text"
|
||||
id="authoring.problemeditor.settings.hint.addHintButton"
|
||||
/>
|
||||
</Button>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
exports[`HintsCard snapshot snapshot: renders hints setting card no hints 1`] = `
|
||||
<SettingsOption
|
||||
className=""
|
||||
extraSections={[]}
|
||||
hasExpandableTextArea={true}
|
||||
none={true}
|
||||
summary="None"
|
||||
title="Hints"
|
||||
>
|
||||
<Button
|
||||
className="m-0 p-0 font-weight-bold"
|
||||
onClick={[MockFunction hintsCardHooks.handleAdd]}
|
||||
size="sm"
|
||||
text={null}
|
||||
variant="add"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add hint"
|
||||
description="Add hint button text"
|
||||
id="authoring.problemeditor.settings.hint.addHintButton"
|
||||
/>
|
||||
</Button>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
exports[`HintsCard snapshot snapshot: renders hints setting card one hint 1`] = `
|
||||
<SettingsOption
|
||||
className=""
|
||||
extraSections={[]}
|
||||
hasExpandableTextArea={true}
|
||||
none={false}
|
||||
summary="hint1 {count, plural, =0 {} other {(+# more)}}"
|
||||
title="Hints"
|
||||
>
|
||||
<HintRow
|
||||
handleChange={[MockFunction hintsRowHooks.handleChange]}
|
||||
handleDelete={[MockFunction hintsRowHooks.handleDelete]}
|
||||
id={1}
|
||||
key="1"
|
||||
value="hint1"
|
||||
/>
|
||||
<Button
|
||||
className="m-0 p-0 font-weight-bold"
|
||||
onClick={[MockFunction hintsCardHooks.handleAdd]}
|
||||
size="sm"
|
||||
text={null}
|
||||
variant="add"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add hint"
|
||||
description="Add hint button text"
|
||||
id="authoring.problemeditor.settings.hint.addHintButton"
|
||||
/>
|
||||
</Button>
|
||||
</SettingsOption>
|
||||
`;
|
||||
@@ -1,238 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
|
||||
<SettingsOption
|
||||
className="scoringCard"
|
||||
extraSections={[]}
|
||||
hasExpandableTextArea={false}
|
||||
summary="{weight, plural, =0 {Ungraded} other {# points}} · {attempts, plural, =1 {# attempt} other {# attempts}}"
|
||||
title="Scoring"
|
||||
>
|
||||
<div
|
||||
className="mb-4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Specify point weight and the number of answer attempts"
|
||||
description="Descriptive text for scoring settings"
|
||||
id="authoring.problemeditor.settings.scoring.label"
|
||||
/>
|
||||
</div>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Points"
|
||||
min={0}
|
||||
onChange={[MockFunction scoringCardHooks.handleWeightChange]}
|
||||
step={0.1}
|
||||
type="number"
|
||||
value={1.5}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, the problem is worth one point"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.weight.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
disabled={false}
|
||||
floatingLabel="Attempts"
|
||||
min={0}
|
||||
onBlur={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
onChange={[MockFunction scoringCardHooks.handleOnChange]}
|
||||
type="number"
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a default value is not set in advanced settings, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={false}
|
||||
className="mt-3 decoration-control-label"
|
||||
disabled={true}
|
||||
>
|
||||
<div
|
||||
className="x-small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unlimited attempts"
|
||||
description="Label for unlimited attempts checkbox"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox"
|
||||
/>
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink
|
||||
destination="undefined/settings/advanced/null#max_attempts"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set a default value in advanced settings"
|
||||
description="Advanced settings link text"
|
||||
id="authoring.problemeditor.settings.advancedSettingLink.text"
|
||||
/>
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] = `
|
||||
<SettingsOption
|
||||
className="scoringCard"
|
||||
extraSections={[]}
|
||||
hasExpandableTextArea={false}
|
||||
summary="{weight, plural, =0 {Ungraded} other {# points}} · Unlimited attempts"
|
||||
title="Scoring"
|
||||
>
|
||||
<div
|
||||
className="mb-4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Specify point weight and the number of answer attempts"
|
||||
description="Descriptive text for scoring settings"
|
||||
id="authoring.problemeditor.settings.scoring.label"
|
||||
/>
|
||||
</div>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Points"
|
||||
min={0}
|
||||
onChange={[MockFunction scoringCardHooks.handleWeightChange]}
|
||||
step={0.1}
|
||||
type="number"
|
||||
value={1.5}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, the problem is worth one point"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.weight.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
disabled={true}
|
||||
floatingLabel="Attempts"
|
||||
min={0}
|
||||
onBlur={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
onChange={[MockFunction scoringCardHooks.handleOnChange]}
|
||||
type="number"
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a default value is not set in advanced settings, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={true}
|
||||
className="mt-3 decoration-control-label"
|
||||
disabled={true}
|
||||
>
|
||||
<div
|
||||
className="x-small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unlimited attempts"
|
||||
description="Label for unlimited attempts checkbox"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox"
|
||||
/>
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink
|
||||
destination="undefined/settings/advanced/null#max_attempts"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set a default value in advanced settings"
|
||||
description="Advanced settings link text"
|
||||
id="authoring.problemeditor.settings.advancedSettingLink.text"
|
||||
/>
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`] = `
|
||||
<SettingsOption
|
||||
className="scoringCard"
|
||||
extraSections={[]}
|
||||
hasExpandableTextArea={false}
|
||||
summary="{weight, plural, =0 {Ungraded} other {# points}} · {attempts, plural, =1 {# attempt} other {# attempts}}"
|
||||
title="Scoring"
|
||||
>
|
||||
<div
|
||||
className="mb-4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Specify point weight and the number of answer attempts"
|
||||
description="Descriptive text for scoring settings"
|
||||
id="authoring.problemeditor.settings.scoring.label"
|
||||
/>
|
||||
</div>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Points"
|
||||
min={0}
|
||||
onChange={[MockFunction scoringCardHooks.handleWeightChange]}
|
||||
step={0.1}
|
||||
type="number"
|
||||
value={0}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, the problem is worth one point"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.weight.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
disabled={false}
|
||||
floatingLabel="Attempts"
|
||||
min={0}
|
||||
onBlur={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
onChange={[MockFunction scoringCardHooks.handleOnChange]}
|
||||
type="number"
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a default value is not set in advanced settings, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={false}
|
||||
className="mt-3 decoration-control-label"
|
||||
disabled={true}
|
||||
>
|
||||
<div
|
||||
className="x-small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unlimited attempts"
|
||||
description="Label for unlimited attempts checkbox"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox"
|
||||
/>
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink
|
||||
destination="undefined/settings/advanced/null#max_attempts"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set a default value in advanced settings"
|
||||
description="Advanced settings link text"
|
||||
id="authoring.problemeditor.settings.advancedSettingLink.text"
|
||||
/>
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
`;
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Form } from '@openedx/paragon';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
@@ -22,39 +22,40 @@ const AltTextControls = ({
|
||||
setValue,
|
||||
validation,
|
||||
value,
|
||||
// inject
|
||||
intl,
|
||||
}) => (
|
||||
<Form.Group className="mt-4.5">
|
||||
<Form.Label as="h4">
|
||||
<FormattedMessage {...messages.accessibilityLabel} />
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="mt-4.5"
|
||||
disabled={isDecorative}
|
||||
floatingLabel={intl.formatMessage(messages.altTextFloatingLabel)}
|
||||
isInvalid={validation.show}
|
||||
onChange={hooks.onInputChange(setValue)}
|
||||
type="input"
|
||||
value={value}
|
||||
/>
|
||||
{validation.show
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<Form.Group className="mt-4.5">
|
||||
<Form.Label as="h4">
|
||||
<FormattedMessage {...messages.accessibilityLabel} />
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="mt-4.5"
|
||||
disabled={isDecorative}
|
||||
floatingLabel={intl.formatMessage(messages.altTextFloatingLabel)}
|
||||
isInvalid={validation.show}
|
||||
onChange={hooks.onInputChange(setValue)}
|
||||
type="input"
|
||||
value={value}
|
||||
/>
|
||||
{validation.show
|
||||
&& (
|
||||
<Form.Control.Feedback type="invalid">
|
||||
<FormattedMessage {...messages.altTextLocalFeedback} />
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
<Form.Checkbox
|
||||
checked={isDecorative}
|
||||
className="mt-4.5 decorative-control-label"
|
||||
onChange={hooks.onCheckboxChange(setIsDecorative)}
|
||||
>
|
||||
<Form.Label>
|
||||
<FormattedMessage {...messages.decorativeAltTextCheckboxLabel} />
|
||||
</Form.Label>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
);
|
||||
<Form.Checkbox
|
||||
checked={isDecorative}
|
||||
className="mt-4.5 decorative-control-label"
|
||||
onChange={hooks.onCheckboxChange(setIsDecorative)}
|
||||
>
|
||||
<Form.Label>
|
||||
<FormattedMessage {...messages.decorativeAltTextCheckboxLabel} />
|
||||
</Form.Label>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
);
|
||||
};
|
||||
AltTextControls.propTypes = {
|
||||
error: PropTypes.shape({
|
||||
show: PropTypes.bool,
|
||||
@@ -66,9 +67,7 @@ AltTextControls.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
}).isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
// inject
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export const AltTextControlsInternal = AltTextControls; // For testing only
|
||||
export default injectIntl(AltTextControls);
|
||||
export default AltTextControls;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'CourseAuthoring/editors/setupEditorTest';
|
||||
import React from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { formatMessage } from '../../../testUtils';
|
||||
import { AltTextControlsInternal as AltTextControls } from './AltTextControls';
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
onInputChange: (handler) => ({ 'hooks.onInputChange': handler }),
|
||||
onCheckboxChange: (handler) => ({ 'hooks.onCheckboxChange': handler }),
|
||||
}));
|
||||
|
||||
describe('AltTextControls', () => {
|
||||
const props = {
|
||||
isDecorative: true,
|
||||
value: 'props.value',
|
||||
// inject
|
||||
intl: { formatMessage },
|
||||
};
|
||||
beforeEach(() => {
|
||||
props.setValue = jest.fn().mockName('props.setValue');
|
||||
props.setIsDecorative = jest.fn().mockName('props.setIsDecorative');
|
||||
props.validation = { show: true };
|
||||
});
|
||||
describe('render', () => {
|
||||
test('snapshot: isDecorative=true errorProps.showAltTextSubmissionError=true', () => {
|
||||
expect(shallow(<AltTextControls {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: isDecorative=true errorProps.showAltTextSubmissionError=false', () => {
|
||||
props.validation.show = false;
|
||||
expect(shallow(<AltTextControls {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
render, screen, initializeMocks, fireEvent,
|
||||
} from '@src/testUtils';
|
||||
import AltTextControls from './AltTextControls';
|
||||
|
||||
describe('AltTextControls', () => {
|
||||
const props = {
|
||||
isDecorative: true,
|
||||
value: 'props.value',
|
||||
setValue: jest.fn().mockName('props.setValue'),
|
||||
setIsDecorative: jest.fn().mockName('props.setIsDecorative'),
|
||||
validation: { show: false },
|
||||
error: { show: false },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
test('renders component on screen', () => {
|
||||
render(<AltTextControls {...props} />);
|
||||
expect(screen.getByRole('checkbox', { name: 'This image is decorative (no alt text required).' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('textbox', { name: 'Accessibility' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders validation feedback when validation.show is true', () => {
|
||||
const feedbackMessage = 'Enter alt text';
|
||||
render(<AltTextControls {...props} validation={{ show: true }} />);
|
||||
expect(screen.getByText(feedbackMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not render validation feedback when validation.show is false', () => {
|
||||
const feedbackMessage = 'Enter alt text';
|
||||
render(<AltTextControls {...props} validation={{ show: false }} />);
|
||||
expect(screen.queryByText(feedbackMessage)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('disables textbox when isDecorative is true', () => {
|
||||
render(<AltTextControls {...props} isDecorative />);
|
||||
expect(screen.getByRole('textbox', { name: 'Accessibility' })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('enables textbox when isDecorative is false', () => {
|
||||
render(<AltTextControls {...props} isDecorative={false} />);
|
||||
expect(screen.getByRole('textbox', { name: 'Accessibility' })).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('calls setValue on textbox change', () => {
|
||||
render(<AltTextControls {...props} isDecorative={false} />);
|
||||
const textbox = screen.getByRole('textbox', { name: 'Accessibility' });
|
||||
fireEvent.change(textbox, { target: { value: 'new alt text' } });
|
||||
expect(props.setValue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls setIsDecorative on checkbox change', () => {
|
||||
render(<AltTextControls {...props} />);
|
||||
const checkbox = screen.getByRole('checkbox', { name: 'This image is decorative (no alt text required).' });
|
||||
checkbox.click();
|
||||
expect(props.setIsDecorative).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Locked,
|
||||
Unlocked,
|
||||
} from '@openedx/paragon/icons';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
@@ -32,42 +32,44 @@ const DimensionControls = ({
|
||||
unlock,
|
||||
updateDimensions,
|
||||
value,
|
||||
// inject
|
||||
intl,
|
||||
}) => ((value !== null) && (
|
||||
<Form.Group>
|
||||
<Form.Label as="h4">
|
||||
<FormattedMessage {...messages.imageDimensionsLabel} />
|
||||
</Form.Label>
|
||||
<div className="mt-4.5">
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
value={value.width}
|
||||
onChange={hooks.onInputChange(setWidth)}
|
||||
onBlur={updateDimensions}
|
||||
floatingLabel={intl.formatMessage(messages.widthFloatingLabel)}
|
||||
/>
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
value={value.height}
|
||||
onChange={hooks.onInputChange(setHeight)}
|
||||
onBlur={updateDimensions}
|
||||
floatingLabel={intl.formatMessage(messages.heightFloatingLabel)}
|
||||
/>
|
||||
<IconButton
|
||||
className="d-inline-block"
|
||||
alt={
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
if (!value) { return null; }
|
||||
return (
|
||||
<Form.Group>
|
||||
<Form.Label as="h4">
|
||||
<FormattedMessage {...messages.imageDimensionsLabel} />
|
||||
</Form.Label>
|
||||
<div className="mt-4.5">
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
value={value.width}
|
||||
onChange={hooks.onInputChange(setWidth)}
|
||||
onBlur={updateDimensions}
|
||||
floatingLabel={intl.formatMessage(messages.widthFloatingLabel)}
|
||||
/>
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
value={value.height}
|
||||
onChange={hooks.onInputChange(setHeight)}
|
||||
onBlur={updateDimensions}
|
||||
floatingLabel={intl.formatMessage(messages.heightFloatingLabel)}
|
||||
/>
|
||||
<IconButton
|
||||
className="d-inline-block"
|
||||
alt={
|
||||
isLocked
|
||||
? intl.formatMessage(messages.unlockDimensionsLabel)
|
||||
: intl.formatMessage(messages.lockDimensionsLabel)
|
||||
}
|
||||
iconAs={Icon}
|
||||
src={isLocked ? Locked : Unlocked}
|
||||
onClick={isLocked ? unlock : lock}
|
||||
/>
|
||||
</div>
|
||||
</Form.Group>
|
||||
));
|
||||
iconAs={Icon}
|
||||
src={isLocked ? Locked : Unlocked}
|
||||
onClick={isLocked ? unlock : lock}
|
||||
/>
|
||||
</div>
|
||||
</Form.Group>
|
||||
);
|
||||
};
|
||||
DimensionControls.defaultProps = {
|
||||
value: {
|
||||
height: '100',
|
||||
@@ -85,9 +87,6 @@ DimensionControls.propTypes = ({
|
||||
lock: PropTypes.func.isRequired,
|
||||
unlock: PropTypes.func.isRequired,
|
||||
updateDimensions: PropTypes.func.isRequired,
|
||||
// inject
|
||||
intl: intlShape.isRequired,
|
||||
});
|
||||
|
||||
export const DimensionControlsInternal = DimensionControls; // For testing only
|
||||
export default injectIntl(DimensionControls);
|
||||
export default DimensionControls;
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import 'CourseAuthoring/editors/setupEditorTest';
|
||||
import React, { useEffect } from 'react';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
import * as paragon from '@openedx/paragon';
|
||||
import * as icons from '@openedx/paragon/icons';
|
||||
|
||||
import {
|
||||
fireEvent, render, screen, waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { formatMessage } from '../../../testUtils';
|
||||
import { DimensionControlsInternal as DimensionControls } from './DimensionControls';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
const WrappedDimensionControls = () => {
|
||||
const dimensions = hooks.dimensionHooks('altText');
|
||||
|
||||
useEffect(() => {
|
||||
dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } });
|
||||
}, []);
|
||||
|
||||
return <DimensionControls {...dimensions} intl={{ formatMessage }} />;
|
||||
};
|
||||
|
||||
const UnlockedDimensionControls = () => {
|
||||
const dimensions = hooks.dimensionHooks('altText');
|
||||
|
||||
useEffect(() => {
|
||||
dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } });
|
||||
dimensions.unlock();
|
||||
}, []);
|
||||
|
||||
return <DimensionControls {...dimensions} intl={{ formatMessage }} />;
|
||||
};
|
||||
|
||||
describe('DimensionControls', () => {
|
||||
describe('render', () => {
|
||||
const props = {
|
||||
lockAspectRatio: { width: 4, height: 5 },
|
||||
locked: { 'props.locked': 'lockedValue' },
|
||||
isLocked: true,
|
||||
value: { width: 20, height: 40 },
|
||||
// inject
|
||||
intl: { formatMessage },
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.spyOn(hooks, 'onInputChange').mockImplementation((handler) => ({ 'hooks.onInputChange': handler }));
|
||||
props.setWidth = jest.fn().mockName('props.setWidth');
|
||||
props.setHeight = jest.fn().mockName('props.setHeight');
|
||||
props.lock = jest.fn().mockName('props.lock');
|
||||
props.unlock = jest.fn().mockName('props.unlock');
|
||||
props.updateDimensions = jest.fn().mockName('props.updateDimensions');
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.spyOn(hooks, 'onInputChange').mockRestore();
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<DimensionControls {...props} />).snapshot).toMatchSnapshot();
|
||||
});
|
||||
test('null value: empty snapshot', () => {
|
||||
const el = shallow(<DimensionControls {...props} value={null} />);
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
expect(el.isEmptyRender()).toEqual(true);
|
||||
});
|
||||
test('unlocked dimensions', () => {
|
||||
const el = shallow(<DimensionControls {...props} isLocked={false} />);
|
||||
expect(el.snapshot).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
describe('component tests for dimensions', () => {
|
||||
beforeEach(() => {
|
||||
paragon.Form.Group = jest.fn().mockImplementation(({ children }) => (
|
||||
<div>{children}</div>
|
||||
));
|
||||
paragon.Form.Label = jest.fn().mockImplementation(({ children }) => (
|
||||
<div>{children}</div>
|
||||
));
|
||||
// eslint-disable-next-line no-import-assign
|
||||
paragon.Icon = jest.fn().mockImplementation(({ children }) => (
|
||||
<div>{children}</div>
|
||||
));
|
||||
// eslint-disable-next-line no-import-assign
|
||||
paragon.IconButton = jest.fn().mockImplementation(({ children }) => (
|
||||
<div>{children}</div>
|
||||
));
|
||||
paragon.Form.Control = jest.fn().mockImplementation(({ value, onChange, onBlur }) => (
|
||||
<input className="formControl" onChange={onChange} onBlur={onBlur} value={value} />
|
||||
));
|
||||
// eslint-disable-next-line no-import-assign
|
||||
icons.Locked = jest.fn().mockImplementation(() => {});
|
||||
// eslint-disable-next-line no-import-assign
|
||||
icons.Unlocked = jest.fn().mockImplementation(() => {});
|
||||
});
|
||||
afterEach(() => {
|
||||
paragon.Form.Group.mockRestore();
|
||||
paragon.Form.Label.mockRestore();
|
||||
paragon.Form.Control.mockRestore();
|
||||
paragon.Icon.mockRestore();
|
||||
paragon.IconButton.mockRestore();
|
||||
icons.Locked.mockRestore();
|
||||
icons.Unlocked.mockRestore();
|
||||
});
|
||||
|
||||
it('renders with initial dimensions', () => {
|
||||
const { container } = render(<WrappedDimensionControls />);
|
||||
const widthInput = container.querySelector('.formControl');
|
||||
expect(widthInput.value).toBe('1517');
|
||||
});
|
||||
|
||||
it('resizes dimensions proportionally', async () => {
|
||||
const { container } = render(<WrappedDimensionControls />);
|
||||
const widthInput = container.querySelector('.formControl');
|
||||
expect(widthInput.value).toBe('1517');
|
||||
fireEvent.change(widthInput, { target: { value: 758 } });
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('.formControl')[0].value).toBe('758');
|
||||
});
|
||||
fireEvent.blur(widthInput);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('.formControl')[0].value).toBe('758');
|
||||
expect(container.querySelectorAll('.formControl')[1].value).toBe('401');
|
||||
});
|
||||
screen.debug();
|
||||
});
|
||||
|
||||
it('resizes only changed dimension when unlocked', async () => {
|
||||
const { container } = render(<UnlockedDimensionControls />);
|
||||
const widthInput = container.querySelector('.formControl');
|
||||
expect(widthInput.value).toBe('1517');
|
||||
fireEvent.change(widthInput, { target: { value: 758 } });
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('.formControl')[0].value).toBe('758');
|
||||
});
|
||||
fireEvent.blur(widthInput);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('.formControl')[0].value).toBe('758');
|
||||
expect(container.querySelectorAll('.formControl')[1].value).toBe('803');
|
||||
});
|
||||
screen.debug();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
fireEvent, render, screen, waitFor, initializeMocks,
|
||||
} from '@src/testUtils';
|
||||
import DimensionControls from './DimensionControls';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
const WrappedDimensionControls = () => {
|
||||
const dimensions = hooks.dimensionHooks('altText');
|
||||
|
||||
useEffect(() => {
|
||||
dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } });
|
||||
}, []);
|
||||
|
||||
return <DimensionControls {...dimensions} />;
|
||||
};
|
||||
|
||||
const UnlockedDimensionControls = () => {
|
||||
const dimensions = hooks.dimensionHooks('altText');
|
||||
|
||||
useEffect(() => {
|
||||
dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } });
|
||||
dimensions.unlock();
|
||||
}, []);
|
||||
|
||||
return <DimensionControls {...dimensions} />;
|
||||
};
|
||||
|
||||
describe('DimensionControls', () => {
|
||||
describe('render', () => {
|
||||
const props = {
|
||||
lockAspectRatio: { width: 4, height: 5 },
|
||||
locked: { 'props.locked': 'lockedValue' },
|
||||
isLocked: true,
|
||||
value: { width: '20', height: '40' },
|
||||
setWidth: jest.fn(),
|
||||
setHeight: jest.fn(),
|
||||
lock: jest.fn(),
|
||||
unlock: jest.fn(),
|
||||
updateDimensions: jest.fn(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.spyOn(hooks, 'onInputChange').mockImplementation((handler) => ({ 'hooks.onInputChange': handler }));
|
||||
initializeMocks();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.spyOn(hooks, 'onInputChange').mockRestore();
|
||||
});
|
||||
test('renders component', () => {
|
||||
render(<DimensionControls {...props} />);
|
||||
expect(screen.getByText('Image Dimensions')).toBeInTheDocument();
|
||||
});
|
||||
test('renders nothing with null value', () => {
|
||||
const reduxProviderWrapper = '<div data-testid="redux-provider"></div>';
|
||||
const { container } = render(<DimensionControls {...props} value={null} />);
|
||||
expect(screen.queryByText('Image Dimensions')).not.toBeInTheDocument();
|
||||
expect(container.innerHTML).toBe(reduxProviderWrapper);
|
||||
expect(container.firstChild?.textContent).toBe('');
|
||||
});
|
||||
|
||||
test('renders locked and unlocked icon button according to isLocked prop', () => {
|
||||
const { rerender } = render(<DimensionControls {...props} isLocked={false} />);
|
||||
expect(screen.getByRole('button', { name: 'lock dimensions' })).toBeInTheDocument();
|
||||
rerender(<DimensionControls {...props} isLocked />);
|
||||
expect(screen.getByRole('button', { name: 'unlock dimensions' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('component tests for dimensions', () => {
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
it('renders with initial dimensions', () => {
|
||||
const { container } = render(<WrappedDimensionControls />);
|
||||
const widthInput = container.querySelector('input.form-control');
|
||||
expect(widthInput).not.toBeNull();
|
||||
expect((widthInput as HTMLInputElement).value).toBe('1517');
|
||||
});
|
||||
|
||||
it('resizes dimensions proportionally', async () => {
|
||||
const { container } = render(<WrappedDimensionControls />);
|
||||
const widthInput = container.querySelector('input.form-control') as HTMLInputElement;
|
||||
expect((widthInput as HTMLInputElement).value).toBe('1517');
|
||||
fireEvent.change(widthInput, { target: { value: 758 } });
|
||||
await waitFor(() => {
|
||||
expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758');
|
||||
});
|
||||
fireEvent.blur(widthInput);
|
||||
await waitFor(() => {
|
||||
expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758');
|
||||
expect((container.querySelectorAll('input.form-control')[1] as HTMLInputElement).value).toBe('401');
|
||||
});
|
||||
});
|
||||
|
||||
it('resizes only changed dimension when unlocked', async () => {
|
||||
const { container } = render(<UnlockedDimensionControls />);
|
||||
const widthInput = container.querySelector('input.form-control') as HTMLInputElement;
|
||||
expect(widthInput.value).toBe('1517');
|
||||
fireEvent.change(widthInput, { target: { value: 758 } });
|
||||
await waitFor(() => {
|
||||
expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758');
|
||||
});
|
||||
fireEvent.blur(widthInput);
|
||||
await waitFor(() => {
|
||||
expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758');
|
||||
expect((container.querySelectorAll('input.form-control')[1] as HTMLInputElement).value).toBe('803');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AltTextControls render snapshot: isDecorative=true errorProps.showAltTextSubmissionError=false 1`] = `
|
||||
<Form.Group
|
||||
className="mt-4.5"
|
||||
>
|
||||
<Form.Label
|
||||
as="h4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Accessibility"
|
||||
description="Label title for accessibility section."
|
||||
id="authoring.texteditor.imagesettingsmodal.accessibilityLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="mt-4.5"
|
||||
disabled={true}
|
||||
floatingLabel="Alt Text"
|
||||
isInvalid={false}
|
||||
onChange={
|
||||
{
|
||||
"hooks.onInputChange": [MockFunction props.setValue],
|
||||
}
|
||||
}
|
||||
type="input"
|
||||
value="props.value"
|
||||
/>
|
||||
<Form.Checkbox
|
||||
checked={true}
|
||||
className="mt-4.5 decorative-control-label"
|
||||
onChange={
|
||||
{
|
||||
"hooks.onCheckboxChange": [MockFunction props.setIsDecorative],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Form.Label>
|
||||
<FormattedMessage
|
||||
defaultMessage="This image is decorative (no alt text required)."
|
||||
description="Checkbox label for whether or not an image is decorative."
|
||||
id="authoring.texteditor.imagesettingsmodal.decorativeAltTextCheckboxLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
`;
|
||||
|
||||
exports[`AltTextControls render snapshot: isDecorative=true errorProps.showAltTextSubmissionError=true 1`] = `
|
||||
<Form.Group
|
||||
className="mt-4.5"
|
||||
>
|
||||
<Form.Label
|
||||
as="h4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Accessibility"
|
||||
description="Label title for accessibility section."
|
||||
id="authoring.texteditor.imagesettingsmodal.accessibilityLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="mt-4.5"
|
||||
disabled={true}
|
||||
floatingLabel="Alt Text"
|
||||
isInvalid={true}
|
||||
onChange={
|
||||
{
|
||||
"hooks.onInputChange": [MockFunction props.setValue],
|
||||
}
|
||||
}
|
||||
type="input"
|
||||
value="props.value"
|
||||
/>
|
||||
<Form.Control.Feedback
|
||||
type="invalid"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Enter alt text"
|
||||
description="Message feedback for user below the alt text field."
|
||||
id="authoring.texteditor.imagesettingsmodal.error.altTextLocalFeedback"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={true}
|
||||
className="mt-4.5 decorative-control-label"
|
||||
onChange={
|
||||
{
|
||||
"hooks.onCheckboxChange": [MockFunction props.setIsDecorative],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Form.Label>
|
||||
<FormattedMessage
|
||||
defaultMessage="This image is decorative (no alt text required)."
|
||||
description="Checkbox label for whether or not an image is decorative."
|
||||
id="authoring.texteditor.imagesettingsmodal.decorativeAltTextCheckboxLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
`;
|
||||
@@ -1,97 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DimensionControls render null value: empty snapshot 1`] = `false`;
|
||||
|
||||
exports[`DimensionControls render snapshot 1`] = `
|
||||
<Form.Group>
|
||||
<Form.Label
|
||||
as="h4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Image Dimensions"
|
||||
description="Label title for the image dimensions section."
|
||||
id="authoring.texteditor.imagesettingsmodal.imageDimensionsLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
<div
|
||||
className="mt-4.5"
|
||||
>
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
floatingLabel="Width"
|
||||
onBlur={[MockFunction props.updateDimensions]}
|
||||
onChange={
|
||||
{
|
||||
"hooks.onInputChange": [MockFunction props.setWidth],
|
||||
}
|
||||
}
|
||||
value={20}
|
||||
/>
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
floatingLabel="Height"
|
||||
onBlur={[MockFunction props.updateDimensions]}
|
||||
onChange={
|
||||
{
|
||||
"hooks.onInputChange": [MockFunction props.setHeight],
|
||||
}
|
||||
}
|
||||
value={40}
|
||||
/>
|
||||
<IconButton
|
||||
alt="unlock dimensions"
|
||||
className="d-inline-block"
|
||||
iconAs="Icon"
|
||||
onClick={[MockFunction props.unlock]}
|
||||
src={[MockFunction icons.Locked]}
|
||||
/>
|
||||
</div>
|
||||
</Form.Group>
|
||||
`;
|
||||
|
||||
exports[`DimensionControls render unlocked dimensions 1`] = `
|
||||
<Form.Group>
|
||||
<Form.Label
|
||||
as="h4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Image Dimensions"
|
||||
description="Label title for the image dimensions section."
|
||||
id="authoring.texteditor.imagesettingsmodal.imageDimensionsLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
<div
|
||||
className="mt-4.5"
|
||||
>
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
floatingLabel="Width"
|
||||
onBlur={[MockFunction props.updateDimensions]}
|
||||
onChange={
|
||||
{
|
||||
"hooks.onInputChange": [MockFunction props.setWidth],
|
||||
}
|
||||
}
|
||||
value={20}
|
||||
/>
|
||||
<Form.Control
|
||||
className="dimension-input"
|
||||
floatingLabel="Height"
|
||||
onBlur={[MockFunction props.updateDimensions]}
|
||||
onChange={
|
||||
{
|
||||
"hooks.onInputChange": [MockFunction props.setHeight],
|
||||
}
|
||||
}
|
||||
value={40}
|
||||
/>
|
||||
<IconButton
|
||||
alt="lock dimensions"
|
||||
className="d-inline-block"
|
||||
iconAs="Icon"
|
||||
onClick={[MockFunction props.lock]}
|
||||
src={[MockFunction icons.Unlocked]}
|
||||
/>
|
||||
</div>
|
||||
</Form.Group>
|
||||
`;
|
||||
Reference in New Issue
Block a user