diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..0a714502 --- /dev/null +++ b/.babelrc @@ -0,0 +1,27 @@ +{ + 'presets': ['react', 'es2015-no-commonjs'], + 'plugins': [ + 'transform-object-assign', + 'transform-object-rest-spread', + 'transform-react-display-name', + [ 'transform-runtime', { + 'polyfill': false, + 'regenerator': false + } ] + ], + 'sourceMaps': true, + + 'env': { + 'demo': { + 'plugins': [ + [ 'react-transform', { + 'transforms': [{ + 'transform': 'react-transform-hmr', + 'imports': ['react'], + 'locals': ['module'] + }] + } ] + ] + } + } +} diff --git a/.bootstraprc b/.bootstraprc new file mode 100644 index 00000000..2267939a --- /dev/null +++ b/.bootstraprc @@ -0,0 +1,16 @@ +--- +bootstrapVersion: 3 + +# Phase this out, first with react-component's bootstrap overrides, and then nothing at all +preBootstrapCustomizations: ./sass/bootstrap/overrides + +styleLoaders: + - style + - css?sourceMap + - postcss + - sass?sourceMap&output=expanded&precision=8 + +# For now, just include everything from Bootstrap +styles: true + +scripts: false diff --git a/.env-template b/.env-template deleted file mode 100644 index 8c4fe11c..00000000 --- a/.env-template +++ /dev/null @@ -1,3 +0,0 @@ -SAUCE_USERNAME=ascribe -SAUCE_ACCESS_KEY= -SAUCE_DEFAULT_URL= diff --git a/.env_template b/.env_template new file mode 100644 index 00000000..aa8ed8ce --- /dev/null +++ b/.env_template @@ -0,0 +1,25 @@ +# App settings +ONION_APP_VERSION= + +ONION_API_URL=http://localhost.com:8000/api +ONION_BASE_PATH= +ONION_SERVER_URL=http://localhost.com:8000 + +ONION_PORT=4000 + +# Raven settings +RAVEN_DSN_URL= + +# S3 settings +S3_ACCESS_KEY= + + +# Dev server configuration +ONION_DEV_HOST=localhost +ONION_DEV_PORT=3000 + + +# Integration testing +SAUCE_USERNAME=ascribe +SAUCE_ACCESS_KEY= +SAUCE_DEFAULT_URL= diff --git a/.eslintignore b/.eslintignore index 6a87a8a8..c90c8347 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,5 @@ -. -gulpfile.js -node_modules +build/* +dist/* +node_modules/* -js/**/__tests__ - -server.js -js/components/ascribe_uploader/vendor \ No newline at end of file +js/components/ascribe_uploader/vendor/* diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 5751f3ad..00000000 --- a/.eslintrc +++ /dev/null @@ -1,61 +0,0 @@ -{ - "parser": "babel-eslint", - "env": { - "browser": true, - "es6": true, - }, - "rules": { - "new-cap": [2, {newIsCap: true, capIsNew: false}], - "quotes": [2, "single"], - "eol-last": [0], - "no-mixed-requires": [0], - "no-underscore-dangle": [0], - "global-strict": [2, "always"], - "no-trailing-spaces": [2, { skipBlankLines: true }], - "no-console": 0, - "camelcase": [2, {"properties": "never"}], - "react/display-name": 0, - "react/jsx-boolean-value": 1, - "react/jsx-no-undef": 1, - "react/jsx-quotes": 1, - "react/jsx-sort-prop-types": 0, - "react/jsx-sort-props": 0, - "react/jsx-uses-react": 1, - "react/jsx-uses-vars": 1, - "react/no-did-mount-set-state": [1, "allow-in-func"], - "react/no-did-update-set-state": 1, - "react/no-multi-comp": 0, - "react/no-unknown-property": 1, - "react/prop-types": 1, - "react/react-in-jsx-scope": 1, - "react/self-closing-comp": 1, - "react/sort-comp": 1, - "react/wrap-multilines": 1 - }, - "globals": { - "Intercom": true, - "fetch": true, - "require": true, - "File": true - }, - "plugins": [ - "react" - ], - "ecmaFeatures": { - "jsx": 1, - "modules": 1, - "arrowFunctions", - "classes": 1, - "blockBindings": 1, - "defaultParams": 1, - "destructuring": 1, - "objectLiteralComputedProperties": 1, - "objectLiteralDuplicateProperties": 0, - "objectLiteralShorthandMethods": 1, - "objectLiteralShorthandProperties": 1, - "restParams": 1, - "spread": 1, - "superInFunctions": 1, - "templateStrings": 1 - } -} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..ac8a49db --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "ascribe", + "rules": { + "no-console": [1, { "allow": ["error", "logGlobal"] }] + } +} diff --git a/.gitignore b/.gitignore index e497465f..492658f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store lib-cov *.seed *.log @@ -8,22 +9,19 @@ lib-cov *.gz *.sublime-project .idea -spool-project.sublime-project *.sublime-workspace -*.sublime-workspace -webapp-dependencies.txt pids logs results +.env + build/* +dist/* gemini-coverage/* gemini-report/* test/gemini/screenshots/* node_modules/* - -.DS_Store -.env diff --git a/README.md b/README.md index 0b24374f..b9a7e2b5 100644 --- a/README.md +++ b/README.md @@ -170,12 +170,11 @@ A: Use `npm dedupe` to remove duplicates in npm. This might fix that you're not Q: How can I use a local copy of SPOOL and Onion? A: Easily by starting the your gulp process with the following command: ``` -ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' gulp serve +ONION_SERVER_URL='http://localhost.com:8000/' gulp serve ``` -Or, by adding these two your environment variables: +Or, by adding the local server url to your environment variables: ``` -ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' ``` diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index afa0d5a9..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,207 +0,0 @@ -'use strict'; - -require("harmonize")(); - -var gulp = require('gulp'); -var template = require('gulp-template'); -var gulpif = require('gulp-if'); -var sourcemaps = require('gulp-sourcemaps'); -var util = require('gulp-util'); -var source = require('vinyl-source-stream'); -var buffer = require('vinyl-buffer'); -var watchify = require('watchify'); -var browserify = require('browserify'); -var browserSync = require('browser-sync'); -var babelify = require('babelify'); -var notify = require('gulp-notify'); -var sass = require('gulp-sass'); -var concat = require('gulp-concat'); -var _ = require('lodash'); -var eslint = require('gulp-eslint'); -var jest = require('jest-cli'); -var argv = require('yargs').argv; -var server = require('./server.js').app; -var minifyCss = require('gulp-minify-css'); -var uglify = require('gulp-uglify'); -var opn = require('opn'); - - -var config = { - bootstrapDir: './node_modules/bootstrap-sass', - jestOptions: { - rootDir: 'js', - scriptPreprocessor: '../node_modules/babel-jest', - testFileExtensions: [ - 'es6', - 'js' - ], - unmockedModulePathPatterns: [ - '/node_modules/react', - '/node_modules/react-tools' - ], - moduleFileExtensions: [ - 'js', - 'json', - 'react', - 'es6' - ] - }, - filesToWatch: [ - 'build/css/*.css', - 'build/js/*.js' - ] -}; - -var SERVER_URL = process.env.ONION_SERVER_URL || 'https://staging.ascribe.io/'; - -var constants = { - BASE_URL: (function () { var baseUrl = process.env.ONION_BASE_URL || '/'; return baseUrl + (baseUrl.match(/\/$/) ? '' : '/'); })(), - SERVER_URL: SERVER_URL, - API_ENDPOINT: SERVER_URL + 'api/' || 'https://staging.ascribe.io/api/', - DEBUG: !argv.production, - CREDENTIALS: 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw' // dimi@mailinator:0000000000 -}; - - -gulp.task('build', ['js:build', 'sass:build', 'copy'], function() { -}); - -gulp.task('js:build', function() { - bundle(false); -}); - -gulp.task('serve', ['browser-sync', 'run-server', 'sass:build', 'sass:watch', 'copy'], function() { - bundle(true); - - // opens the browser window with the correct url, which is localhost.com - opn('http://www.localhost.com:3000'); -}); - -gulp.task('jest', function(done) { - jest.runCLI({ config : config.jestOptions }, ".", function() { - done(); - }); -}); - -gulp.task('jest:watch', function(done) { - gulp.watch([ config.jestOptions.rootDir + "/**/*.js" ], [ 'jest' ]); -}); - -gulp.task('run-server', function() { - server.listen(4000); -}); - -gulp.task('browser-sync', function() { - browserSync({ - files: config.filesToWatch, - proxy: 'http://localhost:4000', - port: 3000, - open: false, // does not open the browser-window anymore (handled manually) - ghostMode: false, - notify: false // stop showing the browsersync pop up - }); -}); - -gulp.task('sass:build', function () { - gulp.src('./sass/**/main.scss') - .pipe(template(constants)) - .pipe(gulpif(!argv.production, sourcemaps.init())) - .pipe(sass({ - includePaths: [ - config.bootstrapDir + '/assets/stylesheets' - ] - }).on('error', sass.logError)) - .pipe(gulpif(!argv.production, sourcemaps.write('./maps'))) - // We need to set `advanced` to false, as it merges - // some of the styles wrongly - .pipe(gulpif(argv.production, minifyCss({ - advanced: false - }))) - .pipe(gulp.dest('./build/css')) - .pipe(browserSync.stream()); -}); - -gulp.task('sass:watch', function () { - gulp.watch('./sass/**/*.scss', ['sass:build']); -}); - -gulp.task('copy', function () { - var staticAssets = [ - './fonts/**/*', - './img/**/*' - ]; - - gulp.src(staticAssets, {base: './'}) - .pipe(gulp.dest('./build')); - - gulp.src(config.bootstrapDir + '/assets/fonts/**/*') - .pipe(gulp.dest('./build/fonts')); - - gulp.src('./index.html') - .pipe(template(constants)) - .pipe(gulp.dest('./build')); -}); - -gulp.task('lint', function () { - return gulp.src(['js/**/*.js']) - // eslint() attaches the lint output to the eslint property - // of the file object so it can be used by other modules. - .pipe(eslint()) - // eslint.format() outputs the lint results to the console. - // Alternatively use eslint.formatEach() (see Docs). - .pipe(eslint.format()); - // To have the process exit with an error code (1) on - // lint error, return the stream and pipe to failOnError last. -}); - -gulp.task('lint:watch', function () { - gulp.watch('js/**/*.js', ['lint']); -}); - -function bundle(watch) { - var bro; - - if (watch) { - bro = watchify(browserify('./js/app.js', - // Assigning debug to have sourcemaps - _.assign(watchify.args, { - debug: true - }))); - bro.on('update', function() { - rebundle(bro, true); - }); - } else { - bro = browserify('./js/app.js', { - debug: true - }); - } - - bro.transform(babelify.configure({ - compact: false - })); - - function rebundle(bundler, watch) { - return bundler.bundle() - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(source('app.js')) - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(buffer()) - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(gulpif(!argv.production, sourcemaps.init({ - loadMaps: true - }))) // loads map from browserify file - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(gulpif(argv.production, uglify({ - mangle: true - }))) - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(gulp.dest('./build/js')) - .on('error', notify.onError('Error: <%= error.message %>')) - .pipe(browserSync.stream()) - .on('error', notify.onError('Error: <%= error.message %>')); - } - - return rebundle(bro); -} diff --git a/index.html b/index_template.html similarity index 73% rename from index.html rename to index_template.html index fd3c59e9..a85d0d87 100644 --- a/index.html +++ b/index_template.html @@ -1,4 +1,4 @@ - + @@ -10,16 +10,7 @@ ascribe - - <% DEBUG && print('') %> - - - - diff --git a/js/app.js b/js/app.js index dc8e3d62..5ae013e5 100644 --- a/js/app.js +++ b/js/app.js @@ -1,100 +1,90 @@ -'use strict'; - -import 'babel/polyfill'; +import 'core-js/es6'; +import 'core-js/stage/4'; import 'classlist-polyfill'; +import 'isomorphic-fetch'; import React from 'react'; -import { Router, Redirect } from 'react-router'; +import { Router } from 'react-router'; + +import AppResolver from './app_resolver'; import history from './history'; -import fetch from 'isomorphic-fetch'; - -import ApiUrls from './constants/api_urls'; - -import AppConstants from './constants/application_constants'; -import getRoutes from './routes'; -import requests from './utils/requests'; - -import { updateApiUrls } from './constants/api_urls'; import { getDefaultSubdomainSettings, getSubdomainSettings } from './utils/constants_utils'; import { initLogging } from './utils/error_utils'; import { getSubdomain } from './utils/general_utils'; +import requests from './utils/requests'; +// FIXME: rename these event actions import EventActions from './actions/event_actions'; // You can comment out the modules you don't need // import DebugHandler from './third_party/debug_handler'; -import FacebookHandler from './third_party/facebook_handler'; -import GoogleAnalyticsHandler from './third_party/ga_handler'; -import IntercomHandler from './third_party/intercom_handler'; -import NotificationsHandler from './third_party/notifications_handler'; -import RavenHandler from './third_party/raven_handler'; +import './third_party/facebook_handler'; +import './third_party/ga_handler'; +import './third_party/intercom_handler'; +import './third_party/notifications_handler'; +import './third_party/raven_handler'; + +// Import global stylesheet +import '../sass/main.scss'; const AppGateway = { start() { - try { - const subdomain = getSubdomain(); - const settings = getSubdomainSettings(subdomain); + let subdomainSettings; - AppConstants.whitelabel = settings; - updateApiUrls(settings.type, subdomain); - this.load(settings); - } catch(err) { - // if there are no matching subdomains, we're routing - // to the default frontend + try { + subdomainSettings = getSubdomainSettings(getSubdomain()); + } catch (err) { + // if there are no matching subdomains, we''ll route to the default frontend console.logGlobal(err); - this.load(getDefaultSubdomainSettings()); + subdomainSettings = getDefaultSubdomainSettings(); } + + this.load(subdomainSettings); }, load(settings) { - const { subdomain, type } = settings; - let redirectRoute = (); - - if (subdomain) { - // Some whitelabels have landing pages so we should not automatically redirect from / to /collection. - // Only www and cc do not have a landing page. - if (subdomain !== 'cc') { - redirectRoute = null; - } - - // Adds a client specific class to the body for whitelabel styling - window.document.body.classList.add('client--' + subdomain); - } - // Send the applicationWillBoot event to the third-party stores EventActions.applicationWillBoot(settings); - // `history.listen` is called on every route change, which is perfect for - // us in that case. + // `history.listen` is called on every route change, which is perfect for routeDidChange + // events. history.listen(EventActions.routeDidChange); - React.render(( - - {redirectRoute} - {getRoutes(type, subdomain)} - - ), document.getElementById('main')); + // Adds a client specific class to the body for whitelabel styling + window.document.body.classList.add(`client--${settings.subdomain}`); - // Send the applicationDidBoot event to the third-party stores - EventActions.applicationDidBoot(settings); + AppResolver + .resolve(settings) + .then(({ apiUrls, redirectRoute, routes }) => { + // Initialize api urls and defaults for outgoing requests + requests.defaults({ + urlMap: apiUrls, + http: { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + credentials: 'include' + } + }); + + React.render(( + + {redirectRoute} + {routes} + + ), document.getElementById('main')); + + // Send the applicationDidBoot event to the third-party stores + EventActions.applicationDidBoot(settings); + }); } }; // Initialize pre-start components initLogging(); -requests.defaults({ - urlMap: ApiUrls, - http: { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - credentials: 'include' - } -}); - // And bootstrap app AppGateway.start(); diff --git a/js/app_resolver.js b/js/app_resolver.js new file mode 100644 index 00000000..97a8fde5 --- /dev/null +++ b/js/app_resolver.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Redirect } from 'react-router'; + +import Routes from './routes'; + +import ApiUrls from './constants/api_urls'; + + +function resolve({ subdomain, type }) { + if (type === 'wallet') { + return System.import('./components/whitelabel/wallet/wallet_app_resolver') + .then(({ default: WalletAppResolver }) => WalletAppResolver.resolve(subdomain)); + } else { + return Promise.resolve({ + apiUrls: ApiUrls, + redirectRoute: (), + routes: Routes + }); + } +} + +export default { resolve }; diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index 681c8c8a..8f23e4e4 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -81,14 +81,14 @@ let FurtherDetailsFileuploader = React.createClass({ deleteFile={{ enabled: true, method: 'DELETE', - endpoint: AppConstants.serverUrl + 's3/delete', + endpoint: `${AppConstants.serverUrl}/s3/delete`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) } }} isReadyForFormSubmission={isReadyForFormSubmission} keyRoutine={{ - url: AppConstants.serverUrl + 's3/key/', + url: `${AppConstants.serverUrl}/s3/key/`, fileClass: 'otherdata', pieceId: pieceId }} @@ -96,7 +96,7 @@ let FurtherDetailsFileuploader = React.createClass({ onValidationFailed={onValidationFailed} setIsUploadReady={setIsUploadReady} session={{ - endpoint: AppConstants.serverUrl + 'api/blob/otherdatas/fineuploader_session/', + endpoint: `${AppConstants.serverUrl}/api/blob/otherdatas/fineuploader_session/`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) }, @@ -109,7 +109,7 @@ let FurtherDetailsFileuploader = React.createClass({ } }} signature={{ - endpoint: AppConstants.serverUrl + 's3/signature/', + endpoint: `${AppConstants.serverUrl}/s3/signature/`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) } diff --git a/js/components/ascribe_forms/form_create_contract.js b/js/components/ascribe_forms/form_create_contract.js index cbdd12c3..c19843d5 100644 --- a/js/components/ascribe_forms/form_create_contract.js +++ b/js/components/ascribe_forms/form_create_contract.js @@ -72,7 +72,7 @@ let CreateContractForm = React.createClass({ this.refs.digitalWorkFineUploader = ref} keyRoutine={{ - url: AppConstants.serverUrl + 's3/key/', + url: `${AppConstants.serverUrl}/s3/key/`, fileClass: 'digitalwork' }} createBlobRoutine={{ @@ -191,7 +191,7 @@ let RegisterPieceForm = React.createClass({ onValidationFailed={this.handleThumbnailValidationFailed} isReadyForFormSubmission={formSubmissionValidation.fileOptional} keyRoutine={{ - url: AppConstants.serverUrl + 's3/key/', + url: `${AppConstants.serverUrl}/s3/key/`, fileClass: 'thumbnail' }} validation={{ diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js index 53afcac2..c62d9183 100644 --- a/js/components/ascribe_forms/input_fineuploader.js +++ b/js/components/ascribe_forms/input_fineuploader.js @@ -121,7 +121,7 @@ const InputFineUploader = React.createClass({ setWarning={setWarning} showErrorPrompt={showErrorPrompt} signature={{ - endpoint: AppConstants.serverUrl + 's3/signature/', + endpoint: `${AppConstants.serverUrl}/s3/signature/`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) } @@ -129,7 +129,7 @@ const InputFineUploader = React.createClass({ deleteFile={{ enabled: true, method: 'DELETE', - endpoint: AppConstants.serverUrl + 's3/delete', + endpoint: `${AppConstants.serverUrl}/s3/delete`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) } diff --git a/js/components/ascribe_media/media_player.js b/js/components/ascribe_media/media_player.js index 888e148f..73921d0b 100644 --- a/js/components/ascribe_media/media_player.js +++ b/js/components/ascribe_media/media_player.js @@ -7,10 +7,15 @@ import Panel from 'react-bootstrap/lib/Panel'; import AppConstants from '../../constants/application_constants'; +import audiojs from '../../third_party/imports/audiojs'; +import shmui from '../../third_party/imports/shmui'; +import videojs from '../../third_party/imports/videojs'; + import { escapeHTML } from '../../utils/general_utils'; import { extractFileExtensionFromUrl } from '../../utils/file_utils'; import { InjectInHeadUtils } from '../../utils/inject_utils'; + /** * This is the component that implements display-specific functionality. * @@ -57,12 +62,9 @@ let Image = React.createClass({ componentDidMount() { if (this.props.url) { - InjectInHeadUtils.inject(AppConstants.jquery.sdkUrl) - .then(() => - Q.all([ - InjectInHeadUtils.inject(AppConstants.shmui.cssUrl), - InjectInHeadUtils.inject(AppConstants.shmui.sdkUrl) - ]).then(() => { window.jQuery('.shmui-ascribe').shmui(); })); + shmui + .importLib() + .then(() => window.jQuery('.shmui-ascribe').shmui()); } }, @@ -89,13 +91,9 @@ let Audio = React.createClass({ }, componentDidMount() { - InjectInHeadUtils.inject(AppConstants.audiojs.sdkUrl).then(this.ready); - }, - - ready() { - window.audiojs.events.ready(function() { - window.audiojs.createAll(); - }); + audiojs + .importLib() + .then(() => window.audiojs.events.ready(() => window.audiojs.createAll())); }, render() { @@ -142,11 +140,10 @@ let Video = React.createClass({ }, componentDidMount() { - Q.all([ - InjectInHeadUtils.inject(AppConstants.videojs.cssUrl), - InjectInHeadUtils.inject(AppConstants.videojs.sdkUrl)]) - .then(() => this.setState({libraryLoaded: true})) - .fail(() => this.setState({libraryLoaded: false})); + videojs + .importLib() + .then(() => this.setState({ libraryLoaded: true })) + .catch(() => this.setState({ libraryLoaded: false })); }, shouldComponentUpdate(nextProps, nextState) { diff --git a/js/components/ascribe_routes/proxy_handler.js b/js/components/ascribe_routes/proxy_handler.js index 882fe65c..cfe64447 100644 --- a/js/components/ascribe_routes/proxy_handler.js +++ b/js/components/ascribe_routes/proxy_handler.js @@ -56,10 +56,11 @@ export function AuthRedirect({ to, when }) { * hence transitionTo cannot be used directly. * * While we're getting rid of `query.redirect` explicitly in the - * above `else if` statement, here it's sufficient to just call - * `baseUrl` + `redirectAuthenticated`, as it gets rid of queries as well. + * above `else if` statement, here it's sufficient to just set the + * location to `${baseUrl}/${redirectAuthenticated}`, as this will + * get rid of queries as well. */ - window.location = AppConstants.baseUrl + redirectAuthenticated; + window.location = `${AppConstants.baseUrl}/${redirectAuthenticated}`; return true; } diff --git a/js/components/ascribe_settings/contract_settings_update_button.js b/js/components/ascribe_settings/contract_settings_update_button.js index dc652b9b..73aa98ea 100644 --- a/js/components/ascribe_settings/contract_settings_update_button.js +++ b/js/components/ascribe_settings/contract_settings_update_button.js @@ -62,7 +62,7 @@ let ContractSettingsUpdateButton = React.createClass({ ref='fineuploader' fileInputElement={UploadButton({ showLabel: false })} keyRoutine={{ - url: AppConstants.serverUrl + 's3/key/', + url: `${AppConstants.serverUrl}/s3/key/`, fileClass: 'contract' }} createBlobRoutine={{ @@ -75,7 +75,7 @@ let ContractSettingsUpdateButton = React.createClass({ }} setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}} signature={{ - endpoint: AppConstants.serverUrl + 's3/signature/', + endpoint: `${AppConstants.serverUrl}/s3/signature/`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) } @@ -83,7 +83,7 @@ let ContractSettingsUpdateButton = React.createClass({ deleteFile={{ enabled: true, method: 'DELETE', - endpoint: AppConstants.serverUrl + 's3/delete', + endpoint: `${AppConstants.serverUrl}/s3/delete`, customHeaders: { 'X-CSRFToken': getCookie(AppConstants.csrftoken) } diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index e9203d99..1a3baf38 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -1,7 +1,8 @@ 'use strict'; import React from 'react/addons'; -import fineUploader from 'fineUploader'; +// FIXME: remove once using react-components +import fineUploader from 'exports?qq!./vendor/s3.fine-uploader'; import Q from 'q'; import S3Fetcher from '../../fetchers/s3_fetcher'; diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js b/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js index d8925748..3b9b94a9 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader_utils.js @@ -1,6 +1,7 @@ 'use strict'; -import fineUploader from 'fineUploader'; +// FIXME: remove once using react-components +import fineUploader from 'exports?qq!./vendor/s3.fine-uploader'; import MimeTypes from '../../constants/mime_types'; diff --git a/js/components/whitelabel/wallet/constants/wallet_api_urls.js b/js/components/whitelabel/wallet/constants/wallet_api_urls.js index e381cc88..be332d6f 100644 --- a/js/components/whitelabel/wallet/constants/wallet_api_urls.js +++ b/js/components/whitelabel/wallet/constants/wallet_api_urls.js @@ -6,27 +6,27 @@ import walletConstants from './wallet_application_constants'; function getWalletApiUrls(subdomain) { if (subdomain === 'cyland') { return { - 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', - 'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/', - 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' + 'pieces_list': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/${piece_id}/extradata/', + 'user': walletConstants.walletApiEndpoint + '/' + subdomain + '/users/' }; } else if (subdomain === 'ikonotv') { return { - 'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/', - 'user': walletConstants.walletApiEndpoint + subdomain + '/users/' + 'pieces_list': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/${piece_id}/', + 'user': walletConstants.walletApiEndpoint + '/' + subdomain + '/users/' }; } else if (subdomain === 'lumenus' || subdomain === '23vivi' || subdomain === 'polline' || subdomain === 'artcity' || subdomain === 'demo' || subdomain === 'liquidgallery') { return { - 'editions_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/editions/', - 'edition': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/editions/${bitcoin_id}/', - 'pieces_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/', - 'piece': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/', - 'piece_extradata': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/extradata/', - 'user': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/users/' + 'editions_list': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/${piece_id}/editions/', + 'edition': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/editions/${bitcoin_id}/', + 'pieces_list': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/', + 'piece': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/${piece_id}/', + 'piece_extradata': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/${piece_id}/extradata/', + 'user': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/users/' }; } return {}; diff --git a/js/components/whitelabel/wallet/constants/wallet_application_constants.js b/js/components/whitelabel/wallet/constants/wallet_application_constants.js index cec456d9..c55744d3 100644 --- a/js/components/whitelabel/wallet/constants/wallet_application_constants.js +++ b/js/components/whitelabel/wallet/constants/wallet_application_constants.js @@ -2,8 +2,8 @@ import AppConstants from '../../../../constants/application_constants'; -let walletConstants = { - walletApiEndpoint: AppConstants.apiEndpoint + 'whitelabel/' +const walletConstants = { + walletApiEndpoint: `${AppConstants.apiEndpoint}/whitelabel` }; -export default walletConstants; \ No newline at end of file +export default walletConstants; diff --git a/js/components/whitelabel/wallet/wallet_app_resolver.js b/js/components/whitelabel/wallet/wallet_app_resolver.js new file mode 100644 index 00000000..bd0daacd --- /dev/null +++ b/js/components/whitelabel/wallet/wallet_app_resolver.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Redirect } from 'react-router'; + +import getWalletApiUrls from './constants/wallet_api_urls'; +import getWalletRoutes from './wallet_routes'; + +import { updateApiUrls } from '../../../constants/api_urls'; + + +function resolve(subdomain) { + // Most whitelabels have landing pages so we should not automatically redirect from / to /collection. + // Only cc does not have a landing page. + const redirectRoute = subdomain === 'cc' ? () : null; + + return { + redirectRoute, + apiUrls: updateApiUrls(getWalletApiUrls(subdomain)), + routes: getWalletRoutes(subdomain) + }; +} + +export default { resolve }; diff --git a/js/components/whitelabel/wallet/wallet_routes.js b/js/components/whitelabel/wallet/wallet_routes.js index 2e0e9a10..fe1d9161 100644 --- a/js/components/whitelabel/wallet/wallet_routes.js +++ b/js/components/whitelabel/wallet/wallet_routes.js @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { Route, IndexRoute } from 'react-router'; @@ -17,7 +15,6 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain import SettingsContainer from '../../../components/ascribe_settings/settings_container'; import ContractSettings from '../../../components/ascribe_settings/contract_settings'; import ErrorNotFoundPage from '../../../components/error_not_found_page'; -import Footer from '../../../components/footer.js'; import CCRegisterPiece from './components/cc/cc_register_piece'; @@ -54,409 +51,407 @@ import WalletApp from './wallet_app'; import { getLangText } from '../../../utils/lang_utils'; -let ROUTES = { +const ROUTES = { 'cyland': ( - + + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(CylandLanding)} /> + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_wallet_submit" /> + disableOn="noPieces" /> ), 'cc': ( - + + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + disableOn="noPieces" /> ), 'ikonotv': ( - + + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(IkonotvLanding)} /> + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_create_contractagreement" /> + aclName="acl_wallet_submit" /> + disableOn="noPieces" /> + path="contract_notifications" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(IkonotvContractNotifications)} /> ), 'lumenus': ( - + + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LumenusLanding)} /> + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_wallet_submit" /> + disableOn="noPieces" /> ), '23vivi': ( - - + + ), 'polline': ( - - + + + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_wallet_submit" /> - - - - + disableOn="noPieces" /> + + + + ), 'artcity': ( - - + + + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_wallet_submit" /> - - - - + disableOn="noPieces" /> + + + + ), 'demo': ( - - + + + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_wallet_submit" /> - - - - + disableOn="noPieces" /> + + + + ), 'liquidgallery': ( - - + + + path="login" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} /> + path="logout" + component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} /> + path="signup" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} /> + path="password_reset" + component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} /> + path="settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} /> + path="contract_settings" + component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} /> + aclName="acl_wallet_submit" /> - - - - + disableOn="noPieces" /> + + + + ) }; -function getRoutes(commonRoutes, subdomain) { - if(subdomain in ROUTES) { - return ROUTES[subdomain]; +export default function getWalletRoutes(subdomain) { + if (subdomain in ROUTES) { + return (ROUTES[subdomain]); } else { - throw new Error('Subdomain wasn\'t specified in the wallet app.'); + throw new Error("Subdomain wasn't specified in the wallet app."); } } - -export default getRoutes; diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index 1690f431..4e556bd8 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -1,88 +1,80 @@ -'use strict'; - import AppConstants from './application_constants'; -import getWalletApiUrls from '../components/whitelabel/wallet/constants/wallet_api_urls'; -import { update } from '../utils/general_utils'; - - -let ApiUrls = { - 'applications': AppConstants.apiEndpoint + 'applications/', - 'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/', - 'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/', - 'blob_otherdatas': AppConstants.apiEndpoint + 'blob/otherdatas/', - 'blob_contracts': AppConstants.apiEndpoint + 'blob/contracts/', - 'blob_thumbnails': AppConstants.apiEndpoint + 'blob/thumbnails/', - 'coa': AppConstants.apiEndpoint + 'coa/${id}/', - 'coa_create': AppConstants.apiEndpoint + 'coa/', - 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', - 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', - 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', - 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/editions/${edition_id}/', - 'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below - 'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', - 'licenses': AppConstants.apiEndpoint + 'ownership/licenses/', - 'note_private_edition': AppConstants.apiEndpoint + 'note/private/editions/', - 'note_private_piece': AppConstants.apiEndpoint + 'note/private/pieces/', - 'note_public_edition': AppConstants.apiEndpoint + 'note/public/editions/', - 'note_public_piece': AppConstants.apiEndpoint + 'note/public/pieces/', - 'notification_piecelist': AppConstants.apiEndpoint + 'notifications/pieces/', - 'notification_piece': AppConstants.apiEndpoint + 'notifications/pieces/${piece_id}/', - 'notification_editionlist': AppConstants.apiEndpoint + 'notifications/editions/', - 'notification_edition': AppConstants.apiEndpoint + 'notifications/editions/${edition_id}/', - 'notification_contractagreementlist': AppConstants.apiEndpoint + 'notifications/contract_agreements/', - 'ownership_contract_agreements': AppConstants.apiEndpoint + 'ownership/contract_agreements/', - 'ownership_contract_agreements_confirm': AppConstants.apiEndpoint + 'ownership/contract_agreements/${contract_agreement_id}/accept/', - 'ownership_contract_agreements_deny': AppConstants.apiEndpoint + 'ownership/contract_agreements/${contract_agreement_id}/reject/', - 'ownership_consigns': AppConstants.apiEndpoint + 'ownership/consigns/', - 'ownership_consigns_confirm': AppConstants.apiEndpoint + 'ownership/consigns/confirm/', - 'ownership_consigns_deny': AppConstants.apiEndpoint + 'ownership/consigns/deny/', - 'ownership_consigns_withdraw': AppConstants.apiEndpoint + 'ownership/consigns/withdraw/', - 'ownership_loans_pieces': AppConstants.apiEndpoint + 'ownership/loans/pieces/', - 'ownership_loans_pieces_confirm': AppConstants.apiEndpoint + 'ownership/loans/pieces/confirm/', - 'ownership_loans_pieces_deny': AppConstants.apiEndpoint + 'ownership/loans/pieces/deny/', - 'ownership_loans_pieces_request': AppConstants.apiEndpoint + 'ownership/loans/pieces/request/', - 'ownership_loans_pieces_request_confirm': AppConstants.apiEndpoint + 'ownership/loans/pieces/request_confirm/', - 'ownership_loans_pieces_request_deny': AppConstants.apiEndpoint + 'ownership/loans/pieces/request_deny/', - 'ownership_loans_editions': AppConstants.apiEndpoint + 'ownership/loans/editions/', - 'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/editions/confirm/', - 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/editions/deny/', - 'ownership_shares_editions': AppConstants.apiEndpoint + 'ownership/shares/editions/', - 'ownership_shares_pieces': AppConstants.apiEndpoint + 'ownership/shares/pieces/', - 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', - 'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/', - 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', - 'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/', - 'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/', - 'ownership_contract': AppConstants.apiEndpoint + 'ownership/contracts/${contract_id}/', - 'ownership_contract_list': AppConstants.apiEndpoint + 'ownership/contracts/', - 'piece': AppConstants.apiEndpoint + 'pieces/${piece_id}/', - 'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/', - 'pieces_list': AppConstants.apiEndpoint + 'pieces/', - 'piece_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/pieces/${piece_id}/', - 'user': AppConstants.apiEndpoint + 'users/', - 'users_login': AppConstants.apiEndpoint + 'users/login/', - 'users_logout': AppConstants.apiEndpoint + 'users/logout/', - 'users_password_reset': AppConstants.apiEndpoint + 'users/reset_password/', - 'users_password_reset_request': AppConstants.apiEndpoint + 'users/request_reset_password/', - 'users_signup': AppConstants.apiEndpoint + 'users/', - 'users_username': AppConstants.apiEndpoint + 'users/username/', - 'users_profile': AppConstants.apiEndpoint + 'users/profile/', - 'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/', - 'webhook': AppConstants.apiEndpoint + 'webhooks/${webhook_id}/', - 'webhooks': AppConstants.apiEndpoint + 'webhooks/', - 'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/', - 'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/', - 'delete_s3_file': AppConstants.serverUrl + 's3/delete/', - 'sign_url_s3': AppConstants.serverUrl + 's3/sign_url/' +const ApiUrls = { + 'applications': AppConstants.apiEndpoint + '/applications/', + 'application_token_refresh': AppConstants.apiEndpoint + '/applications/refresh_token/', + 'blob_digitalworks': AppConstants.apiEndpoint + '/blob/digitalworks/', + 'blob_otherdatas': AppConstants.apiEndpoint + '/blob/otherdatas/', + 'blob_contracts': AppConstants.apiEndpoint + '/blob/contracts/', + 'blob_thumbnails': AppConstants.apiEndpoint + '/blob/thumbnails/', + 'coa': AppConstants.apiEndpoint + '/coa/${id}/', + 'coa_create': AppConstants.apiEndpoint + '/coa/', + 'coa_verify': AppConstants.apiEndpoint + '/coa/verify_coa/', + 'edition': AppConstants.apiEndpoint + '/editions/${bitcoin_id}/', + 'edition_delete': AppConstants.apiEndpoint + '/editions/${edition_id}/', + 'edition_remove_from_collection': AppConstants.apiEndpoint + '/ownership/shares/editions/${edition_id}/', + 'editions': AppConstants.apiEndpoint + '/editions/', // this should be moved to the one below + 'editions_list': AppConstants.apiEndpoint + '/pieces/${piece_id}/editions/', + 'licenses': AppConstants.apiEndpoint + '/ownership/licenses/', + 'note_private_edition': AppConstants.apiEndpoint + '/note/private/editions/', + 'note_private_piece': AppConstants.apiEndpoint + '/note/private/pieces/', + 'note_public_edition': AppConstants.apiEndpoint + '/note/public/editions/', + 'note_public_piece': AppConstants.apiEndpoint + '/note/public/pieces/', + 'notification_piecelist': AppConstants.apiEndpoint + '/notifications/pieces/', + 'notification_piece': AppConstants.apiEndpoint + '/notifications/pieces/${piece_id}/', + 'notification_editionlist': AppConstants.apiEndpoint + '/notifications/editions/', + 'notification_edition': AppConstants.apiEndpoint + '/notifications/editions/${edition_id}/', + 'notification_contractagreementlist': AppConstants.apiEndpoint + '/notifications/contract_agreements/', + 'ownership_contract_agreements': AppConstants.apiEndpoint + '/ownership/contract_agreements/', + 'ownership_contract_agreements_confirm': AppConstants.apiEndpoint + '/ownership/contract_agreements/${contract_agreement_id}/accept/', + 'ownership_contract_agreements_deny': AppConstants.apiEndpoint + '/ownership/contract_agreements/${contract_agreement_id}/reject/', + 'ownership_consigns': AppConstants.apiEndpoint + '/ownership/consigns/', + 'ownership_consigns_confirm': AppConstants.apiEndpoint + '/ownership/consigns/confirm/', + 'ownership_consigns_deny': AppConstants.apiEndpoint + '/ownership/consigns/deny/', + 'ownership_consigns_withdraw': AppConstants.apiEndpoint + '/ownership/consigns/withdraw/', + 'ownership_loans_pieces': AppConstants.apiEndpoint + '/ownership/loans/pieces/', + 'ownership_loans_pieces_confirm': AppConstants.apiEndpoint + '/ownership/loans/pieces/confirm/', + 'ownership_loans_pieces_deny': AppConstants.apiEndpoint + '/ownership/loans/pieces/deny/', + 'ownership_loans_pieces_request': AppConstants.apiEndpoint + '/ownership/loans/pieces/request/', + 'ownership_loans_pieces_request_confirm': AppConstants.apiEndpoint + '/ownership/loans/pieces/request_confirm/', + 'ownership_loans_pieces_request_deny': AppConstants.apiEndpoint + '/ownership/loans/pieces/request_deny/', + 'ownership_loans_editions': AppConstants.apiEndpoint + '/ownership/loans/editions/', + 'ownership_loans_confirm': AppConstants.apiEndpoint + '/ownership/loans/editions/confirm/', + 'ownership_loans_deny': AppConstants.apiEndpoint + '/ownership/loans/editions/deny/', + 'ownership_shares_editions': AppConstants.apiEndpoint + '/ownership/shares/editions/', + 'ownership_shares_pieces': AppConstants.apiEndpoint + '/ownership/shares/pieces/', + 'ownership_transfers': AppConstants.apiEndpoint + '/ownership/transfers/', + 'ownership_transfers_withdraw': AppConstants.apiEndpoint + '/ownership/transfers/withdraw/', + 'ownership_unconsigns': AppConstants.apiEndpoint + '/ownership/unconsigns/', + 'ownership_unconsigns_deny': AppConstants.apiEndpoint + '/ownership/unconsigns/deny/', + 'ownership_unconsigns_request': AppConstants.apiEndpoint + '/ownership/unconsigns/request/', + 'ownership_contract': AppConstants.apiEndpoint + '/ownership/contracts/${contract_id}/', + 'ownership_contract_list': AppConstants.apiEndpoint + '/ownership/contracts/', + 'piece': AppConstants.apiEndpoint + '/pieces/${piece_id}/', + 'piece_extradata': AppConstants.apiEndpoint + '/pieces/${piece_id}/extradata/', + 'pieces_list': AppConstants.apiEndpoint + '/pieces/', + 'piece_remove_from_collection': AppConstants.apiEndpoint + '/ownership/shares/pieces/${piece_id}/', + 'user': AppConstants.apiEndpoint + '/users/', + 'users_login': AppConstants.apiEndpoint + '/users/login/', + 'users_logout': AppConstants.apiEndpoint + '/users/logout/', + 'users_password_reset': AppConstants.apiEndpoint + '/users/reset_password/', + 'users_password_reset_request': AppConstants.apiEndpoint + '/users/request_reset_password/', + 'users_signup': AppConstants.apiEndpoint + '/users/', + 'users_username': AppConstants.apiEndpoint + '/users/username/', + 'users_profile': AppConstants.apiEndpoint + '/users/profile/', + 'wallet_settings': AppConstants.apiEndpoint + '/users/wallet_settings/', + 'webhook': AppConstants.apiEndpoint + '/webhooks/${webhook_id}/', + 'webhooks': AppConstants.apiEndpoint + '/webhooks/', + 'webhooks_events': AppConstants.apiEndpoint + '/webhooks/events/', + 'whitelabel_settings': AppConstants.apiEndpoint + '/whitelabel/settings/${subdomain}/', + 'delete_s3_file': AppConstants.serverUrl + '/s3/delete/', + 'sign_url_s3': AppConstants.serverUrl + '/s3/sign_url/' }; -export function updateApiUrls(type, subdomain) { - if (type === 'wallet') { - update(getWalletApiUrls(subdomain)); - } +export function updateApiUrls(updatedApiUrls) { + return Object.assign(ApiUrls, updatedApiUrls); } export default ApiUrls; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index fd73e568..f448b432 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -1,19 +1,13 @@ 'use strict'; -//const baseUrl = 'http://localhost:8000/api/'; - -//FIXME: referring to a global variable in `window` is not -// super pro. What if we render stuff on the server? -// - super-bro - Senor Developer, 14th July 2015 -//const baseUrl = window.BASE_URL; -const apiEndpoint = window.API_ENDPOINT; -const serverUrl = window.SERVER_URL; -const baseUrl = window.BASE_URL; +const apiEndpoint = process.env.API_URL; +const serverUrl = process.env.SERVER_URL; +const baseUrl = process.env.APP_BASE_PATH; const constants = { apiEndpoint, - serverUrl, baseUrl, + serverUrl, 'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions', 'acl_loan', 'acl_loan_request', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view', 'acl_withdraw_transfer', 'acl_wallet_submit'], @@ -104,12 +98,8 @@ const constants = { 'jquery': { 'sdkUrl': 'https://code.jquery.com/jquery-2.1.4.min.js' }, - 'shmui': { - 'sdkUrl': baseUrl + 'static/thirdparty/shmui/jquery.shmui.js', - 'cssUrl': baseUrl + 'static/thirdparty/shmui/shmui.css' - }, 'audiojs': { - 'sdkUrl': baseUrl + 'static/thirdparty/audiojs/audiojs/audio.min.js' + 'sdkUrl': baseUrl + '/static/third_party/audiojs/audio.min.js' }, 'videojs': { 'sdkUrl': '//vjs.zencdn.net/4.12/video.js', diff --git a/js/history.js b/js/history.js index 3d0ecff5..9fcdd3ff 100644 --- a/js/history.js +++ b/js/history.js @@ -6,11 +6,8 @@ import createBrowserHistory from 'history/lib/createBrowserHistory'; import AppConstants from './constants/application_constants'; -// Remove the trailing slash if present -const baseUrl = AppConstants.baseUrl.replace(/\/$/, ''); - const history = useBasename(useQueries(createBrowserHistory))({ - basename: baseUrl + basename: AppConstants.baseUrl }); history.locationQueue = []; diff --git a/js/routes.js b/js/routes.js index 3c7e1228..442f0c4d 100644 --- a/js/routes.js +++ b/js/routes.js @@ -1,10 +1,6 @@ -'use strict'; - import React from 'react'; import { Route } from 'react-router'; -import getWalletRoutes from './components/whitelabel/wallet/wallet_routes'; - import AscribeApp from './components/ascribe_app'; import PieceList from './components/piece_list'; @@ -31,62 +27,56 @@ import { ProxyHandler, AuthRedirect } from './components/ascribe_routes/proxy_ha import { getLangText } from './utils/lang_utils'; -const COMMON_ROUTES = ( - +const Routes = ( + - - - - ); - -function getRoutes(type, subdomain) { - if (type === 'wallet') { - return getWalletRoutes(COMMON_ROUTES, subdomain); - } else { - return COMMON_ROUTES; - } -} - - -export default getRoutes; +export default Routes; diff --git a/js/third_party/imports/audiojs.js b/js/third_party/imports/audiojs.js new file mode 100644 index 00000000..0b96d3f7 --- /dev/null +++ b/js/third_party/imports/audiojs.js @@ -0,0 +1,18 @@ +import AppConstants from '../../constants/application_constants'; + +import { InjectInHeadUtils } from '../../utils/inject_utils'; + + +/** + * Imports audiojs from the copied directory. + * + * Unfortunately, audiojs' package structure and the way it currently loads image assets prevents us + * from using System.import. + * + * @return {Promise} Promise that resolves with [audio.min.js] on success. + */ +function importLib() { + return InjectInHeadUtils.inject(AppConstants.audiojs.sdkUrl); +} + +export default { importLib }; diff --git a/js/third_party/imports/shmui.js b/js/third_party/imports/shmui.js new file mode 100644 index 00000000..55873b9f --- /dev/null +++ b/js/third_party/imports/shmui.js @@ -0,0 +1,19 @@ +import AppConstants from '../../constants/application_constants'; + +import { InjectInHeadUtils } from '../../utils/inject_utils'; + + +/** + * Imports shmui and its dependencies (jQuery) + * + * @return {Promise} Promise that resolves with [jquery.shmui.js, shmui.css] on success. + */ +function importLib() { + return InjectInHeadUtils.inject(AppConstants.jquery.sdkUrl) + .then(() => Promise.all([ + System.import('shmui/jquery.shmui'), + System.import('shmui/shmui.css') + ])); +} + +export default { importLib }; diff --git a/js/third_party/imports/videojs.js b/js/third_party/imports/videojs.js new file mode 100644 index 00000000..d06e7004 --- /dev/null +++ b/js/third_party/imports/videojs.js @@ -0,0 +1,18 @@ +import AppConstants from '../../constants/application_constants'; + +import { InjectInHeadUtils } from '../../utils/inject_utils'; + + +/** + * Imports videojs and its stylesheet. + * + * @return {Promise} Promise that resolves with [video.js, video-js.css] on success. + */ +function importLib() { + return Promise.all([ + InjectInHeadUtils.inject(AppConstants.videojs.cssUrl), + InjectInHeadUtils.inject(AppConstants.videojs.sdkUrl) + ]); +} + +export default { importLib }; diff --git a/package.json b/package.json index f50b951d..88d63a78 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,30 @@ { "name": "Onion", - "version": "0.0.1", + "version": "0.0.2", "description": "Das neue web client for Ascribe. Onions make you cry", + "homepapge": "https://www.ascribe.io", + "license": "Copyright", + "author": "Ascribe", "main": "js/app.js", + "private": true, "engines": { - "node": "0.10.x" + "node": "6.2.0" }, "scripts": { - "lint": "eslint ./js", "preinstall": "export SAUCE_CONNECT_DOWNLOAD_ON_INSTALL=true", "postinstall": "npm run build", - "build": "gulp build --production", - "start": "node server.js", + "build": "rimraf ./dist && NODE_ENV=production webpack -p", + "start": "NODE_ENV=production node server.js", + + "build:dev": "rimraf ./build && NODE_ENV=development webpack", + "build:extract": "rimraf ./build && NODE_ENV=extract webpack", + "clean": "rimraf ./build ./dist", + "start:dev": "NODE_ENV=development node server.dev.js", + "lint": "eslint ./", "test": "npm run sauce-test", "sauce-test": "mocha ./test/integration/tests/", - "tunnel": "node ./test/integration/tunnel.js", + "sauce-tunnel": "node ./test/integration/tunnel.js", "vi-clean": "rm -rf ./gemini-report", "vi-phantom": "phantomjs --webdriver=4444", @@ -31,78 +40,61 @@ "vi-test:lumenus": "npm run -s vi-test:base -- --browser LumenusDesktop --browser LumenusMobile", "vi-test:23vivi": "npm run -s vi-test:base -- --browser 23viviDesktop --browser 23viviMobile" }, - "browser": { - "fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js" - }, - "browserify-shim": { - "fineUploader": "qq" - }, - "browserify": { - "transform": [ - [ - "babelify", - { - "compact": false - } - ], - "browserify-shim" - ] - }, - "author": "Ascribe", - "license": "Copyright", - "private": true, "devDependencies": { - "babel-eslint": "^3.1.11", "babel-jest": "^5.2.0", "chai": "^3.4.1", "chai-as-promised": "^5.1.0", "colors": "^1.1.2", "dotenv": "^1.2.0", - "gemini": "^2.1.0", + "gemini": "^4.3.0", "jest-cli": "^0.4.0", "mocha": "^2.3.4", - "phantomjs2": "^2.0.2", + "rimraf": "^2.5.2", "sauce-connect-launcher": "^0.13.0", "wd": "^0.4.0" }, "dependencies": { "alt": "^0.16.5", "audiojs": "vrde/audiojs", - "babel": "^5.6.14", - "babelify": "^6.1.2", - "bootstrap-sass": "^3.3.4", - "browser-sync": "^2.7.5", - "browserify": "^9.0.8", - "browserify-shim": "^3.8.10", + "autoprefixer": "^6.3.6", + "babel-cli": "^6.9.0", + "babel-eslint": "^6.0.4", + "babel-loader": "^6.2.4", + "babel-plugin-react-transform": "^2.0.2", + "babel-plugin-transform-object-assign": "^6.8.0", + "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-plugin-transform-react-display-name": "^6.8.0", + "babel-plugin-transform-runtime": "^6.9.0", + "babel-preset-es2015-no-commonjs": "0.0.2", + "babel-preset-react": "^6.5.0", + "babel-runtime": "^6.9.0", + "bootstrap-loader": "^1.0.10", + "bootstrap-sass": "^3.3.6", "camelcase": "^1.2.1", "classlist-polyfill": "^1.0.2", "classnames": "^1.2.2", - "compression": "^1.4.4", + "compression": "^1.6.2", + "copy-webpack-plugin": "^3.0.1", + "core-js": "^2.4.0", + "css-loader": "^0.23.1", "decamelize": "^1.1.1", - "envify": "^3.4.0", - "eslint": "^0.22.1", - "eslint-plugin-react": "^2.5.0", - "express": "^4.12.4", - "gulp": "^3.8.11", - "gulp-concat": "^2.5.2", - "gulp-eslint": "^0.13.2", - "gulp-if": "^1.2.5", - "gulp-minify-css": "^1.1.6", - "gulp-notify": "^2.2.0", - "gulp-sass": "^2.1.1", - "gulp-sourcemaps": "^1.5.2", - "gulp-template": "~3.0.0", - "gulp-uglify": "^1.2.0", - "gulp-util": "^3.0.4", - "harmonize": "^1.4.2", + "dotenv": "^1.2.0", + "eslint": "^2.11.1", + "eslint-config-ascribe": "^1.0.1", + "eslint-plugin-import": "^1.8.1", + "eslint-plugin-jsx-a11y": "^1.2.2", + "eslint-plugin-react": "^5.1.1", + "exports-loader": "^0.6.3", + "express": "^4.13.4", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.8.5", "history": "1.17.0", + "html-webpack-plugin": "^2.19.0", "invariant": "^2.1.1", "isomorphic-fetch": "^2.0.2", - "jest-cli": "^0.4.0", - "lodash": "^3.9.3", "moment": "^2.10.6", - "object-assign": "^2.0.0", - "opn": "^3.0.2", + "node-sass": "^3.7.0", + "postcss-loader": "^0.9.1", "q": "^1.4.1", "query-string": "^3.0.0", "raven-js": "^1.1.19", @@ -113,15 +105,18 @@ "react-router-bootstrap": "^0.19.0", "react-star-rating": "~1.3.2", "react-textarea-autosize": "^2.5.2", - "reactify": "^1.1.0", + "react-transform-hmr": "^1.0.4", + "remove-trailing-slash": "^0.1.0", + "resolve-url-loader": "^1.4.3", + "sass-loader": "^3.2.0", "shallow-equals": "0.0.0", "shmui": "^0.1.0", "spark-md5": "~1.0.0", - "uglifyjs": "^2.4.10", - "vinyl-buffer": "^1.0.0", - "vinyl-source-stream": "^1.1.0", - "watchify": "^3.1.2", - "yargs": "^3.10.0" + "style-loader": "^0.13.1", + "url-loader": "^0.5.7", + "webpack": "^2.1.0-beta.7", + "webpack-combine-loaders": "^2.0.0", + "webpack-dev-server": "^2.1.0-beta.0" }, "jest": { "scriptPreprocessor": "node_modules/babel-jest", diff --git a/sass/ascribe-fonts/ascribe-fonts.scss b/sass/ascribe-fonts/ascribe-fonts.scss index 6f95a616..95aefe84 100644 --- a/sass/ascribe-fonts/ascribe-fonts.scss +++ b/sass/ascribe-fonts/ascribe-fonts.scss @@ -13,11 +13,11 @@ @font-face { font-family: 'ascribe-font'; - src:url('#{$BASE_URL}static/fonts/ascribe-font.eot?q6qoae'); - src:url('#{$BASE_URL}static/fonts/ascribe-font.eot?q6qoae#iefix') format('embedded-opentype'), - url('#{$BASE_URL}static/fonts/ascribe-font.ttf?q6qoae') format('truetype'), - url('#{$BASE_URL}static/fonts/ascribe-font.woff?q6qoae') format('woff'), - url('#{$BASE_URL}static/fonts/ascribe-font.svg?q6qoae#ascribe-font') format('svg'); + src:url('../fonts/ascribe-font.eot?q6qoae'); + src:url('../fonts/ascribe-font.eot?q6qoae#iefix') format('embedded-opentype'), + url('../fonts/ascribe-font.ttf?q6qoae') format('truetype'), + url('../fonts/ascribe-font.woff?q6qoae') format('woff'), + url('../fonts/ascribe-font.svg?q6qoae#ascribe-font') format('svg'); font-weight: normal; font-style: normal; } diff --git a/sass/bootstrap/_overrides.scss b/sass/bootstrap/_overrides.scss new file mode 100644 index 00000000..59612911 --- /dev/null +++ b/sass/bootstrap/_overrides.scss @@ -0,0 +1,2 @@ +@import '../ascribe_variables'; +@import '../variables'; diff --git a/sass/glyphicons-social.scss b/sass/glyphicons-social.scss index 96065085..3c73feb5 100644 --- a/sass/glyphicons-social.scss +++ b/sass/glyphicons-social.scss @@ -1,10 +1,10 @@ @font-face{ font-family:'Glyphicons Social'; - src:url('#{$BASE_URL}static/fonts/glyphicons-social-regular.eot'); - src:url('#{$BASE_URL}static/fonts/fonts/glyphicons-social-regular.eot?#iefix') format('embedded-opentype'), - url('#{$BASE_URL}static/fonts/glyphicons-social-regular.woff2') format('woff2'), - url('#{$BASE_URL}static/fonts/glyphicons-social-regular.woff') format('woff'), - url('#{$BASE_URL}static/fonts/glyphicons-social-regular.ttf') format('truetype'), - url('#{$BASE_URL}static/fonts/glyphicons-social-regular.svg#glyphicons_socialregular') format('svg') + src:url('../fonts/glyphicons-social-regular.eot'); + src:url('../fonts/glyphicons-social-regular.eot?#iefix') format('embedded-opentype'), + url('../fonts/glyphicons-social-regular.woff2') format('woff2'), + url('../fonts/glyphicons-social-regular.woff') format('woff'), + url('../fonts/glyphicons-social-regular.ttf') format('truetype'), + url('../fonts/glyphicons-social-regular.svg#glyphicons_socialregular') format('svg') } -.social{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Social';font-style:normal;font-weight:normal;line-height:1;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.social.x05{font-size:12px}.social.x2{font-size:48px}.social.x3{font-size:72px}.social.x4{font-size:96px}.social.x5{font-size:120px}.social.light:before{color:#f2f2f2}.social.drop:before{text-shadow:-1px 1px 3px rgba(0,0,0,0.3)}.social.flip{-moz-transform:scaleX(-1);-o-transform:scaleX(-1);-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH"}.social.flipv{-moz-transform:scaleY(-1);-o-transform:scaleY(-1);-webkit-transform:scaleY(-1);transform:scaleY(-1);filter:FlipV;-ms-filter:"FlipV"}.social.rotate90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.social.rotate180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.social.rotate270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.social-pinterest:before{content:"\E001"}.social-dropbox:before{content:"\E002"}.social-google-plus:before{content:"\E003"}.social-jolicloud:before{content:"\E004"}.social-yahoo:before{content:"\E005"}.social-blogger:before{content:"\E006"}.social-picasa:before{content:"\E007"}.social-amazon:before{content:"\E008"}.social-tumblr:before{content:"\E009"}.social-wordpress:before{content:"\E010"}.social-instapaper:before{content:"\E011"}.social-evernote:before{content:"\E012"}.social-xing:before{content:"\E013"}.social-zootool:before{content:"\E014"}.social-dribbble:before{content:"\E015"}.social-deviantart:before{content:"\E016"}.social-read-it-later:before{content:"\E017"}.social-linked-in:before{content:"\E018"}.social-forrst:before{content:"\E019"}.social-pinboard:before{content:"\E020"}.social-behance:before{content:"\E021"}.social-github:before{content:"\E022"}.social-youtube:before{content:"\E023"}.social-skitch:before{content:"\E024"}.social-foursquare:before{content:"\E025"}.social-quora:before{content:"\E026"}.social-badoo:before{content:"\E027"}.social-spotify:before{content:"\E028"}.social-stumbleupon:before{content:"\E029"}.social-readability:before{content:"\E030"}.social-facebook:before{content:"\E031"}.social-twitter:before{content:"\E032"}.social-instagram:before{content:"\E033"}.social-posterous-spaces:before{content:"\E034"}.social-vimeo:before{content:"\E035"}.social-flickr:before{content:"\E036"}.social-last-fm:before{content:"\E037"}.social-rss:before{content:"\E038"}.social-skype:before{content:"\E039"}.social-e-mail:before{content:"\E040"}.social-vine:before{content:"\E041"}.social-myspace:before{content:"\E042"}.social-goodreads:before{content:"\E043"}.social-apple:before{content:"\F8FF"}.social-windows:before{content:"\E045"}.social-yelp:before{content:"\E046"}.social-playstation:before{content:"\E047"}.social-xbox:before{content:"\E048"}.social-android:before{content:"\E049"}.social-ios:before{content:"\E050"}.social-wikipedia:before{content:"\E051"}.social-pocket:before{content:"\E052"}.social-steam:before{content:"\E053"}.social-souncloud:before{content:"\E054"}.social-slideshare:before{content:"\E055"}.social-netflix:before{content:"\E056"}.social-paypal:before{content:"\E057"}.social-google-drive:before{content:"\E058"}.social-linux-foundation:before{content:"\E059"}.social-ebay:before{content:"\E060"}.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1)}50%{-webkit-transform:scale(1.1)}100%{-webkit-transform:scale(1)}}@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center center;-webkit-transform:rotate(-200deg);opacity:0}100%{-webkit-transform-origin:center center;-webkit-transform:rotate(0);opacity:1}}@keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0}100%{transform-origin:center center;transform:rotate(0);opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{-webkit-transform:translateY(0)}40%{-webkit-transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px)}}@keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0)}40%{transform:translateY(-30px)}60%{transform:translateY(-15px)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce}@-webkit-keyframes swing{20%,40%,60%,80%,100%{-webkit-transform-origin:top center}20%{-webkit-transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg)}100%{-webkit-transform:rotate(0deg)}}@keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}100%{transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0)}}@keyframes tada{0%{transform:scale(1)}10%,20%{transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg)}40%,60%,80%{transform:scale(1.1) rotate(-3deg)}100%{transform:scale(1) rotate(0)}}.tada{-webkit-animation-name:tada;animation-name:tada} \ No newline at end of file +.social{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Social';font-style:normal;font-weight:normal;line-height:1;vertical-align:top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.social.x05{font-size:12px}.social.x2{font-size:48px}.social.x3{font-size:72px}.social.x4{font-size:96px}.social.x5{font-size:120px}.social.light:before{color:#f2f2f2}.social.drop:before{text-shadow:-1px 1px 3px rgba(0,0,0,0.3)}.social.flip{-moz-transform:scaleX(-1);-o-transform:scaleX(-1);-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH"}.social.flipv{-moz-transform:scaleY(-1);-o-transform:scaleY(-1);-webkit-transform:scaleY(-1);transform:scaleY(-1);filter:FlipV;-ms-filter:"FlipV"}.social.rotate90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.social.rotate180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.social.rotate270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.social-pinterest:before{content:"\E001"}.social-dropbox:before{content:"\E002"}.social-google-plus:before{content:"\E003"}.social-jolicloud:before{content:"\E004"}.social-yahoo:before{content:"\E005"}.social-blogger:before{content:"\E006"}.social-picasa:before{content:"\E007"}.social-amazon:before{content:"\E008"}.social-tumblr:before{content:"\E009"}.social-wordpress:before{content:"\E010"}.social-instapaper:before{content:"\E011"}.social-evernote:before{content:"\E012"}.social-xing:before{content:"\E013"}.social-zootool:before{content:"\E014"}.social-dribbble:before{content:"\E015"}.social-deviantart:before{content:"\E016"}.social-read-it-later:before{content:"\E017"}.social-linked-in:before{content:"\E018"}.social-forrst:before{content:"\E019"}.social-pinboard:before{content:"\E020"}.social-behance:before{content:"\E021"}.social-github:before{content:"\E022"}.social-youtube:before{content:"\E023"}.social-skitch:before{content:"\E024"}.social-foursquare:before{content:"\E025"}.social-quora:before{content:"\E026"}.social-badoo:before{content:"\E027"}.social-spotify:before{content:"\E028"}.social-stumbleupon:before{content:"\E029"}.social-readability:before{content:"\E030"}.social-facebook:before{content:"\E031"}.social-twitter:before{content:"\E032"}.social-instagram:before{content:"\E033"}.social-posterous-spaces:before{content:"\E034"}.social-vimeo:before{content:"\E035"}.social-flickr:before{content:"\E036"}.social-last-fm:before{content:"\E037"}.social-rss:before{content:"\E038"}.social-skype:before{content:"\E039"}.social-e-mail:before{content:"\E040"}.social-vine:before{content:"\E041"}.social-myspace:before{content:"\E042"}.social-goodreads:before{content:"\E043"}.social-apple:before{content:"\F8FF"}.social-windows:before{content:"\E045"}.social-yelp:before{content:"\E046"}.social-playstation:before{content:"\E047"}.social-xbox:before{content:"\E048"}.social-android:before{content:"\E049"}.social-ios:before{content:"\E050"}.social-wikipedia:before{content:"\E051"}.social-pocket:before{content:"\E052"}.social-steam:before{content:"\E053"}.social-souncloud:before{content:"\E054"}.social-slideshare:before{content:"\E055"}.social-netflix:before{content:"\E056"}.social-paypal:before{content:"\E057"}.social-google-drive:before{content:"\E058"}.social-linux-foundation:before{content:"\E059"}.social-ebay:before{content:"\E060"}.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;animation-iteration-count:infinite;-webkit-animation-iteration-count:infinite}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1)}50%{-webkit-transform:scale(1.1)}100%{-webkit-transform:scale(1)}}@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.1)}100%{transform:scale(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rotateIn{0%{-webkit-transform-origin:center center;-webkit-transform:rotate(-200deg);opacity:0}100%{-webkit-transform-origin:center center;-webkit-transform:rotate(0);opacity:1}}@keyframes rotateIn{0%{transform-origin:center center;transform:rotate(-200deg);opacity:0}100%{transform-origin:center center;transform:rotate(0);opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes bounce{0%,20%,50%,80%,100%{-webkit-transform:translateY(0)}40%{-webkit-transform:translateY(-30px)}60%{-webkit-transform:translateY(-15px)}}@keyframes bounce{0%,20%,50%,80%,100%{transform:translateY(0)}40%{transform:translateY(-30px)}60%{transform:translateY(-15px)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce}@-webkit-keyframes swing{20%,40%,60%,80%,100%{-webkit-transform-origin:top center}20%{-webkit-transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg)}100%{-webkit-transform:rotate(0deg)}}@keyframes swing{20%{transform:rotate(15deg)}40%{transform:rotate(-10deg)}60%{transform:rotate(5deg)}80%{transform:rotate(-5deg)}100%{transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0)}}@keyframes tada{0%{transform:scale(1)}10%,20%{transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{transform:scale(1.1) rotate(3deg)}40%,60%,80%{transform:scale(1.1) rotate(-3deg)}100%{transform:scale(1) rotate(0)}}.tada{-webkit-animation-name:tada;animation-name:tada} diff --git a/sass/main.scss b/sass/main.scss index 9ca2a07a..03bf3cb1 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -1,12 +1,9 @@ // If you import a new .scss file, make sure to restart gulp // otherwise it will not be included -$BASE_URL: '<%= BASE_URL %>'; - @import 'mixins'; @import 'ascribe_variables'; @import 'variables'; -@import '../node_modules/bootstrap-sass/assets/stylesheets/bootstrap'; @import '../node_modules/react-star-rating/dist/css/react-star-rating.min'; @import '../node_modules/react-datepicker/dist/react-datepicker'; @import 'glyphicons-social'; diff --git a/server.dev.js b/server.dev.js new file mode 100644 index 00000000..3040667d --- /dev/null +++ b/server.dev.js @@ -0,0 +1,44 @@ +/* eslint-disable strict, no-console */ +/* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */ +'use strict'; + +const removeTrailingSlash = require('remove-trailing-slash'); + +const WebpackDevServer = require('webpack-dev-server'); +const webpack = require('webpack'); +const config = require('./webpack.config.js'); + +require('dotenv').load({ silent: true }); + +const HOST = process.env.ONION_DEV_HOST || 'localhost'; +const PORT = process.env.ONION_DEV_PORT || 3000; +const BASE_PATH = removeTrailingSlash(process.env.ONION_BASE_PATH || '/'); + +// Enable server hot reloading +// React hot reloading is enabled through .babelrc and babel-react-transform +config.entry.unshift(`webpack-dev-server/client?http://${HOST}:${PORT}/`, + 'webpack/hot/dev-server'); +config.plugins.push(new webpack.HotModuleReplacementPlugin()); + +// Configure server +const compiler = webpack(config); + +const server = new WebpackDevServer(compiler, { + publicPath: config.output.publicPath, + historyApiFallback: { + index: config.output.publicPath + }, + hot: true, + noInfo: true, + stats: { colors: true } +}); + +// Start server +server.listen(PORT, HOST, (err) => { + if (err) { + console.error(`Onion dev server ran into ${err} while starting on ${HOST}:${PORT} ` + + `with basePath set to ${BASE_PATH || '/'}. Shutting down...`); + server.close(); + } + console.log(`Onion server running on ${HOST}:${PORT} with basePath set to ${BASE_PATH || '/'}.`); +}); diff --git a/server.js b/server.js index 156b22f3..d6a7b470 100644 --- a/server.js +++ b/server.js @@ -1,30 +1,28 @@ -var argv = require('yargs').argv; -var express = require('express'); -var compression = require('compression'); +/* eslint-disable strict, no-console */ +'use strict'; -var baseUrl = (function () { var baseUrl = process.env.ONION_BASE_URL || '/'; return baseUrl + (baseUrl.match(/\/$/) ? '' : '/'); })(); +const path = require('path'); +const express = require('express'); +const compression = require('compression'); +const removeTrailingSlash = require('remove-trailing-slash'); -var app = express(); +const BASE_PATH = removeTrailingSlash(process.env.ONION_BASE_PATH || '/'); +const PORT = process.env.ONION_PORT || 4000; + +const app = express(); app.use(compression()); +app.use(path.resolve(BASE_PATH, '/static'), express.static(path.resolve(__dirname, 'dist'))); -app.use(baseUrl + 'static/js', express.static(__dirname + '/build/js')); -app.use(baseUrl + 'static/img', express.static(__dirname + '/build/img')); -app.use(baseUrl + 'static/css', express.static(__dirname + '/build/css')); -app.use(baseUrl + 'static/fonts', express.static(__dirname + '/build/fonts')); -app.use(baseUrl + 'static/thirdparty', express.static(__dirname + '/node_modules')); - -app.get(/.*/, function(req, res) { +app.get(/.*/, (req, res) => { console.log('%s %s', req.method, req.path); - res.sendFile(__dirname + '/build/index.html'); + res.sendFile(path.resolve(__dirname, 'dist/index.html')); }); if (require.main === module) { - var port = process.env.PORT || 4000; - console.log('Starting Onion server on port', port, - 'baseUrl is set to', baseUrl); - app.listen(port); + console.log(`Starting Onion server on port ${PORT} with basePath set to ${BASE_PATH || '/'}`); + app.listen(PORT); } module.exports.app = app; diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 64f8d90a..00000000 --- a/test/.eslintrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "parser": "babel-eslint", - "env": { - "mocha": true, - "node": true - }, - "rules": { - "new-cap": [2, {newIsCap: true, capIsNew: false}], - "quotes": [2, "single"], - "eol-last": [0], - "no-mixed-requires": [0], - "no-underscore-dangle": [0], - "global-strict": [2, "always"], - "no-trailing-spaces": [2, { skipBlankLines: true }], - "no-console": 0, - "camelcase": [2, {"properties": "never"}], - }, - "globals": {}, - "plugins": [], - "ecmaFeatures": { - "modules": 1, - "arrowFunctions", - "classes": 1, - "blockBindings": 1, - "defaultParams": 1, - "destructuring": 1, - "objectLiteralComputedProperties": 1, - "objectLiteralDuplicateProperties": 0, - "objectLiteralShorthandMethods": 1, - "objectLiteralShorthandProperties": 1, - "restParams": 1, - "spread": 1, - "superInFunctions": 1, - "templateStrings": 1 - } -} diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 00000000..f269b896 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "env": { + "node": true + }, + "rules": { + "func-names": [0], + "no-console": [0], + "no-process-exit": [0], + "strict": [0], + "import/newline-after-import": [0], + "import/no-extraneous-dependencies": [2, { "devDependencies": true, "optionalDependencies": false }] + } +} diff --git a/test/gemini/.eslintrc.json b/test/gemini/.eslintrc.json new file mode 100644 index 00000000..e70774e9 --- /dev/null +++ b/test/gemini/.eslintrc.json @@ -0,0 +1,8 @@ +{ + "globals": { + "gemini": true + }, + "rules": { + "max-len": [2, { "code": 125 }] + } +} diff --git a/test/gemini/README.md b/test/gemini/README.md index 3521056e..d3957cdd 100644 --- a/test/gemini/README.md +++ b/test/gemini/README.md @@ -23,30 +23,41 @@ 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 +npm install 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 -# Navigate to the binary, ie. /Users/Brett/.nvm/versions/node/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin/phantomjs +# Navigate to the binary, ie. node_modules/phantomjs2/lib/phantom/bin/ (for the local installation) upx -d phantomjs +# If using Linux or OSX, you may also have to convert the binary using `dos2unix`: +brew install dos2unix +dos2unix phantomjs ``` -Finally, [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation). +Finally, [install Gemini locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation), if it +hasn't already been installed through `package.json`. Running Tests ============= +Run Spool and Onion (on localhost.com:3000). + Run PhantomJS: ```bash npm run vi-phantom ``` -And then run Gemini tests: +Gather your initial baseline (on the master branch, for example): + +```bash +npm run vi-update +``` + +And then run Gemini tests (on your current branch with changes, to test for regressions): ```bash npm run vi-test @@ -61,8 +72,8 @@ npm run vi-test:whitelabel 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 +Later on, 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 npm run vi-update diff --git a/test/gemini/tests/environment.js b/test/gemini/tests/environment.js index abc5e436..56d7746b 100644 --- a/test/gemini/tests/environment.js +++ b/test/gemini/tests/environment.js @@ -1,3 +1,4 @@ +/* eslint-disable strict, no-console */ 'use strict'; const MAIN_USER = { diff --git a/test/gemini/tests/main/authenticated.js b/test/gemini/tests/main/authenticated.js index 28e4b120..07162d75 100644 --- a/test/gemini/tests/main/authenticated.js +++ b/test/gemini/tests/main/authenticated.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../environment'); const MAIN_USER = environment.MAIN_USER; const TIMEOUTS = environment.TIMEOUTS; @@ -12,7 +11,7 @@ gemini.suite('Authenticated', (suite) => { suite .setUrl('/collection') .setCaptureElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins unless that suite // also defines a `.before()` actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); @@ -30,185 +29,191 @@ gemini.suite('Authenticated', (suite) => { actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), MAIN_USER.password); actions.click(find('.ascribe-login-wrapper button[type=submit]')); - actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG); }); }); - 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)', TIMEOUTS.NORMAL); - }) - .capture('desktop header'); + gemini.suite('Authenticated', (authenticatedSuite) => { + 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) => { + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG); + actions.waitForElementToShow('#header-notification-dropdown', TIMEOUTS.SUPER_DUPER_EXTRA_LONG); + }) + .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('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')); + }); + }); }); - gemini.suite('Notification dropdown', (headerNotificationSuite) => { - headerNotificationSuite - .setCaptureElements('#header-notification-dropdown ~ .dropdown-menu') + // 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) => { + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG); + }) + .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); + actions.waitForElementToShow('#header-notification-dropdown', TIMEOUTS.SUPER_DUPER_EXTRA_LONG); + }) + .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')); }); }); - }); - // 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)', TIMEOUTS.NORMAL); - }) - .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)', TIMEOUTS.NORMAL); - // 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(TIMEOUTS.NORMAL); - }) - .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', TIMEOUTS.LONG); - }) - - 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', TIMEOUTS.NORMAL); - }); - }); - - gemini.suite('PieceListBulkModal', (pieceListBulkModalSuite) => { - pieceListBulkModalSuite - .setCaptureElements('.piece-list-bulk-modal') - .capture('items selected', (actions, find) => { + gemini.suite('Collection', (collectionSuite) => { + collectionSuite + .setCaptureElements('.ascribe-accordion-list') + .before((actions) => { + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); + // 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(TIMEOUTS.NORMAL); + }) + .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', TIMEOUTS.NORMAL); - - actions.click('.ascribe-table thead tr input[type="checkbox"]'); - actions.waitForElementToShow('.piece-list-bulk-modal'); + actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.LONG); }); - }); - }); - 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'); - actions.waitForElementToShow('.ascribe-piece-list-toolbar .search-bar .icon-ascribe-search', TIMEOUTS.NORMAL); - }) - - 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)', TIMEOUTS.NORMAL); - }); - }); - - 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)', TIMEOUTS.NORMAL); - }); - }); - }); - - gemini.suite('Register work', (registerSuite) => { - registerSuite - .setUrl('/register_piece') - .before((actions, find) => { - // The editions options are only rendered after the whitelabel is fetched, so - // we have to wait for it here - // We have to check for the sibling checkbox class as the input itself is hidden - actions.waitForElementToShow('.ascribe-form input[name="num_editions-checkbox"] ~ .checkbox', TIMEOUTS.NORMAL); - }) - .capture('register work', (actions, find) => { - // The uploader options are only rendered after the user is fetched, so - // we also have to wait for it here - actions.waitForElementToShow('.file-drag-and-drop-dialog .present-options', TIMEOUTS.NORMAL); - }) - .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[name="num_editions-checkbox"] ~ .checkbox')); - // Wait for transition - actions.wait(500); - actions.sendKeys(find('.ascribe-form input[name="num_editions"]'), '50'); + 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', TIMEOUTS.NORMAL); + }); }); - gemini.suite('Register work hash', (registerHashSuite) => { - registerHashSuite - .setUrl('/register_piece?method=hash') - .capture('register work hash method'); + 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', TIMEOUTS.NORMAL); + + actions.click('.ascribe-table thead tr input[type="checkbox"]'); + actions.waitForElementToShow('.piece-list-bulk-modal'); + }); + }); }); - gemini.suite('Register work upload', (registerUploadSuite) => { - registerUploadSuite - .setUrl('/register_piece?method=upload') - .capture('register work upload method'); - }); - }); + 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'); + actions.waitForElementToShow('.ascribe-piece-list-toolbar .search-bar .icon-ascribe-search', + TIMEOUTS.NORMAL); + }); - 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', TIMEOUTS.NORMAL); - }) - .capture('user settings'); + 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)', TIMEOUTS.NORMAL); + }); + }); + + 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)', TIMEOUTS.NORMAL); + }); + }); + }); + + gemini.suite('Register work', (registerSuite) => { + registerSuite + .setUrl('/register_piece') + .before((actions) => { + // The editions options are only rendered after the whitelabel is fetched, so + // we have to wait for it here + // We have to check for the sibling checkbox class as the input itself is hidden + actions.waitForElementToShow('.ascribe-form input[name="num_editions-checkbox"] ~ .checkbox', + TIMEOUTS.NORMAL); + }) + .capture('register work', (actions) => { + // The uploader options are only rendered after the user is fetched, so + // we also have to wait for it here + actions.waitForElementToShow('.file-drag-and-drop-dialog .present-options', TIMEOUTS.NORMAL); + }) + .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[name="num_editions-checkbox"] ~ .checkbox')); + // Wait for transition + 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) => { + // This will be called before every nested suite begins unless that suite + // also defines a `.before()` + actions.waitForElementToShow('.settings-container', TIMEOUTS.NORMAL); + }) + .capture('user settings'); + }); }); // Suite just to log out after suites have run @@ -216,7 +221,7 @@ gemini.suite('Authenticated', (suite) => { logoutSuite .setUrl('/logout') .ignoreElements('.ascribe-body') - .capture('logout', (actions, find) => { + .capture('logout', (actions) => { actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG); }); }); diff --git a/test/gemini/tests/main/basic.js b/test/gemini/tests/main/basic.js index fd07af2b..4437ce53 100644 --- a/test/gemini/tests/main/basic.js +++ b/test/gemini/tests/main/basic.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../environment'); const MAIN_USER = environment.MAIN_USER; const TIMEOUTS = environment.TIMEOUTS; @@ -12,7 +11,7 @@ gemini.suite('Basic', (suite) => { suite .setUrl('/login') .setCaptureElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins unless that suite // also defines a `.before()` actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); @@ -22,7 +21,7 @@ gemini.suite('Basic', (suite) => { headerSuite .setCaptureElements('nav.navbar .container') .skip(/Mobile/) - .capture('desktop header', (actions, find) => { + .capture('desktop header', (actions) => { actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL); }) .capture('hover on active item', (actions, find) => { @@ -40,7 +39,7 @@ gemini.suite('Basic', (suite) => { headerMobileSuite .setCaptureElements('nav.navbar .container') .skip(/Desktop/) - .capture('mobile header', (actions, find) => { + .capture('mobile header', (actions) => { actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL); }) .capture('expanded mobile header', (actions, find) => { @@ -56,7 +55,7 @@ gemini.suite('Basic', (suite) => { gemini.suite('Footer', (footerSuite) => { footerSuite .setCaptureElements('.ascribe-footer') - .capture('footer', (actions, find) => { + .capture('footer', (actions) => { actions.waitForElementToShow('.ascribe-footer', TIMEOUTS.NORMAL); }) .capture('hover on footer item', (actions, find) => { @@ -64,14 +63,14 @@ gemini.suite('Basic', (suite) => { actions.mouseMove(footerItem); }) .capture('hover on footer social item', (actions, find) => { - const footerSocialItem = find('.ascribe-footer a.social') + const footerSocialItem = find('.ascribe-footer a.social'); actions.mouseMove(footerSocialItem); }); }); gemini.suite('Login', (loginSuite) => { loginSuite - .capture('login', (actions, find) => { + .capture('login', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .capture('hover on login submit', (actions, find) => { @@ -97,7 +96,7 @@ gemini.suite('Basic', (suite) => { gemini.suite('Sign up', (signUpSuite) => { signUpSuite .setUrl('/signup') - .capture('sign up', (actions, find) => { + .capture('sign up', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .capture('sign up form filled with focus', (actions, find) => { @@ -113,7 +112,7 @@ gemini.suite('Basic', (suite) => { gemini.suite('Password reset', (passwordResetSuite) => { passwordResetSuite .setUrl('/password_reset') - .capture('password reset', (actions, find) => { + .capture('password reset', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .capture('password reset form filled with focus', (actions, find) => { @@ -127,12 +126,13 @@ gemini.suite('Basic', (suite) => { gemini.suite('Coa verify', (coaVerifySuite) => { coaVerifySuite .setUrl('/coa_verify') - .capture('coa verify', (actions, find) => { + .capture('coa verify', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); }) .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'); + 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')); diff --git a/test/gemini/tests/main/detail.js b/test/gemini/tests/main/detail.js index 1a51836b..3d2b0e87 100644 --- a/test/gemini/tests/main/detail.js +++ b/test/gemini/tests/main/detail.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../environment'); const MAIN_USER = environment.MAIN_USER; const TIMEOUTS = environment.TIMEOUTS; @@ -13,24 +12,24 @@ const editionUrl = `/editions/${environment.MAIN_EDITION_ID}`; * 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) => { +gemini.suite('Basic 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()` + .before((actions) => { actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); // Wait for the social media buttons to appear - actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', TIMEOUTS.SUPER_DUPER_EXTRA_LONG); - actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', TIMEOUTS.SUPER_DUPER_EXTRA_LONG); + actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', + TIMEOUTS.SUPER_DUPER_EXTRA_LONG); + actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', + TIMEOUTS.SUPER_DUPER_EXTRA_LONG); actions.waitForElementToShow('.ascribe-media-player', TIMEOUTS.LONG); }); gemini.suite('Basic piece', (basicPieceSuite) => { basicPieceSuite .setUrl(pieceUrl) - .capture('basic piece') + .capture('basic piece'); gemini.suite('Shmui', (shmuiSuite) => { shmuiSuite. @@ -49,72 +48,97 @@ gemini.suite('Work detail', (suite) => { .setUrl(editionUrl) .capture('basic edition'); }); +}); + +gemini.suite('Authenticated work detail', (suite) => { + suite + .setCaptureElements('.ascribe-body') + .before((actions) => { + actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); + }); - // 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-app', TIMEOUTS.NORMAL); - }) .capture('logged in', (actions, find) => { + console.log('logging in'); actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), MAIN_USER.email); actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), MAIN_USER.password); actions.click(find('.ascribe-login-wrapper button[type=submit]')); - actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL); + actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG); }); }); - 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('Authenticated', (authenticatedSuite) => { + authenticatedSuite + .before((actions) => { + // Wait for the social media buttons to appear + actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', + TIMEOUTS.SUPER_DUPER_EXTRA_LONG); + actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', + TIMEOUTS.SUPER_DUPER_EXTRA_LONG); + actions.waitForElementToShow('.ascribe-media-player', TIMEOUTS.LONG); }); - }); - 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(); - } - } + gemini.suite('Authorized piece', (authorizedPieceSuite) => { + console.log('authorized piece'); + 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) => { + console.log('hover on action button'); + 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')); }); - actions.click(find('.ascribe-detail-property .ascribe-button-list button.ascribe-action-button-email')); + }); - // Wait for transition - actions.wait(1000); - }); + 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 + // eslint-disable-next-line prefer-arrow-callback + actions.executeJS(function addButtonTypeAsClass(window) { + /* eslint-disable no-var, prefer-template */ + var actionButtonsSelector = '.ascribe-detail-property .ascribe-button-list button.btn-default'; + var actionButtons = window.document.querySelectorAll(actionButtonsSelector); + var ii = 0; + for (; ii < actionButtons.length; ++ii) { + if (actionButtons[ii].textContent) { + actionButtons[ii].className += ' ascribe-action-button-' + + actionButtons[ii].textContent.toLowerCase(); + } + } + /* eslint-enable no-var */ + }); + 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 @@ -122,10 +146,11 @@ gemini.suite('Work detail', (suite) => { logoutSuite .setUrl('/logout') .ignoreElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { + console.log('before log out'); actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); }) - .capture('logout', (actions, find) => { + .capture('logout', (actions) => { actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG); }); }); diff --git a/test/gemini/tests/whitelabel/23vivi/23vivi.js b/test/gemini/tests/whitelabel/23vivi/23vivi.js index 9d0c397e..17c53358 100644 --- a/test/gemini/tests/whitelabel/23vivi/23vivi.js +++ b/test/gemini/tests/whitelabel/23vivi/23vivi.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../../environment'); const TIMEOUTS = environment.TIMEOUTS; @@ -10,7 +9,7 @@ const TIMEOUTS = environment.TIMEOUTS; gemini.suite('23vivi', (suite) => { suite .setCaptureElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); }); @@ -18,7 +17,7 @@ gemini.suite('23vivi', (suite) => { gemini.suite('Landing', (landingSuite) => { landingSuite .setUrl('/') - .capture('landing', (actions, find) => { + .capture('landing', (actions) => { // Wait for the logo to appear actions.waitForElementToShow('.vivi23-landing--header-logo', TIMEOUTS.LONG); }); diff --git a/test/gemini/tests/whitelabel/cyland/cyland.js b/test/gemini/tests/whitelabel/cyland/cyland.js index 9228186a..5939a73c 100644 --- a/test/gemini/tests/whitelabel/cyland/cyland.js +++ b/test/gemini/tests/whitelabel/cyland/cyland.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../../environment'); const TIMEOUTS = environment.TIMEOUTS; @@ -10,7 +9,7 @@ const TIMEOUTS = environment.TIMEOUTS; gemini.suite('Cyland', (suite) => { suite .setCaptureElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); }); @@ -20,7 +19,7 @@ gemini.suite('Cyland', (suite) => { .setUrl('/') // Ignore Cyland's logo as it's a gif .ignoreElements('.cyland-landing img') - .capture('landing', (actions, find) => { + .capture('landing', (actions) => { actions.waitForElementToShow('.cyland-landing img', TIMEOUTS.LONG); }); }); diff --git a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js index 81e936e9..5616a32c 100644 --- a/test/gemini/tests/whitelabel/ikonotv/ikonotv.js +++ b/test/gemini/tests/whitelabel/ikonotv/ikonotv.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../../environment'); const MAIN_USER = environment.MAIN_USER; const TIMEOUTS = environment.TIMEOUTS; @@ -11,7 +10,7 @@ const TIMEOUTS = environment.TIMEOUTS; gemini.suite('Ikonotv', (suite) => { suite .setCaptureElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); }); @@ -22,9 +21,11 @@ gemini.suite('Ikonotv', (suite) => { // 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) => { + .capture('landing', (actions) => { // Stop background animation - actions.executeJS(function (window) { + // eslint-disable-next-line prefer-arrow-callback + actions.executeJS(function removeBackgroundAnimation(window) { + /* eslint-disable no-var */ var landingBackground = window.document.querySelector('.client--ikonotv .route--landing'); landingBackground.style.animation = 'none'; landingBackground.style.webkitAnimation = 'none'; @@ -37,10 +38,10 @@ gemini.suite('Ikonotv', (suite) => { // 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 + gemini.suite('Ikonotv basic', (basicSuite) => { + basicSuite .setCaptureElements('.ascribe-app') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins unless that suite // also defines a `.before()` actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); diff --git a/test/gemini/tests/whitelabel/lumenus/lumenus.js b/test/gemini/tests/whitelabel/lumenus/lumenus.js index b15f31ee..6245b03b 100644 --- a/test/gemini/tests/whitelabel/lumenus/lumenus.js +++ b/test/gemini/tests/whitelabel/lumenus/lumenus.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../../environment'); const TIMEOUTS = environment.TIMEOUTS; @@ -10,7 +9,7 @@ const TIMEOUTS = environment.TIMEOUTS; gemini.suite('Lumenus', (suite) => { suite .setCaptureElements('.ascribe-body') - .before((actions, find) => { + .before((actions) => { // This will be called before every nested suite begins actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL); }); @@ -18,7 +17,7 @@ gemini.suite('Lumenus', (suite) => { gemini.suite('Landing', (landingSuite) => { landingSuite .setUrl('/') - .capture('landing', (actions, find) => { + .capture('landing', (actions) => { // Wait for the logo to appear actions.waitForElementToShow('.wp-landing-wrapper img', TIMEOUTS.LONG); }); diff --git a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js index 0d5ac26b..df997513 100644 --- a/test/gemini/tests/whitelabel/shared/whitelabel_basic.js +++ b/test/gemini/tests/whitelabel/shared/whitelabel_basic.js @@ -1,6 +1,5 @@ 'use strict'; -const gemini = require('gemini'); const environment = require('../../environment'); const MAIN_USER = environment.MAIN_USER; const TIMEOUTS = environment.TIMEOUTS; @@ -11,7 +10,7 @@ const TIMEOUTS = environment.TIMEOUTS; gemini.suite('Whitelabel basic', (suite) => { suite .setCaptureElements('.ascribe-wallet-app > .container') - .before((actions, find) => { + .before((actions) => { // 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' @@ -26,7 +25,7 @@ gemini.suite('Whitelabel basic', (suite) => { .setUrl('/login') // See Ikono .skip(/Ikono/) - .capture('login', (actions, find) => { + .capture('login', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); // 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 @@ -59,7 +58,7 @@ gemini.suite('Whitelabel basic', (suite) => { .setUrl('/signup') // See Ikono .skip(/Ikono/) - .capture('sign up', (actions, find) => { + .capture('sign up', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); // Wait in case the form reloads due to other assets loading actions.wait(500); @@ -77,7 +76,7 @@ gemini.suite('Whitelabel basic', (suite) => { gemini.suite('Password reset', (passwordResetSuite) => { passwordResetSuite .setUrl('/password_reset') - .capture('password reset', (actions, find) => { + .capture('password reset', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); // Wait in case the form reloads due to other assets loading actions.wait(500); @@ -93,14 +92,15 @@ gemini.suite('Whitelabel basic', (suite) => { gemini.suite('Coa verify', (coaVerifySuite) => { coaVerifySuite .setUrl('/coa_verify') - .capture('coa verify', (actions, find) => { + .capture('coa verify', (actions) => { actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL); // 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'); + 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')); diff --git a/test/integration/.eslintrc.json b/test/integration/.eslintrc.json new file mode 100644 index 00000000..01ff6344 --- /dev/null +++ b/test/integration/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "env": { + "mocha": true + }, + "rules": { + "max-len": [2, { "code": 125 }], + "prefer-arrow-callback": [0] + } +} diff --git a/test/integration/README.md b/test/integration/README.md index 8fca1cac..d43b492d 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -1,10 +1,10 @@ # TL;DR -Copy the contents of `.env-template` to `.env` and [fill up the missing keys with +Copy the contents of `.env_template` to `.env` and [fill up the missing keys with information from your SauceLabs account](#how-to-set-up-your-env-config-file). ```bash $ npm install -$ npm run tunnel +$ npm run sauce-tunnel $ npm test && git commit ``` @@ -99,7 +99,7 @@ On the JavaScript side, we use: ## How to set up your `.env` config file -In the root of this repository there is a file called `.env-template`. Create a +In the root of this repository there is a file called `.env_template`. Create a copy and call it `.env`. This file will store some values we need to connect to Saucelabs. diff --git a/test/integration/setup.js b/test/integration/setup.js index 334423e7..3989a47b 100644 --- a/test/integration/setup.js +++ b/test/integration/setup.js @@ -1,25 +1,25 @@ 'use strict'; -const config = require('./config'); const colors = require('colors'); const sauceConnectLauncher = require('sauce-connect-launcher'); +const config = require('./config'); let globalSauceProcess; if (!process.env.SAUCE_USERNAME) { console.log(colors.red('SAUCE_USERNAME is missing. Please check the README.md file.')); - process.exit(1); //eslint-disable-line no-process-exit + process.exit(1); } if (!process.env.SAUCE_ACCESS_KEY) { console.log(colors.red('SAUCE_ACCESS_KEY is missing. Please check the README.md file.')); - process.exit(1); //eslint-disable-line no-process-exit + process.exit(1); } if (process.env.SAUCE_AUTO_CONNECT) { - before(function(done) { + before(function (done) { console.log(colors.yellow('Setting up tunnel from Saucelabs to your lovely computer, will take a while.')); // Creating the tunnel takes a bit of time. For this case we can safely disable Mocha timeouts. this.timeout(0); diff --git a/test/integration/tests/test-login.js b/test/integration/tests/test-login.js index 853d48e5..7ec5fb79 100644 --- a/test/integration/tests/test-login.js +++ b/test/integration/tests/test-login.js @@ -12,12 +12,12 @@ chai.should(); function testSuite(browserName, version, platform) { - describe(`[${browserName} ${version} ${platform}] Login logs users in`, function() { + describe(`[${browserName} ${version} ${platform}] Login logs users in`, function () { // Set timeout to zero so Mocha won't time out. this.timeout(0); let browser; - before(function() { + before(function () { // No need to inject `username` or `access_key`, by default the constructor // looks up the values in `process.env.SAUCE_USERNAME` and `process.env.SAUCE_ACCESS_KEY` browser = wd.promiseChainRemote('ondemand.saucelabs.com', 80); @@ -35,14 +35,14 @@ function testSuite(browserName, version, platform) { }); }); - after(function() { + after(function () { return browser.quit(); }); - it('should contain "Log in" in the title', function() { - return browser. - waitForElementByCss('.ascribe-login-wrapper', asserters.isDisplayed, 2000) - title().should.become('Log in'); + it('should contain "Log in" in the title', function () { + return browser + .waitForElementByCss('.ascribe-login-wrapper', asserters.isDisplayed, 2000) + .title().should.become('Log in'); }); }); } diff --git a/test/integration/tunnel.js b/test/integration/tunnel.js index 2a7fa371..5399d2ad 100644 --- a/test/integration/tunnel.js +++ b/test/integration/tunnel.js @@ -1,15 +1,17 @@ 'use strict'; -const config = require('./config'); //eslint-disable-line no-unused-vars const colors = require('colors'); const sauceConnectLauncher = require('sauce-connect-launcher'); +const config = require('./config'); // eslint-disable-line no-unused-vars function connect() { - console.log(colors.yellow('Setting up tunnel from Saucelabs to your lovely computer, will take a while.')); + console.log( + colors.yellow('Setting up tunnel from Saucelabs to your lovely computer, will take a while.') + ); // Creating the tunnel takes a bit of time. For this case we can safely disable Mocha timeouts. - sauceConnectLauncher(function (err) { + sauceConnectLauncher((err) => { if (err) { console.error(err.message); return; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..216b27f1 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,223 @@ +/* eslint-disable strict, no-console, object-shorthand, prefer-template */ +/* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */ +'use strict'; + +const path = require('path'); +const removeTrailingSlash = require('remove-trailing-slash'); + +const webpack = require('webpack'); +const autoPrefixer = require('autoprefixer'); +const combineLoaders = require('webpack-combine-loaders'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +require('dotenv').load({ silent: true }); + +const EXTRACT = process.env.NODE_ENV === 'extract'; +const PRODUCTION = process.env.NODE_ENV === 'production'; + +const PATHS = { + APP: path.resolve(__dirname, 'js/app.js'), + BUILD: path.resolve(__dirname, 'build'), + DIST: path.resolve(__dirname, 'dist'), + NODE_MODULES: path.resolve(__dirname, 'node_modules'), +}; + + +/** EXTERNAL DEFINITIONS INJECTED INTO APP **/ +const DEFINITIONS = { + 'process.env': { + NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'), + + APP_VERSION: JSON.stringify(process.env.ONION_APP_VERSION || 'dev'), + + API_URL: JSON.stringify( + removeTrailingSlash(process.env.ONION_API_URL || 'https://staging.ascribe.io/api') + ), + APP_BASE_PATH: JSON.stringify( + removeTrailingSlash(process.env.ONION_BASE_PATH || '') + ), + SERVER_URL: JSON.stringify( + removeTrailingSlash(process.env.ONION_SERVER_URL || 'https://staging.ascribe.io') + ), + + RAVEN_DSN_URL: JSON.stringify(process.env.RAVEN_DSN_URL || ''), + + S3_ACCESS_KEY: JSON.stringify(process.env.S3_ACCESS_KEY || ''), + }, +}; + + +/** PLUGINS **/ +const PLUGINS = [ + new webpack.DefinePlugin(DEFINITIONS), + new webpack.NoErrorsPlugin(), + + // Handle any dependencies that don't play nicely with System.import resolution + new CopyWebpackPlugin([ + { + from: path.resolve(PATHS.NODE_MODULES, 'audiojs/audiojs'), + to: 'third_party/audiojs' + }, + ]), + + // Generate index.html for app with link and style tags addded + new HtmlWebpackPlugin({ + filename: 'index.html', + minify: PRODUCTION ? { + collapseWhitespace: true, + minifyJS: true, + removeComments: true, + removeRedundantAttributes: true + } : false, + template: path.resolve(__dirname, 'index_template.html'), + }), +]; + +const PROD_PLUGINS = [ + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + }, + output: { + comments: false + } + }), + new webpack.LoaderOptionsPlugin({ + debug: false, + minimize: true + }), +]; + +if (PRODUCTION || EXTRACT) { + // Extract stylesheets out of bundle + PLUGINS.push( + new ExtractTextPlugin(PRODUCTION ? 'css/styles.min.css' : 'css/styles.css', { + allChunks: true + }) + ); +} + +if (PRODUCTION) { + PLUGINS.push(...PROD_PLUGINS); +} + + +/** LOADERS **/ +const JS_LOADER = combineLoaders([ + { + loader: 'babel', + query: { + cacheDirectory: true, + }, + }, +]); + +const CSS_LOADER = combineLoaders([ + { + loader: 'css', + query: { + sourceMap: true, + }, + }, + { loader: 'postcss' }, +]); + +const SASS_LOADER = `${CSS_LOADER}!` + combineLoaders([ + { + loader: 'sass', + query: { + precision: '8', // See https://github.com/twbs/bootstrap-sass#sass-number-precision + outputStyle: 'expanded', + sourceMap: true, + }, + } +]); + +const URL_FONT_LOADER = combineLoaders([ + { + loader: 'url', + query: { + limit: 10000, + name: 'fonts/[name].[ext]', + }, + }, +]); + +const FILE_FONT_LOADER = combineLoaders([ + { + loader: 'file', + query: { + name: 'fonts/[name].[ext]', + }, + }, +]); + +const LOADERS = [ + { + test: /\.jsx?$/, + exclude: [PATHS.NODE_MODULES], + loader: JS_LOADER, + }, + { + test: /\.s[ac]ss$/, + exclude: [PATHS.NODE_MODULES], + loader: PRODUCTION || EXTRACT ? ExtractTextPlugin.extract('style', SASS_LOADER) + : `style!${SASS_LOADER}`, + }, + + // Dependencies + // Shmui + { + test: /\.css$/, + include: [path.resolve(PATHS.NODE_MODULES, 'shmui')], + loader: `style!${CSS_LOADER}`, + }, + + // Fonts + // woffs and svgs are typically smaller should we can try to load them as a url + { + test: /\.(woff2?|svg)$/, + loader: URL_FONT_LOADER, + }, + { + test: /\.(ttf|eot)$/, + loader: FILE_FONT_LOADER, + }, +]; + + +/** EXPORTED WEBPACK CONFIG **/ +const config = { + entry: [ + PRODUCTION || EXTRACT ? 'bootstrap-loader/extractStyles' : 'bootstrap-loader', + PATHS.APP + ], + + output: { + filename: PRODUCTION ? 'js/bundle.min.js' : 'js/bundle.js', + path: PRODUCTION ? PATHS.DIST : PATHS.BUILD, + publicPath: '/static/', + }, + + debug: !PRODUCTION, + + devtool: PRODUCTION ? '#source-map' : '#inline-source-map', + + resolve: { + extensions: ['', '.js', '.jsx'], + modules: ['node_modules'], // Don't use absolute path here to allow recursive matching + }, + + plugins: PLUGINS, + + module: { + loaders: LOADERS, + }, + + postcss: [autoPrefixer()], +}; + +module.exports = config;