diff --git a/src/components/menu-bar/confirm-replace-hoc.jsx b/src/components/menu-bar/confirm-replace-hoc.jsx deleted file mode 100644 index 5631b800b..000000000 --- a/src/components/menu-bar/confirm-replace-hoc.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import {connect} from 'react-redux'; -import PropTypes from 'prop-types'; -import bindAll from 'lodash.bindall'; -import React from 'react'; - -const ConfirmReplaceHOC = function (WrappedComponent) { - class ConfirmReplaceProject extends React.PureComponent { - constructor (props) { - super(props); - - bindAll(this, [ - 'confirmReadyToReplaceProject' - ]); - } - - confirmReadyToReplaceProject (message) { - let readyToReplaceProject = true; - if (this.props.projectChanged && !this.props.canCreateNew) { - readyToReplaceProject = confirm(message); // eslint-disable-line no-alert - } - return readyToReplaceProject; - } - - render () { - const { - /* eslint-disable no-unused-vars */ - projectChanged, - /* eslint-enable no-unused-vars */ - ...props - } = this.props; - return (); - } - } - - ConfirmReplaceProject.propTypes = { - canCreateNew: PropTypes.bool, - projectChanged: PropTypes.bool - }; - - const _mapStateToProps = state => ({ - projectChanged: state.scratchGui.projectChanged - }); - - return connect(_mapStateToProps)(ConfirmReplaceProject); -}; - -export default ConfirmReplaceHOC; diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 5c957525c..22cd2d919 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -28,7 +28,7 @@ import LoginDropdown from './login-dropdown.jsx'; import SB3Downloader from '../../containers/sb3-downloader.jsx'; import DeletionRestorer from '../../containers/deletion-restorer.jsx'; import TurboMode from '../../containers/turbo-mode.jsx'; -import ConfirmReplaceHOC from './confirm-replace-hoc.jsx'; +import MenuBarHOC from '../../containers/menu-bar-hoc.jsx'; import {openTipsLibrary} from '../../reducers/modals'; import {setPlayer} from '../../reducers/mode'; @@ -192,8 +192,8 @@ class MenuBar extends React.Component { this.props.onRequestCloseFile(); } handleClickSeeCommunity (waitForUpdate) { - if (this.props.canSave && this.props.projectChanged) { // save before transitioning to project page - this.props.autoUpdateProject(); + if (this.props.shouldSaveBeforeTransition()) { + this.props.autoUpdateProject(); // save before transitioning to project page waitForUpdate(true); // queue the transition to project page } else { waitForUpdate(false); // immediately transition to project page @@ -705,6 +705,7 @@ MenuBar.propTypes = { canSave: PropTypes.bool, canShare: PropTypes.bool, className: PropTypes.string, + confirmReadyToReplaceProject: PropTypes.func, editMenuOpen: PropTypes.bool, enableCommunity: PropTypes.bool, fileMenuOpen: PropTypes.bool, @@ -741,6 +742,7 @@ MenuBar.propTypes = { projectTitle: PropTypes.string, renderLogin: PropTypes.func, sessionExists: PropTypes.bool, + shouldSaveBeforeTransition: PropTypes.func, showComingSoon: PropTypes.bool, username: PropTypes.string, vm: PropTypes.instanceOf(VM).isRequired @@ -794,7 +796,7 @@ const mapDispatchToProps = dispatch => ({ export default compose( injectIntl, - ConfirmReplaceHOC, + MenuBarHOC, connect( mapStateToProps, mapDispatchToProps diff --git a/src/containers/menu-bar-hoc.jsx b/src/containers/menu-bar-hoc.jsx new file mode 100644 index 000000000..71e56fa89 --- /dev/null +++ b/src/containers/menu-bar-hoc.jsx @@ -0,0 +1,66 @@ +import {connect} from 'react-redux'; +import PropTypes from 'prop-types'; +import bindAll from 'lodash.bindall'; +import React from 'react'; + +const MenuBarHOC = function (WrappedComponent) { + class MenuBarContainer extends React.PureComponent { + constructor (props) { + super(props); + + bindAll(this, [ + 'confirmReadyToReplaceProject', + 'shouldSaveBeforeTransition' + ]); + } + confirmReadyToReplaceProject (message) { + let readyToReplaceProject = true; + if (this.props.projectChanged && !this.props.canCreateNew) { + readyToReplaceProject = this.props.confirmWithMessage(message); + } + return readyToReplaceProject; + } + shouldSaveBeforeTransition () { + return (this.props.canSave && this.props.projectChanged); + } + render () { + const { + /* eslint-disable no-unused-vars */ + projectChanged, + /* eslint-enable no-unused-vars */ + ...props + } = this.props; + return (); + } + } + + MenuBarContainer.propTypes = { + canCreateNew: PropTypes.bool, + canSave: PropTypes.bool, + confirmWithMessage: PropTypes.func, + projectChanged: PropTypes.bool + }; + MenuBarContainer.defaultProps = { + // default to using standard js confirm + confirmWithMessage: message => (confirm(message)) // eslint-disable-line no-alert + }; + const mapStateToProps = state => ({ + projectChanged: state.scratchGui.projectChanged + }); + const mapDispatchToProps = () => ({}); + // Allow incoming props to override redux-provided props. Used to mock in tests. + const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign( + {}, stateProps, dispatchProps, ownProps + ); + return connect( + mapStateToProps, + mapDispatchToProps, + mergeProps + )(MenuBarContainer); +}; + +export default MenuBarHOC; diff --git a/test/unit/containers/menu-bar-hoc.test.jsx b/test/unit/containers/menu-bar-hoc.test.jsx new file mode 100644 index 000000000..9099c4f10 --- /dev/null +++ b/test/unit/containers/menu-bar-hoc.test.jsx @@ -0,0 +1,164 @@ +import React from 'react'; +import {mount} from 'enzyme'; +import configureStore from 'redux-mock-store'; +import MenuBarHOC from '../../../src/containers/menu-bar-hoc.jsx'; + +describe('Menu Bar HOC', () => { + const mockStore = configureStore(); + let store; + + beforeEach(() => { + store = mockStore({ + scratchGui: { + projectChanged: true + } + }); + }); + + test('Logged in user who IS owner and HAS changed project will NOT be prompted to save', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + (false)} // eslint-disable-line react/jsx-no-bind + store={store} + /> + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().confirmReadyToReplaceProject('message')).toBe(true); + }); + + test('Logged in user who IS owner and has NOT changed project will NOT be prompted to save', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + (false)} // eslint-disable-line react/jsx-no-bind + projectChanged={false} + store={store} + /> + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().confirmReadyToReplaceProject('message')).toBe(true); + }); + + test('Logged in user who is NOT owner and HAS changed project will NOT be prompted to save', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + (false)} // eslint-disable-line react/jsx-no-bind + store={store} + /> + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().confirmReadyToReplaceProject('message')).toBe(true); + }); + + test('Logged OUT user who HAS changed project WILL be prompted to save', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + (false)} // eslint-disable-line react/jsx-no-bind + store={store} + /> + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().confirmReadyToReplaceProject('message')).toBe(false); + }); + + test('Logged OUT user who has NOT changed project WILL NOT be prompted to save', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + (false)} // eslint-disable-line react/jsx-no-bind + projectChanged={false} + store={store} + /> + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().confirmReadyToReplaceProject('message')).toBe(true); + }); + + test('Logged in user who IS owner and HAS changed project SHOULD save before transition to project page', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().shouldSaveBeforeTransition()).toBe(true); + }); + + test('Logged in user who IS owner and has NOT changed project should NOT save before transition', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().shouldSaveBeforeTransition()).toBe(false); + }); + + test('Logged in user who is NOT owner and HAS changed project should NOT save before transition', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().shouldSaveBeforeTransition()).toBe(false); + }); + + test('Logged in user who is NOT owner and has NOT changed project should NOT save before transition', () => { + const Component = () => (
); + const WrappedComponent = MenuBarHOC(Component); + const wrapper = mount( + + ); + const child = wrapper.find(Component); + expect(child.props().projectChanged).toBeUndefined(); + expect(child.props().shouldSaveBeforeTransition()).toBe(false); + }); + +});