From 43959e4205ef6fc2c24ccf38e8c1d7f71377fac1 Mon Sep 17 00:00:00 2001 From: Paul Kaplan <pkaplan@media.mit.edu> Date: Wed, 5 Dec 2018 10:46:22 -0500 Subject: [PATCH] Add "SaveStatus" component to control showing "Save Now" and save alerts --- src/components/menu-bar/menu-bar.jsx | 4 +- src/components/menu-bar/save-status.css | 4 ++ src/components/menu-bar/save-status.jsx | 61 ++++++++++++++++++ test/unit/containers/save-status.test.jsx | 76 +++++++++++++++++++++++ 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/components/menu-bar/save-status.css create mode 100644 src/components/menu-bar/save-status.jsx create mode 100644 test/unit/containers/save-status.test.jsx diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index a9ebe0c5b..878e6a7b4 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -12,7 +12,7 @@ import ShareButton from './share-button.jsx'; import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import Divider from '../divider/divider.jsx'; import LanguageSelector from '../../containers/language-selector.jsx'; -import InlineMessages from '../../containers/inline-messages.jsx'; +import SaveStatus from './save-status.jsx'; import SBFileUploader from '../../containers/sb-file-uploader.jsx'; import ProjectWatcher from '../../containers/project-watcher.jsx'; import MenuBarMenu from './menu-bar-menu.jsx'; @@ -535,7 +535,7 @@ class MenuBar extends React.Component { logged in, and whether a session is available to log in with */} <div className={styles.accountInfoGroup}> <div className={styles.menuBarItem}> - <InlineMessages /> + <SaveStatus /> </div> {this.props.sessionExists ? ( this.props.username ? ( diff --git a/src/components/menu-bar/save-status.css b/src/components/menu-bar/save-status.css new file mode 100644 index 000000000..d191d5d23 --- /dev/null +++ b/src/components/menu-bar/save-status.css @@ -0,0 +1,4 @@ +.save-now { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + cursor: pointer; +} diff --git a/src/components/menu-bar/save-status.jsx b/src/components/menu-bar/save-status.jsx new file mode 100644 index 000000000..9f83f6274 --- /dev/null +++ b/src/components/menu-bar/save-status.jsx @@ -0,0 +1,61 @@ +import {connect} from 'react-redux'; +import {FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import InlineMessages from '../../containers/inline-messages.jsx'; + +import { + manualUpdateProject +} from '../../reducers/project-state'; + +import { + filterInlineAlerts +} from '../../reducers/alerts'; + +import styles from './save-status.css'; + +// Wrapper for inline messages in the nav bar, which are all related to saving. +// Show any inline messages if present, else show the "Save Now" button if the +// project has changed. +// We decided to not use an inline message for "Save Now" because it is a reflection +// of the project state, rather than an event. +const SaveStatus = ({ + alertsList, + projectChanged, + onClickSave +}) => ( + filterInlineAlerts(alertsList).length > 0 ? ( + <InlineMessages /> + ) : projectChanged && ( + <div + className={styles.saveNow} + onClick={onClickSave} + > + <FormattedMessage + defaultMessage="Save Now" + description="Title bar link for saving now" + id="gui.menuBar.saveNowLink" + /> + </div> + )); + +SaveStatus.propTypes = { + alertsList: PropTypes.arrayOf(PropTypes.object), + onClickSave: PropTypes.func, + projectChanged: PropTypes.bool +}; + +const mapStateToProps = state => ({ + alertsList: state.scratchGui.alerts.alertsList, + projectChanged: state.scratchGui.projectChanged +}); + +const mapDispatchToProps = dispatch => ({ + onClickSave: () => dispatch(manualUpdateProject()) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(SaveStatus); diff --git a/test/unit/containers/save-status.test.jsx b/test/unit/containers/save-status.test.jsx new file mode 100644 index 000000000..c426864d4 --- /dev/null +++ b/test/unit/containers/save-status.test.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import {Provider} from 'react-redux'; +import configureStore from 'redux-mock-store'; +import {mountWithIntl} from '../../helpers/intl-helpers.jsx'; +import SaveStatus from '../../../src/components/menu-bar/save-status.jsx'; +import InlineMessages from '../../../src/containers/inline-messages.jsx'; +import {AlertTypes} from '../../../src/lib/alerts/index.jsx'; + +// Stub the manualUpdateProject action creator for later testing +jest.mock('../../../src/reducers/project-state', () => ({ + manualUpdateProject: jest.fn(() => ({type: 'stubbed'})) +})); + +describe('SaveStatus container', () => { + const mockStore = configureStore(); + + test('if there are inline messages, they are shown instead of save now', () => { + const store = mockStore({ + scratchGui: { + projectChanged: true, + alerts: { + alertsList: [ + {alertId: 'saveSuccess', alertType: AlertTypes.INLINE} + ] + } + } + }); + const wrapper = mountWithIntl( + <Provider store={store}> + <SaveStatus /> + </Provider> + ); + expect(wrapper.find(InlineMessages).exists()).toBe(true); + expect(wrapper.contains('Save Now')).not.toBe(true); + }); + + test('save now is shown if there are project changes and no inline messages', () => { + const store = mockStore({ + scratchGui: { + projectChanged: true, + alerts: { + alertsList: [] + } + } + }); + const wrapper = mountWithIntl( + <Provider store={store}> + <SaveStatus /> + </Provider> + ); + expect(wrapper.find(InlineMessages).exists()).not.toBe(true); + expect(wrapper.contains('Save Now')).toBe(true); + + // Clicking save now should dispatch the manualUpdateProject action (stubbed above) + wrapper.find('[children="Save Now"]').simulate('click'); + expect(store.getActions()[0].type).toEqual('stubbed'); + }); + + test('neither is shown if there are no project changes or inline messages', () => { + const store = mockStore({ + scratchGui: { + projectChanged: false, + alerts: { + alertsList: [] + } + } + }); + const wrapper = mountWithIntl( + <Provider store={store}> + <SaveStatus /> + </Provider> + ); + expect(wrapper.find(InlineMessages).exists()).not.toBe(true); + expect(wrapper.contains('Save Now')).not.toBe(true); + }); +}); -- GitLab