feat: New screens for loading, no videos and error states. Also tests added

This commit is contained in:
XnpioChV
2023-03-31 13:49:52 -05:00
parent 71efe876d3
commit b3bced9875
23 changed files with 1517 additions and 191 deletions

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@edx/paragon';
import { Outline } from '@edx/paragon/icons';
import { Error } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from './messages';
@@ -42,7 +42,7 @@ export const ErrorAlert = ({
return (
<Alert
variant="danger"
icon={Outline}
icon={Error}
dismissible
onClose={dismissAlert}
>

View File

@@ -15,6 +15,7 @@ import messages from './messages';
import GalleryCard from './GalleryCard';
export const Gallery = ({
show,
galleryIsEmpty,
searchIsEmpty,
displayList,
@@ -27,13 +28,24 @@ export const Gallery = ({
// injected
intl,
}) => {
if (!show) {
return null;
}
if (!isLoaded) {
return (
<Spinner
animation="border"
className="mie-3"
screenReaderText={intl.formatMessage(messages.loading)}
/>
<div style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
}}
>
<Spinner
animation="border"
className="mie-3"
screenReaderText={intl.formatMessage(messages.loading)}
/>
</div>
);
}
if (galleryIsEmpty) {
@@ -71,8 +83,10 @@ Gallery.defaultProps = {
highlighted: '',
showIdsOnCards: false,
height: '375px',
show: true,
};
Gallery.propTypes = {
show: PropTypes.bool,
isLoaded: PropTypes.bool.isRequired,
galleryIsEmpty: PropTypes.bool.isRequired,
searchIsEmpty: PropTypes.bool.isRequired,

View File

@@ -37,5 +37,9 @@ describe('TextEditor Image Gallery component', () => {
test('snapshot: loaded, show gallery', () => {
expect(shallow(<Gallery {...props} />)).toMatchSnapshot();
});
test('snapshot: not shot gallery', () => {
const wrapper = shallow(<Gallery {...props} show={false} />);
expect(wrapper.type()).toBeNull();
});
});
});

View File

@@ -6,20 +6,21 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { formatMessage } from '../../../testUtils';
import { sortKeys, sortMessages } from '../ImageUploadModal/SelectImageModal/utils';
import { filterKeys, filterMessages } from '../../containers/VideoGallery/utils';
import { SearchSort } from './SearchSort';
describe('SearchSort component', () => {
const props = {
searchString: 'props.searchString',
onSearchChange: jest.fn().mockName('props.onSearchChange'),
clearSearchString: jest.fn().mockName('props.clearSearchString'),
sortBy: sortKeys.dateOldest,
sortKeys,
sortMessages,
onSortClick: jest.fn().mockName('props.onSortClick'),
intl: { formatMessage },
};
describe('snapshots', () => {
describe('snapshots without filterKeys', () => {
const props = {
searchString: 'props.searchString',
onSearchChange: jest.fn().mockName('props.onSearchChange'),
clearSearchString: jest.fn().mockName('props.clearSearchString'),
sortBy: sortKeys.dateOldest,
sortKeys,
sortMessages,
onSortClick: jest.fn().mockName('props.onSortClick'),
intl: { formatMessage },
};
test('with search string (close button)', () => {
expect(shallow(<SearchSort {...props} />)).toMatchSnapshot();
});
@@ -42,4 +43,59 @@ describe('SearchSort component', () => {
)).toEqual(true);
});
});
describe('snapshots with filterKeys', () => {
const props = {
searchString: 'props.searchString',
onSearchChange: jest.fn().mockName('props.onSearchChange'),
clearSearchString: jest.fn().mockName('props.clearSearchString'),
sortBy: sortKeys.dateOldest,
sortKeys,
sortMessages,
filterKeys,
filterMessages,
showSwitch: true,
onSortClick: jest.fn().mockName('props.onSortClick'),
onFilterClick: jest.fn().mockName('props.onFilterClick'),
intl: { formatMessage },
};
test('with search string (close button)', () => {
expect(shallow(<SearchSort {...props} />)).toMatchSnapshot();
});
test('without search string (search icon)', () => {
expect(shallow(<SearchSort {...props} searchString="" />)).toMatchSnapshot();
});
test('adds a sort option for each sortKey', () => {
const el = shallow(<SearchSort {...props} />);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...sortMessages.dateNewest} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...sortMessages.dateOldest} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...sortMessages.nameAscending} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...sortMessages.nameDescending} />,
)).toEqual(true);
});
test('adds a filter option for each filterKet', () => {
const el = shallow(<SearchSort {...props} />);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...filterMessages.videoStatus} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...filterMessages.uploading} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...filterMessages.processing} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...filterMessages.ready} />,
)).toEqual(true);
expect(el.find(Dropdown).containsMatchingElement(
<FormattedMessage {...filterMessages.failed} />,
)).toEqual(true);
});
});
});

View File

@@ -85,9 +85,20 @@ exports[`TextEditor Image Gallery component component snapshot: loaded, show gal
`;
exports[`TextEditor Image Gallery component component snapshot: not loaded, show spinner 1`] = `
<Spinner
animation="border"
className="mie-3"
screenReaderText="loading..."
/>
<div
style={
Object {
"left": "50%",
"position": "absolute",
"top": "50%",
"transform": "translate(-50%, -50%)",
}
}
>
<Spinner
animation="border"
className="mie-3"
screenReaderText="loading..."
/>
</div>
`;

View File

@@ -1,6 +1,291 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SearchSort component snapshots with search string (close button) 1`] = `
exports[`SearchSort component snapshots with filterKeys with search string (close button) 1`] = `
<ActionRow>
<Form.Group
style={
Object {
"margin": 0,
}
}
>
<Form.Control
autoFocus={true}
onChange={[MockFunction props.onSearchChange]}
placeholder="Search"
trailingElement={
<IconButton
iconAs="Icon"
invertColors={true}
isActive={true}
onClick={[MockFunction props.clearSearchString]}
size="sm"
src={[MockFunction icons.Close]}
/>
}
value="props.searchString"
/>
</Form.Group>
<Dropdown>
<Dropdown.Toggle
id="gallery-sort-button"
variant="tertiary"
>
<FormattedMessage
defaultMessage="By date added (oldest)"
description="Dropdown label for sorting by date (oldest)"
id="authoring.texteditor.selectimagemodal.sort.dateoldest.label"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
key="dateNewest"
>
<FormattedMessage
defaultMessage="By date added (newest)"
description="Dropdown label for sorting by date (newest)"
id="authoring.texteditor.selectimagemodal.sort.datenewest.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="dateOldest"
>
<FormattedMessage
defaultMessage="By date added (oldest)"
description="Dropdown label for sorting by date (oldest)"
id="authoring.texteditor.selectimagemodal.sort.dateoldest.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="nameAscending"
>
<FormattedMessage
defaultMessage="By name (ascending)"
description="Dropdown label for sorting by name (ascending)"
id="authoring.texteditor.selectimagemodal.sort.nameascending.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="nameDescending"
>
<FormattedMessage
defaultMessage="By name (descending)"
description="Dropdown label for sorting by name (descending)"
id="authoring.texteditor.selectimagemodal.sort.namedescending.label"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Dropdown>
<Dropdown.Toggle
id="gallery-filter-button"
variant="tertiary"
>
<FormattedMessage />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
key="videoStatus"
>
<FormattedMessage
defaultMessage="Video status"
description="Dropdown label for filter by video status (none)"
id="authoring.selectvideomodal.filter.videostatusnone.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="uploading"
>
<FormattedMessage
defaultMessage="Uploading"
description="Dropdown label for filter by video status (uploading)"
id="authoring.selectvideomodal.filter.videostatusuploading.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="processing"
>
<FormattedMessage
defaultMessage="Processing"
description="Dropdown label for filter by video status (processing)"
id="authoring.selectvideomodal.filter.videostatusprocessing.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="ready"
>
<FormattedMessage
defaultMessage="Ready"
description="Dropdown label for filter by video status (ready)"
id="authoring.selectvideomodal.filter.videostatusready.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="failed"
>
<FormattedMessage
defaultMessage="Failed"
description="Dropdown label for filter by video status (failed)"
id="authoring.selectvideomodal.filter.videostatusfailed.label"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<ActionRow.Spacer />
<Component
isInline={true}
name="switch"
onChange={null}
>
<Component
floatLabelLeft={true}
value="switch-value"
>
<FormattedMessage />
</Component>
</Component>
</ActionRow>
`;
exports[`SearchSort component snapshots with filterKeys without search string (search icon) 1`] = `
<ActionRow>
<Form.Group
style={
Object {
"margin": 0,
}
}
>
<Form.Control
autoFocus={true}
onChange={[MockFunction props.onSearchChange]}
placeholder="Search"
trailingElement={<Icon />}
value=""
/>
</Form.Group>
<Dropdown>
<Dropdown.Toggle
id="gallery-sort-button"
variant="tertiary"
>
<FormattedMessage
defaultMessage="By date added (oldest)"
description="Dropdown label for sorting by date (oldest)"
id="authoring.texteditor.selectimagemodal.sort.dateoldest.label"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
key="dateNewest"
>
<FormattedMessage
defaultMessage="By date added (newest)"
description="Dropdown label for sorting by date (newest)"
id="authoring.texteditor.selectimagemodal.sort.datenewest.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="dateOldest"
>
<FormattedMessage
defaultMessage="By date added (oldest)"
description="Dropdown label for sorting by date (oldest)"
id="authoring.texteditor.selectimagemodal.sort.dateoldest.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="nameAscending"
>
<FormattedMessage
defaultMessage="By name (ascending)"
description="Dropdown label for sorting by name (ascending)"
id="authoring.texteditor.selectimagemodal.sort.nameascending.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="nameDescending"
>
<FormattedMessage
defaultMessage="By name (descending)"
description="Dropdown label for sorting by name (descending)"
id="authoring.texteditor.selectimagemodal.sort.namedescending.label"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Dropdown>
<Dropdown.Toggle
id="gallery-filter-button"
variant="tertiary"
>
<FormattedMessage />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
key="videoStatus"
>
<FormattedMessage
defaultMessage="Video status"
description="Dropdown label for filter by video status (none)"
id="authoring.selectvideomodal.filter.videostatusnone.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="uploading"
>
<FormattedMessage
defaultMessage="Uploading"
description="Dropdown label for filter by video status (uploading)"
id="authoring.selectvideomodal.filter.videostatusuploading.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="processing"
>
<FormattedMessage
defaultMessage="Processing"
description="Dropdown label for filter by video status (processing)"
id="authoring.selectvideomodal.filter.videostatusprocessing.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="ready"
>
<FormattedMessage
defaultMessage="Ready"
description="Dropdown label for filter by video status (ready)"
id="authoring.selectvideomodal.filter.videostatusready.label"
/>
</Dropdown.Item>
<Dropdown.Item
key="failed"
>
<FormattedMessage
defaultMessage="Failed"
description="Dropdown label for filter by video status (failed)"
id="authoring.selectvideomodal.filter.videostatusfailed.label"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<ActionRow.Spacer />
<Component
isInline={true}
name="switch"
onChange={null}
>
<Component
floatLabelLeft={true}
value="switch-value"
>
<FormattedMessage />
</Component>
</Component>
</ActionRow>
`;
exports[`SearchSort component snapshots without filterKeys with search string (close button) 1`] = `
<ActionRow>
<Form.Group
style={
@@ -80,7 +365,7 @@ exports[`SearchSort component snapshots with search string (close button) 1`] =
</ActionRow>
`;
exports[`SearchSort component snapshots without search string (search icon) 1`] = `
exports[`SearchSort component snapshots without filterKeys without search string (search icon) 1`] = `
<ActionRow>
<Form.Group
style={

View File

@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Selection Modal snapshots rendering correctly with expected Input 1`] = `
<ContextConsumer>
<Component />
</ContextConsumer>
`;
exports[`Selection Modal snapshots rendering with props to null 1`] = `
<ContextConsumer>
<Component />
</ContextConsumer>
`;

View File

@@ -43,8 +43,18 @@ export const SelectionModal = ({
fetchError,
uploadError,
} = modalMessages;
let background = '#FFFFFF';
let showGallery = true;
if (isLoaded && !isFetchError && !isUploadError && !inputError.show) {
background = '#EBEBEB';
} else if (isLoaded) {
showGallery = false;
}
const galleryPropsValues = {
isLoaded,
show: showGallery,
...galleryProps,
};
return (
@@ -64,7 +74,7 @@ export const SelectionModal = ({
</Button>
)}
title={intl.formatMessage(titleMsg)}
bodyStyle={{ background: '#EBEBEB' }}
bodyStyle={{ background, padding: '24px' }}
headerComponent={(
<div style={{ zIndex: 10000, margin: '18px 0' }}>
<SearchSort {...searchSortProps} />

View File

@@ -1,7 +1,9 @@
import React from 'react';
import { shallow } from 'enzyme';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render, screen } from '@testing-library/react';
import { formatMessage } from '../../../testUtils';
import SelectionModal from '.';
import '@testing-library/jest-dom';
const props = {
isOpen: jest.fn(),
@@ -69,7 +71,7 @@ const props = {
jest.mock('../BaseModal', () => 'BaseModal');
jest.mock('./SearchSort', () => 'SearchSort');
jest.mock('./Gallery', () => 'Gallery');
jest.mock('./Gallery', () => () => 'Gallery');
jest.mock('../FileInput', () => 'FileInput');
jest.mock('../ErrorAlerts/ErrorAlert', () => 'ErrorAlert');
jest.mock('../ErrorAlerts/FetchErrorAlert', () => 'FetchErrorAlert');
@@ -77,11 +79,13 @@ jest.mock('../ErrorAlerts/UploadErrorAlert', () => 'UploadErrorAlert');
describe('Selection Modal', () => {
describe('snapshots', () => {
test('rendering correctly with expected Input', () => {
expect(shallow(<SelectionModal {...props} />)).toMatchSnapshot();
});
test('rendering with props to null', () => {
expect(shallow(<SelectionModal />)).toMatchSnapshot();
test('rendering correctly with expected Input', async () => {
render(
<IntlProvider>
<SelectionModal {...props} />
</IntlProvider>,
);
expect(screen.getByText('Gallery')).toBeInTheDocument();
});
});
});