mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-30 02:56:20 -05:00
Merge pull request #3003 from LLK/release/05-23-2019
[Master] Release/05-23-2019
This commit is contained in:
commit
df9e290126
9 changed files with 132 additions and 28 deletions
|
@ -101,7 +101,7 @@
|
||||||
"react-responsive": "3.0.0",
|
"react-responsive": "3.0.0",
|
||||||
"react-slick": "0.16.0",
|
"react-slick": "0.16.0",
|
||||||
"react-string-replace": "0.4.1",
|
"react-string-replace": "0.4.1",
|
||||||
"scratch-gui": "0.1.0-prerelease.20190520180857",
|
"scratch-gui": "0.1.0-prerelease.20190522232752",
|
||||||
"react-telephone-input": "4.3.4",
|
"react-telephone-input": "4.3.4",
|
||||||
"redux": "3.5.2",
|
"redux": "3.5.2",
|
||||||
"redux-thunk": "2.0.1",
|
"redux-thunk": "2.0.1",
|
||||||
|
|
|
@ -78,7 +78,11 @@ class Navigation extends React.Component {
|
||||||
return `/users/${this.props.user.username}/`;
|
return `/users/${this.props.user.username}/`;
|
||||||
}
|
}
|
||||||
handleSearchSubmit (formData) {
|
handleSearchSubmit (formData) {
|
||||||
window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
|
let targetUrl = '/search/projects';
|
||||||
|
if (formData.q) {
|
||||||
|
targetUrl += `?q=${encodeURIComponent(formData.q)}`;
|
||||||
|
}
|
||||||
|
window.location.href = targetUrl;
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tutorial=getStarted';
|
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tutorial=getStarted';
|
||||||
|
|
|
@ -487,7 +487,7 @@ const PreviewPresentation = ({
|
||||||
</Formsy>
|
</Formsy>
|
||||||
)}
|
)}
|
||||||
</FormsyProjectUpdater> :
|
</FormsyProjectUpdater> :
|
||||||
<div className="project-description last">
|
<div className="project-description">
|
||||||
{decorateText(projectInfo.description, {
|
{decorateText(projectInfo.description, {
|
||||||
usernames: true,
|
usernames: true,
|
||||||
hashtags: true,
|
hashtags: true,
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
/* stage size constants */
|
/* stage size constants */
|
||||||
$player-width: 482px;
|
$player-width: 482px;
|
||||||
$player-height: 406px;
|
$player-height: 362px;
|
||||||
|
$player-header: 44px;
|
||||||
$stage-width: 480px;
|
$stage-width: 480px;
|
||||||
|
|
||||||
/* override view padding for share banner */
|
/* override view padding for share banner */
|
||||||
|
@ -129,9 +130,10 @@ $stage-width: 480px;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
|
||||||
&.remix {
|
&.remix {
|
||||||
margin-right: .5em;
|
margin-right: .75em;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,10 +394,11 @@ $stage-width: 480px;
|
||||||
|
|
||||||
.project-notes {
|
.project-notes {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
height: $player-height;
|
height: calc(#{$player-height} + #{$player-header} - .3125rem);
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
margin-top: .3125rem;
|
||||||
|
|
||||||
@media #{$medium-and-smaller} {
|
@media #{$medium-and-smaller} {
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
|
@ -403,8 +406,12 @@ $stage-width: 480px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .description-block:first-child {
|
> * {
|
||||||
margin-top: 1rem;
|
margin-bottom: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .description-block:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,10 +420,11 @@ $stage-width: 480px;
|
||||||
border: 1px solid $ui-blue-10percent;
|
border: 1px solid $ui-blue-10percent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: $ui-blue-10percent;
|
background-color: $ui-blue-10percent;
|
||||||
padding: .5rem;
|
padding: .75rem;
|
||||||
width: calc(100% - 1rem);
|
width: calc(100% - 1.5rem);
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
@media #{$medium-and-smaller} {
|
@media #{$medium-and-smaller} {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -427,15 +435,15 @@ $stage-width: 480px;
|
||||||
.credit-text {
|
.credit-text {
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description-block {
|
.description-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 0;
|
height: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-textlabel {
|
.project-textlabel {
|
||||||
|
@ -458,19 +466,26 @@ $stage-width: 480px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-description.last {
|
.project-description:last-of-type {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-description-form {
|
.project-description-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
// surprisingly, without setting height: 100% here, flex-grow causes the
|
||||||
|
// height to be too large
|
||||||
|
height: 100%;
|
||||||
|
// surprisingly, just having some min-height actually reduces the height
|
||||||
|
// of the element when it is being vertically crowded by other elements
|
||||||
|
min-height: 2rem;
|
||||||
|
// necessary to cause the element to take up the maximum height
|
||||||
|
// available
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-description-edit {
|
.project-description-edit {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: .75rem;
|
|
||||||
border: 1px solid $ui-blue-10percent;
|
border: 1px solid $ui-blue-10percent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: $ui-blue-10percent;
|
background-color: $ui-blue-10percent;
|
||||||
|
|
|
@ -57,7 +57,9 @@ class Search extends React.Component {
|
||||||
|
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const query = decodeURIComponent(window.location.search);
|
// just in case there's a URL in the wild with pluses to indicate spaces,
|
||||||
|
// convert pluses to url-encoded spaces before decoding.
|
||||||
|
const query = decodeURIComponent(window.location.search.split('+').join('%20'));
|
||||||
let term = query;
|
let term = query;
|
||||||
|
|
||||||
const stripQueryValue = function (queryTerm) {
|
const stripQueryValue = function (queryTerm) {
|
||||||
|
@ -91,6 +93,13 @@ class Search extends React.Component {
|
||||||
this.handleGetSearchMore();
|
this.handleGetSearchMore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
encodeSearchTerm () {
|
||||||
|
let termText = '';
|
||||||
|
if (this.props.searchTerm) {
|
||||||
|
termText = encodeURIComponent(this.props.searchTerm);
|
||||||
|
}
|
||||||
|
return termText;
|
||||||
|
}
|
||||||
getSearchState () {
|
getSearchState () {
|
||||||
let pathname = window.location.pathname.toLowerCase();
|
let pathname = window.location.pathname.toLowerCase();
|
||||||
if (pathname[pathname.length - 1] === '/') {
|
if (pathname[pathname.length - 1] === '/') {
|
||||||
|
@ -105,21 +114,24 @@ class Search extends React.Component {
|
||||||
}
|
}
|
||||||
handleChangeSortMode (name, value) {
|
handleChangeSortMode (name, value) {
|
||||||
if (ACCEPTABLE_MODES.indexOf(value) !== -1) {
|
if (ACCEPTABLE_MODES.indexOf(value) !== -1) {
|
||||||
const term = this.props.searchTerm.split(' ').join('+');
|
const termText = this.encodeSearchTerm();
|
||||||
window.location =
|
let newLocation = `${window.location.origin}/search/${this.state.tab}?mode=${value}`;
|
||||||
`${window.location.origin}/search/${this.state.tab}?q=${term}&mode=${value}`;
|
if (termText) {
|
||||||
|
newLocation += `&q=${termText}`;
|
||||||
|
}
|
||||||
|
window.location = newLocation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleGetSearchMore () {
|
handleGetSearchMore () {
|
||||||
let termText = '';
|
const termText = this.encodeSearchTerm();
|
||||||
if (this.props.searchTerm !== '') {
|
|
||||||
termText = `&q=${encodeURIComponent(this.props.searchTerm.split(' ').join('+'))}`;
|
|
||||||
}
|
|
||||||
const locale = this.props.intl.locale;
|
const locale = this.props.intl.locale;
|
||||||
const loadNumber = this.state.loadNumber;
|
const loadNumber = this.state.loadNumber;
|
||||||
const offset = this.state.offset;
|
const offset = this.state.offset;
|
||||||
const mode = this.state.mode;
|
const mode = this.state.mode;
|
||||||
const queryString = `limit=${loadNumber}&offset=${offset}&language=${locale}&mode=${mode}${termText}`;
|
let queryString = `limit=${loadNumber}&offset=${offset}&language=${locale}&mode=${mode}`;
|
||||||
|
if (termText) {
|
||||||
|
queryString += `&q=${termText}`;
|
||||||
|
}
|
||||||
|
|
||||||
api({
|
api({
|
||||||
uri: `/search/${this.state.tab}?${queryString}`
|
uri: `/search/${this.state.tab}?${queryString}`
|
||||||
|
@ -137,9 +149,13 @@ class Search extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getTab (type) {
|
getTab (type) {
|
||||||
const term = this.props.searchTerm.split(' ').join('+');
|
const termText = this.encodeSearchTerm();
|
||||||
|
let targetUrl = `/search/${type}`;
|
||||||
|
if (termText) {
|
||||||
|
targetUrl += `?q=${termText}`;
|
||||||
|
}
|
||||||
let allTab = (
|
let allTab = (
|
||||||
<a href={`/search/${type}?q=${term}/`}>
|
<a href={targetUrl}>
|
||||||
<li>
|
<li>
|
||||||
<img
|
<img
|
||||||
className={`tab-icon ${type}`}
|
className={`tab-icon ${type}`}
|
||||||
|
@ -151,7 +167,7 @@ class Search extends React.Component {
|
||||||
);
|
);
|
||||||
if (this.state.tab === type) {
|
if (this.state.tab === type) {
|
||||||
allTab = (
|
allTab = (
|
||||||
<a href={`/search/${type}?q=${term}/`}>
|
<a href={targetUrl}>
|
||||||
<li className="active">
|
<li className="active">
|
||||||
<img
|
<img
|
||||||
className={`tab-icon ${type}`}
|
className={`tab-icon ${type}`}
|
||||||
|
|
|
@ -72,7 +72,7 @@ const Landing = props => (
|
||||||
<section id="sip">
|
<section id="sip">
|
||||||
<FlexRow className="educators-using">
|
<FlexRow className="educators-using">
|
||||||
<div className="using-scratch-image">
|
<div className="using-scratch-image">
|
||||||
<img src="images/teachers/makey-activity.png" />
|
<img src="/images/teachers/makey-activity.png" />
|
||||||
</div>
|
</div>
|
||||||
<div className="sip-info">
|
<div className="sip-info">
|
||||||
<h2><FormattedMessage id="teacherlanding.howUsingScratch" /></h2>
|
<h2><FormattedMessage id="teacherlanding.howUsingScratch" /></h2>
|
||||||
|
|
|
@ -7,13 +7,14 @@ const remote = process.env.SMOKE_REMOTE || false;
|
||||||
const ci = process.env.CI || false;
|
const ci = process.env.CI || false;
|
||||||
const buildID = process.env.TRAVIS_BUILD_NUMBER;
|
const buildID = process.env.TRAVIS_BUILD_NUMBER;
|
||||||
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
||||||
const {By, until} = webdriver;
|
const {By, Key, until} = webdriver;
|
||||||
|
|
||||||
class SeleniumHelper {
|
class SeleniumHelper {
|
||||||
constructor () {
|
constructor () {
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'getDriver',
|
'getDriver',
|
||||||
'getSauceDriver',
|
'getSauceDriver',
|
||||||
|
'getKey',
|
||||||
'buildDriver',
|
'buildDriver',
|
||||||
'clickXpath',
|
'clickXpath',
|
||||||
'findByXpath',
|
'findByXpath',
|
||||||
|
@ -22,6 +23,7 @@ class SeleniumHelper {
|
||||||
'clickButton',
|
'clickButton',
|
||||||
'findByCss',
|
'findByCss',
|
||||||
'clickCss',
|
'clickCss',
|
||||||
|
'urlMatches',
|
||||||
'getLogs'
|
'getLogs'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -79,6 +81,10 @@ class SeleniumHelper {
|
||||||
return driver;
|
return driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getKey (keyName) {
|
||||||
|
return Key[keyName];
|
||||||
|
}
|
||||||
|
|
||||||
findByXpath (xpath) {
|
findByXpath (xpath) {
|
||||||
return this.driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000));
|
return this.driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000));
|
||||||
}
|
}
|
||||||
|
@ -107,6 +113,10 @@ class SeleniumHelper {
|
||||||
return this.findByCss(css).then(el => el.click());
|
return this.findByCss(css).then(el => el.click());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
urlMatches (regex) {
|
||||||
|
return this.driver.wait(until.urlMatches(regex), 1000 * 5);
|
||||||
|
}
|
||||||
|
|
||||||
getLogs (whitelist) {
|
getLogs (whitelist) {
|
||||||
return this.driver.manage()
|
return this.driver.manage()
|
||||||
.logs()
|
.logs()
|
||||||
|
|
|
@ -101,6 +101,7 @@ test('clicking a project title should take you to the project page', t => {
|
||||||
|
|
||||||
test('Add To button should bring up a list of studios', t => {
|
test('Add To button should bring up a list of studios', t => {
|
||||||
clickXpath('//a[contains(@class, "mystuff-icon")]')
|
clickXpath('//a[contains(@class, "mystuff-icon")]')
|
||||||
|
.then(() => clickXpath('//div[@id="sidebar"]/ul/li[@data-tab="shared"]'))
|
||||||
.then(() => findByXpath('//div[@data-control="add-to"]'))
|
.then(() => findByXpath('//div[@data-control="add-to"]'))
|
||||||
.then((element) => element.getText('span'))
|
.then((element) => element.getText('span'))
|
||||||
.then((text) => t.equal(text, 'Add to', 'there should be an "Add to" button'))
|
.then((text) => t.equal(text, 'Add to', 'there should be an "Add to" button'))
|
||||||
|
|
58
test/integration/smoke-testing/test_search.js
Normal file
58
test/integration/smoke-testing/test_search.js
Normal 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 || 'http://localhost:8333';
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue