Merge pull request #4551 from LLK/release/2020-10-22

[Master] Release/2020-10-22
This commit is contained in:
Benjamin Wheeler 2020-10-22 10:30:38 -04:00 committed by GitHub
commit d53ae21757
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1984 additions and 1915 deletions

View file

@ -48,6 +48,11 @@ env:
- PROJECT_HOST_VAR=PROJECT_HOST_$TRAVIS_BRANCH
- PROJECT_HOST=${!PROJECT_HOST_VAR}
- PROJECT_HOST=${PROJECT_HOST:-$PROJECT_HOST_STAGING}
- TEST_PROJECT_ID_master=414835599
- TEST_PROJECT_ID_STAGING=1300006196
- TEST_PROJECT_ID_VAR=TEST_PROJECT_ID_$TRAVIS_BRANCH
- TEST_PROJECT_ID=${!TEST_PROJECT_ID_VAR}
- TEST_PROJECT_ID=${TEST_PROJECT_ID:-$TEST_PROJECT_ID_STAGING}
- STATIC_HOST_master=https://cdn2.scratch.mit.edu
- STATIC_HOST_STAGING=https://cdn.scratch.ly
- STATIC_HOST_VAR=STATIC_HOST_$TRAVIS_BRANCH

View file

@ -215,11 +215,13 @@ so for the time being our tests run using both.
To run all integration tests from the command-line:
```bash
SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run test:integration
SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu TEST_PROJECT_ID=1 npm run test:integration
```
Both the TAP and Jest tests use the same username and password. The Jest tests will also use the the username you give with a 1 (soon to be higher numbers as well) appended to the end of it. So if you use the username "test" it will also use the username "test1." Make sure you have created accounts with this pattern and use the same password for all accounts involved.
The project page tests require a project id included as an environment variable to pass. The project must be shared and must have at least one remix. At this time, the project does not need to be owned by one of the test users, but that is likely to change.
To run a single file from the command-line using Jest:
```bash

2721
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -68,7 +68,7 @@
"babel-preset-react": "6.22.0",
"bowser": "1.9.4",
"cheerio": "1.0.0-rc.2",
"chromedriver": "84.0.1",
"chromedriver": "86.0.0",
"classnames": "2.2.5",
"cookie": "0.4.1",
"copy-webpack-plugin": "0.2.0",
@ -128,7 +128,7 @@
"redux-mock-store": "^1.2.3",
"redux-thunk": "2.0.1",
"sass-loader": "6.0.6",
"scratch-gui": "0.1.0-prerelease.20201015143201",
"scratch-gui": "0.1.0-prerelease.20201021170733",
"scratch-l10n": "latest",
"selenium-webdriver": "3.6.0",
"slick-carousel": "1.6.0",

View file

@ -0,0 +1,31 @@
const classNames = require('classnames');
const PropTypes = require('prop-types');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const React = require('react');
require('./commenting-status.scss');
const CommentingStatus = props => (
<div className={classNames('commenting-status', props.className)}>
<div className={classNames('commenting-status-inner-content', props.innerClassName)}>
<FlexRow className="comment-status-img">
<img
className="comment-status-icon"
src="/svgs/project/comment-status.svg"
/>
</FlexRow>
<FlexRow>
{props.children}
</FlexRow>
</div>
</div>
);
CommentingStatus.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
innerClassName: PropTypes.string
};
module.exports = CommentingStatus;

View file

@ -0,0 +1,23 @@
@import "../../colors";
.commenting-status {
border: 1px solid $ui-blue-10percent;
border-radius: 8px;
padding: 1.75rem 3rem 2rem;
margin: .5rem 0 2.25rem;
background-color: $ui-blue-10percent;
width: 100%;
text-align: center;
p {
margin-bottom: 0;
line-height: 1.75rem;
}
.bottom-text {
font-size: .875rem;
}
.status-icon-class {
width: 28px;
height: 28px;
}
}

View file

@ -0,0 +1,115 @@
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const Modal = require('../base/modal.jsx');
const ModalInnerContent = require('../base/modal-inner-content.jsx');
const Button = require('../../forms/button.jsx');
const Progression = require('../../progression/progression.jsx');
const FlexRow = require('../../flex-row/flex-row.jsx');
const MuteStep = require('./mute-step.jsx');
const classNames = require('classnames');
require('./modal.scss');
class MuteModal extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleNext',
'handlePrevious'
]);
this.state = {
step: 0
};
}
handleNext () {
this.setState({
step: this.state.step + 1
});
}
handlePrevious () {
// This shouldn't get called when we're on the first step, but
// the Math.max is here as a safeguard so state doesn't go negative.
this.setState({
step: Math.max(0, this.state.step - 1)
});
}
render () {
return (
<Modal
isOpen
useStandardSizes
className="modal-mute"
showCloseButton={false}
onRequestClose={this.props.onRequestClose}
>
<div className="mute-modal-header modal-header" />
<ModalInnerContent className="mute-inner-content">
<Progression step={this.state.step}>
<MuteStep
bottomImg="/images/bottom_placeholder.png"
bottomImgClass="bottom-img"
header="The Scratch comment filter thinks your comment was unconstructive."
>
<p>
If you think something could be better, you can say something you like about the project,
and make a suggestion about how to improve it. For example, you could say:
</p>
</MuteStep>
<MuteStep
header="For the next X minutes you won't be able to post comments"
sideImg="/images/side_placeholder.png"
sideImgClass="side-img"
>
<p>
Once X minutes have passed, you will be able to comment again.
</p>
<p>
If you would like more information, you can read the Scratch community guidelines.
</p>
</MuteStep>
</Progression>
<FlexRow className={classNames('nav-divider')} />
<FlexRow className={classNames('mute-nav')}>
{this.state.step > 0 ? (
<Button
className={classNames(
'back-button',
)}
onClick={this.handlePrevious}
>
<div className="action-button-text">
Back
</div>
</Button>
) : null }
{this.state.step >= 1 ? (
<Button
className={classNames('close-button')}
onClick={this.props.onRequestClose}
>
<div className="action-button-text">
Close
</div>
</Button>
) : (
<Button
className={classNames('next-button')}
onClick={this.handleNext}
>
<div className="action-button-text">
Next
</div>
</Button>
)}
</FlexRow>
</ModalInnerContent>
</Modal>
);
}
}
MuteModal.propTypes = {
onRequestClose: PropTypes.func
};
module.exports = MuteModal;

View file

@ -0,0 +1,60 @@
@import "../../../colors";
@import "../../../frameless";
.modal-mute {
width: 30rem;
.mute-modal-header {
box-shadow: inset 0 -1px 0 0 $ui-mint-green;
background-color: $ui-mint-green;
border-radius: 1rem 1rem 0 0;
}
.mute-step {
display: flex;
padding: 48px 16px;
}
.mute-content {
padding-top: 16px;
}
.mute-inner-content {
padding: 0 32px;
}
.left-column {
padding-right: 32px;
}
.mute-header {
font-size: 1.5rem;
font-weight: bold;
line-height: 2rem;
}
.mute-bottom-row {
padding-top: 32px;
}
.bottom-img {
width: 100%;
}
.mute-side-image {
margin-left: -49px;
}
.side-img {
height: 212px;
width: 129px;
}
.nav-divider {
border-top: 1px solid $ui-blue-25percent;
}
.mute-nav {
display:flex;
justify-content: space-between;
padding: 24px 0;
}
.back-button {
margin-top: 0;
margin-bottom: 0;
}
.next-button, .close-button {
margin-left: auto;
margin-top: 0;
margin-bottom: 0;
}
}

View file

@ -0,0 +1,55 @@
const PropTypes = require('prop-types');
const React = require('react');
const classNames = require('classnames');
const FlexRow = require('../../flex-row/flex-row.jsx');
require('./modal.scss');
const MuteStep = ({
bottomImg,
bottomImgClass,
children,
header,
sideImg,
sideImgClass
}) => (
<div className="mute-step">
{sideImg &&
<FlexRow className={classNames('left-column')}>
<div className={classNames('mute-side-image')}>
<img
className={sideImgClass}
src={sideImg}
/>
</div>
</FlexRow>
}
<FlexRow className={classNames('mute-right-column')}>
<FlexRow className={classNames('mute-header')}>
{header}
</FlexRow>
<FlexRow className={classNames('mute-content')}>
{children}
</FlexRow>
<FlexRow className={classNames('mute-bottom-row')}>
{bottomImg &&
<img
className={bottomImgClass}
src={bottomImg}
/>
}
</FlexRow>
</FlexRow>
</div>
);
MuteStep.propTypes = {
bottomImg: PropTypes.string,
bottomImgClass: PropTypes.string,
children: PropTypes.node,
header: PropTypes.string,
sideImg: PropTypes.string,
sideImgClass: PropTypes.string
};
module.exports = MuteStep;

View file

@ -0,0 +1,46 @@
const PropTypes = require('prop-types');
const React = require('react');
const Avatar = require('../../components/avatar/avatar.jsx');
require('./people-grid.scss');
const PeopleGrid = props => (
<ul className="avatar-grid">
{props.people.map((person, index) => (
<li
className="avatar-item"
key={`person-${index}`}
>
<div>
{person.userName ? (
<a href={`https://scratch.mit.edu/users/${person.userName}/`}>
<Avatar
alt=""
src={`https://cdn.scratch.mit.edu/get_image/user/${person.userId || 'default'}_80x80.png`}
/>
</a>
) : (
/* if userName is not given, there's no chance userId is given */
<Avatar
alt=""
src={`https://cdn.scratch.mit.edu/get_image/user/default_80x80.png`}
/>
)}
</div>
<span className="avatar-text">
{person.name}
</span>
</li>
))}
</ul>
);
PeopleGrid.propTypes = {
people: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
userId: PropTypes.number,
userName: PropTypes.string
}))
};
module.exports = PeopleGrid;

View file

@ -0,0 +1,36 @@
@import "../../colors";
@import "../../frameless";
.avatar-grid {
display: flex;
margin: 0 auto;
max-width: 864px;
list-style: none;
flex-wrap: wrap;
flex-flow: row wrap;
justify-content: center;
}
.avatar-item {
display: inline-block;
margin: 0;
padding-bottom: 32px;
text-align: center;
line-height: 1.25rem;
img {
$img-border: rgba(0, 0, 0, .05);
border: 2px solid $img-border;
border-radius: 8px;
background-color: $ui-white;
width: 80px;
height: 80px;
}
.avatar-text {
display: inline-block;
width: 144px;
font-size: .875rem;
word-wrap: break-word;
}
}

View file

@ -180,7 +180,7 @@ class UsernameStep extends React.Component {
this.props.description
) : (
<span>
<intl.FormattedMessage id="registration.usernameStepDescription" />
<intl.FormattedMessage id="registration.usernameStepDescription" />&nbsp;
<b>
<intl.FormattedMessage id="registration.usernameStepRealName" />
</b>

View file

@ -2,9 +2,9 @@ const React = require('react');
const render = require('../../lib/render.jsx');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const Avatar = require('../../components/avatar/avatar.jsx');
const Page = require('../../components/page/www/page.jsx');
const People = require('./people.json');
const PeopleGrid = require('../../components/people-grid/people-grid.jsx');
const Supporters = require('./supporters.json');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
@ -25,34 +25,7 @@ const Credits = () => (
<FormattedMessage id="credits.developers" />
</p>
</div>
<ul className="avatar-grid">
{People.map((person, index) => (
<li
className="avatar-item"
key={`person-${index}`}
>
<div>
{person.userName ? (
<a href={`https://scratch.mit.edu/users/${person.userName}/`}>
<Avatar
alt=""
src={`https://cdn.scratch.mit.edu/get_image/user/${person.userId || 'default'}_80x80.png`}
/>
</a>
) : (
/* if userName is not given, there's no chance userId is given */
<Avatar
alt=""
src={`https://cdn.scratch.mit.edu/get_image/user/default_80x80.png`}
/>
)}
</div>
<span className="avatar-text">
{person.name}
</span>
</li>
))}
</ul>
<PeopleGrid people={People} />
</div>
<div
className="supporters"

View file

@ -6,44 +6,12 @@
}
.credits {
.avatar-grid {
display: flex;
margin: 0 auto;
padding: 64px 0;
max-width: 864px;
list-style: none;
flex-wrap: wrap;
flex-flow: row wrap;
justify-content: center;
}
.content {
padding-top: 40px;
}
.avatar-item {
display: inline-block;
margin: 0;
padding-bottom: 32px;
text-align: center;
line-height: 1.25rem;
img {
$img-border: rgba(0, 0, 0, .05);
border: 2px solid $img-border;
border-radius: 8px;
background-color: $ui-white;
width: 80px;
height: 80px;
}
.avatar-text {
display: inline-block;
width: 144px;
font-size: .875rem;
word-wrap: break-word;
}
.avatar-grid {
padding: 64px 0;
}
.acknowledge-content {

View file

@ -110,6 +110,7 @@ $base-bg: $ui-white;
padding-top: 16px;
padding-bottom: 32px;
width: 100%;
overflow-anchor: none;
.button {
display: block;

View file

@ -173,7 +173,7 @@
"faq.chatRoomBody":"While it is technically possible to create chat rooms with cloud variables, they are not allowed on the Scratch website.",
"faq.changeCloudVarTitle":"Who can change the information in a cloud variable?",
"faq.changeCloudVarBody":"Only you and viewers of your project can store data in your projects cloud variables. If people \"see inside\" or remix your code, it creates a copy of the variable and does not affect or change the original variable.",
"faq.newScratcherCloudTitle":"I am logged in, but I cannot use projects with cloud variables What is going on?",
"faq.newScratcherCloudTitle":"I am logged in, but I cannot use projects with cloud variables. What is going on?",
"faq.newScratcherCloudBody":"If you are still a \"New Scratcher\" on the website, you will not be able to use projects with cloud variables. You need to become a \"Scratcher\" to have access to cloud variables. See the Accounts section (above) for more information about the transition from “New Scratcher” to \"Scratcher\".",
"faq.multiplayerTitle":"Is it possible to make multiplayer games with cloud variables?",
"faq.multiplayerBody":"Multiplayer games may be difficult to create, due to network speed and synchronization issues. However, some Scratchers are coming up with creative ways to use the cloud variables for turn-by-turn and other types of games.",

View file

@ -33,6 +33,10 @@
margin-bottom: 3rem;
}
.messages-social {
overflow-anchor: none;
}
.messages-admin-list,
.messages-social-list {
padding: 0;

View file

@ -230,6 +230,7 @@ $stage-width: 480px;
min-width: 65%;
max-width: 100%;
flex: 1;
overflow-anchor: none;
@media #{$medium-and-smaller} {
padding: 0;

View file

@ -68,7 +68,7 @@ const OnePointFour = () => (
<p><FormattedMessage id="onePointFour.macBody" /></p>
<ul className="installation-downloads">
<li className="installation-downloads-item">
<a href="http://download.scratch.mit.edu/MacScratch1.4.dmg">
<a href="https://download.scratch.mit.edu/MacScratch1.4.dmg">
MacScratch1.4.dmg
</a>
</li>
@ -86,7 +86,7 @@ const OnePointFour = () => (
key="installation-downloads"
>
<li className="installation-downloads-item">
<a href="http://download.scratch.mit.edu/ScratchInstaller1.4.exe">
<a href="https://download.scratch.mit.edu/ScratchInstaller1.4.exe">
ScratchInstaller1.4.exe
</a>
</li>
@ -95,7 +95,7 @@ const OnePointFour = () => (
id="onePointFour.windowsNetwork"
values={{
windowsNetworkInstaller: (
<a href="http://download.scratch.mit.edu/Scratch1.4.msi.installer.zip">
<a href="https://download.scratch.mit.edu/Scratch1.4.msi.installer.zip">
<FormattedMessage id="onePointFour.windowsNetworkInstaller" />
</a>
)
@ -144,17 +144,17 @@ const OnePointFour = () => (
id="onePointFour.resourcesA"
values={{
gettingStartedGuide: (
<a href="http://download.scratch.mit.edu/ScratchGettingStartedv14.pdf">
<a href="https://download.scratch.mit.edu/ScratchGettingStartedv14.pdf">
<FormattedMessage id="onePointFour.gettingStartedGuide" />
</a>
),
referenceGuide: (
<a href="http://download.scratch.mit.edu/ScratchReferenceGuide14.pdf">
<a href="https://download.scratch.mit.edu/ScratchReferenceGuide14.pdf">
<FormattedMessage id="onePointFour.referenceGuide" />
</a>
),
scratchCards: (
<a href="http://download.scratch.mit.edu/ScratchCardsAll-v1.4-PDF.zip">
<a href="https://download.scratch.mit.edu/ScratchCardsAll-v1.4-PDF.zip">
<FormattedMessage id="onePointFour.scratchCards" />
</a>
)

View file

@ -0,0 +1 @@
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M9 16c-1 1-3.5 2.5-4 2s0-1 0-2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9zm-3-6a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H6zm0-4a1 1 0 1 0 0 2h8a1 1 0 0 0 0-2H6z" id="a"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#575E75" xlink:href="#a"/><g mask="url(#b)" fill="#4D97FF"><path d="M0 0h20v20H0z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 510 B

View file

@ -1,242 +0,0 @@
/*
* Checks that the links in the footer on the homepage have the right URLs to redirect to
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
const tap = require('tap');
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test_footer_links');
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
// timeout for each test; timeout for suite set at command line level
const options = {timeout: 30000};
tap.tearDown(function () {
// quit the instance of the browser
driver.quit();
});
tap.beforeEach(function () {
// load the page with the driver
return driver.get(rootUrl);
});
// Function clicks the link and returns the url of the resulting page
const clickFooterLinks = function (linkText) {
return driver.wait(webdriver.until.elementLocated(webdriver.By.id('footer')))
.then(function (element) {
return element.findElement(webdriver.By.linkText(linkText));
})
.then(function (element) {
return element.click();
})
.then(function () {
return driver.getCurrentUrl();
});
};
// ==== ABOUT SCRATCH column ====
// ABOUT SCRATCH
tap.test('clickAboutScratchLink', options, t => {
const linkText = 'About Scratch';
const expectedHref = '/about';
clickFooterLinks(linkText).then(url => {
// the href should be at the end of the URL
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FOR PARENTS
tap.test('clickForParentsLink', options, t => {
const linkText = 'For Parents';
const expectedHref = '/parents/';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FOR EDUCATORS
tap.test('clickForEducatorsLink', options, t => {
const linkText = 'For Educators';
const expectedHref = '/educators';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FOR DEVELOPERS
tap.test('clickForDevelopersScratchLink', options, t => {
const linkText = 'For Developers';
const expectedHref = '/developers';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// CREDITS
tap.test('clickCreditsLink', options, t => {
const linkText = 'Credits';
const expectedHref = '/credits';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// JOBS
tap.test('clickJobsLink', options, t => {
const linkText = 'Jobs';
const expectedUrl = 'https://www.scratchfoundation.org/opportunities/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});
// PRESS
tap.test('clickPressLink', options, t => {
const linkText = 'Press';
const expectedUrl = 'https://www.scratchfoundation.org/media-kit/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});
// ==== COMMUNITY column ====
// COMMUNITY GUIDELINES
tap.test('clickCommunityGuidelinesLink', options, t => {
const linkText = 'Community Guidelines';
const expectedHref = '/community_guidelines';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// DISCUSSION FORUMS
tap.test('clickDiscussionForumsLink', options, t => {
const linkText = 'Discussion Forums';
const expectedHref = '/discuss/';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// STATISTICS
tap.test('clickStatisticsLink', options, t => {
const linkText = 'Statistics';
const expectedHref = '/statistics/';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== SUPPORT column ====
// IDEAS PAGE
tap.test('clickIdeasPageLink', options, t => {
const linkText = 'Ideas';
const expectedHref = '/ideas';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// FAQ
tap.test('clickFAQLink', options, t => {
const linkText = 'FAQ';
const expectedHref = '/info/faq';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// OFFLINE EDITOR
tap.test('clickOfflineEditorLink', options, t => {
const linkText = 'Offline Editor';
const expectedHref = '/download';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// CONTACT US
tap.test('clickContactUsLink', options, t => {
const linkText = 'Contact Us';
const expectedHref = '/contact-us/';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== LEGAL column ====
// TERMS OF USE
tap.test('clickTermsOfUseLink', options, t => {
const linkText = 'Terms of Use';
const expectedHref = '/terms_of_use';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// PRIVACY POLICY
tap.test('clickPrivacyPolicyLink', options, t => {
const linkText = 'Privacy Policy';
const expectedHref = '/privacy_policy';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// DMCA
tap.test('clickDMCALink', options, t => {
const linkText = 'DMCA';
const expectedHref = '/DMCA';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== SCRATCH FAMILY column ====
// SCRATCH CONFERENCE
tap.test('clickScratchConferenceLink', options, t => {
const linkText = 'Scratch Conference';
const expectedHref = '/conference/20';
clickFooterLinks(linkText).then(url => {
t.match(url.substr(-(expectedHref.length + 2)), expectedHref);
t.end();
});
});
// The following links in are skipped because they are not on scratch.mit.edu
// SCRATCH STORE
// DONATE
// SCRATCH WIKI
// SCRATCH ED (SCRATCHED)
// SCRATCH JR (SCRATCHJR)
// SCRATCH DAY
// SCRATCH FOUNDATION

View file

@ -1,61 +0,0 @@
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const driver = helper.buildDriver('www-smoke test_sign_in_out_homepage');
const {
clickText,
clickXpath,
dragFromXpathToXpath,
findByXpath,
waitUntilGone
} = helper;
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
const projectId = 1;
const projectUrl = `${rootUrl}/projects/${projectId}`;
tap.plan(3);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(projectUrl);
});
test('Find fullscreen button', {skip: true}, t => {
findByXpath('//div[starts-with(@class, "loader_background")]')
.then(el => waitUntilGone(el))
.then(() => clickXpath('//div[starts-with(@class, "stage_green-flag-overlay")]'))
.then(() => clickXpath('//img[contains(@alt, "Enter full screen mode")]'))
.then(() => t.end());
});
test('Open and close Copy Link modal', {skip: true}, t => {
findByXpath('//div[starts-with(@class, "loader_background")]')
.then(el => waitUntilGone(el))
.then(() => clickText('Copy Link'))
.then(() => clickXpath('//div[contains(@class, "social-label-title")]'))
.then(() => clickXpath('//img[contains(@alt, "close-icon")]'))
.then(() => clickXpath('//img[contains(@alt, "Enter full screen mode")]'))
.then(() => t.end());
});
test('Dragging out of modal should not close modal', {skip: true}, t => {
findByXpath('//div[starts-with(@class, "loader_background")]')
.then(el => waitUntilGone(el))
.then(() => clickXpath('//div[starts-with(@class, "stage_green-flag-overlay")]'))
.then(() => clickText('Copy Link'))
.then(() => clickXpath('//div[contains(@class, "social-label-title")]'))
.then(() => dragFromXpathToXpath(
'//div[contains(@class, "social-label-title")]',
'//li[contains(@class, "logo")]'
))
.then(() => clickXpath('//div[contains(@class, "social-label-title")]'))
.then(() => t.end());
});

View file

@ -0,0 +1,157 @@
const SeleniumHelper = require('./selenium-helpers.js');
const {
clickText,
buildDriver
} = new SeleniumHelper();
let remote = process.env.SMOKE_REMOTE || false;
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
if (remote) {
jest.setTimeout(60000);
} else {
jest.setTimeout(10000);
}
let driver;
describe('www-integration footer links', () => {
beforeAll(async () => {
driver = await buildDriver('www-integration footer links');
});
beforeEach(async () => {
await driver.get(rootUrl);
});
afterAll(async () => await driver.quit());
// ==== About Scratch column ====
test('click About Scratch link', async () => {
await clickText('About Scratch');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/about\/?$/);
});
test('click For Parents link', async () => {
await clickText('For Parents');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/parents\/?$/);
});
test('click For Educators link', async () => {
await clickText('For Educators');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/educators\/?$/);
});
test('click For Developers link', async () => {
await clickText('For Developers');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/developers\/?$/);
});
// ==== COMMUNITY column ====
test('click Community Guidelines link', async () => {
await clickText('Community Guidelines');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/community_guidelines\/?$/);
});
test('click Discussion Forums link', async () => {
await clickText('Discussion Forums');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/discuss\/?$/);
});
test('click Statistics link', async () => {
await clickText('Statistics');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/statistics\/?$/);
});
// ==== SUPPORT column ====
test('click Ideas link', async () => {
await clickText('Ideas');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/ideas\/?$/);
});
test('click FAQ link', async () => {
await clickText('FAQ');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/info\/faq\/?$/);
});
test('click Download link', async () => {
await clickText('Download');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/download\/?$/);
});
test('click Contact Us link', async () => {
await clickText('Contact Us');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/contact-us\/?$/);
});
// ==== LEGAL column ====
test('click Terms of Use link', async () => {
await clickText('Terms of Use');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/terms_of_use\/?$/);
});
test('click Privacy Policy link', async () => {
await clickText('Privacy Policy');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/privacy_policy\/?$/);
});
test('click DMCA link', async () => {
await clickText('DMCA');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/DMCA\/?$/);
});
// ==== SCRATCH FAMILY column ====
test('click Scratch Conference link', async () => {
await clickText('Scratch Conference');
let url = await driver.getCurrentUrl();
let pathname = (new URL(url)).pathname;
expect(pathname).toMatch(/^\/conference\/2020\/?$/);
});
});
// The following links in are skipped because they are not on scratch.mit.edu
// Jobs
// Press
// SCRATCH STORE
// DONATE
// SCRATCH WIKI
// SCRATCH ED (SCRATCHED)
// SCRATCH JR (SCRATCHJR)
// SCRATCH DAY
// SCRATCH FOUNDATION

View file

@ -0,0 +1,74 @@
const SeleniumHelper = require('./selenium-helpers.js');
const {
findByXpath,
clickXpath,
buildDriver
} = new SeleniumHelper();
let remote = process.env.SMOKE_REMOTE || false;
let rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
let projectId = process.env.TEST_PROJECT_ID || 1300006196;
let projectUrl = rootUrl + '/projects/' + projectId;
if (remote){
jest.setTimeout(60000);
} else {
jest.setTimeout(10000);
}
let driver;
describe('www-integration project-page signed out', () => {
beforeAll(async () => {
// expect(projectUrl).toBe(defined);
driver = await buildDriver('www-integration project-page signed out');
await driver.get(rootUrl);
});
beforeEach(async () => {
await driver.get(projectUrl);
let gfOverlay = await findByXpath('//div[@class="stage-wrapper_stage-wrapper_2bejr box_box_2jjDp"]');
await gfOverlay.isDisplayed();
});
afterAll(async () => await driver.quit());
// LOGGED OUT TESTS
test('Find fullscreen button', async () => {
await clickXpath('//div[starts-with(@class, "stage_green-flag-overlay")]');
await clickXpath('//img[contains(@alt, "Enter full screen mode")]');
let fullscreenGui = await findByXpath('//div[@class="guiPlayer fullscreen"]');
let guiVisible = await fullscreenGui.isDisplayed();
await expect(guiVisible).toBe(true);
});
test.skip('Open Copy Link modal', async () => {
await clickXpath('//button[@class="button action-button copy-link-button"]');
let projectLink = await findByXpath('//input[@name="link"]');
let linkValue = await projectLink.getAttribute('value');
await expect(linkValue).toEqual(projectUrl);
});
test('Click Username to go to profile page', async ()=> {
await clickXpath('//div[@class="title"]/a');
let userContent = await findByXpath('//div[@class="user-content"]');
let contentVisible = await userContent.isDisplayed();
await expect(contentVisible).toBe(true);
});
test('click See Inside to go to the editor', async ()=> {
await clickXpath('//button[@class="button button see-inside-button"]');
let infoArea = await findByXpath('//div[@class="sprite-info_sprite-info_3EyZh box_box_2jjDp"]');
let areaVisible = await infoArea.isDisplayed();
await expect(areaVisible).toBe(true);
});
test('click View All remixes takes you to remix page', async ()=> {
await clickXpath('//div[@class="list-header-link"]');
let originalLink = await findByXpath('//h2/a');
let link = await originalLink.getAttribute('href');
await expect(link).toEqual(rootUrl + '/projects/' + projectId + '/');
});
});

View file

@ -0,0 +1,33 @@
const React = require('react');
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
const CommentingStatus = require('../../../src/components/commenting-status/commenting-status.jsx');
describe('CommentingStatus', () => {
test('Basic render', () => {
const component = shallowWithIntl(
<CommentingStatus />
);
expect(component.find('div.commenting-status').exists()).toBe(true);
expect(component.find('img.comment-status-icon').exists()).toBe(true);
});
test('ClassNames added', () => {
const component = shallowWithIntl(
<CommentingStatus
className="class1"
innerClassName="class2"
/>
);
expect(component.find('div.class1').exists()).toBe(true);
expect(component.find('div.class2').exists()).toBe(true);
});
test('Children added', () => {
const component = shallowWithIntl(
<CommentingStatus>
<img className="myChildDiv" />
</CommentingStatus>
);
expect(component.find('img.myChildDiv').exists()).toBe(true);
});
});

View file

@ -0,0 +1,93 @@
import React from 'react';
import {shallowWithIntl} from '../../helpers/intl-helpers.jsx';
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
import MuteModal from '../../../src/components/modal/mute/modal';
import Modal from '../../../src/components/modal/base/modal';
describe('MuteModalTest', () => {
test('Mute Modal rendering', () => {
const component = shallowWithIntl(
<MuteModal />
);
expect(component.find('div.mute-modal-header').exists()).toEqual(true);
});
test('Mute Modal only shows next button on initial step', () => {
const component = mountWithIntl(
<MuteModal />
);
expect(component.find('div.mute-nav').exists()).toEqual(true);
expect(component.find('button.next-button').exists()).toEqual(true);
expect(component.find('button.next-button').getElements()[0].props.onClick)
.toEqual(component.instance().handleNext);
expect(component.find('button.close-button').exists()).toEqual(false);
expect(component.find('button.back-button').exists()).toEqual(false);
});
test('Mute Modal shows back & close button on last step', () => {
const component = mountWithIntl(
<MuteModal />
);
// Step 1 is the last step.
component.instance().setState({step: 1});
component.update();
expect(component.find('div.mute-nav').exists()).toEqual(true);
expect(component.find('button.next-button').exists()).toEqual(false);
expect(component.find('button.back-button').exists()).toEqual(true);
expect(component.find('button.back-button').getElements()[0].props.onClick)
.toEqual(component.instance().handlePrevious);
expect(component.find('button.close-button').exists()).toEqual(true);
expect(component.find('button.close-button').getElements()[0].props.onClick)
.toEqual(component.instance().props.onRequestClose);
});
test('Mute modal sends correct props to Modal', () => {
const closeFn = jest.fn();
const component = shallowWithIntl(
<MuteModal
onRequestClose={closeFn}
/>
);
const modal = component.find(Modal);
expect(modal).toHaveLength(1);
expect(modal.props().showCloseButton).toBe(false);
expect(modal.props().isOpen).toBe(true);
expect(modal.props().className).toBe('modal-mute');
expect(modal.props().onRequestClose).toBe(closeFn);
});
test('Mute modal handle next step', () => {
const closeFn = jest.fn();
const component = shallowWithIntl(
<MuteModal
onRequestClose={closeFn}
/>
);
expect(component.instance().state.step).toBe(0);
component.instance().handleNext();
expect(component.instance().state.step).toBe(1);
});
test('Mute modal handle previous step', () => {
const component = shallowWithIntl(
<MuteModal />
);
component.instance().setState({step: 1});
component.instance().handlePrevious();
expect(component.instance().state.step).toBe(0);
});
test('Mute modal handle previous step stops at 0', () => {
const component = shallowWithIntl(
<MuteModal />
);
component.instance().setState({step: 0});
component.instance().handlePrevious();
expect(component.instance().state.step).toBe(0);
});
});

View file

@ -0,0 +1,49 @@
import React from 'react';
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
import MuteStep from '../../../src/components/modal/mute/mute-step';
describe('MuteStepTest', () => {
test('Mute Step with no images ', () => {
const component = mountWithIntl(
<MuteStep
header="header text"
/>
);
expect(component.find('div.mute-step').exists()).toEqual(true);
expect(component.find('div.mute-header').exists()).toEqual(true);
expect(component.find('div.mute-right-column').exists()).toEqual(true);
// No images and no left column.
expect(component.find('img').exists()).toEqual(false);
expect(component.find('div.left-column').exists()).toEqual(false);
});
test('Mute Step with side image ', () => {
const component = mountWithIntl(
<MuteStep
sideImg="/path/to/img.png"
sideImgClass="side-img"
/>
);
expect(component.find('div.mute-step').exists()).toEqual(true);
expect(component.find('div.mute-header').exists()).toEqual(true);
expect(component.find('div.mute-right-column').exists()).toEqual(true);
expect(component.find('div.left-column').exists()).toEqual(true);
expect(component.find('img.side-img').exists()).toEqual(true);
});
test('Mute Step with bottom image ', () => {
const component = mountWithIntl(
<MuteStep
bottomImg="/path/to/img.png"
bottomImgClass="bottom-image"
/>
);
expect(component.find('div.mute-step').exists()).toEqual(true);
expect(component.find('div.mute-header').exists()).toEqual(true);
expect(component.find('div.mute-right-column').exists()).toEqual(true);
expect(component.find('img.bottom-image').exists()).toEqual(true);
});
});