diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cd0a24ec..c14909783 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,12 +15,20 @@ workflows: - test-unit: requires: - prep-deps-npm - - test-integration-mascara: + - test-integration-mascara-chrome: + requires: + - prep-deps-npm + - prep-scss + - test-integration-mascara-firefox: requires: - prep-deps-npm - prep-deps-firefox - prep-scss - - test-integration-flat: + - test-integration-flat-chrome: + requires: + - prep-deps-npm + - prep-scss + - test-integration-flat-firefox: requires: - prep-deps-npm - prep-deps-firefox @@ -99,7 +107,9 @@ jobs: name: test:coverage command: npm run test:coverage - test-integration-flat: + test-integration-flat-firefox: + environment: + browsers: '["Firefox"]' docker: - image: circleci/node:8-browsers steps: @@ -125,7 +135,28 @@ jobs: name: test:integration:flat command: npm run test:flat - test-integration-mascara: + test-integration-flat-chrome: + environment: + browsers: '["Chrome"]' + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package-lock.json" }} + - run: + name: Get Scss Cache key + # this allows us to checksum against a whole directory + command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum + - restore_cache: + key: scss-cache-{{ checksum "scss_checksum" }} + - run: + name: test:integration:flat + command: npm run test:flat + + test-integration-mascara-firefox: + environment: + browsers: '["Firefox"]' docker: - image: circleci/node:8-browsers steps: @@ -150,3 +181,22 @@ jobs: - run: name: test:integration:mascara command: npm run test:mascara + + test-integration-mascara-chrome: + environment: + browsers: '["Chrome"]' + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package-lock.json" }} + - run: + name: Get Scss Cache key + # this allows us to checksum against a whole directory + command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum + - restore_cache: + key: scss-cache-{{ checksum "scss_checksum" }} + - run: + name: test:integration:mascara + command: npm run test:mascara diff --git a/development/genStates.js b/development/genStates.js index 39a672ee0..d92e2bc2e 100644 --- a/development/genStates.js +++ b/development/genStates.js @@ -1,18 +1,21 @@ const fs = require('fs') const path = require('path') +const { promisify } = require('util') -const statesPath = path.join(__dirname, 'states') -const stateNames = fs.readdirSync(statesPath) +start().catch(console.error) -const states = stateNames.reduce((result, stateFileName) => { - const statePath = path.join(__dirname, 'states', stateFileName) - const stateFile = fs.readFileSync(statePath).toString() - const state = JSON.parse(stateFile) - result[stateFileName.split('.')[0].replace(/-/g, ' ', 'g')] = state - return result -}, {}) - -const result = `module.exports = ${JSON.stringify(states)}` - -const statesJsonPath = path.join(__dirname, 'states.js') -fs.writeFileSync(statesJsonPath, result) +async function start () { + const statesPath = path.join(__dirname, 'states') + const stateFilesNames = await promisify(fs.readdir)(statesPath) + const states = {} + await Promise.all(stateFilesNames.map(async (stateFileName) => { + const stateFilePath = path.join(__dirname, 'states', stateFileName) + const stateFileContent = await promisify(fs.readFile)(stateFilePath, 'utf8') + const state = JSON.parse(stateFileContent) + const stateName = stateFileName.split('.')[0].replace(/-/g, ' ', 'g') + states[stateName] = state + })) + const generatedFileContent = `module.exports = ${JSON.stringify(states)}` + const generatedFilePath = path.join(__dirname, 'states.js') + await promisify(fs.writeFile)(generatedFilePath, generatedFileContent) +} diff --git a/mock-dev.js b/development/mock-dev.js similarity index 82% rename from mock-dev.js rename to development/mock-dev.js index 6627c1153..a1fb3a86d 100644 --- a/mock-dev.js +++ b/development/mock-dev.js @@ -15,16 +15,16 @@ const extend = require('xtend') const render = require('react-dom').render const h = require('react-hyperscript') -const Root = require('./ui/app/root') -const configureStore = require('./ui/app/store') -const actions = require('./ui/app/actions') -const states = require('./development/states') -const backGroundConnectionModifiers = require('./development/backGroundConnectionModifiers') -const Selector = require('./development/selector') -const MetamaskController = require('./app/scripts/metamask-controller') -const firstTimeState = require('./app/scripts/first-time-state') -const ExtensionPlatform = require('./app/scripts/platforms/extension') -const extension = require('./development/mockExtension') +const Root = require('../ui/app/root') +const configureStore = require('../ui/app/store') +const actions = require('../ui/app/actions') +const states = require('./states') +const backGroundConnectionModifiers = require('./backGroundConnectionModifiers') +const Selector = require('./selector') +const MetamaskController = require('../app/scripts/metamask-controller') +const firstTimeState = require('../app/scripts/first-time-state') +const ExtensionPlatform = require('../app/scripts/platforms/extension') +const extension = require('./mockExtension') const noop = function () {} const log = require('loglevel') @@ -51,7 +51,7 @@ function updateQueryParams(newView) { // CSS // -const MetaMaskUiCss = require('./ui/css') +const MetaMaskUiCss = require('../ui/css') const injectCss = require('inject-css') // diff --git a/ui-dev.js b/development/ui-dev.js similarity index 89% rename from ui-dev.js rename to development/ui-dev.js index 620d81667..cac433909 100644 --- a/ui-dev.js +++ b/development/ui-dev.js @@ -17,10 +17,10 @@ const render = require('react-dom').render const h = require('react-hyperscript') -const Root = require('./ui/app/root') -const configureStore = require('./development/uiStore') -const states = require('./development/states') -const Selector = require('./development/selector') +const Root = require('../ui/app/root') +const configureStore = require('./uiStore') +const states = require('./states') +const Selector = require('./selector') // logger const log = require('loglevel') @@ -35,7 +35,7 @@ const firstState = states[selectedView] updateQueryParams(selectedView) // CSS -const MetaMaskUiCss = require('./ui/css') +const MetaMaskUiCss = require('../ui/css') const injectCss = require('inject-css') diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js index 450d6a479..192da3399 100644 --- a/mascara/src/app/first-time/create-password-screen.js +++ b/mascara/src/app/first-time/create-password-screen.js @@ -2,6 +2,7 @@ import EventEmitter from 'events' import React, { Component } from 'react' import PropTypes from 'prop-types' import {connect} from 'react-redux' +import classnames from 'classnames' import {createNewVaultAndKeychain} from '../../../../ui/app/actions' import LoadingScreen from './loading-screen' import Breadcrumbs from './breadcrumbs' @@ -14,6 +15,7 @@ class CreatePasswordScreen extends Component { goToImportWithSeedPhrase: PropTypes.func.isRequired, goToImportAccount: PropTypes.func.isRequired, next: PropTypes.func.isRequired, + isMascara: PropTypes.bool.isRequired, } state = { @@ -53,14 +55,17 @@ class CreatePasswordScreen extends Component { } render () { - const { isLoading, goToImportWithSeedPhrase } = this.props + const { isLoading, goToImportWithSeedPhrase, isMascara } = this.props return isLoading ? : ( -
-
-
+
+
+ {isMascara &&
It allows you to hold ether & tokens, and interact with decentralized applications.
-
+
}
Create Password @@ -127,7 +132,7 @@ class CreatePasswordScreen extends Component { } export default connect( - ({ appState: { isLoading } }) => ({ isLoading }), + ({ appState: { isLoading }, metamask: { isMascara } }) => ({ isLoading, isMascara }), dispatch => ({ createAccount: password => dispatch(createNewVaultAndKeychain(password)), }) diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css index f59eb4ce1..6c45816bd 100644 --- a/mascara/src/app/first-time/index.css +++ b/mascara/src/app/first-time/index.css @@ -1,3 +1,10 @@ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), url('/fonts/Roboto/Roboto-Regular.ttf') format('truetype'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} .first-time-flow { width: 100vw; @@ -6,21 +13,52 @@ display: flex; justify-content: center; flex: 1 0 auto; + font-weight: 400; + font-family: Roboto; } -.alpha-warning { +.alpha-warning, +.alpha-warning-welcome-screen { background: #f7861c; color: #fff; line-height: 2em; padding-left: 10vw; } -.first-view-main { +.alpha-warning-welcome-screen { + padding-left: 0; + text-align: center; +} + +.first-view-main-wrapper { + display: flex; + width: 100%; + padding-left: 10vw; +} + +.first-view-main, +.first-view-main__mascara { display: flex; flex-direction: row-reverse; + justify-content: center; +} + +.first-view-main__mascara { justify-content: space-between; } +@media screen and (max-width: 575px) { + .first-view-main-wrapper { + padding: 0; + } +} + +@media screen and (min-width: 1281px) { + .first-view-main { + width: 62vw; + } +} + .mascara-info { display: flex; flex-flow: column; @@ -81,6 +119,45 @@ width: initial !important; } + .alpha-warning, + .alpha-warning-welcome-screen { + line-height: 1em; + padding: 8px 12px; + } + + .first-view-main { + height: 100%; + flex-direction: column; + align-items: center; + justify-content: space-evenly; + margin-top: 12px; + } + + .first-view-main .create-password { + margin-top: 0px; + } + + .mascara-info { + margin-top: 0px; + width: 100%; + align-items: center; + } + + .mascara-info .info { + text-align: center; + font-size: 16px; + margin: 0 10px; + padding-left: 0px; + } + + .mascara-info :first-child { + align-self: center; + } + + .first-view-phone-invisible { + display: none; + } + .first-time-flow__input { width: initial !important; font-size: 14px !important; @@ -180,11 +257,11 @@ color: #1B344D; font-size: 16px; line-height: 23px; - font-family: Montserrat UltraLight; + font-family: Roboto; } .buy-ether__small-body-text { - font-family: Montserrat UltraLight; + font-family: Roboto; height: 14px; color: #757575; font-size: 12px; @@ -212,7 +289,7 @@ height: 334px; overflow-y: auto; color: #757575; - font-family: Montserrat UltraLight; + font-family: Roboto; font-size: 12px; line-height: 15px; text-align: justify; @@ -237,7 +314,7 @@ color: #5B5D67; font-size: 16px; line-height: 23px; - font-family: Montserrat UltraLight; + font-family: Roboto; } .backup-phrase__secret { @@ -255,7 +332,7 @@ .backup-phrase__secret-words { width: 310px; color: #5B5D67; - font-family: Montserrat Light; + font-family: Roboto; font-size: 20px; line-height: 26px; text-align: center; @@ -284,7 +361,7 @@ background: none; box-shadow: none; color: #FFFFFF; - font-family: Montserrat Regular; + font-family: Roboto; font-size: 12px; font-weight: bold; line-height: 15px; @@ -338,7 +415,7 @@ button.backup-phrase__reveal-button:hover { .backup-phrase__confirm-seed-option { color: #5B5D67; - font-family: Montserrat Light; + font-family: Roboto; font-size: 16px; line-height: 21px; background-color: #E7E7E7; @@ -360,7 +437,7 @@ button.backup-phrase__confirm-seed-option:hover { .import-account__faq-link { font-size: 18px; line-height: 23px; - font-family: Montserrat Light; + font-family: Roboto; } .import-account__selector-label { @@ -375,7 +452,7 @@ button.backup-phrase__confirm-seed-option:hover { background-color: #FFFFFF; margin-top: 14px; color: #5B5D67; - font-family: Montserrat Light; + font-family: Roboto; font-size: 18px; line-height: 23px; padding: 14px 21px; @@ -390,7 +467,7 @@ button.backup-phrase__confirm-seed-option:hover { font-size: 18px; line-height: 23px; margin-top: 21px; - font-family: Montserrat UltraLight; + font-family: Roboto; } .import-account__input-wrapper { @@ -436,7 +513,7 @@ button.backup-phrase__confirm-seed-option:hover { border: 1px solid #1B344D; border-radius: 4px; color: #1B344D; - font-family: Montserrat Light; + font-family: Roboto; font-size: 18px; display: flex; flex-flow: column nowrap; @@ -453,7 +530,7 @@ button.backup-phrase__confirm-seed-option:hover { .import-account__file-name { color: #000000; - font-family: Montserrat Light; + font-family: Roboto; font-size: 18px; line-height: 23px; margin-left: 22px; @@ -474,7 +551,7 @@ button.backup-phrase__confirm-seed-option:hover { .buy-ether__content-headline { color: #1B344D; - font-family: Montserrat Light; + font-family: Roboto; font-size: 18px; line-height: 23px; } @@ -503,7 +580,7 @@ button.backup-phrase__confirm-seed-option:hover { align-items: center; padding: 20px 0; color: #9B9B9B; - font-family: Montserrat Light; + font-family: Roboto; font-size: 14px; line-height: 18px; cursor: pointer; @@ -538,7 +615,7 @@ button.backup-phrase__confirm-seed-option:hover { .buy-ether__button-separator-text { font-size: 20px; line-height: 26px; - font-family: Montserrat Light; + font-family: Roboto; margin: 35px 0 14px 30px; display: flex; flex-flow: column nowrap; @@ -550,7 +627,7 @@ button.backup-phrase__confirm-seed-option:hover { color: #1B344D !important; font-size: 14px !important; line-height: 18px !important; - font-family: Montserrat UltraLight !important; + font-family: Roboto; } .buy-ether__action-content-wrapper { @@ -584,6 +661,7 @@ button.backup-phrase__confirm-seed-option:hover { color: #FFFFFF; font-size: 20px; font-weight: 500; + font-family: Roboto; line-height: 26px; text-align: center; text-transform: uppercase; @@ -608,7 +686,7 @@ button.first-time-flow__button:hover { color: #1B344D; font-size: 20px; line-height: 26px; - font-family: Montserrat Light; + font-family: Roboto; text-align: center; margin: 35px 0 14px; background-color: transparent; @@ -660,7 +738,7 @@ button.first-time-flow__button--tertiary:hover { font-size: 20px; line-height: 26px; text-align: center; - font-family: Montserrat UltraLight; + font-family: Roboto; } .icon { @@ -708,7 +786,7 @@ button.first-time-flow__button--tertiary:hover { .shapeshift-form__deposit-instruction { color: #757575; color: rgba(0, 0, 0, 0.45); - font-family: Montserrat Light; + font-family: Roboto; font-weight: 300; line-height: 19px; padding-bottom: 6px; @@ -725,7 +803,7 @@ button.first-time-flow__button--tertiary:hover { width: 100%; height: 45px; line-height: 44px; - font-family: Montserrat Light; + font-family: Roboto; } .shapeshift-form__address-input-label { @@ -734,7 +812,7 @@ button.first-time-flow__button--tertiary:hover { font-weight: 500; line-height: 18px; padding-bottom: 6px; - font-family: Montserrat Light; + font-family: Roboto; } .shapeshift-form__address-input { @@ -753,7 +831,7 @@ button.first-time-flow__button--tertiary:hover { .shapeshift-form__address-input-error-message { color: #FF001F; - font-family: Montserrat Light; + font-family: Roboto; font-size: 12px; height: 24px; line-height: 18px; @@ -763,7 +841,7 @@ button.first-time-flow__button--tertiary:hover { display: flex; flex-flow: row wrap; color: #9B9B9B; - font-family: Montserrat Light; + font-family: Roboto; font-size: 10px; line-height: 16px; } diff --git a/package.json b/package.json index c5d9d3c45..8921cc1b4 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "start": "npm run dev", "dev": "gulp dev --debug", - "ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", - "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", + "ui": "npm run test:flat:build:states && beefy development/ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", + "mock": "beefy development/mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "watch": "mocha watch --recursive \"test/unit/**/*.js\"", "mascara": "gulp build && METAMASK_DEBUG=true node ./mascara/example/server", "dist": "npm run dist:clear && npm install && gulp dist", @@ -23,7 +23,7 @@ "test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests", "test:flat:build:tests": "node test/integration/index.js", "test:flat:build:states": "node development/genStates.js", - "test:flat:build:ui": "npm run test:flat:build:states && browserify ./mock-dev.js -o ./development/bundle.js", + "test:flat:build:ui": "npm run test:flat:build:states && browserify ./development/mock-dev.js -o ./development/bundle.js", "test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js", "test:mascara:build": "mkdir -p dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests", "test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js", diff --git a/test/base.conf.js b/test/base.conf.js index 82b9d8eec..adb5357e8 100644 --- a/test/base.conf.js +++ b/test/base.conf.js @@ -46,7 +46,9 @@ module.exports = function(config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome', 'Firefox'], + browsers: process.env.browsers ? + JSON.parse(process.env.browsers) + : ['Chrome', 'Firefox'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits diff --git a/test/integration/index.js b/test/integration/index.js index 144303dbb..b266ddf37 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -23,4 +23,4 @@ pump( console.log(`Integration test build completed: "${bundlePath}"`) process.exit(0) } -) \ No newline at end of file +) diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index dd4251cc4..42ed28dca 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -1,4 +1,9 @@ const reactTriggerChange = require('react-trigger-change') +const { + timeout, + queryAsync, + findAsync, +} = require('../../lib/util') QUnit.module('Add token flow') @@ -13,74 +18,60 @@ QUnit.test('successful add token flow', (assert) => { }) async function runAddTokenFlowTest (assert, done) { - const selectState = $('select') + const selectState = await queryAsync($, 'select') selectState.val('add token') reactTriggerChange(selectState[0]) - await timeout(2000) - // Check that no tokens have been added assert.ok($('.token-list-item').length === 0, 'no tokens added') // Go to Add Token screen - let addTokenButton = $('button.btn-clear.wallet-view__add-token-button') + let addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - await timeout(1000) - // Verify Add Token screen - let addTokenWrapper = $('.add-token__wrapper') + let addTokenWrapper = await queryAsync($, '.add-token__wrapper') assert.ok(addTokenWrapper[0], 'add token wrapper renders') - let addTokenTitle = $('.add-token__title') + let addTokenTitle = await queryAsync($, '.add-token__title') assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct') // Cancel Add Token - const cancelAddTokenButton = $('button.btn-cancel.add-token__button') + const cancelAddTokenButton = await queryAsync($, 'button.btn-cancel.add-token__button') assert.ok(cancelAddTokenButton[0], 'cancel add token button present') cancelAddTokenButton.click() - await timeout(1000) - assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view') // Return to Add Token Screen - addTokenButton = $('button.btn-clear.wallet-view__add-token-button') + addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - await timeout(1000) - // Verify Add Token Screen - addTokenWrapper = $('.add-token__wrapper') - addTokenTitle = $('.add-token__title') + addTokenWrapper = await queryAsync($, '.add-token__wrapper') + addTokenTitle = await queryAsync($, '.add-token__title') assert.ok(addTokenWrapper[0], 'add token wrapper renders') assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct') // Search for token - const searchInput = $('input.add-token__input') + const searchInput = await queryAsync($, 'input.add-token__input') searchInput.val('a') reactTriggerChange(searchInput[0]) - await timeout() - // Click token to add - const tokenWrapper = $('div.add-token__token-wrapper') + const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper') assert.ok(tokenWrapper[0], 'token found') const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image') const tokenImageUrl = tokenImageProp.slice(5, -2) tokenWrapper[0].click() - await timeout() - // Click Next button - let nextButton = $('button.btn-clear.add-token__button') + let nextButton = await queryAsync($, 'button.btn-clear.add-token__button') assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') nextButton[0].click() - await timeout() - // Confirm Add token assert.equal( $('.add-token__description')[0].textContent, @@ -90,47 +81,35 @@ async function runAddTokenFlowTest (assert, done) { assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found') $('button.btn-clear.add-token__button')[0].click() - await timeout(2000) - // Verify added token image - let heroBalance = $('.hero-balance') + let heroBalance = await queryAsync($, '.hero-balance') assert.ok(heroBalance, 'rendered hero balance') assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added') // Return to Add Token Screen - addTokenButton = $('button.btn-clear.wallet-view__add-token-button') + addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button') assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - await timeout(1000) - - const addCustom = $('.add-token__add-custom') + const addCustom = await queryAsync($, '.add-token__add-custom') assert.ok(addCustom[0], 'add custom token button present') addCustom[0].click() - await timeout() - // Input token contract address - const customInput = $('input.add-token__add-custom-input') + const customInput = await queryAsync($, 'input.add-token__add-custom-input') customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') reactTriggerChange(customInput[0]) - await timeout(1000) - // Click Next button - nextButton = $('button.btn-clear.add-token__button') + nextButton = await queryAsync($, 'button.btn-clear.add-token__button') assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') nextButton[0].click() - await timeout(1000) - // Verify symbol length error since contract address won't return symbol - const errorMessage = $('.add-token__add-custom-error-message') + const errorMessage = await queryAsync($, '.add-token__add-custom-error-message') assert.ok(errorMessage[0], 'error rendered') $('button.btn-cancel.add-token__button')[0].click() - await timeout(2000) - // // Confirm Add token // assert.equal( // $('.add-token__description')[0].textContent, @@ -141,13 +120,7 @@ async function runAddTokenFlowTest (assert, done) { // $('button.btn-clear.add-token__button')[0].click() // // Verify added token image - // heroBalance = $('.hero-balance') + // heroBalance = await queryAsync($, '.hero-balance') // assert.ok(heroBalance, 'rendered hero balance') // assert.ok(heroBalance.find('.identicon')[0], 'token added') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index e49424c37..9737a2283 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -1,5 +1,9 @@ const reactTriggerChange = require('react-trigger-change') - +const { + timeout, + queryAsync, + findAsync, +} = require('../../lib/util') const PASSWORD = 'password123' QUnit.module('confirm sig requests') @@ -13,55 +17,41 @@ QUnit.test('successful confirmation of sig requests', (assert) => { }) async function runConfirmSigRequestsTest(assert, done) { - let selectState = $('select') + let selectState = await queryAsync($, 'select') selectState.val('confirm sig requests') reactTriggerChange(selectState[0]) - await timeout(2000) - - let confirmSigHeadline = $('.request-signature__headline') + let confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - let confirmSigRowValue = $('.request-signature__row-value') + let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/)) - let confirmSigSignButton = $('.request-signature__footer__sign-button') + let confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') confirmSigSignButton[0].click() - await timeout(2000) - - confirmSigHeadline = $('.request-signature__headline') + confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - let confirmSigMessage = $('.request-signature__notice') + let confirmSigMessage = await queryAsync($, '.request-signature__notice') assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/)) - confirmSigRowValue = $('.request-signature__row-value') + confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') - confirmSigSignButton = $('.request-signature__footer__sign-button') + confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') confirmSigSignButton[0].click() - await timeout(2000) - - confirmSigHeadline = $('.request-signature__headline') + confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - confirmSigRowValue = $('.request-signature__row-value') + confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') assert.equal(confirmSigRowValue[1].textContent, '1337') - confirmSigSignButton = $('.request-signature__footer__sign-button') + confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button') confirmSigSignButton[0].click() - await timeout(2000) - - const txView = $('.tx-view') + const txView = await queryAsync($, '.tx-view') assert.ok(txView[0], 'Should return to the account details screen after confirming') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} \ No newline at end of file diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 764eae47c..052d89518 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -1,6 +1,10 @@ const reactTriggerChange = require('react-trigger-change') const PASSWORD = 'password123' const runMascaraFirstTimeTest = require('./mascara-first-time') +const { + timeout, + findAsync, +} = require('../../lib/util') QUnit.module('first time usage') @@ -21,20 +25,19 @@ async function runFirstTimeUsageTest(assert, done) { selectState.val('first time') reactTriggerChange(selectState[0]) - await timeout(2000) const app = $('#app-content') // recurse notices while (true) { - const button = app.find('button') + const button = await findAsync(app, 'button') if (button.html() === 'Accept') { // still notices to accept - const termsPage = app.find('.markdown')[0] + const termsPageRaw = await findAsync(app, '.markdown') + const termsPage = (await findAsync(app, '.markdown'))[0] + console.log('termsPageRaw', termsPageRaw) termsPage.scrollTop = termsPage.scrollHeight - await timeout() console.log('Clearing notice') button.click() - await timeout() } else { // exit loop console.log('No more notices...') @@ -42,97 +45,68 @@ async function runFirstTimeUsageTest(assert, done) { } } - await timeout() - // Scroll through terms - const title = app.find('h1')[0] + const title = (await findAsync(app, 'h1'))[0] assert.equal(title.textContent, 'MetaMask', 'title screen') // enter password - const pwBox = app.find('#password-box')[0] - const confBox = app.find('#password-box-confirm')[0] + const pwBox = (await findAsync(app, '#password-box'))[0] + const confBox = (await findAsync(app, '#password-box-confirm'))[0] pwBox.value = PASSWORD confBox.value = PASSWORD - await timeout() - // create vault - const createButton = app.find('button.primary')[0] + const createButton = (await findAsync(app, 'button.primary'))[0] createButton.click() - await timeout(3000) - - const created = app.find('h3')[0] + await timeout() + const created = (await findAsync(app, 'h3'))[0] assert.equal(created.textContent, 'Vault Created', 'Vault created screen') // Agree button - const button = app.find('button')[0] + const button = (await findAsync(app, 'button'))[0] assert.ok(button, 'button present') button.click() - await timeout(1000) - - const detail = app.find('.account-detail-section')[0] + const detail = (await findAsync(app, '.account-detail-section'))[0] assert.ok(detail, 'Account detail section loaded.') - const sandwich = app.find('.sandwich-expando')[0] + const sandwich = (await findAsync(app, '.sandwich-expando'))[0] sandwich.click() - await timeout() - - const menu = app.find('.menu-droppo')[0] + const menu = (await findAsync(app, '.menu-droppo'))[0] const children = menu.children const logout = children[2] assert.ok(logout, 'Lock menu item found') logout.click() - await timeout(1000) - - const pwBox2 = app.find('#password-box')[0] + const pwBox2 = (await findAsync(app, '#password-box'))[0] pwBox2.value = PASSWORD - const createButton2 = app.find('button.primary')[0] + const createButton2 = (await findAsync(app, 'button.primary'))[0] createButton2.click() - await timeout(1000) - - const detail2 = app.find('.account-detail-section')[0] + const detail2 = (await findAsync(app, '.account-detail-section'))[0] assert.ok(detail2, 'Account detail section loaded again.') - await timeout() - // open account settings dropdown - const qrButton = app.find('.fa.fa-ellipsis-h')[0] + const qrButton = (await findAsync(app, '.fa.fa-ellipsis-h'))[0] qrButton.click() - await timeout(1000) - // qr code item - const qrButton2 = app.find('.dropdown-menu-item')[1] + const qrButton2 = (await findAsync(app, '.dropdown-menu-item'))[1] qrButton2.click() - await timeout(1000) - - const qrHeader = app.find('.qr-header')[0] - const qrContainer = app.find('#qr-container')[0] + const qrHeader = (await findAsync(app, '.qr-header'))[0] + const qrContainer = (await findAsync(app, '#qr-container'))[0] assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') assert.ok(qrContainer, 'QR Container found') - await timeout() - - const networkMenu = app.find('.network-indicator')[0] + const networkMenu = (await findAsync(app, '.network-indicator'))[0] networkMenu.click() - await timeout() - - const networkMenu2 = app.find('.network-indicator')[0] + const networkMenu2 = (await findAsync(app, '.network-indicator'))[0] const children2 = networkMenu2.children children2.length[3] assert.ok(children2, 'All network options present') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} \ No newline at end of file diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js index 515c7f383..bcbc94ff6 100644 --- a/test/integration/lib/mascara-first-time.js +++ b/test/integration/lib/mascara-first-time.js @@ -1,119 +1,95 @@ const PASSWORD = 'password123' const reactTriggerChange = require('react-trigger-change') +const { + timeout, + findAsync, + queryAsync, +} = require('../../lib/util') async function runFirstTimeUsageTest (assert, done) { await timeout(4000) - const app = $('#app-content') + const app = await queryAsync($, '#app-content') await skipNotices(app) - await timeout() - // Scroll through terms - const title = app.find('.create-password__title').text() + const title = (await findAsync(app, '.create-password__title')).text() assert.equal(title, 'Create Password', 'create password screen') // enter password - const pwBox = app.find('.first-time-flow__input')[0] - const confBox = app.find('.first-time-flow__input')[1] + const pwBox = (await findAsync(app, '.first-time-flow__input'))[0] + const confBox = (await findAsync(app, '.first-time-flow__input'))[1] pwBox.value = PASSWORD confBox.value = PASSWORD reactTriggerChange(pwBox) reactTriggerChange(confBox) - - await timeout() - // Create Password - const createButton = app.find('button.first-time-flow__button')[0] + const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0] createButton.click() - await timeout(3000) - - const created = app.find('.unique-image__title')[0] + const created = (await findAsync(app, '.unique-image__title'))[0] assert.equal(created.textContent, 'Your unique account image', 'unique image screen') // Agree button - let button = app.find('button')[0] + let button = (await findAsync(app, 'button'))[0] assert.ok(button, 'button present') button.click() - await timeout(1000) - await skipNotices(app) // secret backup phrase - const seedTitle = app.find('.backup-phrase__title')[0] + const seedTitle = (await findAsync(app, '.backup-phrase__title'))[0] assert.equal(seedTitle.textContent, 'Secret Backup Phrase', 'seed phrase screen') - app.find('.backup-phrase__reveal-button').click() - - await timeout(1000) - const seedPhrase = app.find('.backup-phrase__secret-words').text().split(' ') - app.find('.first-time-flow__button').click() + ;(await findAsync(app, '.backup-phrase__reveal-button')).click() + const seedPhrase = (await findAsync(app, '.backup-phrase__secret-words')).text().split(' ') + ;(await findAsync(app, '.first-time-flow__button')).click() + await timeout() const selectPhrase = text => { const option = $('.backup-phrase__confirm-seed-option') .filter((i, d) => d.textContent === text)[0] - $(option).click() } - await timeout(1000) - seedPhrase.forEach(sp => selectPhrase(sp)) - app.find('.first-time-flow__button').click() - await timeout(1000) + ;(await findAsync(app, '.first-time-flow__button')).click() // Deposit Ether Screen - const buyEthTitle = app.find('.buy-ether__title')[0] + const buyEthTitle = (await findAsync(app, '.buy-ether__title'))[0] assert.equal(buyEthTitle.textContent, 'Deposit Ether', 'deposit ether screen') - app.find('.buy-ether__do-it-later').click() - await timeout(1000) + ;(await findAsync(app, '.buy-ether__do-it-later')).click() - const menu = app.find('.account-menu__icon')[0] + const menu = (await findAsync(app, '.account-menu__icon'))[0] menu.click() - await timeout() - - const lock = app.find('.account-menu__logout-button')[0] + const lock = (await findAsync(app, '.account-menu__logout-button'))[0] assert.ok(lock, 'Lock menu item found') lock.click() - await timeout(1000) - - const pwBox2 = app.find('#password-box')[0] + const pwBox2 = (await findAsync(app, '#password-box'))[0] pwBox2.value = PASSWORD - const createButton2 = app.find('button.primary')[0] + const createButton2 = (await findAsync(app, 'button.primary'))[0] createButton2.click() - await timeout(1000) - - const detail2 = app.find('.wallet-view')[0] + const detail2 = (await findAsync(app, '.wallet-view'))[0] assert.ok(detail2, 'Account detail section loaded again.') - await timeout() - // open account settings dropdown - const qrButton = app.find('.wallet-view__details-button')[0] + const qrButton = (await findAsync(app, '.wallet-view__details-button'))[0] qrButton.click() - await timeout(1000) - - const qrHeader = app.find('.editable-label__value')[0] - const qrContainer = app.find('.qr-wrapper')[0] + const qrHeader = (await findAsync(app, '.editable-label__value'))[0] + const qrContainer = (await findAsync(app, '.qr-wrapper'))[0] assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') assert.ok(qrContainer, 'QR Container found') - await timeout() - - const networkMenu = app.find('.network-component')[0] + const networkMenu = (await findAsync(app, '.network-component'))[0] networkMenu.click() - await timeout() - - const networkMenu2 = app.find('.network-indicator')[0] + const networkMenu2 = (await findAsync(app, '.network-indicator'))[0] const children2 = networkMenu2.children children2.length[3] assert.ok(children2, 'All network options present') @@ -121,18 +97,12 @@ async function runFirstTimeUsageTest (assert, done) { module.exports = runFirstTimeUsageTest -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} - async function skipNotices (app) { while (true) { - const button = app.find('button') + const button = await findAsync(app, 'button') if (button && button.html() === 'Accept') { // still notices to accept - const termsPage = app.find('.markdown')[0] + const termsPage = (await findAsync(app, '.markdown'))[0] if (!termsPage) { break } diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 3456f2367..faab10fdf 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -1,4 +1,9 @@ const reactTriggerChange = require('react-trigger-change') +const { + timeout, + queryAsync, + findAsync, +} = require('../../lib/util') const PASSWORD = 'password123' @@ -18,83 +23,65 @@ global.ethQuery = { async function runSendFlowTest(assert, done) { console.log('*** start runSendFlowTest') - const selectState = $('select') + const selectState = await queryAsync($, 'select') selectState.val('send new ui') reactTriggerChange(selectState[0]) - await timeout(2000) - - const sendScreenButton = $('button.btn-clear.hero-balance-button') + const sendScreenButton = await queryAsync($, 'button.btn-clear.hero-balance-button') assert.ok(sendScreenButton[1], 'send screen button present') sendScreenButton[1].click() - await timeout(1000) - - const sendTitle = $('.page-container__title') + const sendTitle = await queryAsync($, '.page-container__title') assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct') - const sendCopy = $('.page-container__subtitle') + const sendCopy = await queryAsync($, '.page-container__subtitle') assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy') - const sendFromField = $('.send-v2__form-field') + const sendFromField = await queryAsync($, '.send-v2__form-field') assert.ok(sendFromField[0], 'send screen has a from field') - let sendFromFieldItemAddress = $('.account-list-item__account-name') + let sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name') assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 4', 'send from field shows correct account name') - const sendFromFieldItem = $('.account-list-item') + const sendFromFieldItem = await queryAsync($, '.account-list-item') sendFromFieldItem[0].click() - await timeout() - - const sendFromDropdownList = $('.send-v2__from-dropdown__list') + // this seems to fail if the firefox window is not in focus... + const sendFromDropdownList = await queryAsync($, '.send-v2__from-dropdown__list') assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts') - console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromDropdownList.children()[1]`, sendFromDropdownList.children()[1]); sendFromDropdownList.children()[1].click() - await timeout() - - sendFromFieldItemAddress = $('.account-list-item__account-name') - console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromFieldItemAddress[0]`, sendFromFieldItemAddress[0]); + sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name') assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name') - let sendToFieldInput = $('.send-v2__to-autocomplete__input') + let sendToFieldInput = await queryAsync($, '.send-v2__to-autocomplete__input') sendToFieldInput[0].focus() - await timeout() - - const sendToDropdownList = $('.send-v2__from-dropdown__list') + const sendToDropdownList = await queryAsync($, '.send-v2__from-dropdown__list') assert.equal(sendToDropdownList.children().length, 5, 'send to dropdown shows all accounts and address book accounts') sendToDropdownList.children()[2].click() - await timeout() - const sendToAccountAddress = sendToFieldInput.val() assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address') - const sendAmountField = $('.send-v2__form-row:eq(2)') + const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountField.find('.currency-display')[0].click() - await timeout() - - const sendAmountFieldInput = sendAmountField.find('input:text') + const sendAmountFieldInput = await findAsync(sendAmountField, 'input:text') sendAmountFieldInput.val('5.1') reactTriggerChange(sendAmountField.find('input')[0]) - await timeout() - - let errorMessage = $('.send-v2__error') + let errorMessage = await queryAsync($, '.send-v2__error') assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message') sendAmountFieldInput.val('2.0') reactTriggerChange(sendAmountFieldInput[0]) - await timeout() errorMessage = $('.send-v2__error') assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected') - const sendGasField = $('.send-v2__gas-fee-display') + const sendGasField = await queryAsync($, '.send-v2__gas-fee-display') assert.equal( sendGasField.find('.currency-display__input-wrapper > input').val(), '0.000198', @@ -106,120 +93,86 @@ async function runSendFlowTest(assert, done) { 'send gas field should show estimated gas total converted to USD' ) - const sendGasOpenCustomizeModalButton = $('.send-v2__sliders-icon-container' - ) + const sendGasOpenCustomizeModalButton = await queryAsync($, '.send-v2__sliders-icon-container') sendGasOpenCustomizeModalButton[0].click() - await timeout(1000) - - const customizeGasModal = $('.send-v2__customize-gas') + const customizeGasModal = await queryAsync($, '.send-v2__customize-gas') assert.ok(customizeGasModal[0], 'should render the customize gas modal') - const customizeGasPriceInput = $('.send-v2__gas-modal-card').first().find('input') + const customizeGasPriceInput = (await queryAsync($, '.send-v2__gas-modal-card')).first().find('input') customizeGasPriceInput.val(50) reactTriggerChange(customizeGasPriceInput[0]) - const customizeGasLimitInput = $('.send-v2__gas-modal-card').last().find('input') + const customizeGasLimitInput = (await queryAsync($, '.send-v2__gas-modal-card')).last().find('input') customizeGasLimitInput.val(60000) reactTriggerChange(customizeGasLimitInput[0]) - await timeout() - - const customizeGasSaveButton = $('.send-v2__customize-gas__save') + const customizeGasSaveButton = await queryAsync($, '.send-v2__customize-gas__save') customizeGasSaveButton[0].click() - await timeout() - assert.equal( - sendGasField.find('.currency-display__input-wrapper > input').val(), + (await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(), '0.003', 'send gas field should show customized gas total' ) assert.equal( - sendGasField.find('.currency-display__converted-value')[0].textContent, + (await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent, '3.60 USD', 'send gas field should show customized gas total converted to USD' ) - const sendButton = $('button.btn-clear.page-container__footer-button') + const sendButton = await queryAsync($, 'button.btn-clear.page-container__footer-button') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') sendButton[0].click() - - await timeout(2000) + await timeout() selectState.val('send edit') reactTriggerChange(selectState[0]) - await timeout(2000) - - const confirmFromName = $('.confirm-screen-account-name').first() + const confirmFromName = (await queryAsync($, '.confirm-screen-account-name')).first() assert.equal(confirmFromName[0].textContent, 'Send Account 2', 'confirm screen should show correct from name') - const confirmToName = $('.confirm-screen-account-name').last() + const confirmToName = (await queryAsync($, '.confirm-screen-account-name')).last() assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name') - const confirmScreenRows = $('.confirm-screen-rows') + const confirmScreenRows = await queryAsync($, '.confirm-screen-rows') const confirmScreenGas = confirmScreenRows.find('.confirm-screen-row-info')[2] assert.equal(confirmScreenGas.textContent, '3.6 USD', 'confirm screen should show correct gas') const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[3] assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total') - const confirmScreenBackButton = $('.confirm-screen-back-button') + const confirmScreenBackButton = await queryAsync($, '.confirm-screen-back-button') confirmScreenBackButton[0].click() - await timeout(1000) - - const sendFromFieldItemInEdit = $('.account-list-item') + const sendFromFieldItemInEdit = await queryAsync($, '.account-list-item') sendFromFieldItemInEdit[0].click() - await timeout() - - const sendFromDropdownListInEdit = $('.send-v2__from-dropdown__list') + const sendFromDropdownListInEdit = await queryAsync($, '.send-v2__from-dropdown__list') sendFromDropdownListInEdit.children()[2].click() - await timeout() - - const sendToFieldInputInEdit = $('.send-v2__to-autocomplete__input') + const sendToFieldInputInEdit = await queryAsync($, '.send-v2__to-autocomplete__input') sendToFieldInputInEdit[0].focus() sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb') - await timeout() - - const sendAmountFieldInEdit = $('.send-v2__form-row:eq(2)') + const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountFieldInEdit.find('.currency-display')[0].click() - await timeout() - const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('input:text') sendAmountFieldInputInEdit.val('1.0') reactTriggerChange(sendAmountFieldInputInEdit[0]) - await timeout() - - const sendButtonInEdit = $('.btn-clear.page-container__footer-button') + const sendButtonInEdit = await queryAsync($, '.btn-clear.page-container__footer-button') assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') sendButtonInEdit[0].click() - await timeout() - // TODO: Need a way to mock background so that we can test correct transition from editing to confirm selectState.val('confirm new ui') reactTriggerChange(selectState[0]) - - await timeout(2000) - const confirmScreenConfirmButton = $('.confirm-screen-confirm-button') + const confirmScreenConfirmButton = await queryAsync($, '.confirm-screen-confirm-button') console.log(`+++++++++++++++++++++++++++++++= confirmScreenConfirmButton[0]`, confirmScreenConfirmButton[0]); confirmScreenConfirmButton[0].click() - await timeout(2000) - - const txView = $('.tx-view') + const txView = await queryAsync($, '.tx-view') console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]); assert.ok(txView[0], 'Should return to the account details screen after confirming') } - -function timeout (time) { - return new Promise((resolve, reject) => { - setTimeout(resolve, time || 1500) - }) -} \ No newline at end of file diff --git a/test/lib/util.js b/test/lib/util.js new file mode 100644 index 000000000..626280745 --- /dev/null +++ b/test/lib/util.js @@ -0,0 +1,53 @@ +module.exports = { + timeout, + queryAsync, + findAsync, + pollUntilTruthy, +} + +function timeout (time) { + return new Promise((resolve, reject) => { + setTimeout(resolve, time || 1500) + }) +} + +async function findAsync(container, selector, opts) { + try { + return await pollUntilTruthy(() => { + const result = container.find(selector) + if (result.length > 0) return result + }, opts) + } catch (err) { + throw new Error(`Failed to find element within interval: "${selector}"`) + } +} + +async function queryAsync(jQuery, selector, opts) { + try { + return await pollUntilTruthy(() => { + const result = jQuery(selector) + if (result.length > 0) return result + }, opts) + } catch (err) { + throw new Error(`Failed to find element within interval: "${selector}"`) + } +} + +async function pollUntilTruthy(fn, opts = {}){ + const pollingInterval = opts.pollingInterval || 100 + const timeoutInterval = opts.timeoutInterval || 5000 + const start = Date.now() + let result + while (!result) { + // check if timedout + const now = Date.now() + if ((now - start) > timeoutInterval) { + throw new Error(`pollUntilTruthy - failed to return truthy within interval`) + } + // check for result + result = fn() + // run again after timeout + await timeout(pollingInterval, timeoutInterval) + } + return result +} diff --git a/ui/app/actions.js b/ui/app/actions.js index b56265edc..092af080b 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -75,6 +75,8 @@ var actions = { resetAccount, showNewVaultSeed: showNewVaultSeed, showInfoPage: showInfoPage, + CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN', + closeWelcomeScreen, // seed recovery actions REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION', revealSeedConfirmation: revealSeedConfirmation, @@ -788,7 +790,7 @@ function updateTransaction (txData) { function updateAndApproveTx (txData) { log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) return (dispatch) => { - log.debug(`actions calling background.updateAndApproveTx`) + log.debug(`actions calling background.updateAndApproveTx.`) background.updateAndApproveTransaction(txData, (err) => { dispatch(actions.hideLoadingIndication()) dispatch(actions.updateTransactionParams(txData.id, txData.txParams)) @@ -947,6 +949,12 @@ function showNewVaultSeed (seed) { } } +function closeWelcomeScreen () { + return { + type: actions.CLOSE_WELCOME_SCREEN, + } +} + function backToUnlockView () { return { type: actions.BACK_TO_UNLOCK_VIEW, diff --git a/ui/app/app.js b/ui/app/app.js index 8511ee396..9708a2485 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -13,6 +13,8 @@ const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ethe const OldUIInitializeMenuScreen = require('./first-time/init-menu') const InitializeMenuScreen = MascaraFirstTime const NewKeyChainScreen = require('./new-keychain') +const WelcomeScreen = require('./welcome-screen').default + // accounts const MainContainer = require('./main-container') const SendTransactionScreen2 = require('./components/send/send-v2-container') @@ -92,6 +94,7 @@ function mapStateToProps (state) { betaUI: state.metamask.featureFlags.betaUI, isRevealingSeedWords: state.metamask.isRevealingSeedWords, Qr: state.appState.Qr, + welcomeScreenSeen: state.metamask.welcomeScreenSeen, // state needed to get account dropdown temporarily rendering from app bar identities, @@ -245,6 +248,7 @@ App.prototype.renderAppBar = function () { isInitialized, betaUI, isPopup, + welcomeScreenSeen, } = this.props if (window.METAMASK_UI_TYPE === 'notification') { @@ -270,7 +274,7 @@ App.prototype.renderAppBar = function () { style: {}, }, [ - h('.app-header.flex-row.flex-space-between', { + (isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', { className: classnames({ 'app-header--initialized': !isOnboarding, }), @@ -295,7 +299,7 @@ App.prototype.renderAppBar = function () { ]), ]), - h('div.header__right-actions', [ + betaUI && isInitialized && h('div.header__right-actions', [ h('div.network-component-wrapper', { style: {}, }, [ @@ -325,8 +329,12 @@ App.prototype.renderAppBar = function () { ]), ]), - !isInitialized && !isPopup && betaUI && h('h2.alpha-warning', - 'Please be aware that this version is still under development'), + !isInitialized && !isPopup && betaUI && h('h2', { + className: classnames({ + 'alpha-warning': welcomeScreenSeen, + 'alpha-warning-welcome-screen': !welcomeScreenSeen, + }), + }, 'Please be aware that this version is still under development'), ]) ) @@ -370,11 +378,18 @@ App.prototype.renderPrimary = function () { isOnboarding, betaUI, isRevealingSeedWords, + welcomeScreenSeen, Qr, + isInitialized, + isUnlocked, } = props const isMascaraOnboarding = isMascara && isOnboarding const isBetaUIOnboarding = betaUI && isOnboarding && !props.isPopup && !isRevealingSeedWords + if (!welcomeScreenSeen && betaUI && !isInitialized && !isUnlocked) { + return h(WelcomeScreen) + } + if (isMascaraOnboarding || isBetaUIOnboarding) { return h(MascaraFirstTime) } diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 85892b93b..96d776270 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -101,9 +101,10 @@ TxView.prototype.render = function () { h('div.flex-row.phone-visible', { style: { - margin: '1.5em 1.2em 0', justifyContent: 'space-between', alignItems: 'center', + flex: '0 0 auto', + margin: '10px', }, }, [ @@ -111,11 +112,10 @@ TxView.prototype.render = function () { style: { fontSize: '1.3em', cursor: 'pointer', + padding: '10px', }, - onClick: () => { - this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar() - }, - }, []), + onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(), + }), h('.identicon-wrapper.select-none', { style: { diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss index 4af0c2c55..a3f051361 100644 --- a/ui/app/css/itcss/components/hero-balance.scss +++ b/ui/app/css/itcss/components/hero-balance.scss @@ -5,9 +5,6 @@ flex-direction: column; justify-content: flex-start; align-items: center; - margin: .3em .9em 0; - // height: 80vh; - // max-height: 225px; flex: 0 0 auto; } diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 0219f9fb2..f107b7aca 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -55,3 +55,6 @@ @import './new-account.scss'; @import './tooltip.scss'; + +@import './welcome-screen.scss'; + diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index ecf5e1036..5cdda5e6c 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -51,6 +51,7 @@ $wallet-view-bg: $alabaster; cursor: pointer; display: flex; justify-content: center; + padding: 10px; } // wallet view and sidebar @@ -291,7 +292,6 @@ $wallet-view-bg: $alabaster; padding-right: 6px; } - // first time .first-view-main { display: flex; @@ -313,4 +313,23 @@ $wallet-view-bg: $alabaster; @media screen and (min-width: 1281px) { width: 62vw; } -} \ No newline at end of file +} + +.unlock-screen-container { + z-index: $main-container-z-index; + font-family: Roboto; + display: flex; + justify-content: center; + align-items: center; + flex: 1 0 auto; + background: #f7f7f7; + width: 100%; +} + +.unlock-screen { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1 0 auto; +} diff --git a/ui/app/css/itcss/components/welcome-screen.scss b/ui/app/css/itcss/components/welcome-screen.scss new file mode 100644 index 000000000..bfd174ad9 --- /dev/null +++ b/ui/app/css/itcss/components/welcome-screen.scss @@ -0,0 +1,59 @@ +.welcome-screen { + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + font-family: Roboto; + font-weight: 400; + width: 100%; + flex: 1 0 auto; + padding: 70px 0; + background: $white; + + @media screen and (max-width: 575px) { + padding: 0; + } + + &__info { + display: flex; + flex-flow: column; + width: 100%; + height: 100%; + align-items: center; + + &__header { + font-size: 1.65em; + margin-bottom: 14px; + + @media screen and (max-width: 575px) { + font-size: 1.5em; + } + } + + &__copy { + font-size: 1em; + width: 400px; + max-width: 90vw; + text-align: center; + + @media screen and (max-width: 575px) { + font-size: 0.9em; + } + } + } + + &__button { + height: 54px; + width: 198px; + box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14); + color: #FFFFFF; + font-size: 20px; + font-weight: 500; + line-height: 26px; + text-align: center; + text-transform: uppercase; + margin: 35px 0 14px; + transition: 200ms ease-in-out; + background-color: rgba(247, 134, 28, 0.9); + } +} diff --git a/ui/app/main-container.js b/ui/app/main-container.js index 292abcc3d..eed4bd164 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -32,19 +32,7 @@ MainContainer.prototype.render = function () { return h(Settings, {key: 'config'}) default: log.debug('rendering locked screen') - contents = { - component: UnlockScreen, - style: { - boxShadow: 'none', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - background: '#F7F7F7', - // must force 100%, because lock screen is full-width - width: '100%', - }, - key: 'locked', - } + return h('.unlock-screen-container', {}, h(UnlockScreen, { key: 'locked' })) } } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 029d830ec..4ca7d221e 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -44,6 +44,7 @@ function reduceMetamask (state, action) { featureFlags: {}, networkEndpointType: OLD_UI_NETWORK_TYPE, isRevealingSeedWords: false, + welcomeScreenSeen: false, }, state.metamask) switch (action.type) { @@ -347,6 +348,11 @@ function reduceMetamask (state, action) { networkEndpointType: action.value, }) + case actions.CLOSE_WELCOME_SCREEN: + return extend(metamaskState, { + welcomeScreenSeen: true, + }) + default: return metamaskState diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 185b0b527..bbaf55f21 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -29,83 +29,72 @@ UnlockScreen.prototype.render = function () { const state = this.props const warning = state.warning return ( - h('.flex-column', { - style: { - width: 'inherit', - }, - }, [ - h('.unlock-screen.flex-column.flex-center.flex-grow', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, t('appName')), - - h('input.large-input', { - type: 'password', - id: 'password-box', - placeholder: 'enter password', - style: { - background: 'white', - }, - onKeyPress: this.onKeyPress.bind(this), - onInput: this.inputChanged.bind(this), - }), - - h('.error', { - style: { - display: warning ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, warning), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, 'Log In'), - ]), - - h('.flex-row.flex-center.flex-grow', [ - h('p.pointer', { - onClick: () => { - this.props.dispatch(actions.markPasswordForgotten()) - if (environmentType() === 'popup') { - global.platform.openExtensionInBrowser() - } - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, 'Restore from seed phrase'), - ]), - - h('.flex-row.flex-center.flex-grow', [ - h('p.pointer', { - onClick: () => { - this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) - .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) - }, - style: { - fontSize: '0.8em', - color: '#aeaeae', - textDecoration: 'underline', - marginTop: '32px', - }, - }, 'Use classic interface'), - ]), - + h('.unlock-screen', [ + + h(Mascot, { + animationEventEmitter: this.animationEventEmitter, + }), + + h('h1', { + style: { + fontSize: '1.4em', + textTransform: 'uppercase', + color: '#7F8082', + }, + }, t('appName'), + + h('input.large-input', { + type: 'password', + id: 'password-box', + placeholder: 'enter password', + style: { + background: 'white', + }, + onKeyPress: this.onKeyPress.bind(this), + onInput: this.inputChanged.bind(this), + }), + + h('.error', { + style: { + display: warning ? 'block' : 'none', + padding: '0 20px', + textAlign: 'center', + }, + }, warning), + + h('button.primary.cursor-pointer', { + onClick: this.onSubmit.bind(this), + style: { + margin: 10, + }, + }, 'Log In'), + + h('p.pointer', { + onClick: () => { + this.props.dispatch(actions.markPasswordForgotten()) + if (environmentType() === 'popup') { + global.platform.openExtensionInBrowser() + } + }, + style: { + fontSize: '0.8em', + color: 'rgb(247, 134, 28)', + textDecoration: 'underline', + }, + }, 'Restore from seed phrase'), + + h('p.pointer', { + onClick: () => { + this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) + .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) + }, + style: { + fontSize: '0.8em', + color: '#aeaeae', + textDecoration: 'underline', + marginTop: '32px', + }, + }, 'Use classic interface'), ]) ) } diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js new file mode 100644 index 000000000..cdbb6dba8 --- /dev/null +++ b/ui/app/welcome-screen.js @@ -0,0 +1,56 @@ +import EventEmitter from 'events' +import h from 'react-hyperscript' +import { Component } from 'react' +import PropTypes from 'prop-types' +import {connect} from 'react-redux' +import {closeWelcomeScreen} from './actions' +import Mascot from './components/mascot' + +class WelcomeScreen extends Component { + static propTypes = { + closeWelcomeScreen: PropTypes.func.isRequired, + } + + constructor(props) { + super(props) + this.animationEventEmitter = new EventEmitter() + } + + initiateAccountCreation = () => { + this.props.closeWelcomeScreen() + } + + render () { + return h('div.welcome-screen', [ + + h('div.welcome-screen__info', [ + + h(Mascot, { + animationEventEmitter: this.animationEventEmitter, + width: '225', + height: '225', + }), + + h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'), + + h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'), + + h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens, + and serves as your bridge to decentralized applications.`), + + h('button.welcome-screen__button', { + onClick: this.initiateAccountCreation, + }, 'Continue'), + + ]), + + ]) + } +} + +export default connect( + null, + dispatch => ({ + closeWelcomeScreen: () => dispatch(closeWelcomeScreen()), + }) +)(WelcomeScreen)