diff --git a/__tests__/src/components/WindowTopBarTitle.test.js b/__tests__/src/components/WindowTopBarTitle.test.js index fb7b84d0affc414efaae0bfdfe37188171d8df1f..f3b01c10e2b080db1f03ce955ee04ebf250e02f8 100644 --- a/__tests__/src/components/WindowTopBarTitle.test.js +++ b/__tests__/src/components/WindowTopBarTitle.test.js @@ -1,7 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; - -import Typography from '@material-ui/core/Typography'; +import Skeleton from '@material-ui/lab/Skeleton'; import { WindowTopBarTitle } from '../../../src/components/WindowTopBarTitle'; @@ -20,16 +19,26 @@ function createWrapper(props) { describe('WindowTopBarTitle', () => { it('renders all needed elements', () => { const wrapper = createWrapper(); - expect(wrapper.find(Typography).length).toBe(1); + expect(wrapper.find('TitleTypography').length).toBe(1); }); it('passes correct props to <Typography/>', () => { const wrapper = createWrapper(); - expect(wrapper.find(Typography).first().render().text()).toBe('awesome manifest'); + expect(wrapper.find('TitleTypography').first().render().text()).toBe('awesome manifest'); + }); + + it('renders a Skeleton when loading', () => { + const wrapper = createWrapper({ isFetching: true }); + expect(wrapper.find('TitleTypography').dive().find(Skeleton).length).toBe(1); + }); + + it('renders an error', () => { + const wrapper = createWrapper({ error: 'some error message' }); + expect(wrapper.find('TitleTypography').render().text()).toBe('some error message'); }); it('title is configurable', () => { - expect(createWrapper({ hideWindowTitle: true }).find(Typography).length).toEqual(0); + expect(createWrapper({ hideWindowTitle: true }).find('TitleTypography').length).toEqual(0); expect(createWrapper({ hideWindowTitle: true }).find('div').length).toEqual(1); }); }); diff --git a/__tests__/src/selectors/manifests.test.js b/__tests__/src/selectors/manifests.test.js index 15cda397e94ed6ef81d603eff1ec68e71340f191..f59ed884f65486ccd5efaccdcb8e8ad00205d200 100644 --- a/__tests__/src/selectors/manifests.test.js +++ b/__tests__/src/selectors/manifests.test.js @@ -13,6 +13,7 @@ import { getManifestLocale, getDestructuredMetadata, getManifest, + getManifestStatus, getManifestLogo, getManifestCanvases, getManifestDescription, @@ -54,7 +55,6 @@ describe('getManifest()', () => { expect(received).toEqual(expected); }); - it('should return the manifest of a certain window', () => { const received = getManifest(state, { windowId: 'a' }); const expected = { id: 'x' }; @@ -77,6 +77,26 @@ describe('getManifest()', () => { }); }); +describe('getManifestStatus', () => { + const state = { + manifests: { + x: { id: 'x' }, + }, + }; + + it('returns the manifest of a certain id', () => { + const received = getManifestStatus(state, { manifestId: 'x' }); + const expected = { id: 'x' }; + expect(received).toEqual(expected); + }); + + it('returns a placeholder', () => { + const received = getManifestStatus(state, { manifestId: 'y' }); + const expected = { missing: true }; + expect(received).toEqual(expected); + }); +}); + describe('getManifestoInstance', () => { it('creates a manifesto instance', () => { const state = { manifests: { x: { json: manifestFixture019 } } }; diff --git a/src/components/WindowTopBarTitle.js b/src/components/WindowTopBarTitle.js index 29f042b3c469dc6b2b777d7d749039ad7a5ad052..ed36e8aff8847d47fe33e70b65f489d6a7bb01e2 100644 --- a/src/components/WindowTopBarTitle.js +++ b/src/components/WindowTopBarTitle.js @@ -1,6 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Typography from '@material-ui/core/Typography'; +import Skeleton from '@material-ui/lab/Skeleton'; +import ErrorIcon from '@material-ui/icons/ErrorOutlineSharp'; /** * WindowTopBarTitle @@ -12,16 +14,39 @@ export class WindowTopBarTitle extends Component { */ render() { const { - classes, hideWindowTitle, manifestTitle, + classes, error, hideWindowTitle, isFetching, manifestTitle, } = this.props; + + /** */ + const TitleTypography = props => ( + <Typography variant="h2" noWrap color="inherit" className={classes.title} {...props}> + {props.children} + </Typography> + ); + let title = null; - if (hideWindowTitle) { + if (isFetching) { + title = ( + <TitleTypography> + <Skeleton variant="text" /> + </TitleTypography> + ); + } else if (error) { + title = ( + <> + <ErrorIcon color="error" /> + <TitleTypography color="textSecondary"> + {error} + </TitleTypography> + </> + ); + } else if (hideWindowTitle) { title = (<div className={classes.title} />); } else { title = ( - <Typography variant="h2" noWrap color="inherit" className={classes.title}> + <TitleTypography> {manifestTitle} - </Typography> + </TitleTypography> ); } return title; @@ -30,11 +55,15 @@ export class WindowTopBarTitle extends Component { WindowTopBarTitle.propTypes = { classes: PropTypes.objectOf(PropTypes.string).isRequired, + error: PropTypes.string, hideWindowTitle: PropTypes.bool, + isFetching: PropTypes.bool, manifestTitle: PropTypes.string, }; WindowTopBarTitle.defaultProps = { + error: null, hideWindowTitle: false, + isFetching: false, manifestTitle: '', }; diff --git a/src/containers/WindowTopBar.js b/src/containers/WindowTopBar.js index a48b7a7f00f7869a318e091ccc93c6d5429013b4..967f91e185773ffb063ed43fdc414edccc127aeb 100644 --- a/src/containers/WindowTopBar.js +++ b/src/containers/WindowTopBar.js @@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getManifestTitle, getWindow } from '../state/selectors'; +import { getWindow } from '../state/selectors'; import { WindowTopBar } from '../components/WindowTopBar'; /** mapStateToProps */ @@ -15,7 +15,6 @@ const mapStateToProps = (state, { windowId }) => ({ allowTopMenuButton: state.config.window.allowTopMenuButton, allowWindowSideBar: state.config.window.allowWindowSideBar, focused: state.workspace.focusedWindowId === windowId, - manifestTitle: getManifestTitle(state, { windowId }), maximized: (getWindow(state, { windowId }) || {}).maximized, }); diff --git a/src/containers/WindowTopBarTitle.js b/src/containers/WindowTopBarTitle.js index e17fecfa9c73df6d925a927280bb29c328ed7a7d..e1c65661119c323eabbd78cb9ef1fe2c792bec71 100644 --- a/src/containers/WindowTopBarTitle.js +++ b/src/containers/WindowTopBarTitle.js @@ -3,12 +3,14 @@ import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core'; import { withPlugins } from '../extend/withPlugins'; -import { getManifestTitle } from '../state/selectors'; +import { getManifestStatus, getManifestTitle } from '../state/selectors'; import { WindowTopBarTitle } from '../components/WindowTopBarTitle'; /** mapStateToProps */ const mapStateToProps = (state, { windowId }) => ({ + error: getManifestStatus(state, { windowId }).error, hideWindowTitle: state.config.window.hideWindowTitle, + isFetching: getManifestStatus(state, { windowId }).isFetching, manifestTitle: getManifestTitle(state, { windowId }), }); diff --git a/src/state/selectors/manifests.js b/src/state/selectors/manifests.js index 39e45811c90197da3a68c9a96c111f732f8d08b0..373aef7ce42b453c4a8fc7e86008cbb733e1b8cd 100644 --- a/src/state/selectors/manifests.js +++ b/src/state/selectors/manifests.js @@ -19,6 +19,12 @@ export function getManifest(state, { manifestId, windowId }) { ]; } +/** Convenience selector to get a manifest (or placeholder) */ +export const getManifestStatus = createSelector( + [getManifest], + manifest => manifest || { missing: true }, +); + /** Instantiate a manifesto instance */ export const getManifestoInstance = createCachedSelector( getManifest,