diff --git a/__tests__/src/components/Window.test.js b/__tests__/src/components/Window.test.js index 0314462641216de887746ecfc17daed0d0d60ffb..9a2e2587c96efc89274f281cd68508dbc176219d 100644 --- a/__tests__/src/components/Window.test.js +++ b/__tests__/src/components/Window.test.js @@ -7,6 +7,10 @@ import WindowMiddleContent from '../../../src/containers/WindowMiddleContent'; describe('Window', () => { let wrapper; const window = { id: 123, xywh: [0, 0, 400, 500] }; + it('should render nothing, if provided with no window data', () => { + wrapper = shallow(<Window />); + expect(wrapper.find('.mirador-window')).toHaveLength(0); + }); it('should render outer element', () => { wrapper = shallow(<Window window={window} />); expect(wrapper.find('.mirador-window')).toHaveLength(1); diff --git a/__tests__/src/components/WorkspaceMosaic.test.js b/__tests__/src/components/WorkspaceMosaic.test.js index 64b2a013163298979de207e54edeeae2540525ea..ea11f83901d10ba4db813e654cca6530561cacfd 100644 --- a/__tests__/src/components/WorkspaceMosaic.test.js +++ b/__tests__/src/components/WorkspaceMosaic.test.js @@ -3,44 +3,48 @@ import { shallow } from 'enzyme'; import { Mosaic } from 'react-mosaic-component'; import WorkspaceMosaic from '../../../src/components/WorkspaceMosaic'; +/** create wrapper */ +function createWrapper(props) { + return shallow( + <WorkspaceMosaic + windows={{}} + workspace={{}} + updateWorkspaceMosaicLayout={() => {}} + {...props} + />, + ); +} + describe('WorkspaceMosaic', () => { const windows = { 1: { id: 1 }, 2: { id: 2 } }; let wrapper; beforeEach(() => { - wrapper = shallow( - <WorkspaceMosaic - windows={windows} - workspace={{}} - updateWorkspaceMosaicLayout={() => {}} - />, - ); + wrapper = createWrapper({ windows }); }); it('should render properly with an initialValue', () => { expect(wrapper.matchesElement( <Mosaic initialValue={{ direction: 'row', first: '1', second: '2' }} />, )).toBe(true); }); + describe('componentDidUpdate', () => { + it('updates the workspace layout when windows change', () => { + const updateWorkspaceMosaicLayout = jest.fn(); + wrapper = createWrapper({ windows, updateWorkspaceMosaicLayout }); + + wrapper.setProps({ windows: { ...windows, 3: { id: 3 } } }); + + expect(updateWorkspaceMosaicLayout).toHaveBeenCalled(); + }); + }); describe('determineWorkspaceLayout', () => { it('when window ids do not match workspace layout', () => { - wrapper = shallow( - <WorkspaceMosaic - windows={windows} - workspace={{ layout: 'foo' }} - updateWorkspaceMosaicLayout={() => {}} - />, - ); + wrapper = createWrapper({ windows, workspace: { layout: 'foo' } }); expect(wrapper.instance().determineWorkspaceLayout()).toMatchObject({ direction: 'row', first: '1', second: '2', }); }); it('when window ids match workspace layout', () => { - wrapper = shallow( - <WorkspaceMosaic - windows={{ foo: { id: 'foo' } }} - workspace={{ layout: 'foo' }} - updateWorkspaceMosaicLayout={() => {}} - />, - ); + wrapper = createWrapper({ windows: { foo: { id: 'foo' } }, workspace: { layout: 'foo' } }); expect(wrapper.instance().determineWorkspaceLayout()).toBeNull(); }); }); @@ -54,16 +58,11 @@ describe('WorkspaceMosaic', () => { }); describe('mosaicChange', () => { it('calls the provided prop to update layout', () => { - const mock = jest.fn(); - wrapper = shallow( - <WorkspaceMosaic - windows={{ foo: { id: 'foo' } }} - workspace={{ layout: 'foo' }} - updateWorkspaceMosaicLayout={mock} - />, - ); + const updateWorkspaceMosaicLayout = jest.fn(); + wrapper = createWrapper({ windows, updateWorkspaceMosaicLayout }); + wrapper.instance().mosaicChange(); - expect(mock).toBeCalled(); + expect(updateWorkspaceMosaicLayout).toBeCalled(); }); }); }); diff --git a/src/components/Window.js b/src/components/Window.js index 380c1c8383a26237e7f19ec7be96dbcbb56dd500..af4819da86836a54b968a875165896185eb75920 100644 --- a/src/components/Window.js +++ b/src/components/Window.js @@ -15,6 +15,8 @@ class Window extends Component { */ render() { const { manifest, window } = this.props; + if (!window) return <></>; + return ( <div id={window.id} className={ns('window')}> <WindowTopBar @@ -38,11 +40,12 @@ class Window extends Component { } Window.propTypes = { - window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + window: PropTypes.object, // eslint-disable-line react/forbid-prop-types manifest: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; Window.defaultProps = { + window: null, manifest: null, }; diff --git a/src/components/WorkspaceMosaic.js b/src/components/WorkspaceMosaic.js index 25417adea04374d6a9152fd1eb52f228601ace51..15006a5ee7a9e5fba0dad6250fc1176c9ec63ff8 100644 --- a/src/components/WorkspaceMosaic.js +++ b/src/components/WorkspaceMosaic.js @@ -23,6 +23,44 @@ class WorkspaceMosaic extends React.Component { this.zeroStateView = <div />; } + /** */ + componentDidMount() { + const { updateWorkspaceMosaicLayout } = this.props; + + const newLayout = this.determineWorkspaceLayout(); + if (newLayout) updateWorkspaceMosaicLayout(newLayout); + } + + /** */ + componentDidUpdate(prevProps) { + const { windows, workspace, updateWorkspaceMosaicLayout } = this.props; + if (prevProps.windows !== windows || prevProps.workspace !== workspace) { + const newLayout = this.determineWorkspaceLayout(); + if (newLayout) updateWorkspaceMosaicLayout(newLayout); + } + } + + /** + * Used to determine whether or not a "new" layout should be autogenerated. + * If a Window is added or removed, generate that new layout and use that for + * this render. When the Mosaic changes, that will trigger a new store update. + */ + determineWorkspaceLayout() { + const { windows, workspace } = this.props; + const windowKeys = Object.keys(windows).sort(); + const leaveKeys = getLeaves(workspace.layout); + // Check every window is in the layout, and all layout windows are present + // in store + if (!windowKeys.every(e => leaveKeys.includes(e)) + || !leaveKeys.every(e => windowKeys.includes(e))) { + const newLayout = createBalancedTreeFromLeaves(windowKeys); + + return newLayout; + } + + return null; + } + /** * Render a tile (Window) in the Mosaic. */ @@ -46,35 +84,14 @@ class WorkspaceMosaic extends React.Component { updateWorkspaceMosaicLayout(newLayout); } - /** - * Used to determine whether or not a "new" layout should be autogenerated. - * If a Window is added or removed, generate that new layout and use that for - * this render. When the Mosaic changes, that will trigger a new store update. - */ - determineWorkspaceLayout() { - const { windows, workspace, updateWorkspaceMosaicLayout } = this.props; - const windowKeys = Object.keys(windows).sort(); - const leaveKeys = getLeaves(workspace.layout); - // Check every window is in the layout, and all layout windows are present - // in store - if (!windowKeys.every(e => leaveKeys.includes(e)) - || !leaveKeys.every(e => windowKeys.includes(e))) { - const newLayout = createBalancedTreeFromLeaves(windowKeys); - updateWorkspaceMosaicLayout(newLayout); - return newLayout; - } - return null; - } - /** */ render() { const { workspace } = this.props; - const newLayout = this.determineWorkspaceLayout(); return ( <Mosaic renderTile={this.tileRenderer} - initialValue={newLayout || workspace.layout} + initialValue={workspace.layout || this.determineWorkspaceLayout()} onChange={this.mosaicChange} className="mirador-mosaic" zeroStateView={this.zeroStateView}