From 16024769c8a7b4b27d28ac5211cb61afd164fa0e Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Tue, 30 Apr 2019 15:02:22 -0400 Subject: [PATCH 1/2] renamed confirm-replace-hoc to menu-bar-hoc; moved project changed checking there --- src/components/menu-bar/menu-bar.jsx | 8 +++++--- .../menu-bar-hoc.jsx} | 20 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) rename src/{components/menu-bar/confirm-replace-hoc.jsx => containers/menu-bar-hoc.jsx} (68%) diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 5c957525c..1857a6315 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,7 +192,7 @@ class MenuBar extends React.Component { this.props.onRequestCloseFile(); } handleClickSeeCommunity (waitForUpdate) { - if (this.props.canSave && this.props.projectChanged) { // save before transitioning to project page + if (this.props.shouldSaveBeforeTransition()) { // save before transitioning to project page this.props.autoUpdateProject(); waitForUpdate(true); // queue the transition to project page } else { @@ -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/components/menu-bar/confirm-replace-hoc.jsx b/src/containers/menu-bar-hoc.jsx similarity index 68% rename from src/components/menu-bar/confirm-replace-hoc.jsx rename to src/containers/menu-bar-hoc.jsx index 5631b800b..a33f36107 100644 --- a/src/components/menu-bar/confirm-replace-hoc.jsx +++ b/src/containers/menu-bar-hoc.jsx @@ -3,16 +3,16 @@ import PropTypes from 'prop-types'; import bindAll from 'lodash.bindall'; import React from 'react'; -const ConfirmReplaceHOC = function (WrappedComponent) { - class ConfirmReplaceProject extends React.PureComponent { +const MenuBarHOC = function (WrappedComponent) { + class MenuBarComponent extends React.PureComponent { constructor (props) { super(props); bindAll(this, [ - 'confirmReadyToReplaceProject' + 'confirmReadyToReplaceProject', + 'shouldSaveBeforeTransition' ]); } - confirmReadyToReplaceProject (message) { let readyToReplaceProject = true; if (this.props.projectChanged && !this.props.canCreateNew) { @@ -20,7 +20,9 @@ const ConfirmReplaceHOC = function (WrappedComponent) { } return readyToReplaceProject; } - + shouldSaveBeforeTransition () { + return (this.props.canSave && this.props.projectChanged); + } render () { const { /* eslint-disable no-unused-vars */ @@ -30,13 +32,15 @@ const ConfirmReplaceHOC = function (WrappedComponent) { } = this.props; return (); } } - ConfirmReplaceProject.propTypes = { + MenuBarComponent.propTypes = { canCreateNew: PropTypes.bool, + canSave: PropTypes.bool, projectChanged: PropTypes.bool }; @@ -44,7 +48,7 @@ const ConfirmReplaceHOC = function (WrappedComponent) { projectChanged: state.scratchGui.projectChanged }); - return connect(_mapStateToProps)(ConfirmReplaceProject); + return connect(_mapStateToProps)(MenuBarComponent); }; -export default ConfirmReplaceHOC; +export default MenuBarHOC; From 516baedad94349c5457ae63ca46b2fe3d7bcc8d8 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Tue, 30 Apr 2019 18:28:46 -0400 Subject: [PATCH 2/2] added tests, rewrote menu-bar-hoc to accomodate tests --- src/components/menu-bar/menu-bar.jsx | 4 +- src/containers/menu-bar-hoc.jsx | 26 +++- test/unit/containers/menu-bar-hoc.test.jsx | 164 +++++++++++++++++++++ 3 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 test/unit/containers/menu-bar-hoc.test.jsx diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 1857a6315..22cd2d919 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -192,8 +192,8 @@ class MenuBar extends React.Component { this.props.onRequestCloseFile(); } handleClickSeeCommunity (waitForUpdate) { - if (this.props.shouldSaveBeforeTransition()) { // 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 diff --git a/src/containers/menu-bar-hoc.jsx b/src/containers/menu-bar-hoc.jsx index a33f36107..71e56fa89 100644 --- a/src/containers/menu-bar-hoc.jsx +++ b/src/containers/menu-bar-hoc.jsx @@ -4,7 +4,7 @@ import bindAll from 'lodash.bindall'; import React from 'react'; const MenuBarHOC = function (WrappedComponent) { - class MenuBarComponent extends React.PureComponent { + class MenuBarContainer extends React.PureComponent { constructor (props) { super(props); @@ -16,7 +16,7 @@ const MenuBarHOC = function (WrappedComponent) { confirmReadyToReplaceProject (message) { let readyToReplaceProject = true; if (this.props.projectChanged && !this.props.canCreateNew) { - readyToReplaceProject = confirm(message); // eslint-disable-line no-alert + readyToReplaceProject = this.props.confirmWithMessage(message); } return readyToReplaceProject; } @@ -38,17 +38,29 @@ const MenuBarHOC = function (WrappedComponent) { } } - MenuBarComponent.propTypes = { + MenuBarContainer.propTypes = { canCreateNew: PropTypes.bool, canSave: PropTypes.bool, + confirmWithMessage: PropTypes.func, projectChanged: PropTypes.bool }; - - const _mapStateToProps = state => ({ + MenuBarContainer.defaultProps = { + // default to using standard js confirm + confirmWithMessage: message => (confirm(message)) // eslint-disable-line no-alert + }; + const mapStateToProps = state => ({ projectChanged: state.scratchGui.projectChanged }); - - return connect(_mapStateToProps)(MenuBarComponent); + 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); + }); + +});