test: replacing snapshot tests with RTL tests part 8 (#2184)

This commit is contained in:
jacobo-dominguez-wgu
2025-06-18 16:09:21 -06:00
committed by GitHub
parent 4b4ab92383
commit a5c17452e7
16 changed files with 197 additions and 393 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow,
Container,
@@ -19,34 +19,35 @@ const HintRow = ({
images,
isLibrary,
learningContextId,
// injected
intl,
}) => (
<ActionRow className="mb-4">
<Container fluid className="p-0">
<ExpandableTextArea
value={value}
setContent={handleChange}
placeholder={intl.formatMessage(messages.hintInputLabel)}
id={`hint-${id}`}
{...{
images,
isLibrary,
learningContextId,
}}
/>
</Container>
<div className="d-flex flex-row flex-nowrap">
<IconButton
src={DeleteOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.settingsDeleteIconAltText)}
onClick={handleDelete}
variant="primary"
/>
</div>
</ActionRow>
);
}) => {
const intl = useIntl();
return (
<ActionRow className="mb-4">
<Container fluid className="p-0">
<ExpandableTextArea
value={value}
setContent={handleChange}
placeholder={intl.formatMessage(messages.hintInputLabel)}
id={`hint-${id}`}
{...{
images,
isLibrary,
learningContextId,
}}
/>
</Container>
<div className="d-flex flex-row flex-nowrap">
<IconButton
src={DeleteOutline}
iconAs={Icon}
alt={intl.formatMessage(messages.settingsDeleteIconAltText)}
onClick={handleDelete}
variant="primary"
/>
</div>
</ActionRow>
);
};
HintRow.propTypes = {
value: PropTypes.string.isRequired,
@@ -56,9 +57,6 @@ HintRow.propTypes = {
images: PropTypes.shape({}).isRequired,
learningContextId: PropTypes.string.isRequired,
isLibrary: PropTypes.bool.isRequired,
// injected
intl: intlShape.isRequired,
};
export const HintRowInternal = HintRow; // For testing only
export default injectIntl(HintRow);
export default HintRow;

View File

@@ -1,24 +0,0 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { formatMessage } from '../../../../../../testUtils';
import { HintRowInternal as HintRow } from './HintRow';
describe('HintRow', () => {
const props = {
value: 'hint_1',
handleChange: jest.fn(),
handleDelete: jest.fn(),
id: '0',
intl: { formatMessage },
images: {},
isLibrary: false,
learningContextId: 'course+org+run',
};
describe('snapshot', () => {
test('snapshot: renders hints row', () => {
expect(shallow(<HintRow {...props} />).snapshot).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,36 @@
import React from 'react';
import {
render, screen, initializeMocks, fireEvent,
} from '@src/testUtils';
import HintRow from './HintRow';
jest.mock('../../../../../../sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea');
describe('HintRow', () => {
const props = {
value: 'hint_1',
handleChange: jest.fn(),
handleDelete: jest.fn(),
id: '0',
images: {},
isLibrary: false,
learningContextId: 'course+org+run',
};
beforeEach(() => {
initializeMocks();
});
test('renders hints row', () => {
render(<HintRow {...props} />);
expect(screen.getByPlaceholderText('Hint')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Delete answer' })).toBeInTheDocument();
});
test('calls handleDelete when button is clicked', () => {
render(<HintRow {...props} />);
expect(screen.getByRole('button', { name: 'Delete answer' })).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Delete answer' }));
expect(props.handleDelete).toHaveBeenCalled();
});
});

View File

@@ -1,34 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HintRow snapshot snapshot: renders hints row 1`] = `
<ActionRow
className="mb-4"
>
<Container
className="p-0"
fluid={true}
>
<ExpandableTextArea
error={false}
errorMessage={null}
id="hint-0"
images={{}}
isLibrary={false}
learningContextId="course+org+run"
placeholder="Hint"
setContent={[MockFunction]}
value="hint_1"
/>
</Container>
<div
className="d-flex flex-row flex-nowrap"
>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[MockFunction]}
variant="primary"
/>
</div>
</ActionRow>
`;

View File

@@ -9,14 +9,14 @@ exports[`HintsCard snapshot snapshot: renders hints setting card multiple hints
summary=" {count, plural, =0 {} other {(+# more)}}"
title="Hints"
>
<injectIntl(ShimmedIntlComponent)
<HintRow
handleChange={[MockFunction hintsRowHooks.handleChange]}
handleDelete={[MockFunction hintsRowHooks.handleDelete]}
id={1}
key="1"
value="hint1"
/>
<injectIntl(ShimmedIntlComponent)
<HintRow
handleChange={[MockFunction hintsRowHooks.handleChange]}
handleDelete={[MockFunction hintsRowHooks.handleDelete]}
id={2}
@@ -73,7 +73,7 @@ exports[`HintsCard snapshot snapshot: renders hints setting card one hint 1`] =
summary="hint1 {count, plural, =0 {} other {(+# more)}}"
title="Hints"
>
<injectIntl(ShimmedIntlComponent)
<HintRow
handleChange={[MockFunction hintsRowHooks.handleChange]}
handleDelete={[MockFunction hintsRowHooks.handleDelete]}
id={1}

View File

@@ -1,6 +1,7 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import {
render, screen, initializeMocks,
} from '@src/testUtils';
import * as mod from './ErrorSummary';
@@ -11,22 +12,29 @@ describe('ErrorSummary', () => {
widgetWithError: [{ err1: 'mSg', err2: 'msG2' }, jest.fn()],
widgetWithNoError: [{}, jest.fn()],
};
beforeEach(() => {
initializeMocks();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('render', () => {
beforeEach(() => {
jest.spyOn(React, 'useContext').mockReturnValueOnce({});
});
test('snapshots: renders as expected when there are no errors', () => {
describe('renders', () => {
jest.spyOn(React, 'useContext').mockReturnValueOnce({});
test('renders as expected when there are no errors', () => {
jest.spyOn(mod, 'showAlert').mockReturnValue(false);
expect(shallow(<ErrorSummary />).snapshot).toMatchSnapshot();
render(<ErrorSummary />);
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});
test('snapshots: renders as expected when there are errors', () => {
jest.spyOn(mod, 'showAlert').mockReturnValue(true);
expect(shallow(<ErrorSummary />).snapshot).toMatchSnapshot();
render(<ErrorSummary />);
expect(screen.getByRole('alert')).toBeInTheDocument();
});
});
describe('hasNoError', () => {
it('returns true', () => {
expect(mod.hasNoError(errors.widgetWithError)).toEqual(false);
@@ -35,6 +43,7 @@ describe('ErrorSummary', () => {
expect(mod.hasNoError(errors.widgetWithNoError)).toEqual(true);
});
});
describe('showAlert', () => {
it('returns true', () => {
jest.spyOn(mod, 'hasNoError').mockReturnValue(false);

View File

@@ -1,45 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ErrorSummary render snapshots: renders as expected when there are errors 1`] = `
<Alert
show={true}
variant="danger"
>
<Alert.Heading>
<FormattedMessage
defaultMessage="We couldn't add your video."
description="Title of validation error."
id="authoring.videoeditor.validate.error.title"
/>
</Alert.Heading>
<p>
<FormattedMessage
defaultMessage="Please check your entries and try again."
description="Body of validation error."
id="authoring.videoeditor.validate.error.body"
/>
</p>
</Alert>
`;
exports[`ErrorSummary render snapshots: renders as expected when there are no errors 1`] = `
<Alert
show={false}
variant="danger"
>
<Alert.Heading>
<FormattedMessage
defaultMessage="We couldn't add your video."
description="Title of validation error."
id="authoring.videoeditor.validate.error.title"
/>
</Alert.Heading>
<p>
<FormattedMessage
defaultMessage="Please check your entries and try again."
description="Body of validation error."
id="authoring.videoeditor.validate.error.body"
/>
</p>
</Alert>
`;

View File

@@ -10,16 +10,11 @@ import {
import { Check } from '@openedx/paragon/icons';
import { connect, useDispatch } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { thunkActions, selectors } from '../../../../../../data/redux';
import { videoTranscriptLanguages } from '../../../../../../data/constants/video';
import { FileInput, fileInput } from '../../../../../../sharedComponents/FileInput';
import messages from './messages';
// This 'module' self-import hack enables mocking during tests.
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
// should be re-thought and cleaned up to avoid this pattern.
// eslint-disable-next-line import/no-self-import
import * as module from './LanguageSelector';
export const hooks = {
onSelectLanguage: ({
@@ -54,13 +49,11 @@ const LanguageSelector = ({
language,
// Redux
openLanguages, // Only allow those languages not already associated with a transcript to be selected
// intl
intl,
}) => {
const intl = useIntl();
const [localLang, setLocalLang] = React.useState(language);
const input = fileInput({ onAddFile: hooks.addFileCallback({ dispatch: useDispatch(), localLang }) });
const onLanguageChange = module.hooks.onSelectLanguage({
const onLanguageChange = hooks.onSelectLanguage({
dispatch: useDispatch(), languageBeforeChange: localLang, setLocalLang, triggerupload: input.click,
});
@@ -124,7 +117,6 @@ LanguageSelector.propTypes = {
openLanguages: PropTypes.arrayOf(PropTypes.string),
index: PropTypes.number.isRequired,
language: PropTypes.string.isRequired,
intl: intlShape.isRequired,
};
export const mapStateToProps = (state) => ({
@@ -134,4 +126,4 @@ export const mapStateToProps = (state) => ({
export const mapDispatchToProps = {};
export const LanguageSelectorInternal = LanguageSelector; // For testing only
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(LanguageSelector));
export default connect(mapStateToProps, mapDispatchToProps)(LanguageSelector);

View File

@@ -1,8 +1,9 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import { LanguageSelectorInternal as LanguageSelector } from './LanguageSelector';
import { formatMessage } from '../../../../../../testUtils';
import {
render, screen, initializeMocks, fireEvent,
} from '@src/testUtils';
import LanguageSelector from './LanguageSelector';
import { selectors } from '../../../../../../data/redux';
const lang1 = 'kLinGon';
const lang1Code = 'kl';
@@ -21,25 +22,37 @@ jest.mock('../../../../../../data/constants/video', () => ({
describe('LanguageSelector', () => {
const props = {
intl: { formatMessage },
onSelect: jest.fn().mockName('props.OnSelect'),
title: 'tITle',
index: 1,
language: lang1Code,
openLanguages: [[lang2Code, lang2], [lang3Code, lang3]],
};
describe('snapshot', () => {
test('transcript option', () => {
expect(
shallow(<LanguageSelector {...props} />).snapshot,
).toMatchSnapshot();
});
beforeEach(() => {
initializeMocks();
});
describe('snapshots -- no', () => {
test('transcripts no Open Languages, all should be disabled', () => {
expect(
shallow(<LanguageSelector {...props} openLanguages={[]} />).snapshot,
).toMatchSnapshot();
});
test('renders component with selected language', () => {
const { video } = selectors;
jest.spyOn(video, 'openLanguages').mockReturnValue(props.openLanguages);
const { container } = render(<LanguageSelector {...props} />);
expect(screen.getByRole('button', { name: 'Languages' })).toBeInTheDocument();
expect(screen.getByText(lang1)).toBeInTheDocument();
expect(container.querySelector('input.upload[type="file"]')).toBeInTheDocument();
});
test('renders component with no selection', () => {
const { video } = selectors;
jest.spyOn(video, 'openLanguages').mockReturnValue(props.openLanguages);
render(<LanguageSelector {...props} language="" />);
expect(screen.getByText('Select Language')).toBeInTheDocument();
});
test('transcripts no Open Languages, all dropdown items should be disabled', () => {
const { video } = selectors;
jest.spyOn(video, 'openLanguages').mockReturnValue([]);
const { container } = render(<LanguageSelector {...props} language="" />);
fireEvent.click(screen.getByRole('button', { name: 'Languages' }));
const disabledItems = container.querySelectorAll('.disabled.dropdown-item');
expect(disabledItems.length).toBe(3);
});
});

View File

@@ -1,111 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LanguageSelector snapshot transcript option 1`] = `
<Fragment>
<Dropdown
className="w-100 mb-2"
>
<Dropdown.Toggle
aria-label="Languages"
block={true}
className="w-100"
iconAs="Button"
id="selectLanguage-form-undefined"
variant="outline-primary"
>
<ActionRow>
kLinGon
<ActionRow.Spacer />
<Icon
className="text-primary-500"
/>
</ActionRow>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item>
kLinGon
<Icon
className="text-primary-500"
/>
</Dropdown.Item>
<Dropdown.Item
onClick={[Function]}
>
eLvIsh
</Dropdown.Item>
<Dropdown.Item
onClick={[Function]}
>
sImLisH
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<FileInput
acceptedFiles=".srt"
fileInput={
{
"addFile": [Function],
"click": [Function],
"ref": {
"current": undefined,
},
}
}
/>
</Fragment>
`;
exports[`LanguageSelector snapshots -- no transcripts no Open Languages, all should be disabled 1`] = `
<Fragment>
<Dropdown
className="w-100 mb-2"
>
<Dropdown.Toggle
aria-label="Languages"
block={true}
className="w-100"
iconAs="Button"
id="selectLanguage-form-undefined"
variant="outline-primary"
>
<ActionRow>
kLinGon
<ActionRow.Spacer />
<Icon
className="text-primary-500"
/>
</ActionRow>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item>
kLinGon
<Icon
className="text-primary-500"
/>
</Dropdown.Item>
<Dropdown.Item
className="disabled"
>
eLvIsh
</Dropdown.Item>
<Dropdown.Item
className="disabled"
>
sImLisH
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<FileInput
acceptedFiles=".srt"
fileInput={
{
"addFile": [Function],
"click": [Function],
"ref": {
"current": undefined,
},
}
}
/>
</Fragment>
`;

View File

@@ -1,53 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BaseModal ImageUploadModal template component snapshot 1`] = `
<ModalDialog
hasCloseButton={true}
isFullscreenOnMobile={true}
isFullscreenScroll={true}
isOpen={true}
onClose={[MockFunction props.close]}
size="lg"
title="props.title node"
variant="default"
>
<ModalDialog.Header
style={
{
"boxShadow": "2px 2px 5px rgba(0, 0, 0, 0.3)",
"zIndex": 1,
}
}
>
<ModalDialog.Title>
props.title node
</ModalDialog.Title>
</ModalDialog.Header>
<Scrollable
style={null}
>
<ModalDialog.Body>
props.children node
</ModalDialog.Body>
</Scrollable>
<ModalDialog.Footer>
<ActionRow>
<Fragment>
props.footerAction node
<ActionRow.Spacer />
</Fragment>
<ModalDialog.CloseButton
onClick={[MockFunction props.close]}
variant="tertiary"
>
<FormattedMessage
defaultMessage="Cancel"
description="Label for cancel button."
id="authoring.baseModal.cancelButtonLabel"
/>
</ModalDialog.CloseButton>
props.confirmAction node
</ActionRow>
</ModalDialog.Footer>
</ModalDialog>
`;

View File

@@ -1,19 +0,0 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import BaseModal from '.';
describe('BaseModal ImageUploadModal template component', () => {
test('snapshot', () => {
const props = {
isOpen: true,
close: jest.fn().mockName('props.close'),
title: 'props.title node',
children: 'props.children node',
confirmAction: 'props.confirmAction node',
footerAction: 'props.footerAction node',
};
expect(shallow(<BaseModal {...props} />).snapshot).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { render, screen, initializeMocks } from '@src/testUtils';
import BaseModal from '.';
const props = {
isOpen: true,
close: jest.fn().mockName('props.close'),
title: 'title node',
children: 'children node',
confirmAction: 'confirmAction node',
footerAction: 'footerAction node',
};
describe('BaseModal', () => {
beforeEach(() => {
initializeMocks();
});
test('renders component with all elements', () => {
render(<BaseModal {...props} />);
expect(screen.getByText(props.title)).toBeInTheDocument();
expect(screen.getByText(props.children)).toBeInTheDocument();
const confirmAction = screen.getByText((content) => content.includes(props.confirmAction));
expect(confirmAction).toBeInTheDocument();
const footerAction = screen.getByText((content) => content.includes(props.footerAction));
expect(footerAction).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
});
test('does not render cancel buton when hideCancelButton is true', () => {
render(<BaseModal {...props} hideCancelButton />);
expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
});
test('does not render footerAction node when not provided', () => {
render(<BaseModal {...props} footerAction={undefined} />);
const footerAction = screen.queryByText((content) => content.includes(props.footerAction));
expect(footerAction).not.toBeInTheDocument();
});
});

View File

@@ -1,20 +0,0 @@
import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import { shallow } from '@edx/react-unit-test-utils';
import UploadErrorAlert from './UploadErrorAlert';
jest.mock('../../data/redux', () => ({
selectors: {
requests: {
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),
},
},
}));
describe('UploadErrorAlert', () => {
describe('Snapshots', () => {
test('snapshot: is ErrorAlert with Message error (ErrorAlert)', () => {
expect(shallow(<UploadErrorAlert isUploadError />).snapshot).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { render, screen, initializeMocks } from '@src/testUtils';
import UploadErrorAlert from './UploadErrorAlert';
const errorMessage = {
id: 'error.errorTitle',
defaultMessage: 'Example error message',
description: 'Title of message presented to user when something goes wrong',
};
const defaultProps = {
isUploadError: true,
message: errorMessage,
};
describe('UploadErrorAlert', () => {
beforeEach(() => {
initializeMocks();
});
test('renders the error message', () => {
render(<UploadErrorAlert {...defaultProps} />);
expect(screen.queryByRole('alert')).toBeInTheDocument();
expect(screen.getByText(errorMessage.defaultMessage)).toBeInTheDocument();
});
test('does not render the error message when isUploadError is false', () => {
render(<UploadErrorAlert {...defaultProps} isUploadError={false} />);
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
expect(screen.queryByText(errorMessage.defaultMessage)).not.toBeInTheDocument();
});
});

View File

@@ -1,11 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UploadErrorAlert Snapshots snapshot: is ErrorAlert with Message error (ErrorAlert) 1`] = `
<ErrorAlert
dismissError={null}
hideHeading={false}
isError={true}
>
<FormattedMessage />
</ErrorAlert>
`;