mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-26 17:16:11 -05:00
commit
9e0dee34af
8 changed files with 131 additions and 27 deletions
|
@ -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.20190522193014",
|
||||
"react-telephone-input": "4.3.4",
|
||||
"redux": "3.5.2",
|
||||
"redux-thunk": "2.0.1",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -487,7 +487,7 @@ const PreviewPresentation = ({
|
|||
</Formsy>
|
||||
)}
|
||||
</FormsyProjectUpdater> :
|
||||
<div className="project-description last">
|
||||
<div className="project-description">
|
||||
{decorateText(projectInfo.description, {
|
||||
usernames: true,
|
||||
hashtags: true,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}`}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'))
|
||||
|
|
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