From fce578e854c1bd9c02a9f5600451b8465f7211f0 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 8 Jan 2016 11:44:25 +0100 Subject: [PATCH 01/30] Decorate rackt/history with history of previous locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Could be turned into a createHistory enhancer function (https://github.com/rackt/history/blob/master/docs/Glossary.md#createhis toryenhancer), but we’ll see what the guys at rackt/history say about it… --- js/components/ascribe_app.js | 28 +++++++++++++++++++++++---- js/constants/application_constants.js | 2 ++ js/history.js | 8 ++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index cda5637f..774395ae 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -1,11 +1,14 @@ 'use strict'; import React from 'react'; +import { History } from 'react-router'; -import Header from '../components/header'; -import Footer from '../components/footer'; +import Header from './header'; +import Footer from './footer'; import GlobalNotification from './global_notification'; +import AppConstants from '../constants/application_constants'; + let AscribeApp = React.createClass({ propTypes: { @@ -13,11 +16,28 @@ let AscribeApp = React.createClass({ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element ]), - routes: React.PropTypes.arrayOf(React.PropTypes.object) + routes: React.PropTypes.arrayOf(React.PropTypes.object), + location: React.PropTypes.object + }, + + mixins: [History], + + componentDidMount() { + this.history.locationQueue.push(this.props.location); + }, + + componentWillReceiveProps(nextProps) { + const { locationQueue } = this.history; + locationQueue.unshift(nextProps.location); + + // Limit the number of locations to keep in memory to avoid too much memory usage + if (locationQueue.length > AppConstants.locationThreshold) { + locationQueue.length = AppConstants.locationThreshold; + } }, render() { - let { children, routes } = this.props; + const { children, routes } = this.props; return (
diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 824ed4b2..bfb758f0 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -104,6 +104,8 @@ const constants = { 'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV', 'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA'], + 'locationThreshold': 10, + 'searchThreshold': 500, 'supportedThumbnailFileFormats': [ diff --git a/js/history.js b/js/history.js index 903f2b73..0683fcb3 100644 --- a/js/history.js +++ b/js/history.js @@ -6,8 +6,12 @@ import AppConstants from './constants/application_constants'; // Remove the trailing slash if present -let baseUrl = AppConstants.baseUrl.replace(/\/$/, ''); +const baseUrl = AppConstants.baseUrl.replace(/\/$/, ''); -export default useBasename(createBrowserHistory)({ +const history = useBasename(createBrowserHistory)({ basename: baseUrl }); + +history.locationQueue = []; + +export default history; From 7e77cb58dcfe036d762690f087926e608dc7c6d6 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 8 Jan 2016 11:45:01 +0100 Subject: [PATCH 02/30] Log the previous location if a 404 is encountered --- js/components/error_not_found_page.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/js/components/error_not_found_page.js b/js/components/error_not_found_page.js index 0e111ce7..c42d2926 100644 --- a/js/components/error_not_found_page.js +++ b/js/components/error_not_found_page.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import { History } from 'react-router'; import { getLangText } from '../utils/lang_utils'; @@ -10,12 +11,25 @@ let ErrorNotFoundPage = React.createClass({ message: React.PropTypes.string }, + mixins: [History], + getDefaultProps() { return { message: getLangText("Oops, the page you are looking for doesn't exist.") }; }, + componentDidMount() { + // The previous page, if any, is the second item in the locationQueue + const { locationQueue: [ _, previousPage ] } = this.history; + + if (previousPage) { + console.logGlobal('Page not found', { + previousPath: previousPage.pathname + }); + } + }, + render() { return (
@@ -32,4 +46,4 @@ let ErrorNotFoundPage = React.createClass({ } }); -export default ErrorNotFoundPage; \ No newline at end of file +export default ErrorNotFoundPage; From 7e1cfbb4901b0a7337e9d7deedc2df3848239fcd Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 19 Jan 2016 15:00:50 +0100 Subject: [PATCH 03/30] Add AppBase HOC to improve DRYness of functionality reused across apps --- js/components/app_base.js | 55 +++++++++++++++++++ js/components/ascribe_app.js | 31 ++--------- .../ascribe_buttons/acls/acl_button.js | 2 +- .../prize/portfolioreview/pr_app.js | 34 ++++++------ .../prize/simple_prize/prize_app.js | 28 +++++----- js/components/whitelabel/wallet/wallet_app.js | 30 +++++----- 6 files changed, 105 insertions(+), 75 deletions(-) create mode 100644 js/components/app_base.js diff --git a/js/components/app_base.js b/js/components/app_base.js new file mode 100644 index 00000000..044b984c --- /dev/null +++ b/js/components/app_base.js @@ -0,0 +1,55 @@ +'use strict'; + +import React from 'react'; +import classNames from 'classnames'; +import { History } from 'react-router'; + +import Footer from './footer'; +import GlobalNotification from './global_notification'; + +import AppConstants from '../constants/application_constants'; + + +export default function AppBase(App) { + return React.createClass({ + displayName: 'AppBase', + + propTypes: { + history: React.PropTypes.object.isRequired, + location: React.PropTypes.object.isRequired, + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]) + }, + + mixins: [History], + + componentDidMount() { + this.history.locationQueue.push(this.props.location); + }, + + componentWillReceiveProps(nextProps) { + const { locationQueue } = this.history; + locationQueue.unshift(nextProps.location); + + // Limit the number of locations to keep in memory to avoid too much memory usage + if (locationQueue.length > AppConstants.locationThreshold) { + locationQueue.length = AppConstants.locationThreshold; + } + }, + + render() { + return ( +
+ +
+ + + ); + } + }); +}; diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 774395ae..6d159c26 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -1,39 +1,21 @@ 'use strict'; import React from 'react'; -import { History } from 'react-router'; +import AppBase from './app_base'; import Header from './header'; -import Footer from './footer'; -import GlobalNotification from './global_notification'; import AppConstants from '../constants/application_constants'; let AscribeApp = React.createClass({ propTypes: { + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element - ]), - routes: React.PropTypes.arrayOf(React.PropTypes.object), - location: React.PropTypes.object - }, - - mixins: [History], - - componentDidMount() { - this.history.locationQueue.push(this.props.location); - }, - - componentWillReceiveProps(nextProps) { - const { locationQueue } = this.history; - locationQueue.unshift(nextProps.location); - - // Limit the number of locations to keep in memory to avoid too much memory usage - if (locationQueue.length > AppConstants.locationThreshold) { - locationQueue.length = AppConstants.locationThreshold; - } + ]) }, render() { @@ -46,12 +28,9 @@ let AscribeApp = React.createClass({
{children}
-
- -
); } }); -export default AscribeApp; +export default AppBase(AscribeApp); diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js index 97f2e173..2525c52a 100644 --- a/js/components/ascribe_buttons/acls/acl_button.js +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -14,7 +14,7 @@ import AppConstants from '../../../constants/application_constants'; import { AclInformationText } from '../../../constants/acl_information_text'; -export default function ({ action, displayName, title, tooltip }) { +export default function AclButton({ action, displayName, title, tooltip }) { if (AppConstants.aclList.indexOf(action) < 0) { console.warn('Your specified aclName did not match a an acl class.'); } diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js index c672bdf5..2fe1c2d6 100644 --- a/js/components/whitelabel/prize/portfolioreview/pr_app.js +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -1,28 +1,30 @@ 'use strict'; import React from 'react'; -import GlobalNotification from '../../../global_notification'; - -import Hero from './components/pr_hero'; -import Header from '../../../header'; import EventActions from '../../../../actions/event_actions'; import UserStore from '../../../../stores/user_store'; import UserActions from '../../../../actions/user_actions'; +import Hero from './components/pr_hero'; + +import AppBase from '../../../app_base'; +import Header from '../../../header'; + import { getSubdomain } from '../../../../utils/general_utils'; import { getCookie } from '../../../../utils/fetch_api_utils'; let PRApp = React.createClass({ propTypes: { + history: React.PropTypes.object.isRequired, + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element - ]), - history: React.PropTypes.object, - routes: React.PropTypes.arrayOf(React.PropTypes.object) + ]) }, getInitialState() { @@ -55,19 +57,19 @@ let PRApp = React.createClass({ this.setState(state); }, + render() { - const { history, children, routes } = this.props; + const { children, history, routes } = this.props; const { currentUser } = this.state; + const subdomain = getSubdomain(); + let style = {}; - let subdomain = getSubdomain(); let header; - - if (currentUser && currentUser.email && history.isActive(`/pieces/${getCookie(currentUser.email)}`)) { - header = ; + header = (); style = { paddingTop: '0 !important' }; - } else if(currentUser && (currentUser.is_admin || currentUser.is_jury || currentUser.is_judge)) { - header =
; + } else if (currentUser && (currentUser.is_admin || currentUser.is_jury || currentUser.is_judge)) { + header = (
); } else { style = { paddingTop: '0 !important' }; } @@ -79,12 +81,10 @@ let PRApp = React.createClass({ style={style} className={'container ascribe-prize-app client--' + subdomain}> {children} - -
); } }); -export default PRApp; +export default AppBase(PRApp); diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index d5b55d5f..f1779437 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -1,50 +1,50 @@ 'use strict'; import React from 'react'; + import Hero from './components/prize_hero'; + +import AppBase from '../../../app_base'; import Header from '../../../header'; -import Footer from '../../../footer'; -import GlobalNotification from '../../../global_notification'; import { getSubdomain } from '../../../../utils/general_utils'; let PrizeApp = React.createClass({ propTypes: { + history: React.PropTypes.object.isRequired, + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element - ]), - history: React.PropTypes.object, - routes: React.PropTypes.arrayOf(React.PropTypes.object) + ]) }, render() { - const { history, routes } = this.props; - let header = null; - let subdomain = getSubdomain(); + const { children, history, routes } = this.props; + const subdomain = getSubdomain(); // The second element of routes is always the active component object, where we can // extract the path. let path = routes[1] ? routes[1].path : null; + let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute if (!path || history.isActive('/login') || history.isActive('/signup')) { - header = ; + header = (); } else { - header =
; + header = (
); } return (
{header} {this.props.children} - - -
+ {children}
); } }); -export default PrizeApp; +export default AppBase(PrizeApp); diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index bce7106b..57088969 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -1,41 +1,40 @@ 'use strict'; import React from 'react'; -import Header from '../../header'; -import Footer from '../../footer'; - -import GlobalNotification from '../../global_notification'; - import classNames from 'classnames'; +import AppBase from '../../app_base'; +import Header from '../../header'; + import { getSubdomain } from '../../../utils/general_utils'; let WalletApp = React.createClass({ propTypes: { + history: React.PropTypes.object.isRequired, + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element - ]), - history: React.PropTypes.object, - routes: React.PropTypes.arrayOf(React.PropTypes.object) + ]) }, render() { - let header = null; - let subdomain = getSubdomain(); - const { history, routes, children } = this.props; + const { children, history, routes } = this.props; + const subdomain = getSubdomain(); // The second element of routes is always the active component object, where we can // extract the path. let path = routes[1] ? routes[1].path : null; + let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute if ((!path || history.isActive('/login') || history.isActive('/signup') || history.isActive('/contract_notifications')) && (['cyland', 'ikonotv', 'lumenus', '23vivi']).indexOf(subdomain) > -1) { - header = (
); + header = (
); } else { - header =
; + header = (
); } // In react-router 1.0, Routes have no 'name' property anymore. To keep functionality however, @@ -45,13 +44,10 @@ let WalletApp = React.createClass({
{header} {children} - - -
); } }); -export default WalletApp; +export default AppBase(WalletApp); From efbc605fc88df0fc7f7f8cc2160b07e54219a863 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 19 Jan 2016 15:04:46 +0100 Subject: [PATCH 04/30] Standardize render structure across main app and white label apps Also fixes some whitelabel landing pages broken from the standardization and removes unnecessary class names --- js/components/ascribe_app.js | 6 +- .../prize/portfolioreview/pr_app.js | 9 +-- .../prize/simple_prize/prize_app.js | 6 +- .../components/23vivi/23vivi_landing.js | 2 +- .../components/cyland/cyland_landing.js | 64 ++++++++++--------- js/components/whitelabel/wallet/wallet_app.js | 7 +- .../wallet/23vivi/23vivi_custom_style.scss | 5 ++ .../wallet/cyland/cyland_custom_style.scss | 5 ++ 8 files changed, 60 insertions(+), 44 deletions(-) diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 6d159c26..2e05cc7b 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -22,10 +22,10 @@ let AscribeApp = React.createClass({ const { children, routes } = this.props; return ( -
+
- {/* Routes are injected here */} -
+
+ {/* Routes are injected here */} {children}
diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js index 2fe1c2d6..9637c167 100644 --- a/js/components/whitelabel/prize/portfolioreview/pr_app.js +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -75,11 +75,12 @@ let PRApp = React.createClass({ } return ( -
+
{header} -
+
+ {/* Routes are injected here */} {children}
diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index f1779437..e40e8dc8 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -38,10 +38,12 @@ let PrizeApp = React.createClass({ } return ( -
+
{header} - {this.props.children} +
+ {/* Routes are injected here */} {children} +
); } diff --git a/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js index f6b2d50c..13368549 100644 --- a/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js +++ b/js/components/whitelabel/wallet/components/23vivi/23vivi_landing.js @@ -36,7 +36,7 @@ let Vivi23Landing = React.createClass({ render() { return ( -
+
diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 21f8835a..95419bd4 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -57,38 +57,40 @@ let CylandLanding = React.createClass({ setDocumentTitle('CYLAND MediaArtLab'); return ( -
-
-
-
- -
- {getLangText('Submissions to Cyland Archive are powered by') + ' '} - - - +
+
+
+
+
+ +
+ {getLangText('Submissions to Cyland Archive are powered by') + ' '} + + + +
-
-
-
-

- {getLangText('Existing ascribe user?')} -

- - - -
-
-

- {getLangText('Do you need an account?')} -

- - - +
+
+

+ {getLangText('Existing ascribe user?')} +

+ + + +
+
+

+ {getLangText('Do you need an account?')} +

+ + + +
diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 57088969..4735bdde 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -40,9 +40,10 @@ let WalletApp = React.createClass({ // In react-router 1.0, Routes have no 'name' property anymore. To keep functionality however, // we split the path by the first occurring slash and take the first splitter. return ( -
-
- {header} +
+ {header} +
+ {/* Routes are injected here */} {children}
diff --git a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss index a5026272..b6365dd2 100644 --- a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss +++ b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss @@ -20,8 +20,13 @@ $vivi23--highlight-color: #de2600; vertical-align: middle; } + .hero { + display: none; + } + .vivi23-landing { font-weight: normal; + padding: 0 15px; text-align: center; } diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss index 9af18fcf..549b2004 100644 --- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss +++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss @@ -178,8 +178,13 @@ $cyland--button-sec-color: #515151; vertical-align: middle; } + .hero { + display: none; + } + .cyland-landing { font-weight: normal; + padding: 0 15px; text-align: center; } } From 86ff1f88d03d9042ecc19bd622d49b8a81f62921 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 19 Jan 2016 15:15:33 +0100 Subject: [PATCH 05/30] Fix small issues --- js/components/error_not_found_page.js | 2 +- .../components/cyland/cyland_landing.js | 64 +++++++++---------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/js/components/error_not_found_page.js b/js/components/error_not_found_page.js index c42d2926..4ea0f4d1 100644 --- a/js/components/error_not_found_page.js +++ b/js/components/error_not_found_page.js @@ -21,7 +21,7 @@ let ErrorNotFoundPage = React.createClass({ componentDidMount() { // The previous page, if any, is the second item in the locationQueue - const { locationQueue: [ _, previousPage ] } = this.history; + const { locationQueue: [ , previousPage ] } = this.history; if (previousPage) { console.logGlobal('Page not found', { diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js index 95419bd4..5f7cfeeb 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_landing.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_landing.js @@ -57,40 +57,38 @@ let CylandLanding = React.createClass({ setDocumentTitle('CYLAND MediaArtLab'); return ( -
-
-
-
-
- -
- {getLangText('Submissions to Cyland Archive are powered by') + ' '} - - - -
+
+
+
+
+ +
+ {getLangText('Submissions to Cyland Archive are powered by') + ' '} + + +
-
-
-

- {getLangText('Existing ascribe user?')} -

- - - -
-
-

- {getLangText('Do you need an account?')} -

- - - -
+
+
+
+

+ {getLangText('Existing ascribe user?')} +

+ + + +
+
+

+ {getLangText('Do you need an account?')} +

+ + +
From 9c1b229d8580dc34dc3ea1b1cb11ae27599baf00 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Tue, 19 Jan 2016 15:06:26 +0100 Subject: [PATCH 06/30] Forgot to remove unnecessary import --- js/components/ascribe_app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 2e05cc7b..8999246e 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -5,8 +5,6 @@ import React from 'react'; import AppBase from './app_base'; import Header from './header'; -import AppConstants from '../constants/application_constants'; - let AscribeApp = React.createClass({ propTypes: { From 95bbe410dea171071fc3fcf87bccb5eba775b267 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:28:49 +0100 Subject: [PATCH 07/30] Rebase remerge --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 478562d3..508e84d5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ Introduction ============ -Onion is the web client for Ascribe. The idea is to have a well documented, -easy to test, easy to hack, JavaScript application. +Onion is the web client for Ascribe. The idea is to have a well documented, modern, easy to test, easy to hack, JavaScript application. -The code is JavaScript ECMA 6. +The code is JavaScript 2015 / ECMAScript 6. Getting started =============== + Install some nice extension for Chrom(e|ium): - [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) - +- [Alt Developer Tools](https://github.com/goatslacker/alt-devtool) + ```bash git clone git@github.com:ascribe/onion.git cd onion @@ -37,17 +38,34 @@ Additionally, to work on the white labeling functionality, you need to edit your JavaScript Code Conventions =========================== + For this project, we're using: * 4 Spaces -* We use ES6 +* ES6 * We don't use ES6's class declaration for React components because it does not support Mixins as well as Autobinding ([Blog post about it](http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding)) * We don't use camel case for file naming but in everything Javascript related -* We use `let` instead of `var`: [SA Post](http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword) -* We don't use Javascript's `Date` object, as its interface introduced bugs previously and we're including `momentjs` for other dependencies anyways +* We use `momentjs` instead of Javascript's `Date` object, as the native `Date` interface previously introduced bugs and we're including `momentjs` for other dependencies anyway + +Make sure to check out the [style guide](https://github.com/ascribe/javascript). + +Linting +------- + +We use [ESLint](https://github.com/eslint/eslint) with our own [custom ruleset](.eslintrc). + + +SCSS Code Conventions +===================== + +Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor. + +Some interesting links: +* [Improving Sass code quality on theguardian.com](https://www.theguardian.com/info/developer-blog/2014/may/13/improving-sass-code-quality-on-theguardiancom) + Branch names -===================== +============ To allow Github and JIRA to track branches while still allowing us to switch branches quickly using a ticket's number (and keep our peace of mind), we have the following rules for naming branches: @@ -61,22 +79,21 @@ AD--brief-and-sane-description-of-the-ticket where `brief-and-sane-description-of-the-ticket` does not need to equal to the issue or ticket's title. + Example -------------- +------- + **JIRA ticket name:** `AD-1242 - Frontend caching for simple endpoints to measure perceived page load ` **Github branch name:** `AD-1242-caching-solution-for-stores` -SCSS Code Conventions -===================== -Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor. - -Some interesting links: -* [Improving Sass code quality on theguardian.com](https://www.theguardian.com/info/developer-blog/2014/may/13/improving-sass-code-quality-on-theguardiancom) - Testing -=============== +======= + +Unit Testing +------------ + We're using Facebook's jest to do testing as it integrates nicely with react.js as well. Tests are always created per directory by creating a `__tests__` folder. To test a specific file, a `_tests.js` file needs to be created. @@ -86,7 +103,15 @@ This is due to the fact that jest's function mocking and ES6 module syntax are [ Therefore, to require a module in your test file, you need to use CommonJS's `require` syntax. Except for this, all tests can be written in ES6 syntax. -## Workflow +Visual Regression Testing +------------------------- + +We're using [Gemini](https://github.com/gemini-testing/gemini) for visual regression tests because it supports PhantomJS and SauceLabs. + + +Workflow +======== + Generally, when you're runing `gulp serve`, all tests are being run. If you want to test exclusively (without having the obnoxious ES6Linter warnings), you can just run `gulp jest:watch`. @@ -137,9 +162,16 @@ A: Easily by starting the your gulp process with the following command: ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' gulp serve ``` +Or, by adding these two your environment variables: +``` +ONION_BASE_URL='/' +ONION_SERVER_URL='http://localhost.com:8000/' +``` + Q: I want to know all dependencies that get bundled into the live build. A: ```browserify -e js/app.js --list > webapp-dependencies.txt``` + Reading list ============ @@ -152,7 +184,6 @@ Start here - [alt.js](http://alt.js.org/) - [alt.js readme](https://github.com/goatslacker/alt) - Moar stuff ---------- From bd53151964897268c17736920f6090fe0c45f054 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 20 Jan 2016 21:18:24 +0100 Subject: [PATCH 08/30] Add Gemini configuration for desktop and mobile on phantoms --- .gemini.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gemini.yml diff --git a/.gemini.yml b/.gemini.yml new file mode 100644 index 00000000..5af1ef27 --- /dev/null +++ b/.gemini.yml @@ -0,0 +1,14 @@ +rootUrl: https://www.ascribe.ninja/app + +browsers: + PhantomJSDesktop: + screenshotsDir: './gemini/screens/desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + PhantomJSMobile: + screenshotsDir: './gemini/screens/mobile' + windowSize: 767x1364 + desiredCapabilities: + browserName: phantomjs From 3594229a6eb62518691088132db25171d4c5d1b9 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:29:33 +0100 Subject: [PATCH 09/30] Rebase remerge --- .gitignore | 9 ++- README.md | 4 +- docs/visual-regression-testing.md | 126 ++++++++++++++++++++++++++++++ package.json | 2 + phantomjs/launch_app_and_login.js | 62 +++++++++++++++ 5 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 docs/visual-regression-testing.md create mode 100644 phantomjs/launch_app_and_login.js diff --git a/.gitignore b/.gitignore index f5bf11e8..0b4d85c7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,10 +16,13 @@ webapp-dependencies.txt pids logs results - -node_modules/* -build +build/* + +gemini-coverage/* +gemini-report/* + +node_modules/* .DS_Store .env diff --git a/README.md b/README.md index 508e84d5..1b918e5d 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,9 @@ Therefore, to require a module in your test file, you need to use CommonJS's `re Visual Regression Testing ------------------------- -We're using [Gemini](https://github.com/gemini-testing/gemini) for visual regression tests because it supports PhantomJS and SauceLabs. +We're using [Gemini](https://github.com/gemini-testing/gemini) for visual regression tests because it supports both PhantomJS2 and SauceLabs. + +See the [helper docs](docs/visual-regression-testing.md) for information on installing Gemini, its dependencies, and running and writing tests. Workflow diff --git a/docs/visual-regression-testing.md b/docs/visual-regression-testing.md new file mode 100644 index 00000000..30a711ff --- /dev/null +++ b/docs/visual-regression-testing.md @@ -0,0 +1,126 @@ +Introduction +============ + +When in doubt, see [Gemini](https://github.com/gemini-testing/gemini) and [their docs](https://github.com/gemini-testing/gemini/tree/master/doc) for more information as well as configuration options. + +Contents +======== + + 1. [Installation](#installation) + 1. [Running Tests](#running-tests) + 1. [Gemini Usage](#gemini-usage) + 1. [PhantomJS](#phantomjs) + + +Installation +============ + +First install [PhantomJS2](https://www.npmjs.com/package/phantomjs2): + +```bash +npm install -g phantomjs2 + +# If using OSX, install upx and decompress the binary downloaded by npm manually: +brew install upx +# Navigate to the binary, ie. /Users/Brett/.nvm/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin +upx -d phantomjs + +``` + +Then [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). + + +Running Tests +============= + +Run PhantomJS: + +```bash +phantomjs --webdriver=4444 +``` + +And then run Gemini tests: + +```bash +# In root onion/ +gemini test gemini/* --report html +``` + +If you've made changes and want them to be the new baseline (ie. it's a correct change--**make sure** to test there are no regressions first!), use + +```bash +# In root onion/ +gemini update gemini/* +``` + + +Gemini Usage +============ + +While Gemini itself is easy to use on simple, static pages, there are some nice to knows when dealing with a single page app like ours (where much of it is behind an authentication barrier as well). + +Authentication +-------------- + +Authentication presents a tricky problem with Gemini, since we can't inject any cookies or even run a start up script through the browser before letting Gemini hook in. The solution is to script the log in process through Gemini, and put waits for the log in to succeed, before testing parts of the app that require the authentication. + +Browser Session States +---------------------- + +Gemini will start a new instance of the browser for each browser configuration defined in the .gemini.yml file when Gemini's launched (ie. `gemini update`, `gemini test`, etc). + +Although each new suite will cause the testing browser to be refreshed, the above means that cookies and other persistent state will be kept across suites for a browser across all runs, even if the suites are from different files. + +**What this comes down to is**: once you've logged in, you'll stay logged in until you decide to log out or the running instance of Gemini ends. In general practice, it's a good idea to clear the state of the app at the end of each suite of tests by logging out. + +(**Note**: Persistent storage, such as local storage, has not been explicitly tested as to whether they are kept, but as the cookies are cleared each time, this seems unlikely) + +Test Reporting +-------------- + +Using the `--report html` flag with Gemini will produce a webpage with the test's results in /gemini-report that will show the old, new, and diff images. Using this is highly recommended (and fun!). + + +PhantomJS +========= + +[PhantomJS](http://phantomjs.org/) is a headless browser that allows us to run tests and take screenshots without needing a browser. + +Its second version (PhantomJS2) uses a much more recent version of Webkit, and is a big reason why Gemini (as opposed to other utilities, ie. PhantomCSS) was chosen. Due to the large number of breaking changes introduced between PhantomJS 1.9 to 2.0, a large number of tools (ie. CasperJS) are, at the time of writing, lacking support for 2.0. + +While you don't need to know too much about PhantomJS to use and write Gemini tests, there are still a number of useful things to know about. + +Useful features +--------------- + +You can find the full list of CLI commands in the [documentation](http://phantomjs.org/api/command-line.html). + +Flags that are of particular interest to us: + * `--webdriver=4444`: sets the webdriver port to be 4444, the default webdriver port that Gemini expects. + * `--ignore-ssl-errors=true`: ignores any SSL errors that may occur. Particular useful when hooking up the tests to staging, as the certificate we use is self-signed. + * `--ssl-protocol=any`: allows any ssl protocol to be used. May be useful when `--ignore-ssl-errors=true` doesn't work. + * '--remote-debugger-port`: allows for remote debugging the running PhantomJS instance. More on this later. + +Troubleshooting and Debugging +----------------------------- + +Remote debugging is possible with PhantomJS using the `--remote-debugger-port` option. See the [troubleshooting docs](http://phantomjs.org/troubleshooting.html). + +To begin using it, add `debugger;` statements to the file being run by `phantomjs`, and access the port number specified after `--remote-debugger-port` on localhost: + +```bash +phantomjs --remote-debugger-port=9000 debug.js +``` + +PhantomJS will start and then immediately breakpoint. Go to http://localhost:9000/webkit/inspector/inspector.html?page=1 and then to its console tab. Go to your first breakpoint (the first `debugger;` statement executed) by running `__run()` in the console tab. Subsequent breakpoints can be reached by successively running `__run()` in that same console tab. + +At each breakpoint, you can to http://localhost:9000 on a new browser tab and click on one of the links to go to the current execution state of that breakpoint on the page you're on. + +--- + +To simplify triaging simple issues and test if everything is working, I've added a short test script that can be run with PhantomJS to check if it can access the web app and log in. You can edit the `lauch_app_and_login.js` file to change the environment to run against. + +```bash +# In root /onion folder +phantomjs phantomjs/launch_app_and_login.js +``` diff --git a/package.json b/package.json index 091203b5..d8e76da5 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,10 @@ "chai-as-promised": "^5.1.0", "colors": "^1.1.2", "dotenv": "^1.2.0", + "gemini": "^2.0.3", "jest-cli": "^0.4.0", "mocha": "^2.3.4", + "phantomjs2": "^2.0.2", "sauce-connect-launcher": "^0.13.0", "wd": "^0.4.0" }, diff --git a/phantomjs/launch_app_and_login.js b/phantomjs/launch_app_and_login.js new file mode 100644 index 00000000..e5418519 --- /dev/null +++ b/phantomjs/launch_app_and_login.js @@ -0,0 +1,62 @@ +'use strict'; + +var liveEnv = 'https://www.ascribe.io/app/login'; +// Note that if you are trying to access staging, you will need to use +// the --ignore-ssl-errors=true flag on phantomjs +var stagingEnv = 'https://www.ascribe.ninja/app/login'; +var localEnv = 'http://localhost.com:3000/login'; + +var page = require('webpage').create(); +page.open(localEnv, function(status) { + var attemptedToLogIn; + var loginCheckInterval; + + console.log('Status: ' + status); + + if (status === 'success') { + console.log('Attempting to log in...'); + + attemptedToLogIn = page.evaluate(function () { + try { + var inputForm = document.querySelector('.ascribe-login-wrapper'); + var email = inputForm.querySelector('input[type=email]'); + var password = inputForm.querySelector('input[type=password]'); + var submitBtn = inputForm.querySelector('button[type=submit]'); + + email.value = 'dimi@mailinator.com'; + password.value = '0000000000'; + submitBtn.click(); + + return true; + } catch (ex) { + console.log('Error while trying to find login elements, not logging in.'); + return false; + } + }); + + if (attemptedToLogIn) { + loginCheckInterval = setInterval(function () { + var loggedIn = page.evaluate(function () { + // When they log in, they are taken to the collections page. + // When the piece list is loaded, the accordion list is either available or + // shows a placeholder, so let's check for these elements to determine + // when login is finished + return !!(document.querySelector('.ascribe-accordion-list:not(.ascribe-loading-position)') || + document.querySelector('.ascribe-accordion-list-placeholder')); + }); + + if (loggedIn) { + clearInterval(loginCheckInterval); + console.log('Successfully logged in.'); + } + }, 1000); + } else { + console.log('Something happened while trying to log in, aborting...'); + phantom.exit(); + } + + } else { + console.log('Failed to load page, exiing...'); + phantom.exit(); + } +}); From cb59d042b5c898490117d547a499d3af65784ee3 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:17:30 +0100 Subject: [PATCH 10/30] Update docs for PhantomJS 2.1, whitespace, and more tips --- docs/visual-regression-testing.md | 107 +++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/docs/visual-regression-testing.md b/docs/visual-regression-testing.md index 30a711ff..d544b1fa 100644 --- a/docs/visual-regression-testing.md +++ b/docs/visual-regression-testing.md @@ -1,33 +1,39 @@ Introduction ============ -When in doubt, see [Gemini](https://github.com/gemini-testing/gemini) and [their docs](https://github.com/gemini-testing/gemini/tree/master/doc) for more information as well as configuration options. +When in doubt, see [Gemini](https://github.com/gemini-testing/gemini) and [their +docs](https://github.com/gemini-testing/gemini/tree/master/doc) for more information as well as configuration options. Contents ======== 1. [Installation](#installation) 1. [Running Tests](#running-tests) - 1. [Gemini Usage](#gemini-usage) + 1. [Gemini Usage and Writing Tests](#gemini-usage-and-writing-tests) 1. [PhantomJS](#phantomjs) + 1. [TODO](#todo) Installation ============ -First install [PhantomJS2](https://www.npmjs.com/package/phantomjs2): +First make sure that you're using NodeJS 5.0+ as the tests are written using ES6 syntax. + +Then, install [PhantomJS2](https://www.npmjs.com/package/phantomjs2): ```bash -npm install -g phantomjs2 +# Until phantomjs2 is updated for the new 2.1 version of PhantomJS, use the following (go to https://bitbucket.org/ariya/phantomjs/downloads to find a build for your OS) +npm install -g phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip -# If using OSX, install upx and decompress the binary downloaded by npm manually: +# If using OSX, you may have to install upx and decompress the binary downloaded by npm manually: brew install upx -# Navigate to the binary, ie. /Users/Brett/.nvm/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin + +# Navigate to the binary, ie. /Users/Brett/.nvm/versions/node/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin/phantomjs upx -d phantomjs ``` -Then [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). +Finally, [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). Running Tests @@ -54,41 +60,84 @@ gemini update gemini/* ``` -Gemini Usage -============ +Gemini Usage and Writing Tests +============================== -While Gemini itself is easy to use on simple, static pages, there are some nice to knows when dealing with a single page app like ours (where much of it is behind an authentication barrier as well). +While Gemini itself is easy to use on simple, static pages, there are some nice to knows when dealing with a single page +app like ours (where much of it is behind an authentication barrier as well). + +Command Line Interface +---------------------- + +See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/commands.md) on the commands that are available. +`npm run vi-*` is set up with some of these commands, but you may want to build your own or learn about some of the +other functions. Authentication -------------- -Authentication presents a tricky problem with Gemini, since we can't inject any cookies or even run a start up script through the browser before letting Gemini hook in. The solution is to script the log in process through Gemini, and put waits for the log in to succeed, before testing parts of the app that require the authentication. +Authentication presents a tricky problem with Gemini, since we can't inject any cookies or even run a start up script +through the browser before letting Gemini hook in. The solution is to script the log in process through Gemini, and put +waits for the log in to succeed, before testing parts of the app that require the authentication. Browser Session States ---------------------- -Gemini will start a new instance of the browser for each browser configuration defined in the .gemini.yml file when Gemini's launched (ie. `gemini update`, `gemini test`, etc). +Gemini will start a new instance of the browser for each browser configuration defined in the .gemini.yml file when +Gemini's launched (ie. `gemini update`, `gemini test`, etc). -Although each new suite will cause the testing browser to be refreshed, the above means that cookies and other persistent state will be kept across suites for a browser across all runs, even if the suites are from different files. +Although each new suite will cause the testing browser to be refreshed, the above means that cookies and other +persistent state will be kept across suites for a browser across all runs, even if the suites are from different files. -**What this comes down to is**: once you've logged in, you'll stay logged in until you decide to log out or the running instance of Gemini ends. In general practice, it's a good idea to clear the state of the app at the end of each suite of tests by logging out. +**What this comes down to is**: once you've logged in, you'll stay logged in until you decide to log out or the running +instance of Gemini ends. In general practice, it's a good idea to clear the state of the app at the end of each suite of +tests by logging out. -(**Note**: Persistent storage, such as local storage, has not been explicitly tested as to whether they are kept, but as the cookies are cleared each time, this seems unlikely) +(**Note**: Persistent storage, such as local storage, has not been explicitly tested as to whether they are kept, but as +the cookies are cleared each time, this seems unlikely) Test Reporting -------------- -Using the `--report html` flag with Gemini will produce a webpage with the test's results in /gemini-report that will show the old, new, and diff images. Using this is highly recommended (and fun!). +Using the `--reporter html` flag with Gemini will produce a webpage with the test's results in `onion/gemini-report` +that will show the old, new, and diff images. Using this is highly recommended (and fun!) and is used by default in `npm +run vi-test`. +Writing Tests +------------- + +See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md), and the [section on the available +actions](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md#available-actions) for what scripted actions +are available. + +Our tests are located in `onion/gemini/`. + +Some useful tips: + * The `find()` method in the callbacks is equivalent to `document.querySelector`; it will only return the first + element found that matches the selector. Use pseudo classes like `nth-of-type()`, `nth-child()`, and etc. to select + later elements. + * Nested suites inherit from their parent suites' configurations, but will **override** their inherited configuration + if another is specified. For example, if `parentSuite` had a `.before()` method, all children of `parentSuite` would + run its `.before()`, but if any of the children specified their own `.before()`, those children would **not** run + `parentSuite`'s `.before()`. + * Gemini takes a screenshot of the minimum bounding rect for all specified selectors, so this means you can't take a + screenshot of two items far away from each other without the rest being considered (ie. trying to get the header and + footer) + * Unfortunately, `setCaptureElements` and `ignoreElements` will only apply for the first element found matching those + selectors. PhantomJS ========= -[PhantomJS](http://phantomjs.org/) is a headless browser that allows us to run tests and take screenshots without needing a browser. +[PhantomJS](http://phantomjs.org/) is a headless browser that allows us to run tests and take screenshots without +needing a browser. -Its second version (PhantomJS2) uses a much more recent version of Webkit, and is a big reason why Gemini (as opposed to other utilities, ie. PhantomCSS) was chosen. Due to the large number of breaking changes introduced between PhantomJS 1.9 to 2.0, a large number of tools (ie. CasperJS) are, at the time of writing, lacking support for 2.0. +Its second version (PhantomJS2) uses a much more recent version of Webkit, and is a big reason why Gemini (as opposed to +other utilities, ie. PhantomCSS) was chosen. Due to the large number of breaking changes introduced between PhantomJS +1.9 to 2.0, a large number of tools (ie. CasperJS) are, at the time of writing, lacking support for 2.0. -While you don't need to know too much about PhantomJS to use and write Gemini tests, there are still a number of useful things to know about. +While you don't need to know too much about PhantomJS to use and write Gemini tests, there are still a number of useful +things to know about. Useful features --------------- @@ -97,28 +146,36 @@ You can find the full list of CLI commands in the [documentation](http://phantom Flags that are of particular interest to us: * `--webdriver=4444`: sets the webdriver port to be 4444, the default webdriver port that Gemini expects. - * `--ignore-ssl-errors=true`: ignores any SSL errors that may occur. Particular useful when hooking up the tests to staging, as the certificate we use is self-signed. + * `--ignore-ssl-errors=true`: ignores any SSL errors that may occur. Particular useful when hooking up the tests to + staging, as the certificate we use is self-signed. * `--ssl-protocol=any`: allows any ssl protocol to be used. May be useful when `--ignore-ssl-errors=true` doesn't work. * '--remote-debugger-port`: allows for remote debugging the running PhantomJS instance. More on this later. Troubleshooting and Debugging ----------------------------- -Remote debugging is possible with PhantomJS using the `--remote-debugger-port` option. See the [troubleshooting docs](http://phantomjs.org/troubleshooting.html). +Remote debugging is possible with PhantomJS using the `--remote-debugger-port` option. See the [troubleshooting +docs](http://phantomjs.org/troubleshooting.html). -To begin using it, add `debugger;` statements to the file being run by `phantomjs`, and access the port number specified after `--remote-debugger-port` on localhost: +To begin using it, add `debugger;` statements to the file being run by `phantomjs`, and access the port number specified +after `--remote-debugger-port` on localhost: ```bash phantomjs --remote-debugger-port=9000 debug.js ``` -PhantomJS will start and then immediately breakpoint. Go to http://localhost:9000/webkit/inspector/inspector.html?page=1 and then to its console tab. Go to your first breakpoint (the first `debugger;` statement executed) by running `__run()` in the console tab. Subsequent breakpoints can be reached by successively running `__run()` in that same console tab. +PhantomJS will start and then immediately breakpoint. Go to http://localhost:9000/webkit/inspector/inspector.html?page=1 +and then to its console tab. Go to your first breakpoint (the first `debugger;` statement executed) by running `__run()` +in the console tab. Subsequent breakpoints can be reached by successively running `__run()` in that same console tab. -At each breakpoint, you can to http://localhost:9000 on a new browser tab and click on one of the links to go to the current execution state of that breakpoint on the page you're on. +At each breakpoint, you can to http://localhost:9000 on a new browser tab and click on one of the links to go to the +current execution state of that breakpoint on the page you're on. --- -To simplify triaging simple issues and test if everything is working, I've added a short test script that can be run with PhantomJS to check if it can access the web app and log in. You can edit the `lauch_app_and_login.js` file to change the environment to run against. +To simplify triaging simple issues and test if everything is working, I've added a short test script that can be run +with PhantomJS to check if it can access the web app and log in. You can edit the `lauch_app_and_login.js` file to +change the environment to run against. ```bash # In root /onion folder From 5cfbe5cc398b38f97b333c88bab0e39e0605d7c0 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:30:16 +0100 Subject: [PATCH 11/30] Rebase remerge --- .../piece_list_toolbar_filter_widget.js | 1 + .../piece_list_toolbar_order_widget.js | 7 ++++--- js/components/header.js | 1 + js/components/header_notification.js | 1 + js/components/nav_routes_links_link.js | 1 + 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js index edb29e85..c9791dbe 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_filter_widget.js @@ -84,6 +84,7 @@ let PieceListToolbarFilterWidget = React.createClass({ if (this.props.filterParams && this.props.filterParams.length) { return ( diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js index 5257cc07..da9bae43 100644 --- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js +++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar_order_widget.js @@ -45,7 +45,7 @@ let PieceListToolbarOrderWidget = React.createClass({ }, render() { - let filterIcon = ( + let orderIcon = ( · @@ -55,9 +55,10 @@ let PieceListToolbarOrderWidget = React.createClass({ if (this.props.orderParams && this.props.orderParams.length) { return ( + className="ascribe-piece-list-toolbar-filter-widget" + title={orderIcon}>
  • {getLangText('Sort by')}:
  • diff --git a/js/components/header.js b/js/components/header.js index d2ecb76b..2587c92c 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -173,6 +173,7 @@ let Header = React.createClass({ account = ( diff --git a/js/components/nav_routes_links_link.js b/js/components/nav_routes_links_link.js index 6daec0ea..94fd91a2 100644 --- a/js/components/nav_routes_links_link.js +++ b/js/components/nav_routes_links_link.js @@ -30,6 +30,7 @@ let NavRoutesLinksLink = React.createClass({ return ( {children} From 2661f2ba57db0dfebdeee9dc80a178bf3bcb74f6 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:20:43 +0100 Subject: [PATCH 12/30] Standardize CoaVerify route across apps --- js/components/coa_verify_container.js | 85 +++++++++---------- .../whitelabel/wallet/wallet_routes.js | 10 +-- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/js/components/coa_verify_container.js b/js/components/coa_verify_container.js index 6d0af0fa..4a270cf4 100644 --- a/js/components/coa_verify_container.js +++ b/js/components/coa_verify_container.js @@ -27,7 +27,7 @@ let CoaVerifyContainer = React.createClass({ return (
    -
    +
    {getLangText('Verify your Certificate of Authenticity')}
    @@ -37,7 +37,7 @@ let CoaVerifyContainer = React.createClass({ signature={signature}/>

    - {getLangText('ascribe is using the following public key for verification')}: + {getLangText('ascribe is using the following public key for verification')}:
                     -----BEGIN PUBLIC KEY-----
    @@ -60,9 +60,8 @@ let CoaVerifyForm = React.createClass({
         },
     
         handleSuccess(response){
    -        let notification = null;
             if (response.verdict) {
    -            notification = new GlobalNotificationModel(getLangText('Certificate of Authenticity successfully verified'), 'success');
    +            const notification = new GlobalNotificationModel(getLangText('Certificate of Authenticity successfully verified'), 'success');
                 GlobalNotificationActions.appendGlobalNotification(notification);
             }
         },
    @@ -71,46 +70,44 @@ let CoaVerifyForm = React.createClass({
             const { message, signature } = this.props;
     
             return (
    -            
    -
    - {getLangText('Verify your Certificate of Authenticity')} - } - spinner={ - - - - }> - - - - - - -
    -
    -
    +
    + {getLangText('Verify your Certificate of Authenticity')} + + } + spinner={ + + + + }> + + + + + + +
    +
    ); } }); diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index ba560608..f1613ee5 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -78,7 +78,7 @@ let ROUTES = { headerTitle='COLLECTION' disableOn='noPieces' /> - + @@ -114,7 +114,7 @@ let ROUTES = { disableOn='noPieces' /> - + ), @@ -159,7 +159,7 @@ let ROUTES = { component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} /> - + ), @@ -196,7 +196,7 @@ let ROUTES = { disableOn='noPieces' /> - + ), @@ -233,7 +233,7 @@ let ROUTES = { disableOn='noPieces' /> - + ) From 1d09ef1120bf9f05e00081ab6f8b09fff569b0bb Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:21:57 +0100 Subject: [PATCH 13/30] Main app visual test suite --- .gemini.yml | 15 ++- gemini/main/authenticated.js | 214 +++++++++++++++++++++++++++++++++++ gemini/main/basic.js | 145 ++++++++++++++++++++++++ gemini/main/detail.js | 125 ++++++++++++++++++++ 4 files changed, 493 insertions(+), 6 deletions(-) create mode 100644 gemini/main/authenticated.js create mode 100644 gemini/main/basic.js create mode 100644 gemini/main/detail.js diff --git a/.gemini.yml b/.gemini.yml index 5af1ef27..8d434624 100644 --- a/.gemini.yml +++ b/.gemini.yml @@ -1,14 +1,17 @@ -rootUrl: https://www.ascribe.ninja/app +rootUrl: http://localhost.com:3000/ +sessionsPerBrowser: 1 browsers: - PhantomJSDesktop: - screenshotsDir: './gemini/screens/desktop' + MainDesktop: + rootUrl: http://localhost.com:3000/ + screenshotsDir: './gemini-screens/desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs - PhantomJSMobile: - screenshotsDir: './gemini/screens/mobile' - windowSize: 767x1364 + MainMobile: + rootUrl: http://localhost.com:3000/ + screenshotsDir: './gemini-screens/mobile' + windowSize: 600x1056 desiredCapabilities: browserName: phantomjs diff --git a/gemini/main/authenticated.js b/gemini/main/authenticated.js new file mode 100644 index 00000000..bf579bff --- /dev/null +++ b/gemini/main/authenticated.js @@ -0,0 +1,214 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Suite of tests against routes that require the user to be authenticated. +*/ +gemini.suite('Authenticated', (suite) => { + suite + .setUrl('/collection') + .setCaptureElements('.ascribe-body') + .before((actions, find) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + // FIXME: use a more generic class for this, like just '.app', + // when we can use this file with the whitelabels + actions.waitForElementToShow('.ascribe-default-app', 5000); + }); + + // Suite just to log us in before any other suites run + gemini.suite('Login', (loginSuite) => { + loginSuite + .setUrl('/login') + .ignoreElements('.ascribe-body') + .capture('logged in', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + + actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), '0000000000'); + actions.click(find('.ascribe-login-wrapper button[type=submit]')); + + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }); + }); + + gemini.suite('Header-desktop', (headerSuite) => { + headerSuite + .setCaptureElements('nav.navbar .container') + // Ignore Cyland's logo as it's a gif + .ignoreElements('.client--cyland img.img-brand') + .skip(/Mobile/) + .before((actions, find) => { + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }) + .capture('desktop header'); + + gemini.suite('User dropdown', (headerUserSuite) => { + headerUserSuite + .setCaptureElements('#nav-route-user-dropdown ~ .dropdown-menu') + .capture('expanded user dropdown', (actions, find) => { + actions.click(find('#nav-route-user-dropdown')); + }); + }); + + gemini.suite('Notification dropdown', (headerNotificationSuite) => { + headerNotificationSuite + .setCaptureElements('#header-notification-dropdown ~ .dropdown-menu') + .capture('expanded notifications dropdown', (actions, find) => { + actions.click(find('#header-notification-dropdown')); + }); + }); + }); + + // Test for the collapsed header in mobile + gemini.suite('Header-mobile', (headerMobileSuite) => { + headerMobileSuite + .setCaptureElements('nav.navbar .container') + // Ignore Cyland's logo as it's a gif + .ignoreElements('.client--cyland img.img-brand') + .skip(/Desktop/) + .before((actions, find) => { + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }) + .capture('mobile header') + .capture('expanded mobile header', (actions, find) => { + actions.click(find('nav.navbar .navbar-toggle')); + // Wait for the header to expand + actions.wait(500); + }) + .capture('expanded user dropdown', (actions, find) => { + actions.click(find('#nav-route-user-dropdown')); + }) + .capture('expanded notifications dropdown', (actions, find) => { + actions.click(find('#header-notification-dropdown')); + }); + }); + + gemini.suite('Collection', (collectionSuite) => { + collectionSuite + .setCaptureElements('.ascribe-accordion-list') + .before((actions, find) => { + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + // Wait for the images to load + // FIXME: unfortuntately gemini doesn't support ignoring multiple elements from a single selector + // so we're forced to wait and hope that the images will all finish loading after 5s. + // We could also change the thumbnails with JS, but setting up a test user is probably easier. + actions.wait(5000); + }) + .capture('collection') + .capture('expanded edition in collection', (actions, find) => { + actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget')); + // Wait for editions to load + actions.waitForElementToShow('.ascribe-accordion-list-item-table', 5000); + }) + + gemini.suite('Collection placeholder', (collectionPlaceholderSuite) => { + collectionPlaceholderSuite + .setCaptureElements('.ascribe-accordion-list-placeholder') + .capture('collection empty search', (actions, find) => { + actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'no search result'); + actions.waitForElementToShow('.ascribe-accordion-list-placeholder', 5000); + }); + }); + + gemini.suite('PieceListBulkModal', (pieceListBulkModalSuite) => { + pieceListBulkModalSuite + .setCaptureElements('.piece-list-bulk-modal') + .capture('items selected', (actions, find) => { + actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget')); + // Wait for editions to load + actions.waitForElementToShow('.ascribe-accordion-list-item-table', 5000); + + actions.click('.ascribe-table thead tr input[type="checkbox"]'); + actions.waitForElementToShow('.piece-list-bulk-modal'); + }); + }); + }); + + gemini.suite('PieceListToolbar', (pieceListToolbarSuite) => { + pieceListToolbarSuite + .setCaptureElements('.ascribe-piece-list-toolbar') + .capture('piece list toolbar') + .capture('piece list toolbar search filled', (actions, find) => { + actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'search text'); + }) + + gemini.suite('Order widget dropdown', (pieceListToolbarOrderWidgetSuite) => { + pieceListToolbarOrderWidgetSuite + .setCaptureElements('#ascribe-piece-list-toolbar-order-widget-dropdown', + '#ascribe-piece-list-toolbar-order-widget-dropdown ~ .dropdown-menu') + .capture('expanded order dropdown', (actions, find) => { + actions.click(find('#ascribe-piece-list-toolbar-order-widget-dropdown')); + + // Wait as the dropdown screenshot still includes the collection in the background + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }); + }); + + gemini.suite('Filter widget dropdown', (pieceListToolbarFilterWidgetSuite) => { + pieceListToolbarFilterWidgetSuite + .setCaptureElements('#ascribe-piece-list-toolbar-filter-widget-dropdown', + '#ascribe-piece-list-toolbar-filter-widget-dropdown ~ .dropdown-menu') + .capture('expanded filter dropdown', (actions, find) => { + actions.click(find('#ascribe-piece-list-toolbar-filter-widget-dropdown')); + + // Wait as the dropdown screenshot still includes the collection in the background + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }); + }); + }); + + gemini.suite('Register work', (registerSuite) => { + registerSuite + .setUrl('/register_piece') + .capture('register work', (actions, find) => { + // The uploader options are only rendered after the user is fetched, so + // we have to wait for it here + actions.waitForElementToShow('.file-drag-and-drop-dialog .present-options', 5000); + }) + .capture('register work filled', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name="artist_name"]'), 'artist name'); + actions.sendKeys(find('.ascribe-form input[name="title"]'), 'title'); + actions.sendKeys(find('.ascribe-form input[name="date_created"]'), 'date created'); + }) + .capture('register work filled with editions', (actions, find) => { + actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); + actions.wait(500); + actions.sendKeys(find('.ascribe-form input[name="num_editions"]'), '50'); + }); + + gemini.suite('Register work hash', (registerHashSuite) => { + registerHashSuite + .setUrl('/register_piece?method=hash') + .capture('register work hash method'); + }); + + gemini.suite('Register work upload', (registerUploadSuite) => { + registerUploadSuite + .setUrl('/register_piece?method=upload') + .capture('register work upload method'); + }); + }); + + gemini.suite('User settings', (userSettingsSuite) => { + userSettingsSuite + .setUrl('/settings') + .before((actions, find) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + actions.waitForElementToShow('.settings-container', 5000); + }) + .capture('user settings'); + }); + + // Suite just to log out after suites have run + gemini.suite('Log out', (logoutSuite) => { + logoutSuite + .setUrl('/logout') + .ignoreElements('.ascribe-body') + .capture('logout', (actions, find) => { + actions.waitForElementToShow('.ascribe-login-wrapper', 10000); + }); + }); +}); diff --git a/gemini/main/basic.js b/gemini/main/basic.js new file mode 100644 index 00000000..317c5d84 --- /dev/null +++ b/gemini/main/basic.js @@ -0,0 +1,145 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Basic suite of tests against routes that do not require the user to be authenticated. +*/ +gemini.suite('Basic', (suite) => { + suite + .setUrl('/login') + .setCaptureElements('.ascribe-body') + .before((actions, find) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + // FIXME: use a more generic class for this, like just '.ascribe-app' + actions.waitForElementToShow('.ascribe-default-app', 5000); + }); + + gemini.suite('Header-desktop', (headerSuite) => { + headerSuite + .setCaptureElements('nav.navbar .container') + .skip(/Mobile/) + .capture('desktop header', (actions, find) => { + actions.waitForElementToShow('nav.navbar .container', 5000); + }) + .capture('hover on active item', (actions, find) => { + const activeItem = find('nav.navbar li.active'); + actions.mouseMove(activeItem); + }) + .capture('hover on inactive item', (actions, find) => { + const inactiveItem = find('nav.navbar li:not(.active)'); + actions.mouseMove(inactiveItem); + }); + }); + + // Test for the collapsed header in mobile + gemini.suite('Header-mobile', (headerMobileSuite) => { + headerMobileSuite + .setCaptureElements('nav.navbar .container') + .skip(/Desktop/) + .capture('mobile header', (actions, find) => { + actions.waitForElementToShow('nav.navbar .container', 5000); + }) + .capture('expanded mobile header', (actions, find) => { + actions.click(find('nav.navbar .navbar-toggle')); + // Wait for the header to expand + actions.wait(500); + }) + .capture('hover on expanded mobile header item', (actions, find) => { + actions.mouseMove(find('nav.navbar li')); + }); + }); + + gemini.suite('Footer', (footerSuite) => { + footerSuite + .setCaptureElements('.ascribe-footer') + .capture('footer', (actions, find) => { + actions.waitForElementToShow('.ascribe-footer', 5000); + }) + .capture('hover on footer item', (actions, find) => { + const footerItem = find('.ascribe-footer a:not(.social)'); + actions.mouseMove(footerItem); + }) + .capture('hover on footer social item', (actions, find) => { + const footerSocialItem = find('.ascribe-footer a.social') + actions.mouseMove(footerSocialItem); + }); + }); + + gemini.suite('Login', (loginSuite) => { + loginSuite + .capture('login', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + }) + .capture('hover on login submit', (actions, find) => { + actions.mouseMove(find('.ascribe-form button[type=submit]')); + }) + .capture('hover on sign up link', (actions, find) => { + actions.mouseMove(find('.ascribe-login-text a[href="/signup"]')); + }) + .capture('login form filled with focus', (actions, find) => { + const emailInput = find('.ascribe-form input[name=email]'); + + // Remove hover from sign up link + actions.click(emailInput); + + actions.sendKeys(emailInput, 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + }) + .capture('login form filled', (actions, find) => { + actions.click(find('.ascribe-form-header')); + }); + }); + + gemini.suite('Sign up', (signUpSuite) => { + signUpSuite + .setUrl('/signup') + .capture('sign up', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + }) + .capture('sign up form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + }) + .capture('sign up form filled with check', (actions, find) => { + actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); + }); + }); + + gemini.suite('Password reset', (passwordResetSuite) => { + passwordResetSuite + .setUrl('/password_reset') + .capture('password reset', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + }) + .capture('password reset form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name="email"]'), 'dimi@mailinator.com'); + }) + .capture('password reset form filled', (actions, find) => { + actions.click(find('.ascribe-form-header')); + }); + }); + + gemini.suite('Coa verify', (coaVerifySuite) => { + coaVerifySuite + .setUrl('/coa_verify') + .capture('coa verify', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + }) + .capture('coa verify form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name="message"]'), 'sample text'); + actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'), 'sample signature'); + }) + .capture('coa verify form filled', (actions, find) => { + actions.click(find('.ascribe-login-header')); + }); + }); + + gemini.suite('Not found', (notFoundSuite) => { + notFoundSuite + .setUrl('/not_found_page') + .capture('not found page'); + }); +}); diff --git a/gemini/main/detail.js b/gemini/main/detail.js new file mode 100644 index 00000000..39a02338 --- /dev/null +++ b/gemini/main/detail.js @@ -0,0 +1,125 @@ +'use strict'; + +const gemini = require('gemini'); +const pieceUrl = '/pieces/12374'; +const editionUrl = '/editions/14gw9x3VA9oJaxp4cHaAuK2bvJzvEj4Xvc'; + +/** + * Suite of tests against the piece and edition routes. + * Tests include accessing the piece / edition as the owner or as another user + * (we can just use an anonymous user in this case). +*/ +gemini.suite('Work detail', (suite) => { + suite + .setCaptureElements('.ascribe-body') + .before((actions, find) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + // FIXME: use a more generic class for this, like just '.app', + // when we can use this file with the whitelabels + actions.waitForElementToShow('.ascribe-default-app', 5000); + + // Wait for the social media buttons to appear + actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', 20000); + actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', 20000); + actions.waitForElementToShow('.ascribe-media-player', 10000); + }); + + gemini.suite('Basic piece', (basicPieceSuite) => { + basicPieceSuite + .setUrl(pieceUrl) + .capture('basic piece') + .capture('shmui', (actions, find) => { + actions.click(find('.ascribe-media-player')); + actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); + // Wait for the transition to end + actions.wait(1000); + }); + }); + + gemini.suite('Basic edition', (basicEditionSuite) => { + basicEditionSuite + .setUrl(editionUrl) + .capture('basic edition'); + }); + + // Suite just to log us in before any other suites run + gemini.suite('Login', (loginSuite) => { + loginSuite + .setUrl('/login') + .ignoreElements('.ascribe-body') + .before((actions, find) => { + actions.waitForElementToShow('.ascribe-default-app', 5000); + }) + .capture('logged in', (actions, find) => { + actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), '0000000000'); + actions.click(find('.ascribe-login-wrapper button[type=submit]')); + + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', 5000); + }); + }); + + gemini.suite('Authorized piece', (authorizedPieceSuite) => { + authorizedPieceSuite + .setUrl(pieceUrl) + .capture('authorized piece'); + }); + + gemini.suite('Authorized edition', (authorizedEditionSuite) => { + authorizedEditionSuite + .setUrl(editionUrl) + .capture('authorized edition') + }); + + gemini.suite('Detail action buttons', (detailActionButtonSuite) => { + detailActionButtonSuite + .setUrl(editionUrl) + .capture('hover on action button', (actions, find) => { + actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-default')); + }) + .capture('hover on delete button', (actions, find) => { + actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-tertiary')); + }) + .capture('hover on info button', (actions, find) => { + actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign')); + }) + .capture('expand info text', (actions, find) => { + actions.click(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign')); + }); + }); + + gemini.suite('Action form modal', (actionFormModalSuite) => { + actionFormModalSuite + .setUrl(editionUrl) + .setCaptureElements('.modal-dialog') + .capture('open email form', (actions, find) => { + // Add class names to make the action buttons easier to select + actions.executeJS(function (window) { + var actionButtons = window.document.querySelectorAll('.ascribe-detail-property .ascribe-button-list button.btn-default'); + for (var ii = 0; ii < actionButtons.length; ++ii) { + if (actionButtons[ii].textContent) { + actionButtons[ii].className += ' ascribe-action-button-' + actionButtons[ii].textContent.toLowerCase(); + } + } + }); + actions.click(find('.ascribe-detail-property .ascribe-button-list button.ascribe-action-button-email')); + + // Wait for transition + actions.wait(1000); + }); + }); + + // Suite just to log out after suites have run + gemini.suite('Log out', (logoutSuite) => { + logoutSuite + .setUrl('/logout') + .ignoreElements('.ascribe-body') + .before((actions, find) => { + actions.waitForElementToShow('.ascribe-default-app', 5000); + }) + .capture('logout', (actions, find) => { + actions.waitForElementToShow('.ascribe-login-wrapper', 10000); + }); + }); +}); From e895c765610e96b72c606d242d8ff55a509494f0 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:24:01 +0100 Subject: [PATCH 14/30] Add whitelabel visual test suite Currently just checks landing page and some pages accessible to unauthorized users. --- .gemini.yml | 116 +++++++++++++++++++ gemini/whitelabel/23vivi/23vivi.js | 27 +++++ gemini/whitelabel/cyland/cyland.js | 28 +++++ gemini/whitelabel/ikonotv/ikonotv.js | 95 +++++++++++++++ gemini/whitelabel/lumenus/lumenus.js | 27 +++++ gemini/whitelabel/shared/whitelabel_basic.js | 112 ++++++++++++++++++ 6 files changed, 405 insertions(+) create mode 100644 gemini/whitelabel/23vivi/23vivi.js create mode 100644 gemini/whitelabel/cyland/cyland.js create mode 100644 gemini/whitelabel/ikonotv/ikonotv.js create mode 100644 gemini/whitelabel/lumenus/lumenus.js create mode 100644 gemini/whitelabel/shared/whitelabel_basic.js diff --git a/.gemini.yml b/.gemini.yml index 8d434624..a04903eb 100644 --- a/.gemini.yml +++ b/.gemini.yml @@ -15,3 +15,119 @@ browsers: windowSize: 600x1056 desiredCapabilities: browserName: phantomjs + + CcDesktop: + rootUrl: http://cc.localhost.com:3000/ + screenshotsDir: './gemini-screens/cc-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + CcMobile: + rootUrl: http://cc.localhost.com:3000/ + screenshotsDir: './gemini-screens/cc-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + CylandDesktop: + rootUrl: http://cyland.localhost.com:3000/ + screenshotsDir: './gemini-screens/cyland-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + CylandMobile: + rootUrl: http://cyland.localhost.com:3000/ + screenshotsDir: './gemini-screens/cyland-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + IkonotvDesktop: + rootUrl: http://ikonotv.localhost.com:3000/ + screenshotsDir: './gemini-screens/ikonotv-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + IkonotvMobile: + rootUrl: http://ikonotv.localhost.com:3000/ + screenshotsDir: './gemini-screens/ikonotv-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + LumenusDesktop: + rootUrl: http://lumenus.localhost.com:3000/ + screenshotsDir: './gemini-screens/lumenus-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + LumenusMobile: + rootUrl: http://lumenus.localhost.com:3000/ + screenshotsDir: './gemini-screens/lumenus-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + + 23viviDesktop: + rootUrl: http://23vivi.localhost.com:3000/ + screenshotsDir: './gemini-screens/23vivi-desktop' + windowSize: 1900x1080 + desiredCapabilities: + browserName: phantomjs + + 23viviMobile: + rootUrl: http://23vivi.localhost.com:3000/ + screenshotsDir: './gemini-screens/23vivi-mobile' + windowSize: 600x1056 + desiredCapabilities: + browserName: phantomjs + +sets: + main: + files: + - gemini/main + browsers: + - MainDesktop + - MainMobile + cc: + files: + - gemini/whitelabel/shared + browsers: + - CcDesktop + - CcMobile + + cyland: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/cyland + browsers: + - CylandDesktop + - CylandMobile + + ikonotv: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/ikonotv + browsers: + - IkonotvDesktop + - IkonotvMobile + + lumenus: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/lumenus + browsers: + - LumenusDesktop + - LumenusMobile + + 23vivi: + files: + - gemini/whitelabel/shared + - gemini/whitelabel/23vivi + browsers: + - 23viviDesktop + - 23viviMobile diff --git a/gemini/whitelabel/23vivi/23vivi.js b/gemini/whitelabel/23vivi/23vivi.js new file mode 100644 index 00000000..cafdfc6d --- /dev/null +++ b/gemini/whitelabel/23vivi/23vivi.js @@ -0,0 +1,27 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Suite of tests against 23vivi specific routes + */ +gemini.suite('23vivi', (suite) => { + suite + //TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged + .setCaptureElements('.ascribe-wallet-app') + .before((actions, find) => { + // This will be called before every nested suite begins + actions.waitForElementToShow('.ascribe-wallet-app', 5000); + }); + + gemini.suite('Landing', (landingSuite) => { + landingSuite + .setUrl('/') + .capture('landing', (actions, find) => { + // Wait for the logo to appear + actions.waitForElementToShow('.vivi23-landing--header-logo', 10000); + }); + }); + + // TODO: add more tests for market specific pages after authentication +}); diff --git a/gemini/whitelabel/cyland/cyland.js b/gemini/whitelabel/cyland/cyland.js new file mode 100644 index 00000000..06709f39 --- /dev/null +++ b/gemini/whitelabel/cyland/cyland.js @@ -0,0 +1,28 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Suite of tests against Cyland specific routes + */ +gemini.suite('Cyland', (suite) => { + suite + //TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged + .setCaptureElements('.ascribe-wallet-app') + .before((actions, find) => { + // This will be called before every nested suite begins + actions.waitForElementToShow('.ascribe-wallet-app', 5000); + }); + + gemini.suite('Landing', (landingSuite) => { + landingSuite + .setUrl('/') + // Ignore Cyland's logo as it's a gif + .ignoreElements('.cyland-landing img') + .capture('landing', (actions, find) => { + actions.waitForElementToShow('.cyland-landing img', 10000); + }); + }); + + // TODO: add more tests for cyland specific pages after authentication +}); diff --git a/gemini/whitelabel/ikonotv/ikonotv.js b/gemini/whitelabel/ikonotv/ikonotv.js new file mode 100644 index 00000000..1741aaa0 --- /dev/null +++ b/gemini/whitelabel/ikonotv/ikonotv.js @@ -0,0 +1,95 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Suite of tests against Cyland specific routes + */ +gemini.suite('Ikonotv', (suite) => { + suite + //TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged + .setCaptureElements('.ascribe-wallet-app') + .before((actions, find) => { + // This will be called before every nested suite begins + actions.waitForElementToShow('.ascribe-wallet-app', 5000); + }); + + gemini.suite('Landing', (landingSuite) => { + landingSuite + .setUrl('/') + // Gemini complains if we try to capture the entire app for Ikonotv's landing page for some reason + .setCaptureElements('.ikonotv-landing') + .setTolerance(5) + .capture('landing', (actions, find) => { + // Stop background animation + actions.executeJS(function (window) { + var landingBackground = window.document.querySelector('.client--ikonotv .route--landing'); + landingBackground.style.animation = 'none'; + landingBackground.style.webkitAnimation = 'none'; + }); + + // Wait for logo to appear + actions.waitForElementToShow('.ikonotv-landing header img', 10000); + }); + }); + + // Ikono needs its own set of tests for some pre-authorization pages to wait for + // its logo to appear + gemini.suite('Ikonotv basic', (suite) => { + suite + .setCaptureElements('.ascribe-wallet-app') + .before((actions, find) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + // FIXME: use a more generic class for this, like just '.app', + // when we can use this file with the whitelabels + actions.waitForElementToShow('.ascribe-wallet-app', 5000); + + // Wait for the forms to appear + actions.waitForElementToShow('.ascribe-form', 5000); + + // Just use a dumb wait because the logo is set as a background image + actions.wait(3000); + }); + + gemini.suite('Login', (loginSuite) => { + loginSuite + .setUrl('/login') + .capture('login') + .capture('hover on login submit', (actions, find) => { + actions.mouseMove(find('.ascribe-form button[type=submit]')); + }) + .capture('hover on sign up link', (actions, find) => { + actions.mouseMove(find('.ascribe-login-text a[href="/signup"]')); + }) + .capture('login form filled with focus', (actions, find) => { + const emailInput = find('.ascribe-form input[name=email]'); + + // Remove hover from sign up link + actions.click(emailInput); + + actions.sendKeys(emailInput, 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + }) + .capture('login form filled', (actions, find) => { + actions.click(find('.ascribe-form-header')); + }); + }); + + gemini.suite('Sign up', (signUpSuite) => { + signUpSuite + .setUrl('/signup') + .capture('sign up') + .capture('sign up form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + }) + .capture('sign up form filled with check', (actions, find) => { + actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); + }); + }); + }); + + // TODO: add more tests for ikonotv specific pages after authentication +}); diff --git a/gemini/whitelabel/lumenus/lumenus.js b/gemini/whitelabel/lumenus/lumenus.js new file mode 100644 index 00000000..a9ff53cd --- /dev/null +++ b/gemini/whitelabel/lumenus/lumenus.js @@ -0,0 +1,27 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Suite of tests against lumenus specific routes + */ +gemini.suite('Lumenus', (suite) => { + suite + //TODO: maybe this should be changed to .ascribe-body once the PR that does this is merged + .setCaptureElements('.ascribe-wallet-app') + .before((actions, find) => { + // This will be called before every nested suite begins + actions.waitForElementToShow('.ascribe-wallet-app', 5000); + }); + + gemini.suite('Landing', (landingSuite) => { + landingSuite + .setUrl('/') + .capture('landing', (actions, find) => { + // Wait for the logo to appear + actions.waitForElementToShow('.wp-landing-wrapper img', 10000); + }); + }); + + // TODO: add more tests for market specific pages after authentication +}); diff --git a/gemini/whitelabel/shared/whitelabel_basic.js b/gemini/whitelabel/shared/whitelabel_basic.js new file mode 100644 index 00000000..7fe5c256 --- /dev/null +++ b/gemini/whitelabel/shared/whitelabel_basic.js @@ -0,0 +1,112 @@ +'use strict'; + +const gemini = require('gemini'); + +/** + * Basic suite of tests against whitelabel routes that do not require authentication. +*/ +gemini.suite('Whitelabel basic', (suite) => { + suite + .setCaptureElements('.ascribe-wallet-app > .container') + .before((actions, find) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + // FIXME: use a more generic class for this, like just '.ascribe-app' + actions.waitForElementToShow('.ascribe-wallet-app', 5000); + + // Use a dumb wait in case we're still waiting for other assets, like fonts, to load + actions.wait(1000); + }); + + gemini.suite('Login', (loginSuite) => { + loginSuite + .setUrl('/login') + // See Ikono + .skip(/Ikono/) + .capture('login', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + // For some reason, the screenshots seem to keep catching the whitelabel login form + // on a refresh and without fonts loaded (maybe because they're the first tests run + // and the cache isn't hot yet?). + // Let's wait a bit and hope they load. + actions.wait(3000); + }) + .capture('hover on login submit', (actions, find) => { + actions.mouseMove(find('.ascribe-form button[type=submit]')); + }) + .capture('hover on sign up link', (actions, find) => { + actions.mouseMove(find('.ascribe-login-text a[href="/signup"]')); + }) + .capture('login form filled with focus', (actions, find) => { + const emailInput = find('.ascribe-form input[name=email]'); + + // Remove hover from sign up link + actions.click(emailInput); + + actions.sendKeys(emailInput, 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + }) + .capture('login form filled', (actions, find) => { + actions.click(find('.ascribe-form-header')); + }); + }); + + gemini.suite('Sign up', (signUpSuite) => { + signUpSuite + .setUrl('/signup') + // See Ikono + .skip(/Ikono/) + .capture('sign up', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + // Wait in case the form reloads due to other assets loading + actions.wait(500); + }) + .capture('sign up form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name=email]'), 'dimi@mailinator.com'); + actions.sendKeys(find('.ascribe-form input[name=password]'), '0000000000'); + actions.sendKeys(find('.ascribe-form input[name=password_confirm]'), '0000000000'); + }) + .capture('sign up form filled with check', (actions, find) => { + actions.click(find('.ascribe-form input[type="checkbox"] ~ .checkbox')); + }); + }); + + gemini.suite('Password reset', (passwordResetSuite) => { + passwordResetSuite + .setUrl('/password_reset') + .capture('password reset', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + // Wait in case the form reloads due to other assets loading + actions.wait(500); + }) + .capture('password reset form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name="email"]'), 'dimi@mailinator.com'); + }) + .capture('password reset form filled', (actions, find) => { + actions.click(find('.ascribe-form-header')); + }); + }); + + gemini.suite('Coa verify', (coaVerifySuite) => { + coaVerifySuite + .setUrl('/coa_verify') + .capture('coa verify', (actions, find) => { + actions.waitForElementToShow('.ascribe-form', 5000); + // Wait in case the form reloads due to other assets loading + actions.wait(500); + }) + .capture('coa verify form filled with focus', (actions, find) => { + actions.sendKeys(find('.ascribe-form input[name="message"]'), 'sample text'); + actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'), 'sample signature'); + }) + .capture('coa verify form filled', (actions, find) => { + actions.click(find('.ascribe-login-header')); + }); + }); + + gemini.suite('Not found', (notFoundSuite) => { + notFoundSuite + .setUrl('/not_found_page') + .capture('not found page'); + }); +}); From afb832dc2892030385a3cd93891135aa8949ddba Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 11:24:28 +0100 Subject: [PATCH 15/30] Remove browser-sync notify pop over that shows up in visual tests --- gulpfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index f13945b0..afa0d5a9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -97,7 +97,8 @@ gulp.task('browser-sync', function() { proxy: 'http://localhost:4000', port: 3000, open: false, // does not open the browser-window anymore (handled manually) - ghostMode: false + ghostMode: false, + notify: false // stop showing the browsersync pop up }); }); From 747f8df06bbb54f949fe34bac9af263d7b96b136 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:30:55 +0100 Subject: [PATCH 16/30] Rebase remerge --- docs/visual-regression-testing.md | 33 +++++++++++++++++++++++++------ package.json | 15 +++++++++++++- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/docs/visual-regression-testing.md b/docs/visual-regression-testing.md index d544b1fa..35d328f0 100644 --- a/docs/visual-regression-testing.md +++ b/docs/visual-regression-testing.md @@ -42,21 +42,32 @@ Running Tests Run PhantomJS: ```bash -phantomjs --webdriver=4444 +npm run vi-phantom ``` And then run Gemini tests: ```bash -# In root onion/ -gemini test gemini/* --report html +npm run vi-test + +# Run only main tests +npm run vi-test:main + +# Run only whitelabel tests +npm run vi-test:whitelabel + +# Run only specific whitelabel tests +npm run vi-test:cyland ``` -If you've made changes and want them to be the new baseline (ie. it's a correct change--**make sure** to test there are no regressions first!), use +If you've made changes and want them to be the new baseline (ie. it's a correct change--**make sure** to test there are +no regressions first!), use ```bash -# In root onion/ -gemini update gemini/* +npm run vi-update + +# Update just the main app for desktop and mobile +npm run vi-update -- --browser MainDesktop --browser MainMobile ``` @@ -112,6 +123,8 @@ are available. Our tests are located in `onion/gemini/`. +**It would be nice if we kept the whitelabels up to date.** + Some useful tips: * The `find()` method in the callbacks is equivalent to `document.querySelector`; it will only return the first element found that matches the selector. Use pseudo classes like `nth-of-type()`, `nth-child()`, and etc. to select @@ -181,3 +194,11 @@ change the environment to run against. # In root /onion folder phantomjs phantomjs/launch_app_and_login.js ``` + + +TODO +==== + +* Write scripts to automate creation of test users (and modify tests to accomodate) +* Set scripts with rootUrls pointing to staging / live using environment variables +* Set up with Sauce Labs diff --git a/package.json b/package.json index d8e76da5..9b90e537 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,19 @@ "start": "node server.js", "test": "mocha", "tunnel": "node test/tunnel.js" + + "vi-clean": "rm -rf gemini-report", + "vi-phantom": "phantomjs --webdriver=4444", + "vi-update": "gemini update", + "vi-test": "npm run vi-clean && gemini test --reporter html --reporter vflat || true", + "vi-test:all": "npm run vi-test", + "vi-test:main": "npm run vi-test -- --browser MainDesktop --browser MainMobile", + "vi-test:whitelabel": "GEMINI_BROWSERS='CcDesktop, CcMobile, CylandDesktop, CylandMobile, IkonotvDesktop, IkonotvMobile, LumenusDesktop, LumenusMobile, 23viviDesktop, 23viviMobile' npm run vi-test", + "vi-test:cc": "npm run vi-test -- --browser CcDesktop --browser CcMobile", + "vi-test:cyland": "npm run vi-test -- --browser CylandDesktop --browser CylandMobile", + "vi-test:ikonotv": "npm run vi-test -- --browser IkonotvDesktop --browser IkonotvMobile", + "vi-test:lumenus": "npm run vi-test -- --browser LumenusDesktop --browser LumenusMobile", + "vi-test:23vivi": "npm run vi-test -- --browser 23viviDesktop --browser 23viviMobile" }, "browser": { "fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js" @@ -42,7 +55,7 @@ "chai-as-promised": "^5.1.0", "colors": "^1.1.2", "dotenv": "^1.2.0", - "gemini": "^2.0.3", + "gemini": "^2.1.0", "jest-cli": "^0.4.0", "mocha": "^2.3.4", "phantomjs2": "^2.0.2", From c3b8b596f1d51ca7fe368f6bdfe8327ecfbc50da Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:31:16 +0100 Subject: [PATCH 17/30] Rebase remerge --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9b90e537..bfe0c416 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "postinstall": "npm run build", "build": "gulp build --production", "start": "node server.js", - "test": "mocha", - "tunnel": "node test/tunnel.js" - + "test": "npm run sauce-test", + "sauce-test": "mocha", + "tunnel": "node test/tunnel.js", "vi-clean": "rm -rf gemini-report", "vi-phantom": "phantomjs --webdriver=4444", "vi-update": "gemini update", From a51b33857e4cad8431a5182e11c16b7a765a5db8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 13:39:46 +0100 Subject: [PATCH 18/30] Move sauce tests to /test/integration folder --- package.json | 4 ++-- test/{ => integration}/README.md | 0 test/{ => integration}/config.js | 0 test/{ => integration}/setup.js | 0 test/{ => integration/tests}/test-login.js | 2 +- test/{ => integration}/tunnel.js | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename test/{ => integration}/README.md (100%) rename test/{ => integration}/config.js (100%) rename test/{ => integration}/setup.js (100%) rename test/{ => integration/tests}/test-login.js (97%) rename test/{ => integration}/tunnel.js (100%) diff --git a/package.json b/package.json index bfe0c416..42c8bba9 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "build": "gulp build --production", "start": "node server.js", "test": "npm run sauce-test", - "sauce-test": "mocha", - "tunnel": "node test/tunnel.js", + "sauce-test": "mocha ./test/integration/tests/", + "tunnel": "node ./test/integration/tunnel.js", "vi-clean": "rm -rf gemini-report", "vi-phantom": "phantomjs --webdriver=4444", "vi-update": "gemini update", diff --git a/test/README.md b/test/integration/README.md similarity index 100% rename from test/README.md rename to test/integration/README.md diff --git a/test/config.js b/test/integration/config.js similarity index 100% rename from test/config.js rename to test/integration/config.js diff --git a/test/setup.js b/test/integration/setup.js similarity index 100% rename from test/setup.js rename to test/integration/setup.js diff --git a/test/test-login.js b/test/integration/tests/test-login.js similarity index 97% rename from test/test-login.js rename to test/integration/tests/test-login.js index e2736fe1..853d48e5 100644 --- a/test/test-login.js +++ b/test/integration/tests/test-login.js @@ -5,7 +5,7 @@ const wd = require('wd'); const asserters = wd.asserters; // Commonly used asserters for async waits in the browser const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); -const config = require('./config.js'); +const config = require('../config.js'); chai.use(chaiAsPromised); chai.should(); diff --git a/test/tunnel.js b/test/integration/tunnel.js similarity index 100% rename from test/tunnel.js rename to test/integration/tunnel.js From 67b46fb425e808f67ba41eddf2f458a166eda42d Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 13:58:52 +0100 Subject: [PATCH 19/30] Move visual tests to /test/gemini folder --- package.json | 6 +++--- .gemini.yml => test/gemini/.gemini.yml | 20 +++++++++---------- .../gemini/README.md | 3 ++- .../gemini/tests}/main/authenticated.js | 0 {gemini => test/gemini/tests}/main/basic.js | 0 {gemini => test/gemini/tests}/main/detail.js | 0 .../gemini/tests}/whitelabel/23vivi/23vivi.js | 0 .../gemini/tests}/whitelabel/cyland/cyland.js | 0 .../tests}/whitelabel/ikonotv/ikonotv.js | 0 .../tests}/whitelabel/lumenus/lumenus.js | 0 .../whitelabel/shared/whitelabel_basic.js | 0 11 files changed, 15 insertions(+), 14 deletions(-) rename .gemini.yml => test/gemini/.gemini.yml (89%) rename docs/visual-regression-testing.md => test/gemini/README.md (97%) rename {gemini => test/gemini/tests}/main/authenticated.js (100%) rename {gemini => test/gemini/tests}/main/basic.js (100%) rename {gemini => test/gemini/tests}/main/detail.js (100%) rename {gemini => test/gemini/tests}/whitelabel/23vivi/23vivi.js (100%) rename {gemini => test/gemini/tests}/whitelabel/cyland/cyland.js (100%) rename {gemini => test/gemini/tests}/whitelabel/ikonotv/ikonotv.js (100%) rename {gemini => test/gemini/tests}/whitelabel/lumenus/lumenus.js (100%) rename {gemini => test/gemini/tests}/whitelabel/shared/whitelabel_basic.js (100%) diff --git a/package.json b/package.json index 42c8bba9..5e72b863 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "test": "npm run sauce-test", "sauce-test": "mocha ./test/integration/tests/", "tunnel": "node ./test/integration/tunnel.js", - "vi-clean": "rm -rf gemini-report", + "vi-clean": "rm -rf ./gemini-report", "vi-phantom": "phantomjs --webdriver=4444", - "vi-update": "gemini update", - "vi-test": "npm run vi-clean && gemini test --reporter html --reporter vflat || true", + "vi-update": "gemini update -c ./test/gemini/.gemini.yml", + "vi-test": "npm run vi-clean && gemini test -c ./test/gemini/.gemini.yml --reporter html --reporter vflat || true", "vi-test:all": "npm run vi-test", "vi-test:main": "npm run vi-test -- --browser MainDesktop --browser MainMobile", "vi-test:whitelabel": "GEMINI_BROWSERS='CcDesktop, CcMobile, CylandDesktop, CylandMobile, IkonotvDesktop, IkonotvMobile, LumenusDesktop, LumenusMobile, 23viviDesktop, 23viviMobile' npm run vi-test", diff --git a/.gemini.yml b/test/gemini/.gemini.yml similarity index 89% rename from .gemini.yml rename to test/gemini/.gemini.yml index a04903eb..f64d865f 100644 --- a/.gemini.yml +++ b/test/gemini/.gemini.yml @@ -89,45 +89,45 @@ browsers: sets: main: files: - - gemini/main + - tests/main browsers: - MainDesktop - MainMobile cc: files: - - gemini/whitelabel/shared + - tests/whitelabel/shared browsers: - CcDesktop - CcMobile cyland: files: - - gemini/whitelabel/shared - - gemini/whitelabel/cyland + - tests/whitelabel/shared + - tests/whitelabel/cyland browsers: - CylandDesktop - CylandMobile ikonotv: files: - - gemini/whitelabel/shared - - gemini/whitelabel/ikonotv + - tests/whitelabel/shared + - tests/whitelabel/ikonotv browsers: - IkonotvDesktop - IkonotvMobile lumenus: files: - - gemini/whitelabel/shared - - gemini/whitelabel/lumenus + - tests/whitelabel/shared + - tests/whitelabel/lumenus browsers: - LumenusDesktop - LumenusMobile 23vivi: files: - - gemini/whitelabel/shared - - gemini/whitelabel/23vivi + - tests/whitelabel/shared + - tests/whitelabel/23vivi browsers: - 23viviDesktop - 23viviMobile diff --git a/docs/visual-regression-testing.md b/test/gemini/README.md similarity index 97% rename from docs/visual-regression-testing.md rename to test/gemini/README.md index 35d328f0..da1712d9 100644 --- a/docs/visual-regression-testing.md +++ b/test/gemini/README.md @@ -24,6 +24,7 @@ Then, install [PhantomJS2](https://www.npmjs.com/package/phantomjs2): ```bash # Until phantomjs2 is updated for the new 2.1 version of PhantomJS, use the following (go to https://bitbucket.org/ariya/phantomjs/downloads to find a build for your OS) npm install -g phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip +npm install --save-dev phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip # If using OSX, you may have to install upx and decompress the binary downloaded by npm manually: brew install upx @@ -121,7 +122,7 @@ See [the docs](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md actions](https://github.com/gemini-testing/gemini/blob/master/doc/tests.md#available-actions) for what scripted actions are available. -Our tests are located in `onion/gemini/`. +Our tests are located in `onion/test/gemini/tests/`. **It would be nice if we kept the whitelabels up to date.** diff --git a/gemini/main/authenticated.js b/test/gemini/tests/main/authenticated.js similarity index 100% rename from gemini/main/authenticated.js rename to test/gemini/tests/main/authenticated.js diff --git a/gemini/main/basic.js b/test/gemini/tests/main/basic.js similarity index 100% rename from gemini/main/basic.js rename to test/gemini/tests/main/basic.js diff --git a/gemini/main/detail.js b/test/gemini/tests/main/detail.js similarity index 100% rename from gemini/main/detail.js rename to test/gemini/tests/main/detail.js diff --git a/gemini/whitelabel/23vivi/23vivi.js b/test/gemini/tests/whitelabel/23vivi/23vivi.js similarity index 100% rename from gemini/whitelabel/23vivi/23vivi.js rename to test/gemini/tests/whitelabel/23vivi/23vivi.js diff --git a/gemini/whitelabel/cyland/cyland.js b/test/gemini/tests/whitelabel/cyland/cyland.js similarity index 100% rename from gemini/whitelabel/cyland/cyland.js rename to test/gemini/tests/whitelabel/cyland/cyland.js diff --git a/gemini/whitelabel/ikonotv/ikonotv.js b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js similarity index 100% rename from gemini/whitelabel/ikonotv/ikonotv.js rename to test/gemini/tests/whitelabel/ikonotv/ikonotv.js diff --git a/gemini/whitelabel/lumenus/lumenus.js b/test/gemini/tests/whitelabel/lumenus/lumenus.js similarity index 100% rename from gemini/whitelabel/lumenus/lumenus.js rename to test/gemini/tests/whitelabel/lumenus/lumenus.js diff --git a/gemini/whitelabel/shared/whitelabel_basic.js b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js similarity index 100% rename from gemini/whitelabel/shared/whitelabel_basic.js rename to test/gemini/tests/whitelabel/shared/whitelabel_basic.js From 574378af98d5fbc3f78013332c006b3c52612117 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 13:58:57 +0100 Subject: [PATCH 20/30] Update README for updated paths to tests --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b918e5d..8dcf78ad 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,14 @@ Visual Regression Testing We're using [Gemini](https://github.com/gemini-testing/gemini) for visual regression tests because it supports both PhantomJS2 and SauceLabs. -See the [helper docs](docs/visual-regression-testing.md) for information on installing Gemini, its dependencies, and running and writing tests. +See the [helper docs](test/gemini/README.md) for information on installing Gemini, its dependencies, and running and writing tests. + +Integration Testing +------------------- + +We're using [Sauce Labs](https://saucelabs.com/home) with [WD.js](https://github.com/admc/wd) for integration testing across browser grids with Selenium. + +See the [helper docs](test/integration/README.md) for information on each part of the test stack and how to run and write tests. Workflow From baf75cbee12912f712598417a7b90caddbcadfbc Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 14:37:41 +0100 Subject: [PATCH 21/30] Fix shmui visual test --- test/gemini/tests/main/detail.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/gemini/tests/main/detail.js b/test/gemini/tests/main/detail.js index 39a02338..7adad5d4 100644 --- a/test/gemini/tests/main/detail.js +++ b/test/gemini/tests/main/detail.js @@ -29,12 +29,17 @@ gemini.suite('Work detail', (suite) => { basicPieceSuite .setUrl(pieceUrl) .capture('basic piece') - .capture('shmui', (actions, find) => { - actions.click(find('.ascribe-media-player')); - actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); - // Wait for the transition to end - actions.wait(1000); - }); + + gemini.suite('Shmui', (shmuiSuite) => { + shmuiSuite. + setCaptureElements('.shmui-wrap') + .capture('shmui', (actions, find) => { + actions.click(find('.ascribe-media-player')); + actions.waitForElementToShow('.shmui-wrap:not(.loading)', 30000); + // Wait for the transition to end + actions.wait(1000); + }); + }); }); gemini.suite('Basic edition', (basicEditionSuite) => { From 39417f3d11a18c3bdbc0a6458994dbb113142539 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Fri, 29 Jan 2016 14:37:55 +0100 Subject: [PATCH 22/30] Change screenshot folder for visual tests --- test/gemini/.gemini.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/gemini/.gemini.yml b/test/gemini/.gemini.yml index f64d865f..0c7b8b77 100644 --- a/test/gemini/.gemini.yml +++ b/test/gemini/.gemini.yml @@ -4,84 +4,84 @@ sessionsPerBrowser: 1 browsers: MainDesktop: rootUrl: http://localhost.com:3000/ - screenshotsDir: './gemini-screens/desktop' + screenshotsDir: './screenshots/main-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs MainMobile: rootUrl: http://localhost.com:3000/ - screenshotsDir: './gemini-screens/mobile' + screenshotsDir: './screenshots/main-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs CcDesktop: rootUrl: http://cc.localhost.com:3000/ - screenshotsDir: './gemini-screens/cc-desktop' + screenshotsDir: './screenshots/cc-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs CcMobile: rootUrl: http://cc.localhost.com:3000/ - screenshotsDir: './gemini-screens/cc-mobile' + screenshotsDir: './screenshots/cc-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs CylandDesktop: rootUrl: http://cyland.localhost.com:3000/ - screenshotsDir: './gemini-screens/cyland-desktop' + screenshotsDir: './screenshots/cyland-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs CylandMobile: rootUrl: http://cyland.localhost.com:3000/ - screenshotsDir: './gemini-screens/cyland-mobile' + screenshotsDir: './screenshots/cyland-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs IkonotvDesktop: rootUrl: http://ikonotv.localhost.com:3000/ - screenshotsDir: './gemini-screens/ikonotv-desktop' + screenshotsDir: './screenshots/ikonotv-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs IkonotvMobile: rootUrl: http://ikonotv.localhost.com:3000/ - screenshotsDir: './gemini-screens/ikonotv-mobile' + screenshotsDir: './screenshots/ikonotv-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs LumenusDesktop: rootUrl: http://lumenus.localhost.com:3000/ - screenshotsDir: './gemini-screens/lumenus-desktop' + screenshotsDir: './screenshots/lumenus-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs LumenusMobile: rootUrl: http://lumenus.localhost.com:3000/ - screenshotsDir: './gemini-screens/lumenus-mobile' + screenshotsDir: './screenshots/lumenus-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs 23viviDesktop: rootUrl: http://23vivi.localhost.com:3000/ - screenshotsDir: './gemini-screens/23vivi-desktop' + screenshotsDir: './screenshots/23vivi-desktop' windowSize: 1900x1080 desiredCapabilities: browserName: phantomjs 23viviMobile: rootUrl: http://23vivi.localhost.com:3000/ - screenshotsDir: './gemini-screens/23vivi-mobile' + screenshotsDir: './screenshots/23vivi-mobile' windowSize: 600x1056 desiredCapabilities: browserName: phantomjs From 4e13376d5ff993a58968757426b1a155fb4beaed Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 14:52:05 +0100 Subject: [PATCH 23/30] Add route class names to prize app --- js/components/whitelabel/prize/portfolioreview/pr_app.js | 7 ++++++- js/components/whitelabel/prize/simple_prize/prize_app.js | 5 +++-- js/components/whitelabel/wallet/wallet_app.js | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js index 9637c167..7e21d7c3 100644 --- a/js/components/whitelabel/prize/portfolioreview/pr_app.js +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import classNames from 'classnames'; import EventActions from '../../../../actions/event_actions'; @@ -63,6 +64,10 @@ let PRApp = React.createClass({ const { currentUser } = this.state; const subdomain = getSubdomain(); + // The second element of routes is always the active component object, where we can + // extract the path. + const path = routes[1] ? routes[1].path : null; + let style = {}; let header; if (currentUser && currentUser.email && history.isActive(`/pieces/${getCookie(currentUser.email)}`)) { @@ -77,7 +82,7 @@ let PRApp = React.createClass({ return (
    + className={classNames('ascribe-prize-app', `route--${(path ? path.split('/')[0] : 'landing')}`)}> {header}
    {/* Routes are injected here */} diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index e40e8dc8..da02a597 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import classNames from 'classnames'; import Hero from './components/prize_hero'; @@ -27,7 +28,7 @@ let PrizeApp = React.createClass({ // The second element of routes is always the active component object, where we can // extract the path. - let path = routes[1] ? routes[1].path : null; + const path = routes[1] ? routes[1].path : null; let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute @@ -38,7 +39,7 @@ let PrizeApp = React.createClass({ } return ( -
    +
    {header}
    {/* Routes are injected here */} diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 4735bdde..3940d89e 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -26,7 +26,7 @@ let WalletApp = React.createClass({ // The second element of routes is always the active component object, where we can // extract the path. - let path = routes[1] ? routes[1].path : null; + const path = routes[1] ? routes[1].path : null; let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute From 83012200d168d7551e7b6eb51b0bc1d5f13171b8 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 14:53:54 +0100 Subject: [PATCH 24/30] Separate Sluice styling away from SimplePrize styling --- sass/whitelabel/prize/index.scss | 3 +- .../portfolioreview_custom_style.scss | 2 +- .../simple_prize_custom_style.scss | 189 +----------------- .../simple_prize/simple_prize_variables.scss | 3 + .../prize/sluice/sluice_custom_style.scss | 182 +++++++++++++++++ 5 files changed, 190 insertions(+), 189 deletions(-) create mode 100644 sass/whitelabel/prize/simple_prize/simple_prize_variables.scss create mode 100644 sass/whitelabel/prize/sluice/sluice_custom_style.scss diff --git a/sass/whitelabel/prize/index.scss b/sass/whitelabel/prize/index.scss index dfdcaebd..664fe7a1 100644 --- a/sass/whitelabel/prize/index.scss +++ b/sass/whitelabel/prize/index.scss @@ -1,9 +1,10 @@ +@import 'simple_prize/simple_prize_variables'; @import 'simple_prize/simple_prize_custom_style'; +@import 'sluice/sluice_custom_style'; @import 'portfolioreview/portfolioreview_custom_style'; .ascribe-prize-app { border-radius: 0; - min-height: 100vh; padding-top: 70px; padding-bottom: 10px; } diff --git a/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss b/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss index 0759cf49..b0027bff 100644 --- a/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss +++ b/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss @@ -90,7 +90,7 @@ $pr--button-color: $pr--nav-fg-prim-color; .register-piece--info { text-align: center; - + h1, h2 { font-variant: small-caps; } diff --git a/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss b/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss index 4cfb7c82..8c46d366 100644 --- a/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss +++ b/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss @@ -1,7 +1,3 @@ -$sluice--nav-bg-color: #fcfcfc; -$sluice--nav-fg-prim-color: #1E1E1E; -$sluice--button-color: $sluice--nav-fg-prim-color; - .wp { height: 100%; max-width: 90%; @@ -37,10 +33,10 @@ $sluice--button-color: $sluice--nav-fg-prim-color; .rating-container { - color: lighten($sluice--nav-fg-prim-color, 80%) !important; + color: lighten($simple-prize--nav-fg-prim-color, 80%) !important; .rating-stars { width: 25px; - color: $sluice--nav-fg-prim-color !important; + color: $simple-prize--nav-fg-prim-color !important; } } @@ -73,185 +69,4 @@ $sluice--button-color: $sluice--nav-fg-prim-color; } -.client--sluice { - .navbar-default { - background-color: $sluice--nav-bg-color; - box-shadow: none; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - } - .navbar-nav > li > a, - .navbar-nav > li > .active a { - color: $sluice--nav-fg-prim-color; - background-color: $sluice--nav-bg-color; - } - .navbar-nav > li > a:hover { - color: lighten($sluice--nav-fg-prim-color, 40%); - } - .navbar-nav > .active a, - .navbar-nav > .active a:hover, - .navbar-nav > .active a:focus { - color: $sluice--nav-fg-prim-color; - border-bottom-color: $sluice--nav-fg-prim-color; - background-color: $sluice--nav-bg-color; - } - .dropdown-menu > li > a:hover, - .dropdown-menu > li > a:focus { - color: lighten($sluice--nav-fg-prim-color, 40%); - background-color: $sluice--nav-bg-color; - } - .navbar-nav > .open > a, - .navbar-nav > .open > a:hover, - .navbar-nav > .open > a:focus, - .dropdown-menu > .active > a, - .dropdown-menu > .active > a:hover, - .dropdown-menu > .active > a:focus { - color: lighten($sluice--nav-fg-prim-color, 40%); - background-color: $sluice--nav-bg-color; - } - .dropdown-menu { - background-color: $sluice--nav-bg-color; - } - .dropdown-menu > li > a { - color: $sluice--nav-fg-prim-color; - } - - .navbar-toggle .icon-bar { - background-color: $sluice--nav-fg-prim-color; - } - - .navbar-toggle:hover, - .navbar-toggle:focus { - background-color: $sluice--nav-bg-color; - } -} - -.client--sluice .ascribe-footer { - display: none; -} - - -.client--sluice .icon-ascribe-search{ - color: $sluice--button-color; -} - -.client--sluice .ascribe-piece-list-toolbar .btn-ascribe-add{ - display: none; -} - -// disabled buttons -.client--sluice { - .btn-default.disabled, - .btn-default.disabled:hover, - .btn-default.disabled:focus, - .btn-default.disabled.focus, - .btn-default.disabled:active, - .btn-default.disabled.active, - .btn-default[disabled], - .btn-default[disabled]:hover, - .btn-default[disabled]:focus, - .btn-default[disabled].focus, - .btn-default[disabled]:active, - .btn-default[disabled].active, - fieldset[disabled] .btn-default, - fieldset[disabled] .btn-default:hover, - fieldset[disabled] .btn-default:focus, - fieldset[disabled] .btn-default.focus, - fieldset[disabled] .btn-default:active, - fieldset[disabled] .btn-default.active { - background-color: darken($sluice--button-color, 20%); - border-color: darken($sluice--button-color, 20%); - } -} - -// buttons! -// thought of the day: -// "every great atrocity is the result of people just following orders" -.client--sluice { - .ascribe-piece-list-toolbar-filter-widget button { - color: $sluice--button-color !important; - background-color: transparent !important; - border-color: transparent !important; - - &:hover, - &:active { - background-color: $sluice--button-color !important; - border-color: $sluice--button-color !important; - color: white !important; - } - } - - .btn-wide, - .btn-default { - background-color: $sluice--button-color; - border-color: $sluice--button-color; - - &:hover, - &:active, - &:focus, - &:active:hover, - &:active:focus, - &:active.focus, - &.active:hover, - &.active:focus, - &.active.focus { - background-color: lighten($sluice--button-color, 20%); - border-color: lighten($sluice--button-color, 20%); - } - } - - .open > .btn-default.dropdown-toggle:hover, - .open > .btn-default.dropdown-toggle:focus, - .open > .btn-default.dropdown-toggle.focus, - .open > .btn-default.dropdown-toggle.dropdown-toggle { - background-color: darken($sluice--button-color, 20%); - border-color: darken($sluice--button-color, 20%); - } - - .pager li > a, .pager li > span { - background-color: $sluice--button-color; - border-color: $sluice--button-color; - } - - .pager li.disabled > a, - .pager li.disabled > span { - background-color: $sluice--button-color !important; - border-color: $sluice--button-color; - } -} - -// spinner! -.client--sluice { - .btn-spinner { - color: $sluice--button-color; - } - .spinner-circle { - border-color: $sluice--button-color; - } - .spinner-inner { - color: $sluice--button-color; - display: none; - } -} - -// intercom stuff -.client--sluice { - #intercom-container .intercom-launcher-button { - background-color: $sluice--button-color !important;; - border-color: $sluice--button-color !important;; - } -} - -// notifications -.client--sluice .ascribe-global-notification-success { - background-color: lighten($sluice--button-color, 50%); -} - -// progress bar -.client--sluice .ascribe-progress-bar > .progress-bar { - background-color: $sluice--button-color; -} - -.client--sluice .acl-information-dropdown-list .title { - color: $sluice--button-color; -} \ No newline at end of file diff --git a/sass/whitelabel/prize/simple_prize/simple_prize_variables.scss b/sass/whitelabel/prize/simple_prize/simple_prize_variables.scss new file mode 100644 index 00000000..64ec5c9c --- /dev/null +++ b/sass/whitelabel/prize/simple_prize/simple_prize_variables.scss @@ -0,0 +1,3 @@ +$simple-prize--nav-bg-color: #fcfcfc; +$simple-prize--nav-fg-prim-color: #1E1E1E; +$simple-prize--button-color: $simple-prize--nav-fg-prim-color; diff --git a/sass/whitelabel/prize/sluice/sluice_custom_style.scss b/sass/whitelabel/prize/sluice/sluice_custom_style.scss new file mode 100644 index 00000000..bcc4978c --- /dev/null +++ b/sass/whitelabel/prize/sluice/sluice_custom_style.scss @@ -0,0 +1,182 @@ +.client--sluice { + .navbar-default { + background-color: $simple-prize--nav-bg-color; + box-shadow: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + } + .navbar-nav > li > a, + .navbar-nav > li > .active a { + color: $simple-prize--nav-fg-prim-color; + background-color: $simple-prize--nav-bg-color; + } + .navbar-nav > li > a:hover { + color: lighten($simple-prize--nav-fg-prim-color, 40%); + } + .navbar-nav > .active a, + .navbar-nav > .active a:hover, + .navbar-nav > .active a:focus { + color: $simple-prize--nav-fg-prim-color; + border-bottom-color: $simple-prize--nav-fg-prim-color; + background-color: $simple-prize--nav-bg-color; + } + .dropdown-menu > li > a:hover, + .dropdown-menu > li > a:focus { + color: lighten($simple-prize--nav-fg-prim-color, 40%); + background-color: $simple-prize--nav-bg-color; + } + .navbar-nav > .open > a, + .navbar-nav > .open > a:hover, + .navbar-nav > .open > a:focus, + .dropdown-menu > .active > a, + .dropdown-menu > .active > a:hover, + .dropdown-menu > .active > a:focus { + color: lighten($simple-prize--nav-fg-prim-color, 40%); + background-color: $simple-prize--nav-bg-color; + } + .dropdown-menu { + background-color: $simple-prize--nav-bg-color; + } + + .dropdown-menu > li > a { + color: $simple-prize--nav-fg-prim-color; + } + + .navbar-toggle .icon-bar { + background-color: $simple-prize--nav-fg-prim-color; + } + + .navbar-toggle:hover, + .navbar-toggle:focus { + background-color: $simple-prize--nav-bg-color; + } +} + +.client--sluice .ascribe-footer { + display: none; +} + + +.client--sluice .icon-ascribe-search{ + color: $simple-prize--button-color; +} + +.client--sluice .ascribe-piece-list-toolbar .btn-ascribe-add{ + display: none; +} + +// disabled buttons +.client--sluice { + .btn-default.disabled, + .btn-default.disabled:hover, + .btn-default.disabled:focus, + .btn-default.disabled.focus, + .btn-default.disabled:active, + .btn-default.disabled.active, + .btn-default[disabled], + .btn-default[disabled]:hover, + .btn-default[disabled]:focus, + .btn-default[disabled].focus, + .btn-default[disabled]:active, + .btn-default[disabled].active, + fieldset[disabled] .btn-default, + fieldset[disabled] .btn-default:hover, + fieldset[disabled] .btn-default:focus, + fieldset[disabled] .btn-default.focus, + fieldset[disabled] .btn-default:active, + fieldset[disabled] .btn-default.active { + background-color: darken($simple-prize--button-color, 20%); + border-color: darken($simple-prize--button-color, 20%); + } +} + +// buttons! +// thought of the day: +// "every great atrocity is the result of people just following orders" +.client--sluice { + .ascribe-piece-list-toolbar-filter-widget button { + color: $simple-prize--button-color !important; + background-color: transparent !important; + border-color: transparent !important; + + &:hover, + &:active { + background-color: $simple-prize--button-color !important; + border-color: $simple-prize--button-color !important; + color: white !important; + } + } + + .btn-wide, + .btn-default { + background-color: $simple-prize--button-color; + border-color: $simple-prize--button-color; + + &:hover, + &:active, + &:focus, + &:active:hover, + &:active:focus, + &:active.focus, + &.active:hover, + &.active:focus, + &.active.focus { + background-color: lighten($simple-prize--button-color, 20%); + border-color: lighten($simple-prize--button-color, 20%); + } + } + + .open > .btn-default.dropdown-toggle:hover, + .open > .btn-default.dropdown-toggle:focus, + .open > .btn-default.dropdown-toggle.focus, + .open > .btn-default.dropdown-toggle.dropdown-toggle { + background-color: darken($simple-prize--button-color, 20%); + border-color: darken($simple-prize--button-color, 20%); + } + + .pager li > a, .pager li > span { + background-color: $simple-prize--button-color; + border-color: $simple-prize--button-color; + } + + .pager li.disabled > a, + .pager li.disabled > span { + background-color: $simple-prize--button-color !important; + border-color: $simple-prize--button-color; + } +} + +// spinner! +.client--sluice { + .btn-spinner { + color: $simple-prize--button-color; + } + .spinner-circle { + border-color: $simple-prize--button-color; + } + .spinner-inner { + color: $simple-prize--button-color; + display: none; + } +} + +// intercom stuff +.client--sluice { + #intercom-container .intercom-launcher-button { + background-color: $simple-prize--button-color !important;; + border-color: $simple-prize--button-color !important;; + } +} + +// notifications +.client--sluice .ascribe-global-notification-success { + background-color: lighten($simple-prize--button-color, 50%); +} + +// progress bar +.client--sluice .ascribe-progress-bar > .progress-bar { + background-color: $simple-prize--button-color; +} + +.client--sluice .acl-information-dropdown-list .title { + color: $simple-prize--button-color; +} From 764f81925be4128e25ac29f5501a80edb0bcc1e5 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 14:48:44 +0100 Subject: [PATCH 25/30] Change `children` prop from react-router to only be a Element type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Judging by https://github.com/rackt/react-router/blob/master/docs/API.md#children-1 and a few inspections in the code, as well as tests, the `children` prop injected into routes can only ever be a single React Element object. This allows us to easily get the active route of a child (if there is an active route) by querying the `children`’s route prop. --- js/components/app_base.js | 20 ++++++++++++------- js/components/ascribe_app.js | 11 ++++------ .../prize/portfolioreview/pr_app.js | 11 ++++------ .../prize/simple_prize/prize_app.js | 11 ++++------ js/components/whitelabel/wallet/wallet_app.js | 11 ++++------ js/routes.js | 10 +++++----- 6 files changed, 34 insertions(+), 40 deletions(-) diff --git a/js/components/app_base.js b/js/components/app_base.js index 044b984c..4b5d460e 100644 --- a/js/components/app_base.js +++ b/js/components/app_base.js @@ -15,14 +15,10 @@ export default function AppBase(App) { displayName: 'AppBase', propTypes: { + children: React.PropTypes.element.isRequired, history: React.PropTypes.object.isRequired, location: React.PropTypes.object.isRequired, - routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]) + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, mixins: [History], @@ -42,10 +38,20 @@ export default function AppBase(App) { }, render() { + const { children } = this.props; + + // Get the currently active route of the app by using the injected route parameter + // on the currently active child route. + // Note that despite its name, this.props.children can only ever be a single + // React.PropTypes.element. + const activeRoute = children.props.route; + return (
    -
    + diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 8999246e..0e28aa97 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -8,16 +8,13 @@ import Header from './header'; let AscribeApp = React.createClass({ propTypes: { - routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]) + activeRoute: React.PropTypes.object.isRequired, + children: React.PropTypes.element.isRequired, + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, render() { - const { children, routes } = this.props; + const { activeRoute, children, routes } = this.props; return (
    diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js index 7e21d7c3..04b985db 100644 --- a/js/components/whitelabel/prize/portfolioreview/pr_app.js +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -19,13 +19,10 @@ import { getCookie } from '../../../../utils/fetch_api_utils'; let PRApp = React.createClass({ propTypes: { + activeRoute: React.PropTypes.object.isRequired, + children: React.PropTypes.element.isRequired, history: React.PropTypes.object.isRequired, - routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]) + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, getInitialState() { @@ -60,7 +57,7 @@ let PRApp = React.createClass({ render() { - const { children, history, routes } = this.props; + const { activeRoute, children, history, routes } = this.props; const { currentUser } = this.state; const subdomain = getSubdomain(); diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index da02a597..d2a440dc 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -13,17 +13,14 @@ import { getSubdomain } from '../../../../utils/general_utils'; let PrizeApp = React.createClass({ propTypes: { + activeRoute: React.PropTypes.object.isRequired, + children: React.PropTypes.element.isRequired, history: React.PropTypes.object.isRequired, - routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]) + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, render() { - const { children, history, routes } = this.props; + const { activeRoute, children, history, routes } = this.props; const subdomain = getSubdomain(); // The second element of routes is always the active component object, where we can diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 3940d89e..7f30e203 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -11,17 +11,14 @@ import { getSubdomain } from '../../../utils/general_utils'; let WalletApp = React.createClass({ propTypes: { + activeRoute: React.PropTypes.object.isRequired, + children: React.PropTypes.element.isRequired, history: React.PropTypes.object.isRequired, - routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]) + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, render() { - const { children, history, routes } = this.props; + const { activeRoute, children, history, routes } = this.props; const subdomain = getSubdomain(); // The second element of routes is always the active component object, where we can diff --git a/js/routes.js b/js/routes.js index 24df99c0..d7a1d0c4 100644 --- a/js/routes.js +++ b/js/routes.js @@ -6,7 +6,7 @@ import { Route } from 'react-router'; import getPrizeRoutes from './components/whitelabel/prize/prize_routes'; import getWalletRoutes from './components/whitelabel/wallet/wallet_routes'; -import App from './components/ascribe_app'; +import AscribeApp from './components/ascribe_app'; import PieceList from './components/piece_list'; import PieceContainer from './components/ascribe_detail/piece_container'; @@ -29,14 +29,14 @@ import { ProxyHandler, AuthRedirect } from './components/ascribe_routes/proxy_ha const COMMON_ROUTES = ( - + + headerTitle='+ NEW WORK' /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} /> From e616576fa538ace8bf56f0eb6181ea7a054cc1d7 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 1 Feb 2016 14:49:48 +0100 Subject: [PATCH 26/30] Hide the footer if a route has the `hideFooter` attribute --- js/components/app_base.js | 2 -- js/components/ascribe_app.js | 2 ++ js/components/footer.js | 8 ++++++-- js/components/whitelabel/prize/portfolioreview/pr_app.js | 2 ++ js/components/whitelabel/prize/simple_prize/prize_app.js | 2 ++ js/components/whitelabel/wallet/wallet_app.js | 2 ++ 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/js/components/app_base.js b/js/components/app_base.js index 4b5d460e..de1aab71 100644 --- a/js/components/app_base.js +++ b/js/components/app_base.js @@ -4,7 +4,6 @@ import React from 'react'; import classNames from 'classnames'; import { History } from 'react-router'; -import Footer from './footer'; import GlobalNotification from './global_notification'; import AppConstants from '../constants/application_constants'; @@ -48,7 +47,6 @@ export default function AppBase(App) { return (
    -
    diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 0e28aa97..737a35f5 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -3,6 +3,7 @@ import React from 'react'; import AppBase from './app_base'; +import Footer from './footer'; import Header from './header'; @@ -23,6 +24,7 @@ let AscribeApp = React.createClass({ {/* Routes are injected here */} {children}
    +
    ); } diff --git a/js/components/footer.js b/js/components/footer.js index f2e35dfc..3010da4d 100644 --- a/js/components/footer.js +++ b/js/components/footer.js @@ -5,8 +5,12 @@ import React from 'react'; import { getLangText } from '../utils/lang_utils'; let Footer = React.createClass({ + propTypes: { + activeRoute: React.PropTypes.object.isRequired + }, + render() { - return ( + return !this.props.activeRoute.hideFooter ? (


    @@ -24,7 +28,7 @@ let Footer = React.createClass({

    - ); + ) : null; } }); diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js index 04b985db..c738c0bd 100644 --- a/js/components/whitelabel/prize/portfolioreview/pr_app.js +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -11,6 +11,7 @@ import UserActions from '../../../../actions/user_actions'; import Hero from './components/pr_hero'; import AppBase from '../../../app_base'; +import Footer from '../../../footer'; import Header from '../../../header'; import { getSubdomain } from '../../../../utils/general_utils'; @@ -85,6 +86,7 @@ let PRApp = React.createClass({ {/* Routes are injected here */} {children}
    +
    ); } diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index d2a440dc..e3f1e290 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -6,6 +6,7 @@ import classNames from 'classnames'; import Hero from './components/prize_hero'; import AppBase from '../../../app_base'; +import Footer from '../../../footer'; import Header from '../../../header'; import { getSubdomain } from '../../../../utils/general_utils'; @@ -42,6 +43,7 @@ let PrizeApp = React.createClass({ {/* Routes are injected here */} {children}
    +
    ); } diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 7f30e203..c7afaa01 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -4,6 +4,7 @@ import React from 'react'; import classNames from 'classnames'; import AppBase from '../../app_base'; +import Footer from '../../footer'; import Header from '../../header'; import { getSubdomain } from '../../../utils/general_utils'; @@ -43,6 +44,7 @@ let WalletApp = React.createClass({ {/* Routes are injected here */} {children}
    +
    ); } From 6f2424d1035f4a3a50f32cce8ba17ec6b71bdd01 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:32:00 +0100 Subject: [PATCH 27/30] Rebase remerge --- .../whitelabel/prize/prize_routes.js | 90 +++++-- js/components/whitelabel/wallet/wallet_app.js | 5 +- .../whitelabel/wallet/wallet_routes.js | 241 +++++++++++++----- .../portfolioreview_custom_style.scss | 1 - .../simple_prize_custom_style.scss | 2 - .../wallet/23vivi/23vivi_custom_style.scss | 5 +- .../whitelabel/wallet/cc/cc_custom_style.scss | 11 +- .../wallet/cyland/cyland_custom_style.scss | 5 +- .../wallet/ikonotv/ikonotv_custom_style.scss | 44 ++-- sass/whitelabel/wallet/index.scss | 1 - 10 files changed, 269 insertions(+), 136 deletions(-) diff --git a/js/components/whitelabel/prize/prize_routes.js b/js/components/whitelabel/prize/prize_routes.js index 5f80b30c..73608ae2 100644 --- a/js/components/whitelabel/prize/prize_routes.js +++ b/js/components/whitelabel/prize/prize_routes.js @@ -31,74 +31,116 @@ import { AuthPrizeRoleRedirect } from './portfolioreview/components/pr_routes/pr const ROUTES = { sluice: ( - + + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SPLoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SPSignupContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPSettingsContainer)} + hideFooter /> + headerTitle='+ NEW WORK' + hideFooter /> - - - - + headerTitle='COLLECTION' + hideFooter /> + + + + ), portfolioreview: ( - + + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PRRegisterPiece)} + hideFooter /> + headerTitle='SUBMISSIONS' + hideFooter /> + )(SPLoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + )(SPSignupContainer)} + hideFooter /> + )(PasswordResetContainer)} + hideFooter /> - - - - + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPSettingsContainer)} + hideFooter /> + + + + ) }; diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index c7afaa01..10ebd2b3 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -28,8 +28,9 @@ let WalletApp = React.createClass({ let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute - if ((!path || history.isActive('/login') || history.isActive('/signup') || history.isActive('/contract_notifications')) - && (['cyland', 'ikonotv', 'lumenus', '23vivi']).indexOf(subdomain) > -1) { + if ((!path || history.isActive('/login') || history.isActive('/signup') || + history.isActive('/contract_notifications')) && + (['cyland', 'ikonotv', 'lumenus', '23vivi']).includes(subdomain)) { header = (
    ); } else { header = (
    ); diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index f1613ee5..bf2008c3 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -48,193 +48,302 @@ import WalletApp from './wallet_app'; let ROUTES = { 'cyland': ( - + + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} + hideFooter /> + aclName='acl_wallet_submit' + hideFooter /> - - - - + disableOn='noPieces' + hideFooter /> + + + + ), 'cc': ( + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} + hideFooter /> + headerTitle='+ NEW WORK' + hideFooter /> - - - - + disableOn='noPieces' + hideFooter /> + + + + ), 'ikonotv': ( - + + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} + hideFooter /> + aclName='acl_create_contractagreement' + hideFooter /> + aclName='acl_wallet_submit' + hideFooter /> + disableOn='noPieces' + hideFooter /> - - - - + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} + hideFooter /> + + + + ), 'lumenus': ( - + + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} + hideFooter /> + aclName='acl_wallet_submit' + hideFooter /> - - - - + disableOn='noPieces' + hideFooter /> + + + + ), '23vivi': ( - + + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} + hideFooter /> + component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} + hideFooter /> + aclName='acl_wallet_submit' + hideFooter /> - - - - + disableOn='noPieces' + hideFooter /> + + + + ) }; diff --git a/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss b/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss index b0027bff..385f4ed7 100644 --- a/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss +++ b/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss @@ -2,7 +2,6 @@ $pr--nav-fg-prim-color: black; $pr--button-color: $pr--nav-fg-prim-color; .client--portfolioreview { - .btn-wide, .btn-default { background-color: $pr--button-color; diff --git a/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss b/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss index 8c46d366..e022f34e 100644 --- a/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss +++ b/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss @@ -68,5 +68,3 @@ padding: 0.7em; } - - diff --git a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss index 3e6e20cf..f09d9b13 100644 --- a/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss +++ b/sass/whitelabel/wallet/23vivi/23vivi_custom_style.scss @@ -13,6 +13,7 @@ $vivi23--highlight-color: #de2600; /** Landing page **/ .route--landing { display: table; + min-height: 100vh; > .container { display: table-cell; @@ -252,10 +253,6 @@ $vivi23--highlight-color: #de2600; display: none; } - .ascribe-footer { - display: none; - } - .ascribe-accordion-list-table-toggle:hover { color: $vivi23--fg-color; } diff --git a/sass/whitelabel/wallet/cc/cc_custom_style.scss b/sass/whitelabel/wallet/cc/cc_custom_style.scss index 774f5b27..348fca1f 100644 --- a/sass/whitelabel/wallet/cc/cc_custom_style.scss +++ b/sass/whitelabel/wallet/cc/cc_custom_style.scss @@ -55,16 +55,11 @@ $cc--button-color: $cc--nav-fg-prim-color; } } -.client--cc .ascribe-footer { - display: none; -} - - -.client--cc .icon-ascribe-search{ +.client--cc .icon-ascribe-search { color: $cc--button-color; } -.client--cc .ascribe-piece-list-toolbar .btn-ascribe-add{ +.client--cc .ascribe-piece-list-toolbar .btn-ascribe-add { display: none; } @@ -223,4 +218,4 @@ $cc--button-color: $cc--nav-fg-prim-color; .client--cc .upload-button-wrapper > span { color: $cc--button-color; -} \ No newline at end of file +} diff --git a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss index 549b2004..0a7226ac 100644 --- a/sass/whitelabel/wallet/cyland/cyland_custom_style.scss +++ b/sass/whitelabel/wallet/cyland/cyland_custom_style.scss @@ -56,10 +56,6 @@ $cyland--button-sec-color: #515151; } } -.client--cyland .ascribe-footer { - display: none; -} - .client--cyland .icon-ascribe-search { color: $cyland--button-color; } @@ -171,6 +167,7 @@ $cyland--button-sec-color: #515151; .client--cyland { .route--landing { display: table; + min-height: 100vh; > .container { display: table-cell; diff --git a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss index 8f330911..1d7a4461 100644 --- a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss +++ b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss @@ -108,24 +108,36 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important; } +.client--ikonotv { + .route--landing, + .route--login, + .route--signup { + background-color: $ikono--landing-bg-color; + min-height: 100vh; + } + + .route--login, + .route--signup { + .ascribe-form-bordered { + border: none; + } + } +} + .client--ikonotv .route--landing { - background-color: $ikono--landing-bg-color; animation: color-loop 20s; -o-animation: color-loop 20s infinite; -ms-animation: color-loop 20s infinite; -moz-animation: color-loop 20s infinite; -webkit-animation: color-loop 20s infinite; - + margin: 0; width: 100%; padding: 5em 1em; } - .client--ikonotv .route--login, .client--ikonotv .route--signup { - background-color: $ikono--landing-bg-color; - .btn-wide { display: block; margin: 50px auto 0; @@ -209,27 +221,11 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important; } -.client--ikonotv { - .route--login, - .route--signup { - .ascribe-form-bordered { - border: none; - } - } -} - -.client--ikonotv .ascribe-login-wrapper { -} - -.client--ikonotv .ascribe-footer { - display: none; -} - -.client--ikonotv .icon-ascribe-search{ +.client--ikonotv .icon-ascribe-search { color: $ikono--button-color; } -.client--ikonotv .ascribe-piece-list-toolbar .btn-ascribe-add{ +.client--ikonotv .ascribe-piece-list-toolbar .btn-ascribe-add { display: none; } @@ -560,4 +556,4 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important; .client--ikonotv .upload-button-wrapper > span { color: $ikono--button-color; -} \ No newline at end of file +} diff --git a/sass/whitelabel/wallet/index.scss b/sass/whitelabel/wallet/index.scss index 647bb16c..f480ac8d 100644 --- a/sass/whitelabel/wallet/index.scss +++ b/sass/whitelabel/wallet/index.scss @@ -5,7 +5,6 @@ .ascribe-wallet-app { border-radius: 0; - min-height: 100vh; padding-top: 70px; padding-bottom: 10px; } From 8f2ace77af8da1ceecf80ce664d3572fcdb1857b Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 3 Feb 2016 12:28:49 +0100 Subject: [PATCH 28/30] Use previous method from white labels to get the current active route --- js/components/app_base.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/js/components/app_base.js b/js/components/app_base.js index de1aab71..c188e628 100644 --- a/js/components/app_base.js +++ b/js/components/app_base.js @@ -37,13 +37,11 @@ export default function AppBase(App) { }, render() { - const { children } = this.props; + const { routes } = this.props; - // Get the currently active route of the app by using the injected route parameter - // on the currently active child route. - // Note that despite its name, this.props.children can only ever be a single - // React.PropTypes.element. - const activeRoute = children.props.route; + // The second element of the routes prop given to us by react-router is always the + // active second-level component object (ie. after App). + const activeRoute = routes[1]; return (
    From f1b677dbd16a5dc85331b148a2e4f5980048ab67 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Wed, 3 Feb 2016 12:38:34 +0100 Subject: [PATCH 29/30] Use the activeRoute prop to generate route classnames on whitelabels --- js/components/whitelabel/prize/portfolioreview/pr_app.js | 5 +---- js/components/whitelabel/prize/simple_prize/prize_app.js | 5 +---- js/components/whitelabel/wallet/wallet_app.js | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js index c738c0bd..73df2766 100644 --- a/js/components/whitelabel/prize/portfolioreview/pr_app.js +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -61,10 +61,7 @@ let PRApp = React.createClass({ const { activeRoute, children, history, routes } = this.props; const { currentUser } = this.state; const subdomain = getSubdomain(); - - // The second element of routes is always the active component object, where we can - // extract the path. - const path = routes[1] ? routes[1].path : null; + const path = activeRoute && activeRoute.path; let style = {}; let header; diff --git a/js/components/whitelabel/prize/simple_prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js index e3f1e290..97ccb53c 100644 --- a/js/components/whitelabel/prize/simple_prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -23,10 +23,7 @@ let PrizeApp = React.createClass({ render() { const { activeRoute, children, history, routes } = this.props; const subdomain = getSubdomain(); - - // The second element of routes is always the active component object, where we can - // extract the path. - const path = routes[1] ? routes[1].path : null; + const path = activeRoute && activeRoute.path; let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute diff --git a/js/components/whitelabel/wallet/wallet_app.js b/js/components/whitelabel/wallet/wallet_app.js index 10ebd2b3..ec709ffc 100644 --- a/js/components/whitelabel/wallet/wallet_app.js +++ b/js/components/whitelabel/wallet/wallet_app.js @@ -21,10 +21,7 @@ let WalletApp = React.createClass({ render() { const { activeRoute, children, history, routes } = this.props; const subdomain = getSubdomain(); - - // The second element of routes is always the active component object, where we can - // extract the path. - const path = routes[1] ? routes[1].path : null; + const path = activeRoute && activeRoute.path; let header = null; // if the path of the current activeRoute is not defined, then this is the IndexRoute From 3bb1f65f38696562dbdff5d9aeb7a8c02f8358cb Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 4 Feb 2016 13:40:40 +0100 Subject: [PATCH 30/30] Add whitespace for different npm test scripts --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 5e72b863..a40c19bb 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "build": "gulp build --production", "start": "node server.js", "test": "npm run sauce-test", + "sauce-test": "mocha ./test/integration/tests/", "tunnel": "node ./test/integration/tunnel.js", + "vi-clean": "rm -rf ./gemini-report", "vi-phantom": "phantomjs --webdriver=4444", "vi-update": "gemini update -c ./test/gemini/.gemini.yml",