@@ -180,12 +173,6 @@ class Download extends React.Component {
-
]}
{this.state.swfVersion === -1 ? [
diff --git a/src/views/download/l10n.json b/src/views/download/l10n.json
index 48ecdd50c..3c5144115 100644
--- a/src/views/download/l10n.json
+++ b/src/views/download/l10n.json
@@ -1,6 +1,6 @@
{
"download.title": "Scratch 2.0 Offline Editor",
- "download.intro": "You can install the Scratch 2.0 editor to work on projects without an internet connection. This version will work on Mac, Windows, and some versions of Linux (32 bit).",
+ "download.intro": "You can install the Scratch 2.0 editor to work on projects without an internet connection. This version will work on Windows and MacOS.",
"download.introMac": "
the latest version of Scratch 2.0 Offline requires Adobe AIR 20. To upgrade to Adobe AIR 20 manually, go
.",
"download.installation": "Installation",
"download.airTitle": "Adobe AIR",
@@ -8,7 +8,6 @@
"download.macOSX": "Mac OS X",
"download.macOlder": "Mac OS 10.5 & Older",
"download.windows": "Windows",
- "download.linux": "Linux",
"download.download": "Download",
"download.offlineEditorTitle": "Scratch Offline Editor",
"download.offlineEditorBody": "Next download and install the Scratch 2.0 Offline Editor",
diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx
index 79df3f93b..994dd23da 100644
--- a/src/views/preview/presentation.jsx
+++ b/src/views/preview/presentation.jsx
@@ -1,10 +1,11 @@
+const bindAll = require('lodash.bindall');
const FormattedDate = require('react-intl').FormattedDate;
const injectIntl = require('react-intl').injectIntl;
-const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const Formsy = require('formsy-react').default;
const classNames = require('classnames');
+const approx = require('approximate-number');
const GUI = require('scratch-gui').default;
const IntlGUI = injectIntl(GUI);
@@ -12,293 +13,393 @@ const IntlGUI = injectIntl(GUI);
const sessionActions = require('../../redux/session.js');
const decorateText = require('../../lib/decorate-text.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
+const Button = require('../../components/forms/button.jsx');
const Avatar = require('../../components/avatar/avatar.jsx');
const CappedNumber = require('../../components/cappednumber/cappednumber.jsx');
const ShareBanner = require('../../components/share-banner/share-banner.jsx');
const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx');
const InplaceInput = require('../../components/forms/inplace-input.jsx');
+const ReportModal = require('../../components/modal/report/modal.jsx');
+const projectShape = require('./projectshape.jsx').projectShape;
require('./preview.scss');
-const PreviewPresentation = props => {
- const {
- creditInfo,
- editable,
- faved,
- favoriteCount,
- intl,
- isFullScreen,
- loved,
- loveCount,
- projectId,
- projectInfo,
- remixes,
- sessionStatus,
- studios,
- user,
- onFavoriteClicked,
- onLoveClicked,
- onSeeInside,
- onUpdate
- // ...otherProps TBD
- } = props;
- const shareDate = (projectInfo.history && projectInfo.history.shared) ? projectInfo.history.shared : '';
- return (
-
-
-
-
- This project is not shared — so only you can see it. Click share to let everyone see it!
-
-
- Share
-
-
-
- { projectInfo && projectInfo.author && projectInfo.author.id && (
-
-
+class PreviewPresentation extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleReportClick',
+ 'handleReportClose',
+ 'handleReportSubmit'
+ ]);
+ this.state = {
+ reportOpen: false
+ };
+ }
+ handleReportClick (e) {
+ e.preventDefault();
+ this.setState({reportOpen: true});
+ }
+ handleReportClose () {
+ this.setState({reportOpen: false});
+ }
+
+ handleReportSubmit (formData, callback) {
+ const data = {
+ ...formData,
+ id: this.props.projectId,
+ username: this.props.user.username
+ };
+ console.log('submit report data', data); // eslint-disable-line no-console
+ // TODO: pass error to modal via callback.
+ callback();
+ this.setState({reportOpen: false});
+ }
+ render () {
+ const {
+ editable,
+ faved,
+ favoriteCount,
+ isFullScreen,
+ loved,
+ loveCount,
+ originalInfo,
+ parentInfo,
+ projectId,
+ projectInfo,
+ remixes,
+ sessionStatus,
+ studios,
+ user,
+ onFavoriteClicked,
+ onLoveClicked,
+ onSeeInside,
+ onUpdate
+ // ...otherProps TBD
+ } = this.props;
+ const shareDate = (projectInfo.history && projectInfo.history.shared) ? projectInfo.history.shared : '';
+ return (
+
+ {projectInfo.history && shareDate === '' &&
+
-
-
-
- {editable ?
-
-
:
-
{projectInfo.title}
- }
- {`${intl.formatMessage({id: 'thumbnail.by'})} `}
+
+ This project is not shared — so only you can see it. Click share to let everyone see it!
+
+
+ Share
+
+
+
+ }
+
+ { projectInfo && projectInfo.author && projectInfo.author.id && (
+
+
+
+
- {projectInfo.author.username}
+
+
+ {editable ?
+
+
:
+
{projectInfo.title}
+ }
+
+
+
+ {sessionStatus === sessionActions.Status.FETCHED &&
+ Object.keys(user).length > 0 &&
+ user.id !== projectInfo.author.id &&
+
+ Remix
+
+ }
+
+ See Inside
+
-
- {sessionStatus === sessionActions.Status.FETCHED &&
- Object.keys(user).length > 0 &&
- user.id !== projectInfo.author.id &&
-
- Remix
-
- }
-
- See Inside
-
-
-
-
-
-
- {shareDate && (
+
+
+
+
+
+ {parentInfo && parentInfo.author && parentInfo.id && (
+
+
+
+
+ )}
+ {originalInfo && originalInfo.author && originalInfo.id && (
+
+
+
+
+ )}
+ {/* eslint-disable max-len */}
+
+
+ Instructions
+
+ {editable ?
+ :
+
+ {decorateText(projectInfo.instructions)}
+
+ }
+
+
+
+ Notes and Credits
+
+ {editable ?
+ :
+
+ {decorateText(projectInfo.description)}
+
+ }
+
+ {/* eslint-enable max-len */}
+
+
+
+
+
+ {approx(loveCount, {decimal: false})}
+
+
+ {approx(favoriteCount, {decimal: false})}
+
+
+ {approx(projectInfo.stats.remixes, {decimal: false})}
+
+
+
+
+
+
©
{' '}
{/* eslint-disable react/jsx-sort-props */}
-
+ {shareDate === null ?
+ 'Unshared' :
+
+ }
{/* eslint-enable react/jsx-sort-props */}
- )}
- {creditInfo && creditInfo.author && creditInfo.id && (
-
-
-
+
+
+ Add to Studio
+
+
+ Copy Link
+
+ {
+ sessionStatus === sessionActions.Status.FETCHED &&
+ Object.keys(user).length > 0 &&
+ user.id !== projectInfo.author.id && [
+
+ Report
+ ,
+
+ ]
+ }
- )
- }
- {editable ?
- :
-
- {decorateText(projectInfo.description)}
-
- }
-
-
-
-
-
- {loveCount}
-
-
- {favoriteCount}
-
-
- {projectInfo.remix.count}
-
-
-
-
-
-
-
-
- Add to Studio
-
-
-
-
- Social
-
-
-
-
- Report
-
-
-
-
-
-
-
-
-
- Remixes
-
- {remixes && remixes.length === 0 ? (
- No remixes
- ) : (
-
- )}
-
+
+
+
- Studios
+ Comments go here
- {studios && studios.length === 0 ? (
-
No studios
- ) : (
-
+
+
+ {/* hide remixes if there aren't any */}
+ {remixes && remixes.length !== 0 && (
+
+
+ Remixes
+
+ {remixes && remixes.length === 0 ? (
+ // TODO: style remix invitation
+ Invite user to remix
+ ) : (
+
+ )}
+
+ )}
+ {/* hide studios if there aren't any */}
+ {studios && studios.length !== 0 && (
+
+
+ Studios
+
+ {studios && studios.length === 0 ? (
+ // TODO: invite user to add to studio?
+ None
+ ) : (
+
+ )}
+
)}
-
-
-
- )}
-
-
- );
-};
+
+
+ )}
+
+
+ );
+ }
+}
PreviewPresentation.propTypes = {
- creditInfo: PropTypes.shape({
- id: PropTypes.number,
- title: PropTypes.string,
- description: PropTypes.string,
- author: PropTypes.shape({id: PropTypes.number}),
- history: PropTypes.shape({
- created: PropTypes.string,
- modified: PropTypes.string,
- shared: PropTypes.string
- }),
- stats: PropTypes.shape({
- views: PropTypes.number,
- loves: PropTypes.number,
- favorites: PropTypes.number
- }),
- remix: PropTypes.shape({
- parent: PropTypes.number,
- root: PropTypes.number
- })
- }),
editable: PropTypes.bool,
faved: PropTypes.bool,
favoriteCount: PropTypes.number,
- intl: intlShape,
isFullScreen: PropTypes.bool,
loveCount: PropTypes.number,
loved: PropTypes.bool,
@@ -306,27 +407,10 @@ PreviewPresentation.propTypes = {
onLoveClicked: PropTypes.func,
onSeeInside: PropTypes.func,
onUpdate: PropTypes.func,
- projectId: PropTypes.number,
- projectInfo: PropTypes.shape({
- id: PropTypes.number,
- title: PropTypes.string,
- description: PropTypes.string,
- author: PropTypes.shape({id: PropTypes.number}),
- history: PropTypes.shape({
- created: PropTypes.string,
- modified: PropTypes.string,
- shared: PropTypes.string
- }),
- stats: PropTypes.shape({
- views: PropTypes.number,
- loves: PropTypes.number,
- favorites: PropTypes.number
- }),
- remix: PropTypes.shape({
- parent: PropTypes.number,
- root: PropTypes.number
- })
- }),
+ originalInfo: projectShape,
+ parentInfo: projectShape,
+ projectId: PropTypes.string,
+ projectInfo: projectShape,
remixes: PropTypes.arrayOf(PropTypes.object),
sessionStatus: PropTypes.string.isRequired,
studios: PropTypes.arrayOf(PropTypes.object),
diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx
index fb40c2cff..c45f92646 100644
--- a/src/views/preview/preview.jsx
+++ b/src/views/preview/preview.jsx
@@ -7,24 +7,30 @@ const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
const PreviewPresentation = require('./presentation.jsx');
+const projectShape = require('./projectshape.jsx').projectShape;
const sessionActions = require('../../redux/session.js');
const previewActions = require('../../redux/preview.js');
-const GUI = require('scratch-gui').default;
-const IntlGUI = injectIntl(GUI);
+
+const GUI = require('scratch-gui');
+const IntlGUI = injectIntl(GUI.default);
class Preview extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
+ 'addEventListeners',
'handleFavoriteToggle',
'handleLoveToggle',
'handlePermissions',
+ 'handlePopState',
'handleSeeInside',
'handleUpdate',
- 'initCounts'
+ 'initCounts',
+ 'pushHistory'
]);
this.state = this.initState();
+ this.addEventListeners();
}
componentDidUpdate (prevProps) {
if (this.props.sessionStatus !== prevProps.sessionStatus &&
@@ -48,10 +54,21 @@ class Preview extends React.Component {
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
this.handlePermissions();
- if (this.props.projectInfo.remix.root !== null) {
- this.props.getCreditInfo(this.props.projectInfo.remix.root);
+ if (this.props.projectInfo.remix.parent !== null) {
+ this.props.getParentInfo(this.props.projectInfo.remix.parent);
+ }
+ if (this.props.projectInfo.remix.root !== null &&
+ this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent
+ ) {
+ this.props.getOriginalInfo(this.props.projectInfo.remix.root);
}
}
+ if (this.props.playerMode !== prevProps.playerMode || this.props.fullScreen !== prevProps.fullScreen) {
+ this.pushHistory(history.state === null);
+ }
+ }
+ componentWillUnmount () {
+ this.removeEventListeners();
}
initState () {
const pathname = window.location.pathname.toLowerCase();
@@ -62,12 +79,49 @@ class Preview extends React.Component {
return {
editable: false,
favoriteCount: 0,
- inEditor: parts.indexOf('editor') !== -1,
- isFullScreen: parts.indexOf('fullscreen') !== -1,
loveCount: 0,
- projectId: parts[1] === 'editor' ? null : parts[1]
+ projectId: parts[1] === 'editor' ? 0 : parts[1]
};
}
+ addEventListeners () {
+ window.addEventListener('popstate', this.handlePopState);
+ }
+ removeEventListeners () {
+ window.removeEventListener('popstate', this.handlePopState);
+ }
+ handlePopState () {
+ const path = window.location.pathname.toLowerCase();
+ const playerMode = path.indexOf('editor') === -1;
+ const fullScreen = path.indexOf('fullscreen') !== -1;
+ if (this.props.playerMode !== playerMode) {
+ this.props.setPlayer(playerMode);
+ }
+ if (this.props.fullScreen !== fullScreen) {
+ this.props.setFullScreen(fullScreen);
+ }
+ }
+ pushHistory (push) {
+ // update URI to match mode
+ const idPath = this.state.projectId ? `${this.state.projectId}/` : '';
+ let modePath = '';
+ if (!this.props.playerMode) modePath = 'editor/';
+ // fullscreen overrides editor
+ if (this.props.fullScreen) modePath = 'fullscreen/';
+ const newPath = `/preview/${idPath}${modePath}`;
+ if (push) {
+ history.pushState(
+ {},
+ document.title,
+ newPath
+ );
+ } else {
+ history.replaceState(
+ {},
+ document.title,
+ newPath
+ );
+ }
+ }
handleFavoriteToggle () {
this.props.setFavedStatus(
!this.props.faved,
@@ -109,8 +163,7 @@ class Preview extends React.Component {
}
}
handleSeeInside () {
- this.setState({inEditor: true});
- history.pushState({}, document.title, `/preview/${this.state.projectId}/editor`);
+ this.props.setPlayer(false);
}
handleUpdate (jsonData) {
this.props.updateProject(
@@ -128,23 +181,18 @@ class Preview extends React.Component {
}
render () {
return (
- this.state.inEditor ?
-
:
+ this.props.playerMode ?
-
+ :
+
);
}
}
Preview.propTypes = {
comments: PropTypes.arrayOf(PropTypes.object),
- credit: PropTypes.shape({
- id: PropTypes.number,
- title: PropTypes.string,
- description: PropTypes.string,
- author: PropTypes.shape({
- id: PropTypes.number
- }),
- history: PropTypes.shape({
- created: PropTypes.string,
- modified: PropTypes.string,
- shared: PropTypes.string
- }),
- stats: PropTypes.shape({
- views: PropTypes.number,
- loves: PropTypes.number,
- favorites: PropTypes.number
- }),
- remix: PropTypes.shape({
- parent: PropTypes.number,
- root: PropTypes.number
- })
- }),
faved: PropTypes.bool,
- getCreditInfo: PropTypes.func.isRequired,
+ fullScreen: PropTypes.bool,
getFavedStatus: PropTypes.func.isRequired,
getLovedStatus: PropTypes.func.isRequired,
+ getOriginalInfo: PropTypes.func.isRequired,
+ getParentInfo: PropTypes.func.isRequired,
getProjectInfo: PropTypes.func.isRequired,
getRemixes: PropTypes.func.isRequired,
getStudios: PropTypes.func.isRequired,
loved: PropTypes.bool,
- projectInfo: PropTypes.shape({
- author: PropTypes.shape({
- id: PropTypes.number,
- username: PropTypes.string
- }),
- description: PropTypes.string,
- history: PropTypes.shape({
- created: PropTypes.string,
- modified: PropTypes.string,
- shared: PropTypes.string
- }),
- id: PropTypes.number,
- remix: PropTypes.shape({
- parent: PropTypes.number,
- root: PropTypes.number
- }),
- stats: PropTypes.shape({
- views: PropTypes.number,
- loves: PropTypes.number,
- favorites: PropTypes.number
- }),
- title: PropTypes.string
- }),
+ original: projectShape,
+ parent: projectShape,
+ playerMode: PropTypes.bool,
+ projectInfo: projectShape,
remixes: PropTypes.arrayOf(PropTypes.object),
sessionStatus: PropTypes.string,
setFavedStatus: PropTypes.func.isRequired,
+ setFullScreen: PropTypes.func.isRequired,
setLovedStatus: PropTypes.func.isRequired,
+ setPlayer: PropTypes.func.isRequired,
studios: PropTypes.arrayOf(PropTypes.object),
updateProject: PropTypes.func.isRequired,
user: PropTypes.shape({
@@ -241,20 +258,26 @@ Preview.defaultProps = {
const mapStateToProps = state => ({
projectInfo: state.preview.projectInfo,
- credit: state.preview.credit,
comments: state.preview.comments,
faved: state.preview.faved,
loved: state.preview.loved,
+ original: state.preview.original,
+ parent: state.preview.parent,
remixes: state.preview.remixes,
sessionStatus: state.session.status,
studios: state.preview.studios,
- user: state.session.session.user
+ user: state.session.session.user,
+ playerMode: state.scratchGui.mode.isPlayerOnly,
+ fullScreen: state.scratchGui.mode.isFullScreen
});
const mapDispatchToProps = dispatch => ({
- getCreditInfo: id => {
- dispatch(previewActions.getCreditInfo(id));
+ getOriginalInfo: id => {
+ dispatch(previewActions.getOriginalInfo(id));
+ },
+ getParentInfo: id => {
+ dispatch(previewActions.getParentInfo(id));
},
getProjectInfo: (id, token) => {
dispatch(previewActions.getProjectInfo(id, token));
@@ -269,7 +292,7 @@ const mapDispatchToProps = dispatch => ({
dispatch(previewActions.getFavedStatus(id, username, token));
},
setFavedStatus: (faved, id, username, token) => {
- dispatch(previewActions.setLovedStatus(faved, id, username, token));
+ dispatch(previewActions.setFavedStatus(faved, id, username, token));
},
getLovedStatus: (id, username, token) => {
dispatch(previewActions.getLovedStatus(id, username, token));
@@ -280,11 +303,20 @@ const mapDispatchToProps = dispatch => ({
refreshSession: () => {
dispatch(sessionActions.refreshSession());
},
- setCreditInfo: info => {
- dispatch(previewActions.setCreditInfo(info));
+ setOriginalInfo: info => {
+ dispatch(previewActions.setOriginalInfo(info));
+ },
+ setParentInfo: info => {
+ dispatch(previewActions.setParentInfo(info));
},
updateProject: (id, formData, username, token) => {
dispatch(previewActions.updateProject(id, formData, username, token));
+ },
+ setPlayer: player => {
+ dispatch(GUI.setPlayer(player));
+ },
+ setFullScreen: fullscreen => {
+ dispatch(GUI.setFullScreen(fullscreen));
}
});
@@ -293,8 +325,29 @@ const ConnectedPreview = connect(
mapDispatchToProps
)(Preview);
+GUI.setAppElement(document.getElementById('app'));
+const initGuiState = guiInitialState => {
+ const pathname = window.location.pathname.toLowerCase();
+ const parts = pathname.split('/').filter(Boolean);
+ // parts[0]: 'preview'
+ // parts[1]: either :id or 'editor'
+ // parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
+ if (parts.indexOf('editor') === -1) {
+ guiInitialState = GUI.initPlayer(guiInitialState);
+ }
+ if (parts.indexOf('fullscreen') !== -1) {
+ guiInitialState = GUI.initFullScreen(guiInitialState);
+ }
+ return guiInitialState;
+};
+
render(
,
document.getElementById('app'),
- {preview: previewActions.previewReducer}
+ {
+ preview: previewActions.previewReducer,
+ ...GUI.guiReducers
+ },
+ {scratchGui: initGuiState(GUI.guiInitialState)},
+ GUI.guiMiddleware
);
diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss
index 921749f8d..c276448e3 100644
--- a/src/views/preview/preview.scss
+++ b/src/views/preview/preview.scss
@@ -1,6 +1,22 @@
@import "../../colors";
@import "../../frameless";
+/* stage size contants
+ * this is a hack right now - stage includes padding of .5rem (8px) for alignment in gui
+ * in www the player is placed with margin -.5rem to align the edge.
+ * the height is calculated from the actual height on the page (404)
+*/
+$gui-width: 496px;
+$stage-width: 480px;
+$stage-height: 404px;
+
+
+// remix credit height: 52px
+// project text label line-height + margin-bottom .5rem: 19px + 8px = 27px
+// Formsy wrapper adds 3px to the input height for
+$description-input: 166px; // $stage-height / 2 - $project-label - $wrapper - margin
+$description-input-small: 120px; // normal $description-input - $remix-credit
+
/* override view padding for share banner */
#view {
padding: 0 0 20px 0;
@@ -15,8 +31,6 @@
height: 100%;
}
-// .gui * { box-sizing: border-box; }
-
.preview {
.project-title {
@@ -31,16 +45,29 @@
}
}
+ .project-header {
+ margin-right: 2rem;
+ flex-grow: 1;
+ justify-content: flex-start;
+ align-items: flex-start;
+
+ .inplace-input {
+ height: calc(3rem - 4px);
+ }
+ }
+
img {
&.avatar {
border: 0;
border-radius: 5px;
width: 3rem;
+ height: 3rem;
&.remix {
margin-right: .5em;
- width: 1.5rem;
+ width: 2rem;
+ height: 2rem;
}
}
}
@@ -49,6 +76,7 @@
margin-left: 1rem;
text-align: left;
font-size: .8rem;
+ flex-grow: 1;
}
.validation-message {
@@ -113,6 +141,7 @@
.share-button,
.remix-button,
.see-inside-button {
+ margin-top: 0;
font-size: .875rem;
font-weight: normal;
@@ -122,8 +151,8 @@
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
- width: 1.5rem;
- height: 1.5rem;
+ width: 1.25rem;
+ height: 1.25rem;
vertical-align: middle;
content: "";
}
@@ -137,7 +166,7 @@
background-color: $ui-orange;
&:before {
- background-image: url("/svgs/favorite/favorite_type-gray.svg");
+ background-image: url("/svgs/project/share-white.svg");
}
}
@@ -145,14 +174,14 @@
background-color: $ui-green;
&:before {
- background-image: url("/svgs/remix/remix_type-gray.svg");
+ background-image: url("/svgs/project/remix-white.svg");
}
}
.see-inside-button {
&:before {
- background-image: url("/images/emoji/cool-cat.png");
+ background-image: url("/svgs/project/see-inside-white.svg");
}
}
@@ -164,19 +193,31 @@
.guiPlayer {
display: inline-block;
- width: 480px;
+ margin-left: -.5rem;
+ width: $gui-width;
}
.project-notes {
- width: 45%;
- height: 404px;
+ // not 1.5rem because of stage padding
+ margin-left: 1rem;
+ height: $stage-height;
+ align-items: flex-start;
+ flex: 1;
flex-flow: column;
- align-items: left;
}
.share-date {
- height: 2.5rem;
+ margin-right: .75rem;
vertical-align: middle;
+ line-height: 2rem;
+ color: $type-gray;
+ font-size: .875rem;
+ }
+
+ .subactions {
+ margin-left: 1.5rem;
+ justify-content: flex-end;
+ flex: 1;
}
.remix-credit {
@@ -185,7 +226,7 @@
border-radius: 8px;
background-color: $ui-blue-10percent;
padding: .5rem;
- width: 100%;
+ width: calc(100% - 1rem);
flex-wrap: nowrap;
align-items: flex-start;
}
@@ -194,22 +235,56 @@
font-size: .875rem;
flex-shrink: 1;
}
+
+ .description-block {
+ width: 100%;
+ flex-direction: column;
+ align-items: flex-start;
+ flex-grow: 1;
+ }
+
+ .project-textlabel {
+ margin: 0 0 .5rem 0;
+ font-size: 1rem;
+ font-weight: bold;
+ }
.project-description {
+ margin-bottom: .75rem;
+ border: 1px solid $ui-blue-10percent;
+ border-radius: 8px;
+ background-color: $ui-blue-10percent;
+ padding: .5rem;
+ width: calc(100% - (1rem + 2px));
+ white-space: pre-line;
+ font-size: 1rem;
+ // flex-grow
+ flex: 1;
+ }
+
+ .project-description.last {
+ margin-bottom: 0;
+ }
+
+ .project-description-edit {
+ margin-bottom: .75rem;
border: 1px solid $ui-blue-10percent;
border-radius: 8px;
background-color: $ui-blue-10percent;
padding: .5rem;
width: 100%;
white-space: pre-line;
- overflow-y: scroll;
+ // flex-grow
flex: 1;
+ &.last {
+ margin-bottom: 0;
+ }
+
&.textarea-row {
border: 0;
background-color: inherit;
padding: 0;
- overflow: visible;
}
&.has-error {
@@ -217,6 +292,14 @@
transform: translate(26rem, 0);
}
}
+
+ .inplace-textarea {
+ height: $description-input;
+ }
+ }
+
+ .project-description-edit.remixes .inplace-textarea {
+ height: $description-input-small;
}
.copyleft {
@@ -227,7 +310,7 @@
}
.stats {
- width: 480px;
+ line-height: 2rem;
justify-content: flex-start;
}
@@ -238,17 +321,18 @@
display: inline;
padding-right: 2rem;
- font-size: 1.25rem;
+ font-size: 1rem;
+ font-weight: bold;
&:before {
display: inline-block;
- margin-right: .1rem;
+ margin-right: .5rem;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
width: 1.5rem;
height: 1.5rem;
- vertical-align: text-bottom;
+ vertical-align: -.25rem;
content: "";
}
}
@@ -258,14 +342,16 @@
cursor: pointer;
&:before {
- background-image: url("/svgs/love/love_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/love-gray.svg");
}
}
.project-loves.loved {
&:before {
- background-image: url("/svgs/messages/love.svg");
+ opacity: 1;
+ background-image: url("/svgs/project/love-red.svg");
}
}
@@ -274,85 +360,122 @@
cursor: pointer;
&:before {
- background-image: url("/svgs/favorite/favorite_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/fav-gray.svg");
}
}
.project-favorites.favorited {
&:before {
- background-image: url("/svgs/messages/favorite.svg");
+ opacity: 1;
+ background-image: url("/svgs/project/fav-yellow.svg");
}
}
.project-remixes {
&:before {
- background-image: url("/svgs/remix/remix_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/remix-gray.svg");
}
}
.project-views {
&:before {
- background-image: url("/svgs/view/view_type-gray.svg");
+ opacity: .5;
+ background-image: url("/svgs/project/views-gray.svg");
}
}
.action-buttons {
display: flex;
- width: 40%;
color: $type-white;
font-size: .8rem;
font-weight: 500;
justify-content: flex-end;
flex-wrap: wrap;
-
+ }
- li {
+ .action-button {
+ margin: 0 0 0 .5rem;
+ border-radius: 19px;
+ background-color: $ui-blue;
+ padding: 0 .75rem;
+ height: 2rem;
+ text-decoration: none;
+ line-height: .875rem;
+ font-size: .75rem;
+ font-weight: normal;
+
+ // &:hover {
+ // transition: background-color .25s ease;
+ // border-color: transparent;
+ // background-color: $active-gray;
+ // }
+ //
+ // &:active {
+ // border: 0 solid transparent;
+ // box-shadow: inset 0 0 5px $box-shadow-gray;
+ // background-color: $active-dark-gray;
+ // padding: calc(.75em + 1px) calc(1.5em + 1px);
+ // }
+ //
+ // &.report {
+ // border: 1px solid $ui-coral;
+ // background-color: $ui-coral;
+ //
+ // &:hover {
+ // transition: background-color .25s ease;
+ // border-color: transparent;
+ // background-color: $active-gray;
+ // }
+ //
+ // &:active {
+ // border: 0 solid transparent;
+ // box-shadow: inset 0 0 5px $box-shadow-gray;
+ // background-color: $active-dark-gray;
+ // padding: calc(.75em + 1px) calc(1.5em + 1px);
+ // }
+ // }
+ }
+
+ .studio-button,
+ .copy-link-button,
+ .report-button {
+ &:before {
display: inline-block;
- margin: 0 5px;
- border: 1px solid $ui-blue;
- border-radius: 50px;
- background-color: $ui-blue;
- padding: .5em .75em .5em 1.5em;
- text-decoration: none;
- color: $type-white;
- list-style-type: none;
-
- &:hover {
- transition: background-color .25s ease;
- border-color: transparent;
- background-color: $active-gray;
- }
-
- &:active {
- border: 0 solid transparent;
- box-shadow: inset 0 0 5px $box-shadow-gray;
- background-color: $active-dark-gray;
- padding: calc(.75em + 1px) calc(1.5em + 1px);
- }
-
- &.report {
- border: 1px solid $ui-coral;
- background-color: $ui-coral;
-
- &:hover {
- transition: background-color .25s ease;
- border-color: transparent;
- background-color: $active-gray;
- }
-
- &:active {
- border: 0 solid transparent;
- box-shadow: inset 0 0 5px $box-shadow-gray;
- background-color: $active-dark-gray;
- padding: calc(.75em + 1px) calc(1.5em + 1px);
- }
- }
+ margin-right: .25rem;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: contain;
+ width: .875rem;
+ height: .875rem;
+ vertical-align: bottom;
+ content: "";
+ }
+ }
+ .studio-button {
+ &:before {
+ background-image: url("/svgs/project/studio-add-white.svg");
}
}
+ .copy-link-button {
+ &:before {
+ background-image: url("/svgs/project/copy-link-white.svg");
+ }
+ }
+
+ .report-button {
+ background-color: $ui-coral;
+
+ &:before {
+ background-image: url("/svgs/project/report-white.svg");
+ }
+ }
+
.remix-list,
.studio-list {
flex-direction: column;
@@ -372,3 +495,8 @@
}
}
}
+
+.report-text textarea {
+ // override min-height from default settings (for teacher registration)
+ min-height: 8rem;
+}
diff --git a/src/views/preview/projectshape.jsx b/src/views/preview/projectshape.jsx
new file mode 100644
index 000000000..d2dfeda4a
--- /dev/null
+++ b/src/views/preview/projectshape.jsx
@@ -0,0 +1,35 @@
+const PropTypes = require('prop-types');
+
+const {
+ number,
+ string,
+ shape
+} = PropTypes;
+
+export const projectShape = shape({
+ id: number,
+ instructions: string,
+ title: string,
+ description: string,
+ author: shape({
+ id: number,
+ username: string
+ }),
+ image: string,
+ history: shape({
+ created: string,
+ modified: string,
+ shared: string
+ }),
+ stats: shape({
+ views: number,
+ loves: number,
+ favorites: number,
+ comments: number,
+ remixes: number
+ }),
+ remix: shape({
+ parent: number,
+ root: number
+ })
+});
diff --git a/static/svgs/project/copy-link-gray.svg b/static/svgs/project/copy-link-gray.svg
new file mode 100644
index 000000000..6e4571720
--- /dev/null
+++ b/static/svgs/project/copy-link-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ copy-link-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/copy-link-white.svg b/static/svgs/project/copy-link-white.svg
new file mode 100644
index 000000000..b175b51d6
--- /dev/null
+++ b/static/svgs/project/copy-link-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ copy-link-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/delete-gray.svg b/static/svgs/project/delete-gray.svg
new file mode 100644
index 000000000..a13aef811
--- /dev/null
+++ b/static/svgs/project/delete-gray.svg
@@ -0,0 +1,17 @@
+
+
+
+ delete-gray
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/fav-gray.svg b/static/svgs/project/fav-gray.svg
new file mode 100644
index 000000000..40fff7b6d
--- /dev/null
+++ b/static/svgs/project/fav-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ fav-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/fav-yellow.svg b/static/svgs/project/fav-yellow.svg
new file mode 100644
index 000000000..32c1c25d0
--- /dev/null
+++ b/static/svgs/project/fav-yellow.svg
@@ -0,0 +1,10 @@
+
+
+
+ fav-yellow
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/love-gray.svg b/static/svgs/project/love-gray.svg
new file mode 100644
index 000000000..fe6a0ec3d
--- /dev/null
+++ b/static/svgs/project/love-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ love-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/love-red.svg b/static/svgs/project/love-red.svg
new file mode 100644
index 000000000..c1e9457cf
--- /dev/null
+++ b/static/svgs/project/love-red.svg
@@ -0,0 +1,10 @@
+
+
+
+ love-red
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/remix-gray.svg b/static/svgs/project/remix-gray.svg
new file mode 100644
index 000000000..0701630be
--- /dev/null
+++ b/static/svgs/project/remix-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ remix-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/remix-white.svg b/static/svgs/project/remix-white.svg
new file mode 100644
index 000000000..a82c1bf6d
--- /dev/null
+++ b/static/svgs/project/remix-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ remix-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/report-gray.svg b/static/svgs/project/report-gray.svg
new file mode 100644
index 000000000..3e16a28d3
--- /dev/null
+++ b/static/svgs/project/report-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ report-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/report-white.svg b/static/svgs/project/report-white.svg
new file mode 100644
index 000000000..e3ac6b7bb
--- /dev/null
+++ b/static/svgs/project/report-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ report-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/restore-gray.svg b/static/svgs/project/restore-gray.svg
new file mode 100644
index 000000000..724a2aae1
--- /dev/null
+++ b/static/svgs/project/restore-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ restore-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/revised-date-gray.svg b/static/svgs/project/revised-date-gray.svg
new file mode 100644
index 000000000..6f9decfeb
--- /dev/null
+++ b/static/svgs/project/revised-date-gray.svg
@@ -0,0 +1,13 @@
+
+
+
+ revised-date-gray
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/scripts-gray.svg b/static/svgs/project/scripts-gray.svg
new file mode 100644
index 000000000..92551136a
--- /dev/null
+++ b/static/svgs/project/scripts-gray.svg
@@ -0,0 +1,10 @@
+
+
+
+ scripts-gray
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/see-inside-white.svg b/static/svgs/project/see-inside-white.svg
new file mode 100644
index 000000000..104cbd330
--- /dev/null
+++ b/static/svgs/project/see-inside-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ see-inside-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/share-white.svg b/static/svgs/project/share-white.svg
new file mode 100644
index 000000000..f1ae61a59
--- /dev/null
+++ b/static/svgs/project/share-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ share-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/studio-add-white.svg b/static/svgs/project/studio-add-white.svg
new file mode 100644
index 000000000..35142d5c9
--- /dev/null
+++ b/static/svgs/project/studio-add-white.svg
@@ -0,0 +1,10 @@
+
+
+
+ studio-add-white
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/static/svgs/project/views-gray.svg b/static/svgs/project/views-gray.svg
new file mode 100644
index 000000000..e159580e4
--- /dev/null
+++ b/static/svgs/project/views-gray.svg
@@ -0,0 +1,13 @@
+
+
+
+ views-gray
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/integration/package.json b/test/integration/package.json
index 06fc0f859..909e95e91 100644
--- a/test/integration/package.json
+++ b/test/integration/package.json
@@ -1,6 +1,6 @@
{
- "devDependencies": {
- "selenium-webdriver": "2.45.0",
- "chromedriver": "2.33.0"
+ "dependencies": {
+ "selenium-webdriver": "3.6.0",
+ "chromedriver": "2.37.0"
}
}
diff --git a/test/helpers/selenium-helpers.js b/test/integration/selenium-helpers.js
similarity index 70%
rename from test/helpers/selenium-helpers.js
rename to test/integration/selenium-helpers.js
index 2b55e2a4d..3097ba75c 100644
--- a/test/helpers/selenium-helpers.js
+++ b/test/integration/selenium-helpers.js
@@ -1,8 +1,24 @@
-const webdriver = require('selenium-webdriver');
+var webdriver = require('selenium-webdriver');
-const driver = new webdriver.Builder()
- .forBrowser('chrome')
- .build();
+const headless = process.env.SMOKE_HEADLESS || false;
+
+const getDriver = function () {
+ 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});
+ const newDriver = new webdriver.Builder()
+ .forBrowser('chrome')
+ .withCapabilities(chromeCapabilities)
+ .build();
+ return newDriver;
+};
+
+const driver = getDriver();
const {By, until} = webdriver;
@@ -30,6 +46,10 @@ const findByCss = (css) => {
return driver.wait(until.elementLocated(By.css(css), 1000 * 5));
};
+const clickCss = (css) => {
+ return findByCss(css).then(el => el.click());
+};
+
const getLogs = (whitelist) => {
return driver.manage()
.logs()
@@ -65,5 +85,7 @@ module.exports = {
findText,
clickButton,
findByCss,
- getLogs
+ clickCss,
+ getLogs,
+ getDriver
};
diff --git a/test/integration/smoke-testing/test-login-failures.js b/test/integration/smoke-testing/test-login-failures.js
new file mode 100644
index 000000000..fca6c9c01
--- /dev/null
+++ b/test/integration/smoke-testing/test-login-failures.js
@@ -0,0 +1,84 @@
+const {
+ driver,
+ findByCss,
+ clickCss,
+ until
+} = require('../selenium-helpers.js');
+
+var username = process.env.SMOKE_USERNAME;
+var password = process.env.SMOKE_PASSWORD;
+
+
+var tap = require('tap');
+const test = tap.test;
+
+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());
+});
diff --git a/test/integration/smoke-testing/test_signing_in_and_my_stuff.js b/test/integration/smoke-testing/test-my-stuff.js
similarity index 54%
rename from test/integration/smoke-testing/test_signing_in_and_my_stuff.js
rename to test/integration/smoke-testing/test-my-stuff.js
index 68bed40b5..d6ea50285 100644
--- a/test/integration/smoke-testing/test_signing_in_and_my_stuff.js
+++ b/test/integration/smoke-testing/test-my-stuff.js
@@ -10,10 +10,8 @@ const {
findByXpath,
clickXpath,
clickButton,
- driver,
- until,
- By
-} = require('../../helpers/selenium-helpers.js');
+ driver
+} = require('../selenium-helpers.js');
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
@@ -23,9 +21,9 @@ var tap = require('tap');
const test = tap.test;
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
-var url = rootUrl + '/users/anyuser';
+var url = rootUrl + '/users/' + username;
-tap.plan(12);
+tap.plan(7);
tap.tearDown(function () {
driver.quit();
@@ -35,108 +33,6 @@ tap.beforeEach(function () {
return driver.get(url);
});
-/*
- * This test fails sometimes because blank username eventually
- * triggers the captcha page, which is a bug:
- * https://github.com/LLK/scratchr2/issues/4762
- */
-test('Trying to sign in with no username and no password using scratchr2 navbar', t => {
- clickText('Sign in')
- .then(() => clickButton('Sign in'))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/button[@type="submit"]')))
- )
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/div[@class="error"]')))
- )
- .then(() => findByXpath('//form/div[@class="error"]'))
- .then(element => element.getText())
- .then(text => t.match(text, 'This field is required.',
- '"This field is required" error should be displayed'))
- .then(() => t.end());
-});
-
-/*
- * This test fails sometimes because blank username eventually
- * triggers the captcha page, which is a bug:
- * https://github.com/LLK/scratchr2/issues/4762
- */
-test('Trying to sign in with no username using scratchr2 navbar', t => {
- clickText('Sign in')
- .then(() => findByXpath('//input[@name="password"]'))
- .then((element) => element.sendKeys(password))
- .then(() => clickButton('Sign in'))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/button[@type="submit"]')))
- )
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/div[@class="error"]')))
- )
- .then(() => findByXpath('//form/div[@class="error"]'))
- .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 no password using scratchr2 navbar', t => {
- var nonsenseusername = Math.random().toString(36)
- .replace(/[^a-z]+/g, '')
- .substr(0, 5);
- clickText('Sign in')
- .then(() => findByXpath('//input[@id="login_dropdown_username"]'))
- .then((element) => element.sendKeys(nonsenseusername))
- .then(() => clickButton('Sign in'))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/button[@type="submit"]'))))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/div[@class="error"]'))))
- .then(() => findByXpath('//form/div[@class="error"]'))
- .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);
- clickText('Sign in')
- .then(() => findByXpath('//input[@id="login_dropdown_username"]'))
- .then((element) => element.sendKeys(nonsenseusername))
- .then(() => findByXpath('//input[@name="password"]'))
- .then((element) => element.sendKeys(password))
- .then(() => clickButton('Sign in'))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/button[@type="submit"]'))))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/div[@class="error"]'))))
- .then(() => findByXpath('//form/div[@class="error"]'))
- .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 => {
- clickText('Sign in')
- .then(() => findByXpath('//input[@id="login_dropdown_username"]'))
- .then((element) => element.sendKeys(username))
- .then(() => findByXpath('//input[@name="password"]'))
- .then((element) => element.sendKeys('nonsensepassword'))
- .then(() => clickButton('Sign in'))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/button[@type="submit"]'))))
- .then(() => driver.wait(until
- .elementLocated(By.xpath('//form[@id="login"]/div[@class="error"]'))))
- .then(() => findByXpath('//form/div[@class="error"]'))
- .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('Sign in to Scratch using scratchr2 navbar', t => {
clickText('Sign in')
.then(() => findByXpath('//input[@id="login_dropdown_username"]'))
diff --git a/test/integration/smoke-testing/test_footer_links.js b/test/integration/smoke-testing/test_footer_links.js
index f2b0381e0..8cf84909a 100644
--- a/test/integration/smoke-testing/test_footer_links.js
+++ b/test/integration/smoke-testing/test_footer_links.js
@@ -3,13 +3,13 @@
*
* Test cases: https://github.com/LLK/scratch-www/wiki/Most-Important-Workflows
*/
-
+
const tap = require('tap');
const {
driver,
webdriver
-} = require('../../helpers/selenium-helpers.js');
+} = require('../selenium-helpers.js');
// Selenium's promise driver will be deprecated, so we should not rely on it
webdriver.SELENIUM_PROMISE_MANAGER = 0;
diff --git a/test/integration/smoke-testing/test_navbar_links.js b/test/integration/smoke-testing/test_navbar_links.js
index efe5cf2ba..fbfd72291 100644
--- a/test/integration/smoke-testing/test_navbar_links.js
+++ b/test/integration/smoke-testing/test_navbar_links.js
@@ -5,19 +5,19 @@
*/
require('chromedriver');
-var seleniumWebdriver = require('selenium-webdriver');
+
+const {
+ driver,
+ webdriver
+} = require('../selenium-helpers.js');
var tap = require('tap');
// Selenium's promise driver will be deprecated, so we should not rely on it
-seleniumWebdriver.SELENIUM_PROMISE_MANAGER = 0;
+webdriver.SELENIUM_PROMISE_MANAGER = 0;
// Set test url through environment variable
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
-// chrome driver
-var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
- .build();
-
// number of tests in the plan
tap.plan(7);
@@ -37,7 +37,7 @@ tap.beforeEach(function () {
tap.test('checkCreateLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "create")]/a';
var expectedHref = '/projects/editor/?tip_bar=home';
- driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
+ driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
@@ -50,7 +50,7 @@ tap.test('checkCreateLinkWhenSignedOut', function (t) {
tap.test('checkExploreLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "explore")]/a';
var expectedHref = '/explore/projects/all';
- driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
+ driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
@@ -63,7 +63,7 @@ tap.test('checkExploreLinkWhenSignedOut', function (t) {
tap.test('checkTipsLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "tips")]/a';
var expectedHref = '/tips';
- driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
+ driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
@@ -76,7 +76,7 @@ tap.test('checkTipsLinkWhenSignedOut', function (t) {
tap.test('checkAboutLinkWhenSignedOut', function (t) {
var xPathLink = '//li[contains(@class, "link") and contains(@class, "about")]/a';
var expectedHref = '/about';
- driver.findElement(seleniumWebdriver.By.xpath(xPathLink))
+ driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getAttribute('href');
})
@@ -91,7 +91,7 @@ tap.test('checkAboutLinkWhenSignedOut', function (t) {
tap.test('checkSearchBar', function (t) {
var xPathLink = '//input[@id="frc-q-1088"]';
// search bar should exist
- driver.findElement(seleniumWebdriver.By.xpath(xPathLink)).then(function (element) {
+ driver.findElement(webdriver.By.xpath(xPathLink)).then(function (element) {
t.ok(element);
t.end();
});
@@ -102,7 +102,7 @@ tap.test('checkSearchBar', function (t) {
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(seleniumWebdriver.By.xpath(xPathLink))
+ driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getText('a');
})
@@ -115,7 +115,7 @@ tap.test('checkJoinScratchLinkWhenSignedOut', function (t) {
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(seleniumWebdriver.By.xpath(xPathLink))
+ driver.findElement(webdriver.By.xpath(xPathLink))
.then(function (element) {
return element.getText('a');
})
diff --git a/test/integration/smoke-testing/test_project_rows.js b/test/integration/smoke-testing/test_project_rows.js
index 28e61bf93..a84bc4c61 100644
--- a/test/integration/smoke-testing/test_project_rows.js
+++ b/test/integration/smoke-testing/test_project_rows.js
@@ -12,9 +12,9 @@ var seleniumWebdriver = require('selenium-webdriver');
// Selenium's promise driver will be deprecated, so we should not rely on it
seleniumWebdriver.SELENIUM_PROMISE_MANAGER = 0;
-// chrome driver
-var driver = new seleniumWebdriver.Builder().withCapabilities(seleniumWebdriver.Capabilities.chrome())
- .build();
+const {
+ driver
+} = require('../selenium-helpers.js');
var rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
diff --git a/test/integration/smoke-testing/test_signing_in_and_out_discuss.js b/test/integration/smoke-testing/test_signing_in_and_out_discuss.js
index 030c12040..47abb9ea6 100644
--- a/test/integration/smoke-testing/test_signing_in_and_out_discuss.js
+++ b/test/integration/smoke-testing/test_signing_in_and_out_discuss.js
@@ -12,7 +12,7 @@ const {
clickXpath,
clickButton,
driver
-} = require('../../helpers/selenium-helpers.js');
+} = require('../selenium-helpers.js');
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
diff --git a/test/integration/smoke-testing/test_signing_in_and_out_homepage.js b/test/integration/smoke-testing/test_signing_in_and_out_homepage.js
index 29bdf5dae..e21fbc91e 100644
--- a/test/integration/smoke-testing/test_signing_in_and_out_homepage.js
+++ b/test/integration/smoke-testing/test_signing_in_and_out_homepage.js
@@ -11,7 +11,7 @@ const {
findByXpath,
clickXpath,
driver
-} = require('../../helpers/selenium-helpers.js');
+} = require('../selenium-helpers.js');
var username = process.env.SMOKE_USERNAME;
var password = process.env.SMOKE_PASSWORD;
diff --git a/test/integration/smoke-testing/test_statistics_page.js b/test/integration/smoke-testing/test_statistics_page.js
index 15c044e27..223184b7e 100644
--- a/test/integration/smoke-testing/test_statistics_page.js
+++ b/test/integration/smoke-testing/test_statistics_page.js
@@ -10,7 +10,7 @@ const {
findByXpath,
findByCss,
driver
-} = require('../../helpers/selenium-helpers.js');
+} = require('../selenium-helpers.js');
var tap = require('tap');
const test = tap.test;