Move tap tests into legacy folders

This commit is contained in:
BryceLTaylor 2019-07-17 14:56:48 -04:00
parent 5e868575c6
commit dd6b7ba4e2
28 changed files with 2377 additions and 6 deletions

View file

@ -4,13 +4,13 @@
"description": "Standalone WWW client for Scratch",
"scripts": {
"start": "node ./dev-server/index.js",
"test": "npm run test:lint && npm run build && npm run test:tap",
"test": "npm run test:lint && npm run build && npm run test:unit:tap",
"test:lint": "eslint . --ext .js,.jsx,.json",
"test:smoke": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
"test:smoke:verbose": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
"test:tap": "tap ./test/{unit,localization}/*.js --no-coverage -R classic",
"test:coverage": "tap ./test/{unit,localization}/*.js --coverage --coverage-report=lcov",
"test:smoke": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
"test:smoke:verbose": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration-legacy/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/*.js --no-coverage -R classic",
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/*.js --coverage --coverage-report=lcov",
"build": "npm run clean && npm run translate && webpack --bail",
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
@ -60,6 +60,7 @@
"babel-preset-react": "6.22.0",
"bowser": "1.9.4",
"cheerio": "1.0.0-rc.2",
"chromedriver": "75.1.0",
"classnames": "2.2.5",
"cookie": "0.2.2",
"copy-webpack-plugin": "0.2.0",
@ -115,6 +116,7 @@
"sass-loader": "6.0.6",
"scratch-gui": "0.1.0-prerelease.20190711011201",
"scratch-l10n": "latest",
"selenium-webdriver": "3.6.0",
"slick-carousel": "1.6.0",
"source-map-support": "0.3.2",
"style-loader": "0.12.3",

View file

@ -0,0 +1,35 @@
# Requirements
* Selenium
* See this directory's package.json
* TAP
* In the scratch-www repo's package.json
* [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/)
# 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
* Tests can be run using Saucelabs, an online service that can test browser/os combinations remotely. Currently all tests are written for use for chrome on mac.
## Using tap
* Run all tests in the smoke-testing directory from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run smoke`
* To run a single file from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu 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)
* To run tests using saucelabs run this command `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password SAUCE_USERNAME=saucelabsUsername SAUCE_ACCESS_KEY=saucelabsAccessKey ROOT_URL=https://scratch.mit.edu npm run smoke-sauce`
### Configuration
| Variable | Default | Description |
| --------------------- | --------------------- | --------------------------------------------------------- |
| `ROOT_URL` | `scratch.ly` | Location you want to run the tests against |
| `SMOKE_USERNAME` | `None` | Username for Scratch user you're signing in with to test |
| `SMOKE_PASSWORD` | `None` | Password for Scratch user you're signing in with to test |
| `SMOKE_REMOTE` | `false` | Tests with Sauce Labs or not. True if running smoke-sauce |
| `SMOKE_HEADLESS` | `false` | Run browser in headless mode. Flaky at the moment |
| `SAUCE_USERNAME` | `None` | Username for your Sauce Labs account |
| `SAUCE_ACCESS_KEY` | `None` | Access Key for Sauce Labs found under User Settings |
## Using Saucelabs
* You will need a Saucelabs account in order to use it for testing. To find the Access Key, click your username and select User Settings from the dropdown menu. Near the bottom of the page is your access key that you can copy and use in the command line.

View file

@ -0,0 +1,168 @@
const webdriver = require('selenium-webdriver');
const bindAll = require('lodash.bindall');
require('chromedriver');
const headless = process.env.SMOKE_HEADLESS || false;
const remote = process.env.SMOKE_REMOTE || false;
const ci = process.env.CI || false;
const buildID = process.env.TRAVIS_BUILD_NUMBER;
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
const {By, Key, until} = webdriver;
const DEFAULT_TIMEOUT_MILLISECONDS = 20 * 1000;
class SeleniumHelper {
constructor () {
bindAll(this, [
'buildDriver',
'clickButton',
'clickCss',
'clickText',
'clickXpath',
'dragFromXpathToXpath',
'findByCss',
'findByXpath',
'findText',
'getKey',
'getDriver',
'getLogs',
'getSauceDriver',
'urlMatches',
'waitUntilGone'
]);
}
buildDriver (name) {
if (remote === 'true'){
let nameToUse;
if (ci === 'true'){
nameToUse = 'travis ' + buildID + ' : ' + name;
} else {
nameToUse = name;
}
this.driver = this.getSauceDriver(SAUCE_USERNAME, SAUCE_ACCESS_KEY, nameToUse);
} else {
this.driver = this.getDriver();
}
return this.driver;
}
getDriver () {
const chromeCapabilities = webdriver.Capabilities.chrome();
let args = [];
if (headless) {
args.push('--headless');
args.push('window-size=1024,1680');
args.push('--no-sandbox');
}
chromeCapabilities.set('chromeOptions', {args});
let driver = new webdriver.Builder()
.forBrowser('chrome')
.withCapabilities(chromeCapabilities)
.build();
return driver;
}
getSauceDriver (username, accessKey, name) {
// Driver configs can be generated with the Sauce Platform Configurator
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
let driverConfig = {
browserName: 'chrome',
platform: 'macOS 10.14',
version: '75.0'
};
var driver = new webdriver.Builder()
.withCapabilities({
browserName: driverConfig.browserName,
platform: driverConfig.platform,
version: driverConfig.version,
username: username,
accessKey: accessKey,
name: name
})
.usingServer(`http://${username}:${accessKey
}@ondemand.saucelabs.com:80/wd/hub`)
.build();
return driver;
}
getKey (keyName) {
return Key[keyName];
}
findByXpath (xpath, timeoutMessage = `findByXpath timed out for path: ${xpath}`) {
return this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage)
.then(el => (
this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS, `${xpath} is not visible`)
.then(() => el)
));
}
waitUntilGone (element) {
return this.driver.wait(until.stalenessOf(element));
}
clickXpath (xpath) {
return this.findByXpath(xpath).then(el => el.click());
}
clickText (text) {
return this.clickXpath(`//*[contains(text(), '${text}')]`);
}
findText (text) {
return this.driver.wait(until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`), 5 * 1000));
}
clickButton (text) {
return this.clickXpath(`//button[contains(text(), '${text}')]`);
}
findByCss (css) {
return this.driver.wait(until.elementLocated(By.css(css), 1000 * 5));
}
clickCss (css) {
return this.findByCss(css).then(el => el.click());
}
dragFromXpathToXpath (startXpath, endXpath) {
return this.findByXpath(startXpath).then(startEl => {
return this.findByXpath(endXpath).then(endEl => {
return this.driver.actions()
.dragAndDrop(startEl, endEl)
.perform();
});
});
}
urlMatches (regex) {
return this.driver.wait(until.urlMatches(regex), 1000 * 5);
}
getLogs (whitelist) {
return this.driver.manage()
.logs()
.get('browser')
.then((entries) => {
return entries.filter((entry) => {
const message = entry.message;
for (let i = 0; i < whitelist.length; i++) {
if (message.indexOf(whitelist[i]) !== -1) {
// eslint-disable-next-line no-console
// console.warn('Ignoring whitelisted error: ' + whitelist[i]);
return false;
} else if (entry.level !== 'SEVERE') {
// eslint-disable-next-line no-console
// console.warn('Ignoring non-SEVERE entry: ' + message);
return false;
}
return true;
}
return true;
});
});
}
}
module.exports = SeleniumHelper;

View file

@ -0,0 +1,30 @@
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-login-failures');
const {
clickText,
findByXpath
} = helper;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
tap.plan(1);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(rootUrl);
});
test('Clicking Join Scratch opens scratchr2 iframe', t => {
clickText('Join Scratch')
.then(() => findByXpath('//iframe[contains(@class, "mod-registration")]'))
.then(() => t.end());
});

View file

@ -0,0 +1,89 @@
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
var tap = require('tap');
const test = tap.test;
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test-login-failures');
const {
findByCss,
clickCss
} = helper;
var until = webdriver.until;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/users/' + username;
tap.plan(3);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(url);
});
test('Trying to sign in with no password using scratchr2 navbar', t => {
var nonsenseusername = Math.random().toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, 5);
clickCss('.dropdown-toggle')
.then(() => findByCss('form#login input#login_dropdown_username'))
.then((element) => element.sendKeys(nonsenseusername))
.then(() => clickCss('form#login button'))
.then(() => findByCss('form#login .error'))
.then((element) => {
driver.wait(until.elementIsVisible(element));
return element;
})
.then((element) => element.getText())
.then((text) => t.match(text, 'This field is required',
'"This field is required" error should be displayed'))
.then(() => t.end());
});
test('Trying to sign in with the wrong username using scratchr2 navbar', t => {
var nonsenseusername = Math.random().toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, 5);
clickCss('.dropdown-toggle')
.then(() => findByCss('form#login input#login_dropdown_username'))
.then((element) => element.sendKeys(nonsenseusername))
.then(() => findByCss('form#login input.wide.password'))
.then((element) => element.sendKeys(password))
.then(() => clickCss('form#login button'))
.then(() => findByCss('form#login .error'))
.then((element) => {
driver.wait(until.elementIsVisible(element));
return element;
})
.then((element) => element.getText())
.then((text) => t.match(text, 'Incorrect username or password.',
'"Incorrect username or password" error should be displayed'))
.then(() => t.end());
});
test('Trying to sign in with the wrong password using scratchr2 navbar', t => {
clickCss('.dropdown-toggle')
.then(() => findByCss('form#login input#login_dropdown_username'))
.then((element) => element.sendKeys(username))
.then(() => findByCss('form#login input.wide.password'))
.then((element) => element.sendKeys('nonsensepassword'))
.then(() => clickCss('form#login button'))
.then(() => findByCss('form#login .error'))
.then((element) => {
driver.wait(until.elementIsVisible(element));
return element;
})
.then((element) => element.getText())
.then((text) => t.match(text, 'Incorrect username or password.',
'"Incorrect username or password" error should be displayed'))
.then(() => t.end());
});

View file

@ -0,0 +1,151 @@
/*
* Tests signing in & My Stuff according to smoke-tests at:
*
* https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
*
*/
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-my-stuff');
const {
clickText,
findByXpath,
clickXpath,
clickButton
} = helper;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/users/' + username;
tap.plan(7);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(url)
.then(() => clickText('Sign in'))
.then(() => findByXpath('//input[@id="login_dropdown_username"]'))
.then((element) => element.sendKeys(username))
.then(() => findByXpath('//input[@name="password"]'))
.then((element) => element.sendKeys(password))
.then(() => clickButton('Sign in'));
});
tap.afterEach(function () {
return clickXpath('//span[@class="user-name dropdown-toggle"]')
.then(() => clickXpath('//li[@id="logout"] '))
.then(() => findByXpath('//div[@class="title-banner intro-banner"]'));
});
test('Sign in to Scratch using scratchr2 navbar', t => {
findByXpath('//li[contains(@class, "logged-in-user")' +
'and contains(@class, "dropdown")]/span')
.then((element) => element.getText('span'))
.then((text) => t.match(text.toLowerCase(), username.substring(0, 10).toLowerCase(),
'first part of username should be displayed in navbar'))
.then(() => t.end());
});
test('Sign in to Scratch & verify My Stuff structure (tabs, title)', t => {
clickXpath('//a[contains(@class, "mystuff-icon")]')
.then(() => findByXpath('//div[@class="box-head"]/h2'))
.then((element) => element.getText('h2'))
.then((text) => t.equal('My Stuff', text, 'title should be My Stuff'))
.then(() => findByXpath('//li[@data-tab="projects"]/a'))
.then((element) => element.getText('a'))
.then((text) => t.match(text, 'All Projects', 'All Projects tab should be present'))
.then(() => findByXpath('//li[@data-tab="shared"]/a'))
.then((element) => element.getText('a'))
.then((text) => t.match(text, 'Shared Projects', 'Shared Projects tab should be present'))
.then(() => findByXpath('//li[@data-tab="unshared"]/a'))
.then((element) => element.getText('a'))
.then((text) => t.match(text, 'Unshared Projects', 'Unshared Projects tab should be present'))
.then(() => findByXpath('//li[@data-tab="galleries"]/a'))
.then((element) => element.getText('a'))
.then((text) => t.match(text, 'My Studios', 'My Studios tab should be present'))
.then(() => findByXpath('//li[@data-tab="trash"]/a'))
.then((element) => element.getText('a'))
.then((text) => t.match(text, 'Trash', 'Trash tab should be present'))
.then(() => t.end());
});
test('clicking See Inside should take you to the editor', t => {
clickXpath('//a[contains(@class, "mystuff-icon")]')
.then(() => findByXpath('//a[@data-control="edit"]'))
.then((element) => element.getText('span'))
.then((text) => t.equal(text, 'See inside', 'there should be a "See inside" button'))
.then(() => clickXpath('//a[@data-control="edit"]'))
.then(() => driver.getCurrentUrl())
.then(function (u) {
var expectedUrl = '/editor';
t.equal(u.substr(-expectedUrl.length), expectedUrl, 'after clicking, the URL should end in #editor');
})
.then(() => driver.get(url))
.then(() => t.end());
});
test('clicking a project title should take you to the project page', t => {
clickXpath('//a[contains(@class, "mystuff-icon")]')
.then(() => clickXpath('//a[@data-control="edit"]'))
.then(() => driver.getCurrentUrl())
.then(function (u) {
var expectedUrlRegExp = new RegExp('/projects/.*[0-9].*/?');
t.match(u, expectedUrlRegExp, 'after clicking, the URL should end in projects/PROJECT_ID/');
})
.then(() => driver.get(url))
.then(() => t.end());
});
test('Add To button should bring up a list of studios', t => {
clickXpath('//a[contains(@class, "mystuff-icon")]')
.then(() => clickXpath('//div[@id="sidebar"]/ul/li[@data-tab="shared"]'))
.then(() => findByXpath('//div[@data-control="add-to"]'))
.then((element) => element.getText('span'))
.then((text) => t.equal(text, 'Add to', 'there should be an "Add to" button'))
.then(() => clickXpath('//div[@data-control="add-to"]'))
.then(() => findByXpath('//div[@class="dropdown-menu"]/ul/li'))
.then((element) => element.getText('span'))
.then(function (text) {
var expectedRegExp = new RegExp('.+');
t.match(text, expectedRegExp, 'the dropdown menu should have at least 1 text item in it');
})
.then(() => t.end());
});
test('+ New Studio button should take you to the studio page', {skip: true}, t => {
clickXpath('//a[contains(@class, "mystuff-icon")]')
.then(() => clickXpath('//form[@id="new_studio"]/button[@type="submit"]'))
.then(() => findByXpath('//div[@id="show-add-project"]'))
.then((element) => element.getText('span'))
.then((text) => t.equal(text, 'Add projects', 'there should be an "Add projects" button'))
.then(() => driver.getCurrentUrl())
.then(function (u) {
var expectedUrlRegExp = new RegExp('/studios/.*[0-9].*/?');
t.match(u, expectedUrlRegExp,
'after clicking the + New Studio, the URL should end in studios/STUDIO_ID');
})
.then(() => t.end());
});
test('+ New Project button should open the editor', {skip: true}, t => {
clickXpath('//a[contains(@class, "mystuff-icon")]')
.then(() => clickText('+ New Project'))
.then(() => driver.getCurrentUrl())
.then(function (u) {
var expectedUrlRegExp = new RegExp('/projects/editor');
t.match(u, expectedUrlRegExp,
'after clicking, the URL should end in projects/editor');
})
.then(() => t.end());
});

View file

@ -0,0 +1,305 @@
/*
* 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};
// number of tests in the plan
tap.plan(24);
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 expectedHref = '/jobs';
clickFooterLinks(linkText).then(url => {
t.equal(url.substr(-expectedHref.length), expectedHref);
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();
});
});
// SCRATCH WIKI test has been removed.
// 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();
});
});
// skip this test since it points to an external site
// SCRATCH STORE
tap.test('clickScratchStoreLink', {skip: true}, t => {
const linkText = 'Scratch Store';
const expectedUrl = 'https://scratch-foundation.myshopify.com/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});
// skip this test since it points to an external site
// DONATE
tap.test('clickDonateLink', {skip: true}, t => {
const linkText = 'Donate';
const expectedUrl = 'https://secure.donationpay.org/scratchfoundation/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
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 ====
// skip this test since it points to an external site
// SCRATCH ED (SCRATCHED)
tap.test('clickScratchEdLink', {skip: true}, t => {
const linkText = 'ScratchEd';
const expectedUrl = 'http://scratched.gse.harvard.edu/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});
// skip this test since it points to an external site
// SCRATCH JR (SCRATCHJR)
tap.test('clickScratchJrLink', {skip: true}, t => {
const linkText = 'ScratchJr';
const expectedUrl = 'https://www.scratchjr.org/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});
// skip this test since it points to an external site
// SCRATCH DAY
tap.test('clickScratchDayLink', {skip: true}, t => {
const linkText = 'Scratch Day';
const expectedUrl = 'https://day.scratch.mit.edu/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});
// 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();
});
});
// skip this test since it points to an external site
// SCRATCH FOUNDATION
tap.test('clickScratchFoundationLink', {skip: true}, t => {
const linkText = 'Scratch Foundation';
const expectedUrl = 'https://www.scratchfoundation.org/';
clickFooterLinks(linkText).then(url => {
t.equal(url, expectedUrl);
t.end();
});
});

View file

@ -0,0 +1,123 @@
/*
* Checks that the links in the navbar 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();
var tap = require('tap');
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test_navbar_links');
// Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
// number of tests in the plan
tap.plan(7);
tap.tearDown(function () {
// quit the instance of the browser
driver.quit();
});
tap.beforeEach(function () {
// load the page with the driver
return driver.get(rootUrl);
});
// ==== Links in navbar ====
// the create link changes depending on whether the user is signed in or not (tips window opens)
tap.test('checkCreateLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "create")]/a';
var expectedHref = '/projects/editor/?tutorial=getStarted';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
.then(function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkExploreLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "explore")]/a';
var expectedHref = '/explore/projects/all';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
.then(function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkIdeasLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "ideas")]/a';
var expectedHref = '/ideas';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
.then(function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
tap.test('checkAboutLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "about")]/a';
var expectedHref = '/about';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
.then(function (url) {
t.equal(url.substr(-expectedHref.length), expectedHref);
t.end();
});
});
// ==== Search bar ====
tap.test('checkSearchBar', function (t) {
var xPathLink = '//input[@id="frc-q-1088"]';
// search bar should exist
driver.findElement(webdriver.By.xpath(xPathLink)).then(function (element) {
t.ok(element);
t.end();
});
});
// ==== Join Scratch & Sign In ====
tap.test('checkJoinScratchLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "right") and contains(@class, "join")]/a';
var expectedText = 'Join Scratch';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getText('a');
})
.then(function (text) {
t.equal(text, expectedText);
t.end();
});
});
tap.test('checkSignInLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "right") and contains(@class, "login-item")]/a';
var expectedText = 'Sign in';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getText('a');
})
.then(function (text) {
t.equal(text, expectedText);
t.end();
});
});

View file

@ -0,0 +1,61 @@
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,93 @@
/*
* Checks that the some of the homepage rows on the homepage are displayed and
* contents 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();
var tap = require('tap');
const webdriver = require('selenium-webdriver');
const driver = helper.buildDriver('www-smoke test_project_rows');
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
// number of tests in the plan
tap.plan(4);
tap.tearDown(function () {
// quit the instance of the browser
driver.quit();
});
tap.beforeEach(function () {
// load the page with the driver
return driver.get(rootUrl);
});
// checks that the title of the first row is Featured Projects
tap.test('checkFeaturedProjectsRowTitleWhenSignedOut', function (t) {
var xPathLink = '//div[@class="box"]/div[@class="box-header"]/h4';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
element.getText('h4')
.then(function (text) {
// expected value of the title text
var expectedText = 'Featured Projects';
t.equal(text, expectedText);
t.end();
});
});
});
// checks that the link for a project makes sense
tap.test('checkFeaturedProjectsRowLinkWhenSignedOut', function (t) {
var xPathLink = '//div[contains(@class, "thumbnail") ' +
'and contains(@class, "project") and contains(@class, "slick-slide") ' +
'and contains(@class, "slick-active")]/a[@class="thumbnail-image"]';
driver.wait(webdriver.until
.elementLocated(webdriver.By.xpath(xPathLink)))
.then(function (element) {
element.getAttribute('href')
.then(function (url) {
// expected pattern for the project URL
// since I don't know the length of the project ID number
var expectedUrlRegExp = new RegExp('/projects/.*[0-9].*/?');
t.match(url, expectedUrlRegExp);
t.end();
});
});
});
// checks that the title of the 2nd row is Featured Studios
tap.test('checkFeaturedStudiosRowWhenSignedOut', function (t) {
var xPathLink = '//div[@class="box"][2]/div[@class="box-header"]/h4';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
element.getText('h4')
.then(function (text) {
var expectedText = 'Featured Studios';
t.equal(text, expectedText);
t.end();
});
});
});
// checks that the link for a studio makes sense
tap.test('checkFeaturedStudiosRowLinkWhenSignedOut', function (t) {
var xPathLink = '//div[contains(@class, "thumbnail") and contains(@class, "gallery") ' +
'and contains(@class, "slick-slide") ' +
'and contains(@class, "slick-active")]/a[@class="thumbnail-image"]';
driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
element.getAttribute('href')
.then(function (url) {
var expectedUrlRegExp = new RegExp('/studios/.*[0-9].*/?');
t.match(url, expectedUrlRegExp);
t.end();
});
});
});

View file

@ -0,0 +1,58 @@
/*
* Checks the behavior of the search interface
*/
require('chromedriver');
const seleniumWebdriver = require('selenium-webdriver');
const SeleniumHelper = require('../selenium-helpers.js');
const helper = new SeleniumHelper();
const {
urlMatches
} = helper;
const tap = require('tap');
const test = tap.test;
// Set test url through environment variable
const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
const searchBaseUrl = `${rootUrl}/search/`;
// chrome driver
const driver = helper.buildDriver('www-search test_search');
tap.plan(3);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(searchBaseUrl);
});
test('Search escapes spaces', function (t) {
const searchInput = driver.findElement(seleniumWebdriver.By.name('q'));
searchInput.sendKeys('Test search string', helper.getKey('ENTER')).then(function () {
urlMatches(/^.*\?q=Test%20search%20string$/)
.then(() => t.end());
});
});
test('Search escapes symbols', function (t) {
const searchInput = driver.findElement(seleniumWebdriver.By.name('q'));
searchInput.sendKeys('100% pen', helper.getKey('ENTER')).then(function () {
urlMatches(/^.*\?q=100%25%20pen$/)
.then(() => t.end());
});
});
test('Switching to studios maintains search string', function (t) {
const searchInput = driver.findElement(seleniumWebdriver.By.name('q'));
searchInput.sendKeys('100% pen', helper.getKey('ENTER')).then(function () {
const studiosTab = driver.findElement(seleniumWebdriver.By.xpath(
'//a/li/span[contains(text(),"Studios")]'));
studiosTab.click().then(function () {
urlMatches(/^.*\?q=100%25%20pen$/)
.then(() => t.end());
});
});
});

View file

@ -0,0 +1,62 @@
/*
* Tests from:
*
* https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
*
*/
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_discuss');
const {
clickText,
findByXpath,
findText,
clickXpath,
clickButton
} = helper;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
var url = rootUrl + '/discuss';
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(url);
});
test('Sign in to Scratch using scratchr2 navbar', t => {
clickText('Sign in')
.then(() => findByXpath('//input[@id="login_dropdown_username"]'))
.then((element) => element.sendKeys(username))
.then(() => findByXpath('//input[@name="password"]'))
.then((element) => element.sendKeys(password))
.then(() => clickButton('Sign in'))
.then(() => findByXpath('//li[contains(@class, "logged-in-user")' +
'and contains(@class, "dropdown")]/span'))
.then((element) => element.getText('span'))
.then((text) => t.match(text.toLowerCase(), username.substring(0, 10).toLowerCase(),
'first part of username should be displayed in navbar'))
.then(() => t.end());
});
test('Sign out of Scratch using scratchr2 navbar', t => {
clickXpath('//span[contains(@class, "user-name")' +
' and contains(@class, "dropdown-toggle")]/img[contains(@class, "user-icon")]')
.then(() => clickXpath('//input[@value="Sign out"]'))
.then(() => findText('Sign in'))
.then((element) => t.ok(element, 'Sign in reappeared on the page after signing out'))
.then(() => t.end());
});

View file

@ -0,0 +1,59 @@
/*
* Tests from:
*
* https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
*
*/
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,
findText,
findByXpath,
clickXpath
} = helper;
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
return driver.get(rootUrl);
});
test('Sign in to Scratch using scratch-www navbar', {skip: true}, t => {
clickText('Sign in')
.then(() => findByXpath('//input[@id="frc-username-1088"]'))
.then((element) => element.sendKeys(username))
.then(() => findByXpath('//input[@id="frc-password-1088"]'))
.then((element) => element.sendKeys(password))
.then(() => clickXpath('//button[contains(@class, "button") and ' +
'contains(@class, "submit-button") and contains(@class, "white")]'))
.then(() => findByXpath('//span[contains(@class, "profile-name")]'))
.then((element) => element.getText())
.then((text) => t.match(text.toLowerCase(), username.substring(0, 10).toLowerCase(),
'first part of username should be displayed in navbar'))
.then(() => t.end());
});
test('Sign out of Scratch using scratch-www navbar', {skip: true}, t => {
clickXpath('//a[contains(@class, "user-info")]')
.then(() => clickText('Sign out'))
.then(() => findText('Sign in'))
.then((element) => t.ok(element, 'Sign in reappeared on the page after signing out'))
.then(() => t.end());
});

View file

@ -0,0 +1,68 @@
/*
* Tests stats page according to smoke-tests at:
*
* https://github.com/LLK/scratchr2/wiki/Smoke-Testing-Test-Cases
*
*/
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_statistics_page');
const {
clickText,
findByXpath,
findByCss
} = helper;
tap.plan(2);
tap.tearDown(function () {
driver.quit();
});
tap.beforeEach(function () {
/*
* load the page with the driver
* note that for now this is not testable on Staging,
* so I left it pointing to Production -
* we can at least use it post-deploy.
*
* var stagingURL = 'https://scratch.ly/statistics';
*/
var productionURL = 'https://scratch.mit.edu/statistics';
return driver.get(productionURL);
});
test('check that Monthly Activity Trends title is present & correct', t => {
var chartTitle = 'Monthly Activity Trends';
findByCss('div.box-head h3')
.then((element) => element.getText('h3'))
.then((text) => t.equal(text, chartTitle, 'chart title should be Monthly Activity Trends'))
.then(() => t.end());
});
test('check that Monthly Activity Trends chart > New Projects label is toggleable', t => {
var classXpath = `(//div[@id="activity_chart"]/*[name()='svg']/*[name()='g']/*[name()='g']/*` +
`[name()='g'])[4]/*[name()='g']/*[name()='g']/*[name()='g']`;
findByXpath(classXpath)
.then((element) => element.getAttribute('class'))
.then((classtext) => t.equal(classtext, 'nv-series', 'by default, New Projects should be enabled'))
.then(() => clickText('New Projects'))
.then(() => findByXpath(classXpath))
.then((element) => element.getAttribute('class'))
.then((classtext) => t.equal(
classtext,
'nv-series nv-disabled',
'when clicked, New Projects should be disabled'
))
.then(() => clickText('New Projects'))
.then(() => findByXpath(classXpath))
.then((element) => element.getAttribute('class'))
.then((classtext) => t.equal(classtext, 'nv-series', 'when clicked again, New Projects should be enabled'))
.then(() => t.end());
});

View file

@ -0,0 +1,99 @@
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 () { // eslint-disable-line no-undef
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 () { // eslint-disable-line no-undef
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 () { // eslint-disable-line no-undef
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 () { // eslint-disable-line no-undef
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]) // eslint-disable-line no-undef
.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]) // eslint-disable-line no-undef
.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)) // eslint-disable-line no-invalid-this
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
.then(utils.fillOrganizationSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
});
// 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)); // eslint-disable-line no-invalid-this
});
// 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,89 @@
/*
* 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)) // eslint-disable-line no-invalid-this
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
});
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)) // eslint-disable-line no-invalid-this
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
});
// 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 () { // eslint-disable-line no-undef
var errorBubble = driver.wait(seleniumWebdriver.until
.elementLocated(seleniumWebdriver.By.xpath(errorMessageXPath)), 10000);
t.notEqual(errorBubble, undefined); // eslint-disable-line no-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 () { // eslint-disable-line no-undef
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,71 @@
/*
* 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)) // eslint-disable-line no-invalid-this
.then(utils.fillNameSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
.then(utils.fillPhoneSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
.then(utils.fillOrganizationSlide.bind(this, driver, seleniumWebdriver)) // eslint-disable-line no-invalid-this
.then(utils.fillAddressSlide.bind(this, driver, seleniumWebdriver)); // eslint-disable-line no-invalid-this
});
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,52 @@
/*
* Check that there are no duplicate strings in any individual l10n json file.
*/
var path = require('path');
var fs = require('fs');
var tap = require('tap');
var routes = require('../../src/routes.json');
const noDuplicateValues = (idsToCheck, name) => {
let values = {};
for (const key in idsToCheck) {
if (values.hasOwnProperty(idsToCheck[key])) {
// duplicate values
// return false;
tap.fail(`${name}.${idsToCheck[key]} has duplicates`);
} else {
values[idsToCheck[key]] = key;
}
}
tap.pass();
// return true;
};
tap.test('generalCheckForDuplicates', function (t) {
const ids = require(path.resolve(__dirname, '../../src/l10n.json')); // eslint-disable-line global-require
noDuplicateValues(ids, 'general');
t.end();
});
for (var v in routes) {
if (typeof routes[v].redirect !== 'undefined') {
continue;
}
var subdir = routes[v].view.split('/');
subdir.pop();
var name = routes[v].name;
var uri = path.resolve(__dirname, '../../src/views/' + subdir.join('/') + '/l10n.json');
try {
var file = fs.readFileSync(uri, 'utf8');
var ids = JSON.parse(file);
tap.test(name + 'CheckForDuplicates', function (t) { // eslint-disable-line no-loop-func
noDuplicateValues(ids, name);
t.end();
});
} catch (err) {
if (err.code !== 'ENOENT') {
// ignore if ENOENT for routes with no l10n file, throw error for anything else
throw err;
}
}
}

View file

@ -0,0 +1,72 @@
/**
* 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
*/
const 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); // eslint-disable-line no-eval
/**
* 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);
}

View file

@ -0,0 +1,23 @@
var fs = require('fs');
var glob = require('glob');
var tap = require('tap');
var TRANSLATIONS_PATTERN = './node_modules/scratch-l10n/www/**/*.json';
var files = glob.sync(TRANSLATIONS_PATTERN);
const checkJson = (data, name) => {
try {
JSON.parse(data);
} catch (e) {
tap.fail(name + ' has invalid Json.\n');
}
};
tap.test('check valid json', function (t) {
files.forEach(function (f) {
const data = fs.readFileSync(f);
checkJson(data, f);
});
t.pass();
t.end();
});

View file

@ -0,0 +1,66 @@
const tap = require('tap');
const validate = require('../../../src/lib/validate');
tap.tearDown(() => process.nextTick(process.exit));
tap.test('validate username locally', t => {
let response;
t.type(validate.validateUsernameLocally, 'function');
response = validate.validateUsernameLocally('abc');
t.deepEqual(response, {valid: true});
response = validate.validateUsernameLocally('abcdefghijklmnopqrst');
t.deepEqual(response, {valid: true});
response = validate.validateUsernameLocally('abc-def-ghi');
t.deepEqual(response, {valid: true});
response = validate.validateUsernameLocally('');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationRequired'});
response = validate.validateUsernameLocally('ab');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationUsernameMinLength'});
response = validate.validateUsernameLocally('abcdefghijklmnopqrstu');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationUsernameMaxLength'});
response = validate.validateUsernameLocally('abc def');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationUsernameRegexp'});
response = validate.validateUsernameLocally('abc!def');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationUsernameRegexp'});
response = validate.validateUsernameLocally('abc😄def');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationUsernameRegexp'});
t.end();
});
tap.test('validate password', t => {
let response;
t.type(validate.validatePassword, 'function');
response = validate.validatePassword('abcdef');
t.deepEqual(response, {valid: true});
response = validate.validatePassword('abcdefghijklmnopqrst');
t.deepEqual(response, {valid: true});
response = validate.validatePassword('passwo');
t.deepEqual(response, {valid: true});
response = validate.validatePassword('');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationRequired'});
response = validate.validatePassword('abcde');
t.deepEqual(response, {valid: false, errMsgId: 'registration.validationPasswordLength'});
response = validate.validatePassword('password');
t.deepEqual(response, {valid: false, errMsgId: 'registration.validationPasswordNotEquals'});
t.end();
});
tap.test('validate password confirm', t => {
let response;
t.type(validate.validatePasswordConfirm, 'function');
response = validate.validatePasswordConfirm('abcdef', 'abcdef');
t.deepEqual(response, {valid: true});
response = validate.validatePasswordConfirm('abcdefghijklmnopqrst', 'abcdefghijklmnopqrst');
t.deepEqual(response, {valid: true});
response = validate.validatePasswordConfirm('passwo', 'passwo');
t.deepEqual(response, {valid: true});
response = validate.validatePasswordConfirm('', '');
t.deepEqual(response, {valid: false, errMsgId: 'form.validationRequired'});
response = validate.validatePasswordConfirm('abcdef', 'abcdefg');
t.deepEqual(response, {valid: false, errMsgId: 'general.error'});
response = validate.validatePasswordConfirm('abcdef', '123456');
t.deepEqual(response, {valid: false, errMsgId: 'general.error'});
response = validate.validatePasswordConfirm('', 'abcdefg');
t.deepEqual(response, {valid: false, errMsgId: 'general.error'});
t.end();
});

View file

@ -0,0 +1,176 @@
const tap = require('tap');
const Preview = require('../../../src/redux/preview');
const initialState = Preview.getInitialState();
const reducer = Preview.previewReducer;
let state;
tap.tearDown(() => process.nextTick(process.exit));
tap.test('Reducer', t => {
t.type(reducer, 'function');
t.type(initialState, 'object');
// Reducers should return their default state when called without state
let undefinedState;
t.deepEqual(initialState, reducer(undefinedState, {type: 'fake action'}));
t.end();
});
tap.test('resetProject', t => {
state = reducer({some: 'garbage'}, Preview.resetProject());
t.deepEqual(state, initialState);
t.end();
});
tap.test('setProjectInfo', t => {
// Initial values
t.equal(initialState.projectNotAvailable, false);
t.deepEqual(initialState.projectInfo, {});
// setProjectInfo action with an `info` value sets the projectInfo
state = reducer(initialState, Preview.setProjectInfo('a value'));
t.equal(state.projectInfo, 'a value');
t.equal(state.projectNotAvailable, false);
// setProjectInfo action with null info sets projectNotAvailable to true
// and resets the project info back to default state
state = reducer(initialState, Preview.setProjectInfo(null));
t.deepEqual(state.projectInfo, initialState.projectInfo);
t.equal(state.projectNotAvailable, true);
t.end();
});
tap.test('updateProjectInfo', t => {
const info = {a: 'value a', b: 'value b'};
state = reducer({projectInfo: info}, Preview.updateProjectInfo({
b: 'new value b',
c: 'new value c'
}));
t.deepEqual(state.projectInfo, {
a: 'value a',
b: 'new value b',
c: 'new value c'
});
t.end();
});
tap.test('setComments', t => {
// Initial value
t.deepEqual(initialState.comments, []);
state = reducer(initialState, Preview.setComments([{id: 1}, {id: 2}]));
state = reducer(state, Preview.setComments([{id: 3}, {id: 4}]));
t.deepEqual(state.comments, [{id: 1}, {id: 2}, {id: 3}, {id: 4}]);
t.end();
});
const commentState = {
comments: [
{id: 'id1', visibility: 'visible'},
{id: 'id2', visibility: 'visible'},
{id: 'id3', visibility: 'visible'}
],
replies: {
id1: [
{id: 'id4', visibility: 'visible'},
{id: 'id5', visibility: 'visible'}
]
}
};
tap.test('setComments, discards duplicates', t => {
state = reducer(commentState, Preview.setComments([{id: 'id1'}]));
// Does not increase the number of comments, still 3
t.equal(state.comments.length, 3);
t.end();
});
tap.test('setCommentDeleted, top level comment', t => {
state = reducer(commentState, Preview.setCommentDeleted('id2'));
t.equal(state.comments[1].visibility, 'deleted');
t.end();
});
tap.test('setCommentDeleted, reply comment', t => {
state = reducer(commentState, Preview.setCommentDeleted('id4', 'id1'));
t.equal(state.replies.id1[0].visibility, 'deleted');
t.end();
});
tap.test('setRepliesDeleted/Restored', t => {
state = reducer(commentState, Preview.setRepliesDeleted('id1'));
t.equal(state.replies.id1[0].visibility, 'deleted');
t.equal(state.replies.id1[1].visibility, 'deleted');
state = reducer(state, Preview.setRepliesRestored('id1'));
t.equal(state.replies.id1[0].visibility, 'visible');
t.equal(state.replies.id1[1].visibility, 'visible');
t.end();
});
tap.test('setCommentReported, top level comment', t => {
state = reducer(commentState, Preview.setCommentReported('id2'));
t.equal(state.comments[1].visibility, 'reported');
t.end();
});
tap.test('setCommentReported, reply comment', t => {
state = reducer(commentState, Preview.setCommentReported('id4', 'id1'));
t.equal(state.replies.id1[0].visibility, 'reported');
t.end();
});
tap.test('addNewComment, top level comment', t => {
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}));
// Adds comment to beginning of list
t.equal(state.comments[0].id, 'new comment');
t.end();
});
tap.test('addNewComment, reply comment', t => {
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}, 'id1'));
// Adds replies to the end of the replies list
t.equal(state.replies.id1[2].id, 'new comment');
t.end();
});
tap.test('setReplies', t => {
// setReplies should append new replies
state = reducer(commentState, Preview.setReplies({
id1: {id: 'id6'}
}));
t.equal(state.replies.id1[2].id, 'id6');
t.equal(state.comments[0].moreRepliesToLoad, false);
// setReplies should ignore duplicates, do the same as above again
t.equal(state.replies.id1.length, 3);
state = reducer(state, Preview.setReplies({id1: {id: 'id6'}}));
t.equal(state.replies.id1.length, 3);
// setReplies can add replies to a comment that didn't have any
state = reducer(state, Preview.setReplies({
id2: {id: 'id7'}
}));
t.equal(state.replies.id1.length, 3);
t.equal(state.replies.id2.length, 1);
t.equal(state.replies.id2[0].id, 'id7');
t.equal(state.comments[0].moreRepliesToLoad, false);
t.equal(state.comments[1].moreRepliesToLoad, false);
// Getting 20 (COMMENT_LIMIT) replies sets moreRepliesToLoad to true
state = reducer(state, Preview.setReplies({
id3: (new Array(20)).map((_, i) => ({id: `id${i + 1}`}))
}));
t.equal(state.comments[0].moreRepliesToLoad, false);
t.equal(state.comments[1].moreRepliesToLoad, false);
t.equal(state.comments[2].moreRepliesToLoad, true);
// Getting one more reply sets moreRepliesToLoad back to false
state = reducer(state, Preview.setReplies({
id3: {id: 'id21'}
}));
t.equal(state.comments[2].moreRepliesToLoad, false);
t.end();
});

View file

@ -0,0 +1,79 @@
var defaults = require('lodash.defaults');
var fastlyConfig = require('../../bin/lib/fastly-config-methods');
var routeJson = require('../../src/routes.json');
var tap = require('tap');
var testRoutes = [
{
name: 'less-traveled',
pattern: '^/?$',
routeAlias: '/?$',
view: 'less-traveled/less-traveled',
title: 'Robert Frost Goes Here'
},
{
name: 'more-traveled',
pattern: '^/more?$',
routeAlias: '/more?$',
view: 'more-traveled/more-traveled',
title: 'Robert Frost Does Not Go Here'
}
];
var routes = routeJson.map(function (route) {
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
});
var extraAppRoutes = [
// Homepage with querystring.
// TODO: Should this be added for every route?
'/\\?',
// View html
'/[^/]*.html$'
];
tap.test('getStaticPaths', function (t) {
var staticPaths = fastlyConfig.getStaticPaths(__dirname, '../../build/*');
t.type(staticPaths, 'object');
t.end();
});
tap.test('getViewPaths', function (t) {
var viewPaths = fastlyConfig.getViewPaths(testRoutes);
t.type(viewPaths, 'object');
t.equal(viewPaths[0], '/?$');
t.equal(viewPaths[1], '/more?$');
t.end();
});
tap.test('pathsToCondition', function (t) {
var condition = fastlyConfig.pathsToCondition(['/?$', '/more?$']);
t.type(condition, 'string');
t.equal(condition, 'req.url~"^(/?$|/more?$)"');
t.end();
});
tap.test('getAppRouteCondition', function (t) {
var condition = fastlyConfig.getAppRouteCondition('../../build/*', routes, extraAppRoutes, __dirname);
t.type(condition, 'string');
t.end();
});
tap.test('testSetTTL', function (t) {
var ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
t.equal(ttl, '' +
'if (itsactuallyttyl) {\n' +
' if (req.url ~ "^(/projects/|/fragment/account-nav.json|/session/)" && ' +
'!req.http.Cookie:scratchsessionsid) {\n' +
' set beresp.http.Vary = "Accept-Encoding, Accept-Language";\n' +
' unset beresp.http.set-cookie;\n' +
' return(deliver);\n' +
' } else {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
' }\n' +
'}\n'
);
t.end();
});