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-slick": "0.16.0",
"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",
"redux": "3.5.2",
"redux-thunk": "2.0.1",

View file

@ -78,7 +78,11 @@ class Navigation extends React.Component {
return `/users/${this.props.user.username}/`;
}
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 () {
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tutorial=getStarted';

View file

@ -487,7 +487,7 @@ const PreviewPresentation = ({
</Formsy>
)}
</FormsyProjectUpdater> :
<div className="project-description last">
<div className="project-description">
{decorateText(projectInfo.description, {
usernames: true,
hashtags: true,

View file

@ -3,7 +3,8 @@
/* stage size constants */
$player-width: 482px;
$player-height: 406px;
$player-height: 362px;
$player-header: 44px;
$stage-width: 480px;
/* override view padding for share banner */
@ -129,9 +130,10 @@ $stage-width: 480px;
height: 3rem;
&.remix {
margin-right: .5em;
margin-right: .75em;
width: 2rem;
height: 2rem;
align-self: flex-start;
}
}
}
@ -392,10 +394,11 @@ $stage-width: 480px;
.project-notes {
margin-left: 1rem;
height: $player-height;
height: calc(#{$player-height} + #{$player-header} - .3125rem);
align-items: flex-start;
flex: 1;
flex-flow: column;
margin-top: .3125rem;
@media #{$medium-and-smaller} {
margin-top: .5rem;
@ -403,8 +406,12 @@ $stage-width: 480px;
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-radius: 8px;
background-color: $ui-blue-10percent;
padding: .5rem;
width: calc(100% - 1rem);
padding: .75rem;
width: calc(100% - 1.5rem);
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
@media #{$medium-and-smaller} {
flex-direction: row;
@ -427,15 +435,15 @@ $stage-width: 480px;
.credit-text {
font-size: .875rem;
flex-shrink: 1;
text-align: left;
}
.description-block {
display: flex;
width: 100%;
min-height: 0;
height: 100%;
flex-direction: column;
align-items: flex-start;
flex: 1;
}
.project-textlabel {
@ -458,19 +466,26 @@ $stage-width: 480px;
flex: 1;
}
.project-description.last {
.project-description:last-of-type {
margin-bottom: 0;
}
.project-description-form {
display: flex;
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;
}
.project-description-edit {
display: flex;
margin-bottom: .75rem;
border: 1px solid $ui-blue-10percent;
border-radius: 8px;
background-color: $ui-blue-10percent;

View file

@ -57,7 +57,9 @@ class Search extends React.Component {
}
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;
const stripQueryValue = function (queryTerm) {
@ -91,6 +93,13 @@ class Search extends React.Component {
this.handleGetSearchMore();
}
}
encodeSearchTerm () {
let termText = '';
if (this.props.searchTerm) {
termText = encodeURIComponent(this.props.searchTerm);
}
return termText;
}
getSearchState () {
let pathname = window.location.pathname.toLowerCase();
if (pathname[pathname.length - 1] === '/') {
@ -105,21 +114,24 @@ class Search extends React.Component {
}
handleChangeSortMode (name, value) {
if (ACCEPTABLE_MODES.indexOf(value) !== -1) {
const term = this.props.searchTerm.split(' ').join('+');
window.location =
`${window.location.origin}/search/${this.state.tab}?q=${term}&mode=${value}`;
const termText = this.encodeSearchTerm();
let newLocation = `${window.location.origin}/search/${this.state.tab}?mode=${value}`;
if (termText) {
newLocation += `&q=${termText}`;
}
window.location = newLocation;
}
}
handleGetSearchMore () {
let termText = '';
if (this.props.searchTerm !== '') {
termText = `&q=${encodeURIComponent(this.props.searchTerm.split(' ').join('+'))}`;
}
const termText = this.encodeSearchTerm();
const locale = this.props.intl.locale;
const loadNumber = this.state.loadNumber;
const offset = this.state.offset;
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({
uri: `/search/${this.state.tab}?${queryString}`
@ -137,9 +149,13 @@ class Search extends React.Component {
});
}
getTab (type) {
const term = this.props.searchTerm.split(' ').join('+');
const termText = this.encodeSearchTerm();
let targetUrl = `/search/${type}`;
if (termText) {
targetUrl += `?q=${termText}`;
}
let allTab = (
<a href={`/search/${type}?q=${term}/`}>
<a href={targetUrl}>
<li>
<img
className={`tab-icon ${type}`}
@ -151,7 +167,7 @@ class Search extends React.Component {
);
if (this.state.tab === type) {
allTab = (
<a href={`/search/${type}?q=${term}/`}>
<a href={targetUrl}>
<li className="active">
<img
className={`tab-icon ${type}`}

View file

@ -72,7 +72,7 @@ const Landing = props => (
<section id="sip">
<FlexRow className="educators-using">
<div className="using-scratch-image">
<img src="images/teachers/makey-activity.png" />
<img src="/images/teachers/makey-activity.png" />
</div>
<div className="sip-info">
<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 buildID = process.env.TRAVIS_BUILD_NUMBER;
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
const {By, until} = webdriver;
const {By, Key, until} = webdriver;
class SeleniumHelper {
constructor () {
bindAll(this, [
'getDriver',
'getSauceDriver',
'getKey',
'buildDriver',
'clickXpath',
'findByXpath',
@ -22,6 +23,7 @@ class SeleniumHelper {
'clickButton',
'findByCss',
'clickCss',
'urlMatches',
'getLogs'
]);
}
@ -79,6 +81,10 @@ class SeleniumHelper {
return driver;
}
getKey (keyName) {
return Key[keyName];
}
findByXpath (xpath) {
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());
}
urlMatches (regex) {
return this.driver.wait(until.urlMatches(regex), 1000 * 5);
}
getLogs (whitelist) {
return this.driver.manage()
.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 => {
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'))

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