diff --git a/__tests__/src/components/ManifestListItemError.test.js b/__tests__/src/components/ManifestListItemError.test.js new file mode 100644 index 0000000000000000000000000000000000000000..9606b3de0e3ed051f716e3d8c7d736edfd2dd416 --- /dev/null +++ b/__tests__/src/components/ManifestListItemError.test.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import Typography from '@material-ui/core/Typography'; +import ManifestListItemError from '../../../src/components/ManifestListItemError'; + +/** + * Helper function to wrap creating a ManifestListItemError component +*/ +function createWrapper(props) { + return shallow( + <ManifestListItemError + manifestId="http://example.com" + onDismissClick={() => {}} + onTryAgainClick={() => {}} + t={key => key} + {...props} + />, + ).dive(); // unwrap HOC created by withStyles() +} + +describe('ManifestListItemError', () => { + let wrapper; + let mockFn; + + it('renders the failed manifest url and error key', () => { + wrapper = createWrapper(); + + expect( + wrapper.find(Typography).children().first().text(), + ).toEqual('manifestError'); // the i18n key + + expect( + wrapper.find(Typography).children().last().text(), + ).toEqual('http://example.com'); + }); + + it('has a dismiss button that fires the onDismissClick prop', () => { + mockFn = jest.fn(); + wrapper = createWrapper({ onDismissClick: mockFn }); + + wrapper.find('WithStyles(Button)').first().simulate('click'); + expect(mockFn).toHaveBeenCalledTimes(1); + expect(mockFn).toHaveBeenCalledWith('http://example.com'); + }); + + it('has a try again button that fires the onTryAgainClick prop', () => { + mockFn = jest.fn(); + wrapper = createWrapper({ onTryAgainClick: mockFn }); + + wrapper.find('WithStyles(Button)').last().simulate('click'); + expect(mockFn).toHaveBeenCalledTimes(1); + expect(mockFn).toHaveBeenCalledWith('http://example.com'); + }); +}); diff --git a/locales/en/translation.json b/locales/en/translation.json index cccb1a7cd96c16732278976805381d66fcbebc24..2b316ec58f400c10823d9c1105cc405e566832e1 100644 --- a/locales/en/translation.json +++ b/locales/en/translation.json @@ -16,12 +16,14 @@ "closeMenu": "Close Menu", "closeWindow": "Close window", "dark": "Dark", + "dismiss": "Dismiss", "downloadExport": "Download/Export", "downloadExportWorkspace": "Download/export workspace", "fetchManifest": "Add", "fullScreen": "Full Screen", "light": "Light", "listAllOpenWindows": "List all open windows", + "manifestError": "The resource cannot be added:", "menu": "Menu", "off": "Off", "openInfoCompanionWindow": "Open information companion window", @@ -35,6 +37,7 @@ "theme": "Theme", "thumbnails": "Thumbnails", "toggleWindowSideBar": "Toggle window sidebar", + "tryAgain": "Try again", "untitled": "[Untitled]", "view": "View", "zoomIn": "Zoom in", diff --git a/src/components/ManifestListItemError.js b/src/components/ManifestListItemError.js new file mode 100644 index 0000000000000000000000000000000000000000..2d4c82df387012d7ac2c3317205fb9817ebd16e6 --- /dev/null +++ b/src/components/ManifestListItemError.js @@ -0,0 +1,79 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Button from '@material-ui/core/Button'; +import ErrorIcon from '@material-ui/icons/ErrorOutline'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import { withStyles } from '@material-ui/core/styles'; + +/** + * ManifestListItemError renders a component displaying a + * message to the user about a problem loading a manifest +*/ +class ManifestListItemError extends Component { + /** + * Returns the rendered component + */ + render() { + const { + classes, manifestId, onDismissClick, onTryAgainClick, t, + } = this.props; + + return ( + <Grid container> + <Grid container> + <Grid container item xs={12} sm={6}> + <Grid item xs={4} sm={3}> + <Grid container justify="center"> + <ErrorIcon className={classes.errorIcon} /> + </Grid> + </Grid> + <Grid item xs={8} sm={9}> + <Typography>{t('manifestError')}</Typography> + <Typography className={classes.manifestIdText}>{manifestId}</Typography> + </Grid> + </Grid> + </Grid> + + <Grid container> + <Grid container item xs={12} sm={6} justify="flex-end"> + <Grid item> + <Button onClick={() => { onDismissClick(manifestId); }}> + {t('dismiss')} + </Button> + <Button onClick={() => { onTryAgainClick(manifestId); }}> + {t('tryAgain')} + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + ); + } +} + + +ManifestListItemError.propTypes = { + classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + manifestId: PropTypes.string.isRequired, + onDismissClick: PropTypes.func.isRequired, + onTryAgainClick: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, +}; + +/** + Material UI styles + @private + */ +const styles = theme => ({ + errorIcon: { + color: theme.palette.error.main, + height: '2rem', + width: '2rem', + }, + manifestIdText: { + wordBreak: 'break-all', + }, +}); + +export default withStyles(styles)(ManifestListItemError); diff --git a/src/containers/ManifestListItemError.js b/src/containers/ManifestListItemError.js new file mode 100644 index 0000000000000000000000000000000000000000..237c17f9b5a3048ec1db3f1e3d8e6bdf264c6c01 --- /dev/null +++ b/src/containers/ManifestListItemError.js @@ -0,0 +1,19 @@ +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { withNamespaces } from 'react-i18next'; +import { fetchManifest, removeManifest } from '../state/actions/manifest'; +import ManifestListItemError from '../components/ManifestListItemError'; + +/** */ +const mapDispatchToProps = { + onDismissClick: removeManifest, + onTryAgainClick: fetchManifest, +}; + +const enhance = compose( + connect(null, mapDispatchToProps), + withNamespaces(), + // further HOC +); + +export default enhance(ManifestListItemError);