Merge pull request #3003 from LLK/release/05-23-2019

[Master] Release/05-23-2019
This commit is contained in:
Benjamin Wheeler 2019-05-23 14:47:52 -04:00 committed by GitHub
commit df9e290126
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 132 additions and 28 deletions

View file

@ -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",

View file

@ -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';

View file

@ -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,

View file

@ -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;

View file

@ -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}`}

View file

@ -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>

View file

@ -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()

View file

@ -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'))

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 || '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());
});
});
});