@@ -34,8 +60,8 @@ class InfoButton extends React.Component {
diff --git a/src/components/info-button/info-button.scss b/src/components/info-button/info-button.scss
index f2d93303d..2e9e3e4f4 100644
--- a/src/components/info-button/info-button.scss
+++ b/src/components/info-button/info-button.scss
@@ -4,23 +4,21 @@
.info-button {
position: relative;
display: inline-block;
- width: 1rem;
- height: 1rem;
- margin-left: .375rem;
- margin-top: -.25rem;
- border-radius: 50%;
- background-color: $type-gray-60percent;
+ width: 2rem;
+ height: 2rem;
+ margin-left: -.125rem;
+ margin-top: -.75rem;
background-image: url("/svgs/info-button/info-button.svg");
background-size: cover;
- top: .1875rem;
+ top: .6875rem;
}
.info-button-message {
$arrow-border-width: 1rem;
display: block;
position: absolute;
- top: 0;
- left: 0;
+ top: .375rem;
+ left: .5rem;
transform: translate(1rem, -1rem);
width: 16.5rem;
min-height: 1rem;
@@ -66,6 +64,7 @@
we need to center this element within its width. */
margin: 0 calc((100% - 16.5rem) / 2);;
top: .125rem;
+ left: 0;
&:before {
display: none;
diff --git a/src/components/install-scratch/install-scratch.jsx b/src/components/install-scratch/install-scratch.jsx
index 0e022b609..00b005444 100644
--- a/src/components/install-scratch/install-scratch.jsx
+++ b/src/components/install-scratch/install-scratch.jsx
@@ -3,7 +3,6 @@ const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const OS_ENUM = require('../../lib/os-enum.js');
-const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
const {isDownloaded, isFromGooglePlay} = require('./install-util.js');
@@ -27,24 +26,10 @@ const InstallScratch = ({
- {CHROME_APP_RELEASED ? (
-
- ) : (
-
- {isDownloaded(currentOS) && (
-
- )}
- {isFromGooglePlay(currentOS) && (
-
- )}
-
- )}
+
);
diff --git a/src/components/registration/steps.jsx b/src/components/registration/steps.jsx
index 42fe4714f..42bdb2db4 100644
--- a/src/components/registration/steps.jsx
+++ b/src/components/registration/steps.jsx
@@ -28,6 +28,7 @@ const Spinner = require('../../components/spinner/spinner.jsx');
const StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx');
const TextArea = require('../../components/forms/textarea.jsx');
const Tooltip = require('../../components/tooltip/tooltip.jsx');
+const ValidationMessage = require('../../components/forms/validation-message.jsx');
require('./steps.scss');
@@ -84,11 +85,13 @@ class UsernameStep extends React.Component {
'handleChangeShowPassword',
'handleUsernameBlur',
'handleValidSubmit',
- 'validateUsername'
+ 'validateUsername',
+ 'handleFocus'
]);
this.state = {
showPassword: props.showPassword,
waiting: false,
+ showUsernameTip: true,
validUsername: ''
};
}
@@ -159,6 +162,9 @@ class UsernameStep extends React.Component {
if (isValid) return this.props.onNextStep(formData);
});
}
+ handleFocus () {
+ this.setState({showUsernameTip: false});
+ }
render () {
return (
@@ -205,6 +211,13 @@ class UsernameStep extends React.Component {
null
)}
+ { this.state.showUsernameTip &&
+
+ }
+
{
};
module.exports = {
- CHROME_APP_RELEASED: true,
CONTACT_US_POPUP: isStaging() && flagInUrl('CONTACT_US_POPUP')
};
diff --git a/src/views/download/download.jsx b/src/views/download/download.jsx
index 49664936b..26f0538bb 100644
--- a/src/views/download/download.jsx
+++ b/src/views/download/download.jsx
@@ -10,7 +10,6 @@ const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
const detectOS = require('../../lib/detect-os.js').default;
const OS_ENUM = require('../../lib/os-enum.js');
-const {CHROME_APP_RELEASED} = require('../../lib/feature-flags.js');
const OSChooser = require('../../components/os-chooser/os-chooser.jsx');
const InstallScratch = require('../../components/install-scratch/install-scratch.jsx');
const {isDownloaded, isFromGooglePlay} = require('../../components/install-scratch/install-util.js');
@@ -52,14 +51,12 @@ class Download extends React.Component {
width="40"
/>
@@ -82,24 +79,20 @@ class Download extends React.Component {
/>
macOS 10.13+
- {CHROME_APP_RELEASED && (
-
-
-
+
+
ChromeOS
-
-
-
+
+
+
Android 6.0+
-
-
- )}
+
diff --git a/src/views/download/l10n.json b/src/views/download/l10n.json
index 73118a67c..2de1f8572 100644
--- a/src/views/download/l10n.json
+++ b/src/views/download/l10n.json
@@ -1,6 +1,4 @@
{
- "download.title": "Scratch Desktop",
- "download.intro": "You can install the Scratch Desktop editor to work on projects without an internet connection. This version will work on Windows and MacOS.",
"download.appTitle": "Download the Scratch App",
"download.appIntro": "Would you like to create and save Scratch projects without an internet connection? Download the free Scratch app.",
"download.requirements": "Requirements",
@@ -23,7 +21,7 @@
"download.macMoveToApplications" : "Open the .dmg file. Move Scratch Desktop into Applications.",
"download.winMoveToApplications" : "Run the .exe file.",
"download.doIHaveToDownload" : "Do I have to download an app to use Scratch?",
- "download.doIHaveToDownloadAnswer" : "No. You can also use the Scratch project editor in any web browser on any device by going to scratch.mit.edu and clicking \"Create\".",
+ "download.doIHaveToDownloadAnswer" : "No. You can also use the Scratch project editor in most web browsers on most devices by going to scratch.mit.edu and clicking \"Create\".",
"download.canIUseScratchLink" : "Can I use Scratch Link to connect to extensions?",
"download.canIUseScratchLinkAnswer" : "Yes. However, you will need an Internet connection to use Scratch Link.",
"download.canIUseExtensions" : "Can I connect to hardware extensions?",
diff --git a/src/views/splash/activity-rows/follow-studio.jsx b/src/views/splash/activity-rows/follow-studio.jsx
new file mode 100644
index 000000000..ea97509c9
--- /dev/null
+++ b/src/views/splash/activity-rows/follow-studio.jsx
@@ -0,0 +1,43 @@
+const classNames = require('classnames');
+const FormattedMessage = require('react-intl').FormattedMessage;
+const PropTypes = require('prop-types');
+const React = require('react');
+
+const SocialMessage = require('../../../components/social-message/social-message.jsx');
+
+const FollowStudioMessage = props => (
+
+
+ {props.followerUsername}
+
+ ),
+ studioLink: (
+
+ {props.studioTitle}
+
+ )
+ }}
+ />
+
+);
+
+FollowStudioMessage.propTypes = {
+ className: PropTypes.string,
+ followDateTime: PropTypes.string.isRequired,
+ followerUsername: PropTypes.string.isRequired,
+ studioId: PropTypes.string.isRequired,
+ studioTitle: PropTypes.string.isRequired
+};
+
+module.exports = FollowStudioMessage;
diff --git a/src/views/splash/activity-rows/follow-user.jsx b/src/views/splash/activity-rows/follow-user.jsx
new file mode 100644
index 000000000..f730a2a6a
--- /dev/null
+++ b/src/views/splash/activity-rows/follow-user.jsx
@@ -0,0 +1,42 @@
+const classNames = require('classnames');
+const FormattedMessage = require('react-intl').FormattedMessage;
+const PropTypes = require('prop-types');
+const React = require('react');
+
+const SocialMessage = require('../../../components/social-message/social-message.jsx');
+
+const FollowUserMessage = props => (
+
+
+ {props.followerUsername}
+
+ ),
+ followeeLink: (
+
+ {props.followeeId}
+
+ )
+ }}
+ />
+
+);
+
+FollowUserMessage.propTypes = {
+ className: PropTypes.string,
+ followDateTime: PropTypes.string.isRequired,
+ followeeId: PropTypes.string.isRequired,
+ followerUsername: PropTypes.string.isRequired
+};
+
+module.exports = FollowUserMessage;
diff --git a/src/views/splash/activity-rows/follow.jsx b/src/views/splash/activity-rows/follow.jsx
deleted file mode 100644
index bb1f13baa..000000000
--- a/src/views/splash/activity-rows/follow.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-const classNames = require('classnames');
-const FormattedMessage = require('react-intl').FormattedMessage;
-const PropTypes = require('prop-types');
-const React = require('react');
-
-const SocialMessage = require('../../../components/social-message/social-message.jsx');
-
-const FollowMessage = props => {
- let followeeLink = '';
- let followeeTitle = '';
- if (typeof props.followeeTitle === 'undefined') {
- followeeLink = `/users/${props.followeeId}`;
- followeeTitle = props.followeeId;
- } else {
- followeeLink = `/studios/${props.followeeId}`;
- followeeTitle = props.followeeTitle;
- }
-
- return (
-
-
- {props.followerUsername}
-
- ),
- followeeLink: (
-
- {followeeTitle}
-
- )
- }}
- />
-
- );
-};
-
-FollowMessage.propTypes = {
- className: PropTypes.string,
- followDateTime: PropTypes.string.isRequired,
- followeeId: PropTypes.string.isRequired,
- followeeTitle: PropTypes.string,
- followerUsername: PropTypes.string.isRequired
-};
-
-module.exports = FollowMessage;
diff --git a/src/views/splash/l10n.json b/src/views/splash/l10n.json
index 7ecb0fbc6..4694b40d6 100644
--- a/src/views/splash/l10n.json
+++ b/src/views/splash/l10n.json
@@ -13,7 +13,8 @@
"messages.becomeCuratorText": "{username} became a curator of {studio}",
"messages.becomeManagerText": "{username} was promoted to manager of {studio}",
"messages.favoriteText": "{profileLink} favorited {projectLink}",
- "messages.followText": "{profileLink} is now following {followeeLink}",
+ "messages.followProfileText": "{profileLink} is now following {followeeLink}",
+ "messages.followStudioText": "{profileLink} is now following {studioLink}",
"messages.loveText": "{profileLink} loved {projectLink}",
"messages.remixText": "{profileLink} remixed {remixedProjectLink} as {projectLink}",
"messages.shareText": "{profileLink} shared the project {projectLink}",
diff --git a/src/views/splash/presentation.jsx b/src/views/splash/presentation.jsx
index 030bb1b70..162b2e63c 100644
--- a/src/views/splash/presentation.jsx
+++ b/src/views/splash/presentation.jsx
@@ -26,7 +26,8 @@ const Welcome = require('../../components/welcome/welcome.jsx');
const BecomeCuratorMessage = require('./activity-rows/become-curator.jsx');
const BecomeManagerMessage = require('./activity-rows/become-manager.jsx');
const FavoriteProjectMessage = require('./activity-rows/favorite-project.jsx');
-const FollowMessage = require('./activity-rows/follow.jsx');
+const FollowUserMessage = require('./activity-rows/follow-user.jsx');
+const FollowStudioMessage = require('./activity-rows/follow-studio.jsx');
const LoveProjectMessage = require('./activity-rows/love-project.jsx');
const RemixProjectMessage = require('./activity-rows/remix-project.jsx');
const ShareProjectMessage = require('./activity-rows/share-project.jsx');
@@ -53,7 +54,7 @@ class ActivityList extends React.Component {
switch (message.type) {
case 'followuser':
return (
-
);
case 'loveproject':
diff --git a/src/views/studentregistration/studentregistration.jsx b/src/views/studentregistration/studentregistration.jsx
index 5b8ff8b39..3ad1d70ab 100644
--- a/src/views/studentregistration/studentregistration.jsx
+++ b/src/views/studentregistration/studentregistration.jsx
@@ -126,7 +126,7 @@ class StudentRegistration extends React.Component {
id: 'registration.studentUsernameStepTooltip'
})}
usernameHelp={this.props.intl.formatMessage({
- id: 'registration.studentUsernameFieldHelpText'
+ id: 'registration.studentUsernameSuggestion'
})}
waiting={this.state.waiting}
onNextStep={this.handleAdvanceStep}
diff --git a/static/svgs/info-button/info-button.svg b/static/svgs/info-button/info-button.svg
index 76edc37d8..a5bbdbd01 100644
--- a/static/svgs/info-button/info-button.svg
+++ b/static/svgs/info-button/info-button.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test/unit/components/info-button.test.jsx b/test/unit/components/info-button.test.jsx
index e7e48231d..959e4208f 100644
--- a/test/unit/components/info-button.test.jsx
+++ b/test/unit/components/info-button.test.jsx
@@ -3,6 +3,15 @@ import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
import InfoButton from '../../../src/components/info-button/info-button';
describe('InfoButton', () => {
+ // mock window.addEventListener
+ // for more on this technique, see discussion at https://github.com/airbnb/enzyme/issues/426#issuecomment-253515886
+ const mockedAddEventListener = {};
+ /* eslint-disable no-undef */
+ window.addEventListener = jest.fn((event, cb) => {
+ mockedAddEventListener[event] = cb;
+ });
+ /* eslint-enable no-undef */
+
test('Info button defaults to not visible', () => {
const component = mountWithIntl(
{
);
expect(component.find('div.info-button-message').exists()).toEqual(false);
});
- test('mouseOver on info button makes info message visible', () => {
+
+ test('mouseOver on info button makes info message visible', done => {
const component = mountWithIntl(
);
+
+ // mouseOver info button
component.find('div.info-button').simulate('mouseOver');
- expect(component.find('div.info-button-message').exists()).toEqual(true);
+ setTimeout(function () { // necessary because mouseover uses debounce
+ // crucial: if we don't call update(), then find() below looks through an OLD
+ // version of the DOM! see https://github.com/airbnb/enzyme/issues/1233#issuecomment-358915200
+ component.update();
+ expect(component.find('div.info-button-message').exists()).toEqual(true);
+ done();
+ }, 500);
});
+
test('clicking on info button makes info message visible', () => {
const component = mountWithIntl(
);
- component.find('div.info-button').simulate('click');
+ const buttonRef = component.instance().buttonRef;
+
+ // click on info button
+ mockedAddEventListener.mousedown({target: buttonRef});
+ component.update();
+ expect(component.find('div.info-button').exists()).toEqual(true);
expect(component.find('div.info-button-message').exists()).toEqual(true);
});
- test('after message is visible, mouseOut makes it vanish', () => {
+
+ test('clicking on info button, then mousing out makes info message still appear', done => {
const component = mountWithIntl(
);
- component.find('div.info-button').simulate('mouseOver');
+ const buttonRef = component.instance().buttonRef;
+
+ // click on info button
+ mockedAddEventListener.mousedown({target: buttonRef});
+ component.update();
+ expect(component.find('div.info-button').exists()).toEqual(true);
expect(component.find('div.info-button-message').exists()).toEqual(true);
- component.find('div.info-button').simulate('mouseOut');
+
+ // mouseLeave from info button
+ component.find('div.info-button').simulate('mouseLeave');
+ setTimeout(function () { // necessary because mouseover uses debounce
+ component.update();
+ expect(component.find('div.info-button-message').exists()).toEqual(true);
+ done();
+ }, 500);
+ });
+
+ test('clicking on info button, then clicking on it again makes info message go away', () => {
+ const component = mountWithIntl(
+
+ );
+ const buttonRef = component.instance().buttonRef;
+
+ // click on info button
+ mockedAddEventListener.mousedown({target: buttonRef});
+ component.update();
+ expect(component.find('div.info-button').exists()).toEqual(true);
+ expect(component.find('div.info-button-message').exists()).toEqual(true);
+
+ // click on info button again
+ mockedAddEventListener.mousedown({target: buttonRef});
+ component.update();
+ expect(component.find('div.info-button-message').exists()).toEqual(false);
+ });
+
+ test('clicking on info button, then clicking somewhere else', () => {
+ const component = mountWithIntl(
+
+ );
+ const buttonRef = component.instance().buttonRef;
+
+ // click on info button
+ mockedAddEventListener.mousedown({target: buttonRef});
+ component.update();
+ expect(component.find('div.info-button').exists()).toEqual(true);
+ expect(component.find('div.info-button-message').exists()).toEqual(true);
+
+ // click on some other target
+ mockedAddEventListener.mousedown({target: null});
+ component.update();
+ expect(component.find('div.info-button-message').exists()).toEqual(false);
+ });
+
+ test('after message is visible, mouseLeave makes it vanish', () => {
+ const component = mountWithIntl(
+
+ );
+
+ // mouseOver info button
+ component.find('div.info-button').simulate('mouseOver');
+ component.update();
+ expect(component.find('div.info-button-message').exists()).toEqual(true);
+
+ // mouseLeave away from info button
+ component.find('div.info-button').simulate('mouseLeave');
+ component.update();
expect(component.find('div.info-button-message').exists()).toEqual(false);
});
});