diff --git a/.gitattributes b/.gitattributes index e81c38bb5152eff82556a0701f95af1f3c4d00fd..eb2e827d17f4b30e7ee8cb1a64dbe0d162867965 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,6 +12,7 @@ *.svg binary # Prefer LF for most file types +*.css text eol=lf *.frag text eol=lf *.htm text eol=lf *.html text eol=lf diff --git a/package.json b/package.json index 3e2c4ef145fa4123c68c71326cae9d783f804798..43970865e17528359c3a81d05bd25b67249f9bf4 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,8 @@ ], "moduleNameMapper": { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js", - "\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js" + "\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js", + "editor-msgs(\\.js)?$": "<rootDir>/test/__mocks__/editor-msgs-mock.js" } } } diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 093813182538aaae9f1034a82173994fc75fe305..a87137c50de7cfcc9a4c87f31241d85108336829 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -89,6 +89,7 @@ const GUIComponent = props => { loading, logo, renderLogin, + onClickAbout, onClickAccountNav, onCloseAccountNav, onLogOut, @@ -220,6 +221,7 @@ const GUIComponent = props => { logo={logo} renderLogin={renderLogin} showComingSoon={showComingSoon} + onClickAbout={onClickAbout} onClickAccountNav={onClickAccountNav} onClickLogo={onClickLogo} onCloseAccountNav={onCloseAccountNav} @@ -390,6 +392,7 @@ GUIComponent.propTypes = { onActivateCostumesTab: PropTypes.func, onActivateSoundsTab: PropTypes.func, onActivateTab: PropTypes.func, + onClickAbout: PropTypes.func, onClickAccountNav: PropTypes.func, onClickLogo: PropTypes.func, onCloseAccountNav: PropTypes.func, diff --git a/src/components/menu-bar/icon--about.svg b/src/components/menu-bar/icon--about.svg new file mode 100644 index 0000000000000000000000000000000000000000..0b69a9b465a82d5f0e754f64b6846be2e77160a4 Binary files /dev/null and b/src/components/menu-bar/icon--about.svg differ diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index aa893a097accde74e67b3a53e57a0e539465e905..78c639c15f5d4349d2358044b42587846cb7066e 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -218,3 +218,9 @@ .mystuff > a { background-image: url("/images/mystuff.png"); } + +.about-icon { + height: 1.25rem; + margin: 0.5rem; + vertical-align: middle; +} diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 98bc128a92f11fe272d674513930e53f897d58bb..fec3d576e1d81434b172cd7343f9f47d6bee5b5d 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -69,6 +69,7 @@ import profileIcon from './icon--profile.png'; import remixIcon from './icon--remix.svg'; import dropdownCaret from './dropdown-caret.svg'; import languageIcon from '../language-selector/language-icon.svg'; +import aboutIcon from './icon--about.svg'; import scratchLogo from './scratch-logo.svg'; @@ -141,6 +142,19 @@ MenuItemTooltip.propTypes = { isRtl: PropTypes.bool }; +const AboutButton = props => ( + <Button + className={classNames(styles.menuBarItem, styles.hoverable)} + iconClassName={styles.aboutIcon} + iconSrc={aboutIcon} + onClick={props.onClick} + /> +); + +AboutButton.propTypes = { + onClick: PropTypes.func.isRequired +}; + class MenuBar extends React.Component { constructor (props) { super(props); @@ -311,6 +325,8 @@ class MenuBar extends React.Component { {remixMessage} </Button> ); + // Show the About button only if we have a handler for it (like in the desktop app) + const aboutButton = this.props.onClickAbout ? <AboutButton onClick={this.props.onClickAbout} /> : null; return ( <Box className={classNames( @@ -689,6 +705,8 @@ class MenuBar extends React.Component { </React.Fragment> )} </div> + + {aboutButton} </Box> ); } @@ -722,6 +740,7 @@ MenuBar.propTypes = { locale: PropTypes.string.isRequired, loginMenuOpen: PropTypes.bool, logo: PropTypes.string, + onClickAbout: PropTypes.func, onClickAccount: PropTypes.func, onClickEdit: PropTypes.func, onClickFile: PropTypes.func, diff --git a/test/__mocks__/editor-msgs-mock.js b/test/__mocks__/editor-msgs-mock.js new file mode 100644 index 0000000000000000000000000000000000000000..02ae1d6baaf65d2f3f4697f9b7604e40e878d0b7 --- /dev/null +++ b/test/__mocks__/editor-msgs-mock.js @@ -0,0 +1,3 @@ +export default { + en: {} +}; diff --git a/test/unit/components/menu-bar.test.jsx b/test/unit/components/menu-bar.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3aaadb9deab33a2801f01581597ddf55bc9b7186 --- /dev/null +++ b/test/unit/components/menu-bar.test.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import {mountWithIntl} from '../../helpers/intl-helpers'; +import MenuBar from '../../../src/components/menu-bar/menu-bar'; +import {menuInitialState} from '../../../src/reducers/menus'; +import {LoadingState} from '../../../src/reducers/project-state'; + +import configureStore from 'redux-mock-store'; +import {Provider} from 'react-redux'; +import VM from 'scratch-vm'; + + +describe('MenuBar Component', () => { + const store = configureStore()({ + locales: { + isRtl: false, + locale: 'en-US' + }, + scratchGui: { + menus: menuInitialState, + projectState: { + loadingState: LoadingState.NOT_LOADED + }, + vm: new VM() + } + }); + + const getComponent = function (props = {}) { + return <Provider store={store}><MenuBar {...props} /></Provider>; + }; + + test('menu bar with no About handler has no About button', () => { + const menuBar = mountWithIntl(getComponent()); + const button = menuBar.find('AboutButton'); + expect(button.exists()).toBe(false); + }); + + test('menu bar with an About handler has an About button', () => { + const onClickAbout = jest.fn(); + const menuBar = mountWithIntl(getComponent({onClickAbout})); + const button = menuBar.find('AboutButton'); + expect(button.exists()).toBe(true); + }); + + test('clicking on About button calls the handler', () => { + const onClickAbout = jest.fn(); + const menuBar = mountWithIntl(getComponent({onClickAbout})); + const button = menuBar.find('AboutButton'); + expect(onClickAbout).toHaveBeenCalledTimes(0); + button.simulate('click'); + expect(onClickAbout).toHaveBeenCalledTimes(1); + }); +});