Merge pull request #1336 from LLK/release/2.2.21

[Master] Release 2.2.21
This commit is contained in:
Matthew Taylor 2017-06-01 19:07:18 -04:00 committed by GitHub
commit 8c1bfe0452
22 changed files with 725 additions and 31 deletions

View file

@ -32,12 +32,13 @@
} }
''' '''
*/ */
var fs = require('fs');
var path = require('path');
var merge = require('lodash.merge');
var async = require('async'); var async = require('async');
var fs = require('fs');
var languages = require('../languages.json'); var languages = require('../languages.json');
var localizedUrls = require('./lib/localized-urls'); var localizedUrls = require('./lib/localized-urls');
var merge = require('lodash.merge');
var defaults = require('lodash.defaults');
var path = require('path');
var routes = require('../src/routes.json'); var routes = require('../src/routes.json');
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -207,6 +208,7 @@ async.forEachLimit(views, 5, function (view, cb) {
process.stdout.write('No translations for ' + view + isoCode + ', using english\n'); process.stdout.write('No translations for ' + view + isoCode + ', using english\n');
} }
viewLocales[isoCode] = merge({}, generalLocales[isoCode], defaultLocales[view]); viewLocales[isoCode] = merge({}, generalLocales[isoCode], defaultLocales[view]);
defaults(viewLocales[isoCode], generalLocales['en']);
} }
return cb(); return cb();
} else { } else {
@ -215,6 +217,7 @@ async.forEachLimit(views, 5, function (view, cb) {
} }
try { try {
viewLocales[isoCode] = merge({}, generalLocales[isoCode], JSON.parse(data)); viewLocales[isoCode] = merge({}, generalLocales[isoCode], JSON.parse(data));
defaults(viewLocales[isoCode], viewLocales['en']);
} catch (e) { } catch (e) {
return cb(e); return cb(e);
} }

View file

@ -33,11 +33,14 @@ var FastlyConfigMethods = {
}, },
/* /*
* Translate an express-style pattern e.g. /path/:arg/ to a regex * Translate an express-style pattern to regex one in two ways:
* all :arguments become .+? *
* 1. /path/:arg/ all :arg's become .+?
* 2. /path/:arg([regex]) :arg is removed, leaving just /path/([regex])
*/ */
expressPatternToRegex: function (pattern) { expressPatternToRegex: function (pattern) {
return pattern.replace(/(:[^/]+)/gi, '.+?'); pattern = pattern.replace(/(:\w+)(\([^\)]+\))/gi, '$2');
return pattern.replace(/(:\w+)/gi, '.+?');
}, },
/* /*

View file

@ -84,7 +84,6 @@
"sass-lint": "1.5.1", "sass-lint": "1.5.1",
"sass-loader": "2.0.1", "sass-loader": "2.0.1",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"selenium-webdriver": "2.44.0",
"slick-carousel": "1.5.8", "slick-carousel": "1.5.8",
"source-map-support": "0.3.2", "source-map-support": "0.3.2",
"style-loader": "0.12.3", "style-loader": "0.12.3",

View file

@ -1,4 +1,5 @@
@import "../../colors"; @import "../../colors";
@import "../../frameless";
.dropdown { .dropdown {
display: none; display: none;
@ -83,4 +84,8 @@
content: ""; content: "";
} }
} }
@media only screen and (max-width: $tablet - 1) {
min-width: 160px;
}
} }

View file

@ -127,7 +127,7 @@
}, },
{ {
"name": "explore", "name": "explore",
"pattern": "^/explore/:projects/:all/?$", "pattern": "^/explore/:projects(projects|studios)/:all/?$",
"routeAlias": "/explore(?!/ajax)", "routeAlias": "/explore(?!/ajax)",
"view": "explore/explore", "view": "explore/explore",
"title": "Explore" "title": "Explore"
@ -196,7 +196,7 @@
}, },
{ {
"name": "search", "name": "search",
"pattern": "^/search/:projects?$/?$", "pattern": "^/search/:projects/?$",
"routeAlias": "/search", "routeAlias": "/search",
"view": "search/search", "view": "search/search",
"title": "Search" "title": "Search"

View file

@ -379,14 +379,14 @@ var ConferenceSplash = React.createClass({
<td><FormattedMessage id='conference-2017.date' /></td> <td><FormattedMessage id='conference-2017.date' /></td>
<td> <td>
<FormattedDate <FormattedDate
value={new Date(2017, 8, 28)} value={new Date(2017, 9, 5)}
year='numeric' year='numeric'
month='long' month='long'
day='2-digit' day='2-digit'
/> />
{' - '} {' - '}
<FormattedDate <FormattedDate
value={new Date(2017, 8, 30)} value={new Date(2017, 9, 7)}
year='numeric' year='numeric'
month='long' month='long'
day='2-digit' day='2-digit'

View file

@ -51,6 +51,11 @@ var Credits = React.createClass({
<img src="//cdn.scratch.mit.edu/get_image/user/2752403_170x170.png" alt="Saskia Avatar" /> <img src="//cdn.scratch.mit.edu/get_image/user/2752403_170x170.png" alt="Saskia Avatar" />
<span className="name">Saskia Leggett</span> <span className="name">Saskia Leggett</span>
</li> </li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/527836_170x170.png" alt="DD Avatar" />
<span className="name">DD Liu</span>
</li>
<li> <li>
<img src="//cdn.scratch.mit.edu/get_image/user/3714374_170x170.png" alt="Shruti Avatar" /> <img src="//cdn.scratch.mit.edu/get_image/user/3714374_170x170.png" alt="Shruti Avatar" />

View file

@ -70,7 +70,7 @@ var Explore = injectIntl(React.createClass({
var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*'; var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*';
api({ api({
uri: '/search/' + this.props.itemType + uri: '/explore/' + this.props.itemType +
'?limit=' + this.props.loadNumber + '?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset + '&offset=' + this.state.offset +
'&language=' + this.props.intl.locale + '&language=' + this.props.intl.locale +

View file

@ -13,7 +13,7 @@
"faq.makeGameTitle":"How do I make a game or animation with Scratch?", "faq.makeGameTitle":"How do I make a game or animation with Scratch?",
"faq.makeGameBody":"Check out the <a href=\"/help\">help page</a> to see lots of ways to get started with Scratch. Or just <a href=\"/projects/editor/?tip_bar=getStarted\">dive in</a> to the project editor.", "faq.makeGameBody":"Check out the <a href=\"/help\">help page</a> to see lots of ways to get started with Scratch. Or just <a href=\"/projects/editor/?tip_bar=getStarted\">dive in</a> to the project editor.",
"faq.requirementsTitle":"What are the system requirements for Scratch?", "faq.requirementsTitle":"What are the system requirements for Scratch?",
"faq.requirementsBody":"To run Scratch 2, you need to be using (1) a Mac, Linux, or Windows computer; (2) a version of <a href=\"https://get.adobe.com/flashplayer/\">Adobe Flash Player</a> released on or after June 15, 2016; (3) a relatively recent web browser: one of the latest two versions of <a href=\"http://google.com/chrome/\">Chrome</a> (Mac, Windows, or Linux), <a href=\"http://www.mozilla.org/en-US/firefox/new/\">Firefox</a> (Mac or Windows only), <a href=\"https://support.apple.com/downloads/safari\">Safari</a> (Mac or Windows only), <a href=\"https://www.microsoft.com/EN-US/windows/microsoft-edge\">Edge</a> (Windows only), or <a href=\"https://www.microsoft.com/en-us/download/internet-explorer.aspx\">Internet Explorer 10+</a> (Windows only). If your computer doesnt meet these requirements, you can try downloading and installing <a href=\"/scratch_1.4\">Scratch 1.4</a>, which you can still use to share projects to the Scratch 2 website. We do not support Chromium.", "faq.requirementsBody":"To run Scratch 2, you need to be using (1) a Mac, Linux, or Windows computer; (2) a version of <a href=\"https://get.adobe.com/flashplayer/\">Adobe Flash Player</a> released on or after June 15, 2016; (3) a relatively recent web browser: one of the latest two versions of <a href=\"http://google.com/chrome/\">Chrome</a> (Mac, Windows, or Linux), <a href=\"http://www.mozilla.org/firefox/new/\">Firefox</a> (Mac or Windows only), <a href=\"https://support.apple.com/downloads/safari\">Safari</a> (Mac or Windows only), <a href=\"https://www.microsoft.com/windows/microsoft-edge\">Edge</a> (Windows only), or <a href=\"https://www.microsoft.com/download/internet-explorer.aspx\">Internet Explorer 10+</a> (Windows only). If your computer doesnt meet these requirements, you can try downloading and installing <a href=\"/scratch_1.4\">Scratch 1.4</a>, which you can still use to share projects to the Scratch 2 website. We do not support Chromium.",
"faq.offlineTitle":"Do you have a downloadable version so I can create and view projects offline?", "faq.offlineTitle":"Do you have a downloadable version so I can create and view projects offline?",
"faq.offlineBody":"The Scratch 2 offline editor allows you to create Scratch projects without an internet connection. You can download Scratch 2 from the <a href=\"/scratch2download/\">website</a>. You can also still use <a href=\"/scratch_1.4\">Scratch 1.4</a>. Note: You can have both Scratch 1.4 and 2 on your computer.", "faq.offlineBody":"The Scratch 2 offline editor allows you to create Scratch projects without an internet connection. You can download Scratch 2 from the <a href=\"/scratch2download/\">website</a>. You can also still use <a href=\"/scratch_1.4\">Scratch 1.4</a>. Note: You can have both Scratch 1.4 and 2 on your computer.",
"faq.uploadOldTitle":"Can I still upload projects created with older versions of Scratch to the website?", "faq.uploadOldTitle":"Can I still upload projects created with older versions of Scratch to the website?",
@ -136,7 +136,7 @@
"faq.edBody":"Scratch Teacher Accounts are special user accounts on Scratch that have access to additional features to facilitate the creation and management of student accounts. ScratchEd Accounts are accounts on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd community</a>, a separate website (managed by the Harvard Graduate School of Education) where educators share stories, exchange resources, ask questions, and meet other Scratch educators.", "faq.edBody":"Scratch Teacher Accounts are special user accounts on Scratch that have access to additional features to facilitate the creation and management of student accounts. ScratchEd Accounts are accounts on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd community</a>, a separate website (managed by the Harvard Graduate School of Education) where educators share stories, exchange resources, ask questions, and meet other Scratch educators.",
"faq.dataTitle":"What data does Scratch collect about students?", "faq.dataTitle":"What data does Scratch collect about students?",
"faq.dataBody":"When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, students are not required to provide an email address for account setup.", "faq.dataBody":"When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, students are not required to provide an email address for account setup.",
"faq.lawComplianceTitle":"Is Scratch 2.0 (online version) compliant with local and federal data privacy laws?", "faq.lawComplianceTitle":"Is Scratch 2.0 (online version) compliant with United States local and federal data privacy laws?",
"faq.lawComplianceBody":"Scratch cares deeply about the privacy of students and of all individuals who use our platform. We have in place physical and electronic procedures to protect the information we collect on the Scratch website. Although we are not in a position to offer contractual guarantees with each entity that uses our free educational product, we are in compliance with all federal laws that are applicable to MIT, a 501(c)(3) organization and the entity that created and maintains Scratch. We encourage you to read the Scratch Privacy Policy for more information.", "faq.lawComplianceBody":"Scratch cares deeply about the privacy of students and of all individuals who use our platform. We have in place physical and electronic procedures to protect the information we collect on the Scratch website. Although we are not in a position to offer contractual guarantees with each entity that uses our free educational product, we are in compliance with all United States federal laws that are applicable to MIT, a 501(c)(3) organization and the entity that created and maintains Scratch. We encourage you to read the Scratch Privacy Policy for more information.",
"faq.schoolsMoreInfo":"For more more questions about Teacher Accounts, see the <a href=\"/educators/faq\">Teacher Account FAQ</a>" "faq.schoolsMoreInfo":"For more more questions about Teacher Accounts, see the <a href=\"/educators/faq\">Teacher Account FAQ</a>"
} }

View file

@ -8,8 +8,6 @@ var api = require('../../lib/api');
var Page = require('../../components/page/www/page.jsx'); var Page = require('../../components/page/www/page.jsx');
var TitleBanner = require('../../components/title-banner/title-banner.jsx'); var TitleBanner = require('../../components/title-banner/title-banner.jsx');
var Form = require('../../components/forms/form.jsx');
var Input = require('../../components/forms/input.jsx');
var Button = require('../../components/forms/button.jsx'); var Button = require('../../components/forms/button.jsx');
var Tabs = require('../../components/tabs/tabs.jsx'); var Tabs = require('../../components/tabs/tabs.jsx');
var Grid = require('../../components/grid/grid.jsx'); var Grid = require('../../components/grid/grid.jsx');
@ -99,24 +97,12 @@ var Search = injectIntl(React.createClass({
return allTab; return allTab;
}, },
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage;
return ( return (
<div> <div>
<div className='outer'> <div className='outer'>
<TitleBanner className="masthead"> <TitleBanner className="masthead">
<div className="inner"> <div className="inner">
<h1 className="title-banner-h1"><FormattedMessage id="general.search" /></h1> <h1 className="title-banner-h1"><FormattedMessage id="general.search" /></h1>
<div className="search">
<Form onSubmit={this.onSearchSubmit}>
<Button type="submit" className="btn-search" />
<Input type="text"
aria-label={formatMessage({id: 'general.search'})}
placeholder={formatMessage({id: 'general.search'})}
value={this.props.searchTerm}
name="q" />
</Form>
</div>
</div> </div>
</TitleBanner> </TitleBanner>
<Tabs> <Tabs>

View file

@ -36,8 +36,8 @@
"teacherfaq.studentDiscussBody": "Yes, you can engage in discussions with other teachers at <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a>, an online community for Scratch educators. Check out their forums to join conversations about a <a href=\"http://scratched.gse.harvard.edu/discussions\">number of topics</a>, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.", "teacherfaq.studentDiscussBody": "Yes, you can engage in discussions with other teachers at <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a>, an online community for Scratch educators. Check out their forums to join conversations about a <a href=\"http://scratched.gse.harvard.edu/discussions\">number of topics</a>, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherfaq.studentDataTitle": "What data does Scratch collect about students?", "teacherfaq.studentDataTitle": "What data does Scratch collect about students?",
"teacherfaq.studentDataBody": "When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, students are not required to provide an email address for account setup.", "teacherfaq.studentDataBody": "When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, students are not required to provide an email address for account setup.",
"teacherfaq.studentPrivacyLawsTitle": "Is Scratch 2.0 (online version) compliant with local and federal data privacy laws?", "teacherfaq.studentPrivacyLawsTitle": "Is Scratch 2.0 (online version) compliant with United States local and federal data privacy laws?",
"teacherfaq.studentPrivacyLawsBody": "Scratch cares deeply about the privacy of students and of all individuals who use our platform. We have in place physical and electronic procedures to protect the information we collect on the Scratch website. Although we are not in a position to offer contractual guarantees with each entity that uses our free educational product, we are in compliance with all federal laws that are applicable to MIT, a 501(c)(3) organization and the entity that created and maintains Scratch. We encourage you to read the Scratch Privacy Policy for more information.", "teacherfaq.studentPrivacyLawsBody": "Scratch cares deeply about the privacy of students and of all individuals who use our platform. We have in place physical and electronic procedures to protect the information we collect on the Scratch website. Although we are not in a position to offer contractual guarantees with each entity that uses our free educational product, we are in compliance with all United States federal laws that are applicable to MIT, a 501(c)(3) organization and the entity that created and maintains Scratch. We encourage you to read the Scratch Privacy Policy for more information.",
"teacherfaq.commTitle": "Community", "teacherfaq.commTitle": "Community",
"teacherfaq.commHiddenTitle": "Can I create a hidden class?", "teacherfaq.commHiddenTitle": "Can I create a hidden class?",

View file

@ -0,0 +1,19 @@
# Requirements
* Selenium
* See this directory's package.json
* TAP
* In the scratch-www repo's package.json
* Currently, we also require that you download [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/), but this will be replaced by Saucelabs imminently
# Running the tests
* By default, tests run against our Staging instance, but you can pass in a different location if you want to run the tests against e.g. your local build
## Using tap
* Run all tests in the smoke-testing directory from the command-line: `$ make smoke`
* To run a single file from the command-line: `$ node_modules/.bin/tap ./test/integration/smoke-testing/filename.js --timeout=3600`
* The timeout var is for the length of the entire tap test-suite; if you are getting a timeout error, you may need to adjust this value (some of the Selenium tests take a while to run)
## Using sauce
* We're still working on setting this up; more info coming shortly

View file

@ -0,0 +1,6 @@
{
"testDependencies": {
"selenium-webdriver": "2.44.0",
"chromedriver": "2.27.0"
}
}

View file

@ -0,0 +1,97 @@
module.exports.constants = {
'nextStepXpath': '//button[span[contains(text(), "Next Step")]]',
'generalErrorMessageXpath': '//span[@class="help-block validation-message"]/span[contains(text(),'
+ '"This field is required")]',
'loremIpsumTextLong': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur viverra'
+ 'nec mauris efficitur tincidunt. Vestibulum ut diam odio. Cum sociis natoque penatibus et magnis dis'
+ 'parturient montes, nascetur ridiculus mus. Morbi non enim dolor. Vestibulum at enim vestibulum, ullamcorper'
+ 'Duis eget quam pharetra, ultricies est eu, pharetra nisi. In tempor cursus nisi, non sagittis quam gravida.'
};
module.exports.fillUsernameSlide = function (driver, seleniumWebdriver) {
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
var usernamePromise = usernameInput.sendKeys('clipspringer');
var passwordPromise = passwordInput.sendKeys('educators');
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
return Promise.all([usernamePromise, passwordPromise]).then(function () {
nextStepButton.click().then(function () {
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.className('demographics-step')));
});
});
};
module.exports.fillDemographicsSlide = function (driver, seleniumWebdriver) {
var clickMaleInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@value="male"' +
'and @type="radio"]')).click();
var selectCountry = driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]' +
'/option[@value="us"]')).click();
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
return Promise.all([clickMaleInput, selectCountry]).then(function () {
nextStepButton.click().then(function () {
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.className('name-step')));
});
});
};
module.exports.fillNameSlide = function (driver, seleniumWebdriver) {
var firstNamePromise = driver.findElement(seleniumWebdriver.By.name('user.name.first')).sendKeys('first');
var lastNamePromise = driver.findElement(seleniumWebdriver.By.name('user.name.last')).sendKeys('surname');
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
return Promise.all([firstNamePromise, lastNamePromise]).then(function () {
nextStepButton.click().then(function () {
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.className('phone-step')));
});
});
};
module.exports.fillPhoneSlide = function (driver, seleniumWebdriver) {
var phoneInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="tel"]'));
var consentCheckbox = driver.findElement(seleniumWebdriver.By.name('phoneConsent'));
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
var phoneNumberPromise = phoneInput.sendKeys('6172535960');
var consentPromise = consentCheckbox.click();
return Promise.all([phoneNumberPromise, consentPromise]).then(function () {
nextStepButton.click().then(function () {
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.className('organization-step')));
});
});
};
module.exports.fillOrganizationSlide = function (driver, seleniumWebdriver) {
var organizationInput = driver.findElement(seleniumWebdriver.By.name('organization.name'));
var titleInput = driver.findElement(seleniumWebdriver.By.name('organization.title'));
var typeCheckbox = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="checkbox" and @value="3"]'));
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
var organizationPromise = organizationInput.sendKeys('MIT Media Lab');
var titlePromise = titleInput.sendKeys('Software Developer');
var typePromise = typeCheckbox.click();
return Promise.all([organizationPromise, titlePromise, typePromise]).then(function () {
nextStepButton.click().then(function () {
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.className('address-step')));
});
});
};
module.exports.fillAddressSlide = function (driver, seleniumWebdriver) {
var addressInput = driver.findElement(seleniumWebdriver.By.name('address.line1'));
var cityInput = driver.findElement(seleniumWebdriver.By.name('address.city'));
var zipCodeInput = driver.findElement(seleniumWebdriver.By.name('address.zip'));
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(module.exports.constants.nextStepXpath));
var addressPromise = addressInput.sendKeys('77 Massachusetts Avenue, E14/E15');
var cityPromise = cityInput.sendKeys('Cambridge');
var statePromise = driver.findElement(seleniumWebdriver.By.xpath('//select[@name="address.state"]' +
'/option[@value="us-ma"]')).click();
var zipPromise = zipCodeInput.sendKeys('02139');
return Promise.all([addressPromise, cityPromise, statePromise, zipPromise]).then(function () {
nextStepButton.click().then(function () {
driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.className('usescratch-step')));
});
});
};

View file

@ -0,0 +1,60 @@
/*
* Checks the behavior of the address step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
var utils = require('./teacher_registration_utils.js');
var constants = utils.constants;
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
driver.get(rootUrl + '/educators/register');
return utils.fillUsernameSlide(driver, seleniumWebdriver)
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillOrganizationSlide.bind(this, driver, seleniumWebdriver));
});
//Selects Vatican City as the country, and checks that the state dropdown disappears
tap.test('checkStateDropdownOnlyPresentWhenNeeded', function (t) {
driver.findElement(seleniumWebdriver.By.xpath('//select[@name="address.country"]' +
'/option[@value="va"]')).click() //select Vatican City as the country
.then(function () {
driver.findElements(seleniumWebdriver.By.name('address.state'))
.then(function (stateDropdown) {
t.equal(stateDropdown.length, 0);
t.end();
});
});
});
tap.test('checkZipCodeRequired', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//input[@name="address.zip"]/following-sibling::'
+ 'span[@class="help-block validation-message"]/span[contains(text(),'
+ '"This field is required")]';
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,61 @@
/*
* Checks the behavior of demographics step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
var utils = require('./teacher_registration_utils.js');
var constants = utils.constants;
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
driver.get(rootUrl + '/educators/register');
return utils.fillUsernameSlide(driver, seleniumWebdriver);
});
//if the user selects the other gender option, they must input a gender
//selects the other gender option and attempt to advance the slide
tap.test('checkOtherGenderInput', function (t) {
var otherGenderRadio = driver.findElement(seleniumWebdriver.By.xpath('//input[@value="other"' +
'and @type="radio"]'));
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]/option[2]')).click();
otherGenderRadio.click().then(function () {
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(constants.generalErrorMessageXpath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
});
//the user must select a gender
//tries to advance the slide without selecting a gender
tap.test('checkNoGenderInput', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
driver.findElement(seleniumWebdriver.By.xpath('//select[@name="user.country"]/option[2]')).click();
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(constants.generalErrorMessageXpath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,60 @@
/*
* Checks the behavior of the name step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
var utils = require('./teacher_registration_utils.js');
var constants = utils.constants;
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
driver.get(rootUrl + '/educators/register');
return utils.fillUsernameSlide(driver, seleniumWebdriver)
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver));
});
//attempts to advance the slide without inputting either name, checks that both give the correct error
tap.test('checkFirstNameRequired', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//input[@name="user.name.first"]/following-sibling::'
+ 'span[@class="help-block validation-message"]/span[contains(text(),'
+ '"This field is required")]';
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
//attempts to advance the slide without inputting either name, checks that both give the correct error
tap.test('checkLastNameRequired', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//input[@name="user.name.last"]/following-sibling::'
+ 'span[@class="help-block validation-message"]/span[contains(text(),'
+ '"This field is required")]';
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,88 @@
/*
* Checks the behavior of the organization step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
var utils = require('./teacher_registration_utils.js');
var constants = utils.constants;
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(4);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
driver.get(rootUrl + '/educators/register');
return utils.fillUsernameSlide(driver, seleniumWebdriver)
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver));
});
tap.test('otherFieldRequiredIfChecked', function (t) {
var otherCheckbox = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="checkbox" and @value="8"]'));
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//div[@class="other-input"]' + constants.generalErrorMessageXpath;
otherCheckbox.click().then(function () {
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
});
tap.test('checkOrganizationFieldRequired', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//input[@name="organization.name"]/following-sibling::'
+ 'span[@class="help-block validation-message"]/span[contains(text(),'
+ '"This field is required")]';
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
tap.test('checkRoleFieldRequired', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//input[@name="organization.title"]/following-sibling::'
+ 'span[@class="help-block validation-message"]/span[contains(text(),'
+ '"This field is required")]';
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
tap.test('checkOrganizationTypeRequired', function (t) {
var nextStepButton = driver.findElement(seleniumWebdriver.By.xpath(constants.nextStepXpath));
var errorMessageXPath = '//div[@class="checkbox"]/following-sibling::'
+ 'span[@class="help-block validation-message" and contains(text(),'
+ '"This field is required")]';
nextStepButton.click().then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,45 @@
/*
* Checks the behavior of the phone number step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
var utils = require('./teacher_registration_utils.js');
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(1);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
driver.get(rootUrl + '/educators/register');
return utils.fillUsernameSlide(driver, seleniumWebdriver)
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver));
});
//inputs an invalid phone number and checks that the correct error message appears
tap.test('validatePhoneNumber', function (t) {
var phoneInput = driver.findElement(seleniumWebdriver.By.xpath('//input[@type="tel"]'));
var errorMessage = 'Please enter a valid phone number';
var errorMessageXPath = '//span[@class="help-block validation-message"]/span[contains(text(),"'
+ errorMessage + '")]';
phoneInput.sendKeys(1234567890).then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath))
.then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,114 @@
/*
* Checks the behavior of first step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(5);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(rootUrl + '/educators/register');
});
//an error message should appear for a username less than 3 characters long
//input a username less than 3 characters and look for the validation message
tap.test('checkAtLeastThreeCharacters', function (t) {
//open scratch in a new instance of the browser
driver.get('https://scratch.mit.edu/educators/register');
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
var errorMessage = 'Usernames must be at least 3 characters';
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"'
+ errorMessage + '")]';
usernameInput.sendKeys('hi').then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
//usernames have to be unique
//input a username that exists and check that an error message appears
tap.test('checkUsernameExistsError', function (t) {
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
var inputUsername = usernameInput.sendKeys('mres');
var passwordClick = passwordInput.click();
var errorMessage = 'Sorry, that username already exists';
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"'
+ errorMessage + '")]';
Promise.all([inputUsername, passwordClick]).then(function () {
var errorBubble = driver.wait(seleniumWebdriver.until.
elementLocated(seleniumWebdriver.By.xpath(errorMessageXPath)), 10000);
t.notEqual(errorBubble, undefined);
t.end();
});
});
//passwords must be at least 6 characters
//find the validation message if the input password is less than 6 characters
tap.test('checkPasswordAtLeastSixCharacters', function (t) {
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
var errorMessage = 'Passwords must be at least six characters';
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"'
+ errorMessage + '")]';
passwordInput.sendKeys('hello').then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
//password cannot be "password"
//find the validation message if the user inputs "password"
tap.test('checkPasswordNotPassword', function (t) {
driver.get('https://scratch.mit.edu/educators/register');
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
//keeping "password" in messed with the xPath, may need to find a better way
var errorMessage = 'Your password may not be';
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"'
+ errorMessage + '")]';
passwordInput.sendKeys('password').then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});
//the username and password cannot be the same
//find the validation message if the username and password match
tap.test('checkPasswordNotUsername', function (t) {
driver.get('https://scratch.mit.edu/educators/register');
var passwordInput = driver.findElement(seleniumWebdriver.By.name('user.password'));
var usernameInput = driver.findElement(seleniumWebdriver.By.name('user.username'));
var errorMessage = 'Your password may not be your username';
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"'
+ errorMessage + '")]';
var usernamePromise = usernameInput.sendKeys('educator');
var passwordPromise = passwordInput.sendKeys('educator');
//wait for both inputs to have the same text, and check for validation message
Promise.all([usernamePromise, passwordPromise]).then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
//there should be only one validation message
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,70 @@
/*
* Checks the behavior of the 'use scratch' step in the educators registration process
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Testing-Scratch-www#All_Test_Cases_Teacher_Join_Flow
*/
require('chromedriver');
var seleniumWebdriver = require('selenium-webdriver');
var tap = require('tap');
var utils = require('./teacher_registration_utils.js');
var constants = utils.constants;
//Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'http://localhost:8333';
//chrome driver
var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome()).build();
tap.plan(3);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
driver.get(rootUrl + '/educators/register');
return utils.fillUsernameSlide(driver, seleniumWebdriver)
.then(utils.fillDemographicsSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillOrganizationSlide.bind(this, driver, seleniumWebdriver))
.then(utils.fillAddressSlide.bind(this, driver, seleniumWebdriver));
});
tap.test('checkCharacterCountIsCorrect', function (t) {
var textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
var charCount = driver.findElement(seleniumWebdriver.By.xpath('//p[@class="char-count"]'));
textarea.sendKeys('hello').then(function () {
charCount.getText().then(function (charCountText) {
t.equal(charCountText, '5/300');
t.end();
});
});
});
//Inputs more than 300 characters and checks that the char count gets the class 'overmax'
//which turns the text orange
tap.test('checkCharacterCountTurnsOrangeWhenTooLong', function (t) {
var textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
var charCount = driver.findElement(seleniumWebdriver.By.xpath('//p[@class="char-count"]'));
textarea.sendKeys(constants.loremIpsumTextLong).then(function () {
charCount.getAttribute('class').then(function (charCountClasses) {
t.ok(charCountClasses.includes('overmax'));
t.end();
});
});
});
tap.test('checkCharacterCountErrorAppersWhenTooLong', function (t) {
var textarea = driver.findElement(seleniumWebdriver.By.name('useScratch'));
var errorMessage = 'Description must be at most 300 characters';
var errorMessageXPath = '//span[@class="help-block validation-message" and contains(text(),"'
+ errorMessage + '")]';
textarea.sendKeys(constants.loremIpsumTextLong).then(function () {
driver.findElements(seleniumWebdriver.By.xpath(errorMessageXPath)).then(function (validationMessages) {
t.equal(validationMessages.length, 1);
t.end();
});
});
});

View file

@ -0,0 +1,73 @@
/**
* Tests whether any page in www has any languages which are missing string IDs
* - checks every language against the list of english IDs for that page
* - test fails if the length of the list of languages missing any IDs is not 0
* - if the test fails, you can see which pages/ languages/ IDs are causing the failure:
* - Object.keys(pagesWithLanguagesMissingIds) gives you a list
* of the pages which had languages with missing IDs
* - pagesWithLanguagesMissingIds['pageName.intl.js'] gives you an object
* with languages as keys and the missing IDs as values
*/
var path = require('path');
var fs = require('fs');
var tap = require('tap');
/**
* To get the files (containing message IDs and localized strings for each page in www)
* from the intl directory
*/
var intlDirPath = path.resolve(__dirname, '../../intl/');
var intlFiles = fs.readdirSync(intlDirPath);
/**
* Tells tap whether the test should pass or fail for a given file.
* @param {string} fileName
* @param {Object} missingMessageId
* @param {Object} pagesMissingIds
*/
function noMissingStrings (fileName, missingMessageId, pagesMissingIds) {
if (Object.keys(missingMessageId).length == 0) {
tap.pass();
}
else {
tap.fail(fileName + ' is missing string IDs');
pagesMissingIds[fileName] = [];
pagesMissingIds[fileName].push(missingMessageId);
}
}
var pagesWithLanguagesMissingIds = {};
for (var i in intlFiles) {
var file = intlFiles[i];
var filePath = path.resolve(__dirname, '../../intl/' + file);
var pageMessagesString = fs.readFileSync(filePath,'utf8');
/**
* To make the string of the file of the page.intl.js back into useable objects
*/
var window = {};
var pageMessages = eval(pageMessagesString);
/**
* The goal is to compare the IDs for each language to the IDs for English,
* so we need the list of IDs for the given page in English
*/
var englishIdList = window._messages.en;
var messageIdNotInLanguage = {};
for (var languageKey in pageMessages) {
var currentLanguageObject = pageMessages[languageKey];
for (var messageId in englishIdList) {
if (! (messageId in currentLanguageObject)) {
if (typeof messageIdNotInLanguage[languageKey] == 'undefined') {
messageIdNotInLanguage[languageKey] = [];
}
messageIdNotInLanguage[languageKey].push(messageId);
}
}
}
noMissingStrings(file, messageIdNotInLanguage, pagesWithLanguagesMissingIds);
}