From 91f864b2b79f14d6ccd2e34579483c2b4ef8d1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 5 Jan 2017 13:59:16 +0100 Subject: [PATCH 01/66] Fixing minGasLimit > ceil limit mining issue (#4018) --- ethcore/src/block.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 1eec9110e..81c32674d 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -16,6 +16,7 @@ //! Blockchain block. +use std::cmp; use std::sync::Arc; use std::collections::HashSet; @@ -266,8 +267,9 @@ impl<'x> OpenBlock<'x> { r.block.base.header.set_extra_data(extra_data); r.block.base.header.note_dirty(); - let gas_floor_target = ::std::cmp::max(gas_range_target.0, engine.params().min_gas_limit); - engine.populate_from_parent(&mut r.block.base.header, parent, gas_floor_target, gas_range_target.1); + let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit); + let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target); + engine.populate_from_parent(&mut r.block.base.header, parent, gas_floor_target, gas_ceil_target); engine.on_new_block(&mut r.block); Ok(r) } From a076ffaf8c6117d95a36733dc20ab7b5738cea12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 5 Jan 2017 14:12:54 +0100 Subject: [PATCH 02/66] Using local path on Windows (#4017) * Using non-roaming path on Windows * Fixing test --- parity/cli/mod.rs | 6 +++--- parity/cli/usage.txt | 2 +- parity/configuration.rs | 11 ++++++++--- parity/dir.rs | 21 +++++++++++++++++---- parity/helpers.rs | 9 +++++++-- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 47a0af0bb..3515d0d35 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -16,7 +16,7 @@ #[macro_use] mod usage; -use dir::default_data_path; +use dir; usage! { { @@ -90,8 +90,8 @@ usage! { flag_no_download: bool = false, or |c: &Config| otry!(c.parity).no_download.clone(), flag_no_consensus: bool = false, or |c: &Config| otry!(c.parity).no_consensus.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), - flag_base_path: String = default_data_path(), or |c: &Config| otry!(c.parity).base_path.clone(), - flag_db_path: String = "$BASE/chains", or |c: &Config| otry!(c.parity).db_path.clone(), + flag_base_path: String = dir::default_data_path(), or |c: &Config| otry!(c.parity).base_path.clone(), + flag_db_path: String = dir::CHAINS_PATH, or |c: &Config| otry!(c.parity).db_path.clone(), flag_keys_path: String = "$BASE/keys", or |c: &Config| otry!(c.parity).keys_path.clone(), flag_identity: String = "", or |c: &Config| otry!(c.parity).identity.clone(), diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 7c3e5cc7d..9705c7359 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -336,7 +336,7 @@ Legacy Options: testnet --keys-path $HOME/parity/testnet-keys. Overrides the --keys-path option. --import-geth-keys Attempt to import keys from Geth client. - --datadir PATH Equivalent to --db-path PATH. + --datadir PATH Equivalent to --base-path PATH. --networkid INDEX Equivalent to --network-id INDEX. --peers NUM Equivalent to --min-peers NUM. --nodekey KEY Equivalent to --node-key KEY. diff --git a/parity/configuration.rs b/parity/configuration.rs index 85ff61d5c..f3135e0ee 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -30,11 +30,11 @@ use ethcore::verification::queue::VerifierSettings; use rpc::{IpcConfiguration, HttpConfiguration}; use ethcore_rpc::NetworkSettings; use cache::CacheConfig; -use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, +use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, replace_home_for_db, geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit, to_queue_strategy}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras}; use ethcore_logger::Config as LogConfig; -use dir::{Directories, default_hypervisor_path}; +use dir::{Directories, default_hypervisor_path, default_local_path}; use dapps::Configuration as DappsConfiguration; use signer::{Configuration as SignerConfiguration}; use updater::{UpdatePolicy, UpdateFilter, ReleaseTrack}; @@ -707,9 +707,14 @@ impl Configuration { fn directories(&self) -> Directories { use util::path; + let local_path = default_local_path(); let data_path = replace_home("", self.args.flag_datadir.as_ref().unwrap_or(&self.args.flag_base_path)); - let db_path = replace_home(&data_path, &self.args.flag_db_path); + let db_path = if self.args.flag_datadir.is_some() { + replace_home(&data_path, &self.args.flag_db_path) + } else { + replace_home_for_db(&data_path, &local_path, &self.args.flag_db_path) + }; let keys_path = replace_home(&data_path, &self.args.flag_keys_path); let dapps_path = replace_home(&data_path, &self.args.flag_dapps_path); let ui_path = replace_home(&data_path, &self.args.flag_ui_path); diff --git a/parity/dir.rs b/parity/dir.rs index bd83ef3c0..ffef0d94e 100644 --- a/parity/dir.rs +++ b/parity/dir.rs @@ -18,7 +18,7 @@ use std::fs; use std::path::{PathBuf, Path}; use util::{H64, H256}; use util::journaldb::Algorithm; -use helpers::replace_home; +use helpers::{replace_home, replace_home_for_db}; use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(target_os = "macos")] const AUTHOR: &'static str = "Parity"; @@ -31,6 +31,9 @@ use app_dirs::{AppInfo, get_app_root, AppDataType}; #[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT: &'static str = "io.parity.ethereum"; #[cfg(not(any(target_os = "windows", target_os = "macos")))] const PRODUCT_HYPERVISOR: &'static str = "io.parity.ethereum-updates"; +#[cfg(target_os = "windows")] pub const CHAINS_PATH: &'static str = "$LOCAL/chains"; +#[cfg(not(target_os = "windows"))] pub const CHAINS_PATH: &'static str = "$BASE/chains"; + // this const is irrelevent cause we do have migrations now, // but we still use it for backwards compatibility const LEGACY_CLIENT_DB_VER_STR: &'static str = "5.3"; @@ -47,9 +50,10 @@ pub struct Directories { impl Default for Directories { fn default() -> Self { let data_dir = default_data_path(); + let local_dir = default_local_path(); Directories { base: replace_home(&data_dir, "$BASE"), - db: replace_home(&data_dir, "$BASE/chains"), + db: replace_home_for_db(&data_dir, &local_dir, CHAINS_PATH), keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), @@ -209,6 +213,11 @@ pub fn default_data_path() -> String { get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity".to_owned()) } +pub fn default_local_path() -> String { + let app_info = AppInfo { name: PRODUCT, author: AUTHOR }; + get_app_root(AppDataType::UserCache, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity".to_owned()) +} + pub fn default_hypervisor_path() -> String { let app_info = AppInfo { name: PRODUCT_HYPERVISOR, author: AUTHOR }; get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity-hypervisor".to_owned()) @@ -217,14 +226,18 @@ pub fn default_hypervisor_path() -> String { #[cfg(test)] mod tests { use super::Directories; - use helpers::replace_home; + use helpers::{replace_home, replace_home_for_db}; #[test] fn test_default_directories() { let data_dir = super::default_data_path(); + let local_dir = super::default_local_path(); let expected = Directories { base: replace_home(&data_dir, "$BASE"), - db: replace_home(&data_dir, "$BASE/chains"), + db: replace_home_for_db(&data_dir, &local_dir, + if cfg!(target_os = "windows") { "$LOCAL/chains" } + else { "$BASE/chains" } + ), keys: replace_home(&data_dir, "$BASE/keys"), signer: replace_home(&data_dir, "$BASE/signer"), dapps: replace_home(&data_dir, "$BASE/dapps"), diff --git a/parity/helpers.rs b/parity/helpers.rs index b91b62f1d..680306684 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -135,8 +135,13 @@ pub fn to_price(s: &str) -> Result { pub fn replace_home(base: &str, arg: &str) -> String { // the $HOME directory on mac os should be `~/Library` or `~/Library/Application Support` let r = arg.replace("$HOME", env::home_dir().unwrap().to_str().unwrap()); - let r = r.replace("$BASE", base ); - r.replace("/", &::std::path::MAIN_SEPARATOR.to_string() ) + let r = r.replace("$BASE", base); + r.replace("/", &::std::path::MAIN_SEPARATOR.to_string()) +} + +pub fn replace_home_for_db(base: &str, local: &str, arg: &str) -> String { + let r = replace_home(base, arg); + r.replace("$LOCAL", local) } /// Flush output buffer. From 961314546425b7fc44bb52aa561dd3413b61420e Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 5 Jan 2017 16:51:16 +0100 Subject: [PATCH 03/66] Convert ShapeShift modal to store (#4035) * WIP * WIP store * Store in-place * WIP tests * Store completed * Expand option tests for events * Fix & test for errors found in manual testing * Add missing @observer (rookie mistake) * Fix intl formatting error (completed step) * Pass store to ErrorStep, test all stages for components * Add warning messages (e.g. no price found) * Fix typo --- .../awaitingDepositStep.js | 36 +- .../awaitingDepositStep.spec.js | 50 +++ .../awaitingExchangeStep.js | 23 +- .../awaitingExchangeStep.spec.js | 39 ++ .../Shapeshift/CompletedStep/completedStep.js | 26 +- .../CompletedStep/completedStep.spec.js | 40 ++ .../modals/Shapeshift/ErrorStep/errorStep.js | 17 +- .../Shapeshift/ErrorStep/errorStep.spec.js | 39 ++ .../Shapeshift/OptionsStep/optionsStep.js | 103 +++-- .../OptionsStep/optionsSteps.spec.js | 126 +++++++ js/src/modals/Shapeshift/Price/price.js | 9 +- js/src/modals/Shapeshift/Price/price.spec.js | 40 ++ js/src/modals/Shapeshift/Value/value.spec.js | 36 ++ js/src/modals/Shapeshift/shapeshift.js | 281 +++++--------- js/src/modals/Shapeshift/shapeshift.spec.js | 159 ++++++++ js/src/modals/Shapeshift/store.js | 199 ++++++++++ js/src/modals/Shapeshift/store.spec.js | 355 ++++++++++++++++++ 17 files changed, 1310 insertions(+), 268 deletions(-) create mode 100644 js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js create mode 100644 js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js create mode 100644 js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js create mode 100644 js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js create mode 100644 js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js create mode 100644 js/src/modals/Shapeshift/Price/price.spec.js create mode 100644 js/src/modals/Shapeshift/Value/value.spec.js create mode 100644 js/src/modals/Shapeshift/shapeshift.spec.js create mode 100644 js/src/modals/Shapeshift/store.js create mode 100644 js/src/modals/Shapeshift/store.spec.js diff --git a/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js index 8dfd29f33..3644852e4 100644 --- a/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js +++ b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.js @@ -14,25 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Value from '../Value'; - import styles from '../shapeshift.css'; +@observer export default class AwaitingDepositStep extends Component { static propTypes = { - coinSymbol: PropTypes.string.isRequired, - depositAddress: PropTypes.string, - price: PropTypes.shape({ - rate: PropTypes.number.isRequired, - minimum: PropTypes.number.isRequired, - limit: PropTypes.number.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { coinSymbol, depositAddress, price } = this.props; + const { coinSymbol, depositAddress, price } = this.props.store; const typeSymbol = (
{ coinSymbol } @@ -43,22 +39,38 @@ export default class AwaitingDepositStep extends Component { return (
- Awaiting confirmation of the deposit address for your { typeSymbol } funds exchange +
); } + return (
- ShapeShift.io is awaiting a { typeSymbol } deposit. Send the funds from your { typeSymbol } network client to - + ShapeShift.io, + typeSymbol + } } />
{ depositAddress }
- ( minimum, maximum) + , + minimum: + } } />
diff --git a/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js new file mode 100644 index 000000000..65fdefb07 --- /dev/null +++ b/js/src/modals/Shapeshift/AwaitingDepositStep/awaitingDepositStep.spec.js @@ -0,0 +1,50 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import AwaitingDepositStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/AwaitingDepositStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + it('displays waiting for address with empty depositAddress', () => { + render(); + expect(component.find('FormattedMessage').props().id).to.match(/awaitingConfirmation/); + }); + + it('displays waiting for deposit with non-empty depositAddress', () => { + render({ depositAddress: 'xyz' }); + expect(component.find('FormattedMessage').first().props().id).to.match(/awaitingDeposit/); + }); +}); diff --git a/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js index d3e0ce93c..d3760355f 100644 --- a/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js +++ b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.js @@ -15,32 +15,39 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import Value from '../Value'; +import { FormattedMessage } from 'react-intl'; +import { observer } from 'mobx-react'; +import Value from '../Value'; import styles from '../shapeshift.css'; +@observer export default class AwaitingExchangeStep extends Component { static propTypes = { - depositInfo: PropTypes.shape({ - incomingCoin: PropTypes.number.isRequired, - incomingType: PropTypes.string.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { depositInfo } = this.props; + const { depositInfo } = this.props.store; const { incomingCoin, incomingType } = depositInfo; return (
- ShapeShift.io has received a deposit of - + ShapeShift.io + } } />
- Awaiting the completion of the funds exchange and transfer of funds to your Parity account. +
); diff --git a/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js new file mode 100644 index 000000000..a9ed6e5f2 --- /dev/null +++ b/js/src/modals/Shapeshift/AwaitingExchangeStep/awaitingExchangeStep.spec.js @@ -0,0 +1,39 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import AwaitingExchangeStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/AwaitingExchangeStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/CompletedStep/completedStep.js b/js/src/modals/Shapeshift/CompletedStep/completedStep.js index 2b5a6b162..681e26c55 100644 --- a/js/src/modals/Shapeshift/CompletedStep/completedStep.js +++ b/js/src/modals/Shapeshift/CompletedStep/completedStep.js @@ -14,39 +14,41 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Value from '../Value'; - import styles from '../shapeshift.css'; +@observer export default class CompletedStep extends Component { static propTypes = { - depositInfo: PropTypes.shape({ - incomingCoin: PropTypes.number.isRequired, - incomingType: PropTypes.string.isRequired - }).isRequired, - exchangeInfo: PropTypes.shape({ - outgoingCoin: PropTypes.string.isRequired, - outgoingType: PropTypes.string.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { depositInfo, exchangeInfo } = this.props; + const { depositInfo, exchangeInfo } = this.props.store; const { incomingCoin, incomingType } = depositInfo; const { outgoingCoin, outgoingType } = exchangeInfo; return (
- ShapeShift.io has completed the funds exchange. + ShapeShift.io + } } />
=>
- The change in funds will be reflected in your Parity account shortly. +
); diff --git a/js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js b/js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js new file mode 100644 index 000000000..c14da892a --- /dev/null +++ b/js/src/modals/Shapeshift/CompletedStep/completedStep.spec.js @@ -0,0 +1,40 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import CompletedStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/CompletedStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/ErrorStep/errorStep.js b/js/src/modals/Shapeshift/ErrorStep/errorStep.js index 092494399..441904f22 100644 --- a/js/src/modals/Shapeshift/ErrorStep/errorStep.js +++ b/js/src/modals/Shapeshift/ErrorStep/errorStep.js @@ -14,25 +14,30 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import styles from '../shapeshift.css'; +@observer export default class ErrorStep extends Component { static propTypes = { - error: PropTypes.shape({ - fatal: PropTypes.bool, - message: PropTypes.string.isRequired - }).isRequired + store: PropTypes.object.isRequired } render () { - const { error } = this.props; + const { error } = this.props.store; return (
- The funds shifting via ShapeShift.io failed with a fatal error on the exchange. The error message received from the exchange is as follow: + ShapeShift.io + } } />
{ error.message } diff --git a/js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js b/js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js new file mode 100644 index 000000000..7cb148334 --- /dev/null +++ b/js/src/modals/Shapeshift/ErrorStep/errorStep.spec.js @@ -0,0 +1,39 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import ErrorStep from './'; + +let component; + +function render () { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/ErrorStep', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/OptionsStep/optionsStep.js b/js/src/modals/Shapeshift/OptionsStep/optionsStep.js index 4314d2b5c..5a7afdf7e 100644 --- a/js/src/modals/Shapeshift/OptionsStep/optionsStep.js +++ b/js/src/modals/Shapeshift/OptionsStep/optionsStep.js @@ -14,64 +14,93 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React, { Component, PropTypes } from 'react'; import { Checkbox, MenuItem } from 'material-ui'; +import { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; -import { Form, Input, Select } from '~/ui'; +import { Form, Input, Select, Warning } from '~/ui'; import Price from '../Price'; - +import { WARNING_NO_PRICE } from '../store'; import styles from './optionsStep.css'; +const WARNING_LABELS = { + [WARNING_NO_PRICE]: ( + + ) +}; + +@observer export default class OptionsStep extends Component { static propTypes = { - refundAddress: PropTypes.string.isRequired, - coinSymbol: PropTypes.string.isRequired, - coins: PropTypes.array.isRequired, - price: PropTypes.object, - hasAccepted: PropTypes.bool.isRequired, - onChangeSymbol: PropTypes.func.isRequired, - onChangeRefund: PropTypes.func.isRequired, - onToggleAccept: PropTypes.func.isRequired + store: PropTypes.object.isRequired }; render () { - const { coinSymbol, coins, refundAddress, hasAccepted, onToggleAccept } = this.props; - const label = `(optional) ${coinSymbol} return address`; + const { coinSymbol, coins, hasAcceptedTerms, price, refundAddress, warning } = this.props.store; if (!coins.length) { return (
- There are currently no exchange pairs/coins available to fund with. +
); } - const items = coins.map(this.renderCoinSelectItem); - return (
+ hint={ + + } + label={ + + } + onSubmit={ this.onChangeRefundAddress } + value={ refundAddress } /> + label={ + + } + onCheck={ this.onToggleAcceptTerms } /> - + +
); } @@ -81,7 +110,9 @@ export default class OptionsStep extends Component { const item = (
- +
{ symbol } @@ -103,11 +134,15 @@ export default class OptionsStep extends Component { ); } - onSelectCoin = (event, idx, value) => { - this.props.onChangeSymbol(event, value); + onChangeRefundAddress = (event, refundAddress) => { + this.props.store.setRefundAddress(refundAddress); } - onChangeAddress = (event, value) => { - this.props.onChangeRefund(value); + onSelectCoin = (event, index, coinSymbol) => { + this.props.store.setCoinSymbol(coinSymbol); + } + + onToggleAcceptTerms = () => { + this.props.store.toggleAcceptTerms(); } } diff --git a/js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js b/js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js new file mode 100644 index 000000000..3b49d90de --- /dev/null +++ b/js/src/modals/Shapeshift/OptionsStep/optionsSteps.spec.js @@ -0,0 +1,126 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import Store, { WARNING_NO_PRICE } from '../store'; + +import OptionsStep from './'; + +const ADDRESS = '0x1234567890123456789012345678901234567890'; + +let component; +let instance; +let store; + +function render () { + store = new Store(ADDRESS); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('modals/Shapeshift/OptionsStep', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders no coins when none available', () => { + expect(component.find('FormattedMessage').props().id).to.equal('shapeshift.optionsStep.noPairs'); + }); + + describe('components', () => { + beforeEach(() => { + store.setCoins([{ symbol: 'BTC', name: 'Bitcoin' }]); + store.toggleAcceptTerms(); + }); + + describe('terms Checkbox', () => { + it('shows the state of store.hasAcceptedTerms', () => { + expect(component.find('Checkbox').props().checked).to.be.true; + }); + }); + + describe('warning', () => { + let warning; + + beforeEach(() => { + store.setWarning(WARNING_NO_PRICE); + warning = component.find('Warning'); + }); + + it('shows a warning message when available', () => { + expect(warning.props().warning.props.id).to.equal('shapeshift.warning.noPrice'); + }); + }); + }); + + describe('events', () => { + describe('onChangeRefundAddress', () => { + beforeEach(() => { + sinon.stub(store, 'setRefundAddress'); + }); + + afterEach(() => { + store.setRefundAddress.restore(); + }); + + it('sets the refundAddress on the store', () => { + instance.onChangeRefundAddress(null, 'refundAddress'); + expect(store.setRefundAddress).to.have.been.calledWith('refundAddress'); + }); + }); + + describe('onSelectCoin', () => { + beforeEach(() => { + sinon.stub(store, 'setCoinSymbol'); + }); + + afterEach(() => { + store.setCoinSymbol.restore(); + }); + + it('sets the coinSymbol on the store', () => { + instance.onSelectCoin(null, 0, 'XMR'); + expect(store.setCoinSymbol).to.have.been.calledWith('XMR'); + }); + }); + + describe('onToggleAcceptTerms', () => { + beforeEach(() => { + sinon.stub(store, 'toggleAcceptTerms'); + }); + + afterEach(() => { + store.toggleAcceptTerms.restore(); + }); + + it('toggles the terms on the store', () => { + instance.onToggleAcceptTerms(); + expect(store.toggleAcceptTerms).to.have.been.called; + }); + }); + }); +}); diff --git a/js/src/modals/Shapeshift/Price/price.js b/js/src/modals/Shapeshift/Price/price.js index 206587448..e8eb21e52 100644 --- a/js/src/modals/Shapeshift/Price/price.js +++ b/js/src/modals/Shapeshift/Price/price.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; import Value from '../Value'; import styles from '../shapeshift.css'; @@ -42,7 +43,13 @@ export default class Price extends Component { =
- ( minimum, maximum) + , + minimum: + } } />
); diff --git a/js/src/modals/Shapeshift/Price/price.spec.js b/js/src/modals/Shapeshift/Price/price.spec.js new file mode 100644 index 000000000..9b144746b --- /dev/null +++ b/js/src/modals/Shapeshift/Price/price.spec.js @@ -0,0 +1,40 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import Price from './'; + +let component; + +function render (props = {}) { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/Price', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/Value/value.spec.js b/js/src/modals/Shapeshift/Value/value.spec.js new file mode 100644 index 000000000..8c5ab5d9c --- /dev/null +++ b/js/src/modals/Shapeshift/Value/value.spec.js @@ -0,0 +1,36 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; + +import Value from './'; + +let component; + +function render (props = {}) { + component = shallow( + + ); + + return component; +} + +describe('modals/Shapeshift/Value', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); +}); diff --git a/js/src/modals/Shapeshift/shapeshift.js b/js/src/modals/Shapeshift/shapeshift.js index fc76a8968..bf450122d 100644 --- a/js/src/modals/Shapeshift/shapeshift.js +++ b/js/src/modals/Shapeshift/shapeshift.js @@ -14,26 +14,44 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; -import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; -import ContentClear from 'material-ui/svg-icons/content/clear'; +import { FormattedMessage } from 'react-intl'; +import shapeshiftLogo from '~/../assets/images/shapeshift-logo.png'; import { Button, IdentityIcon, Modal } from '~/ui'; -import initShapeshift from '~/3rdparty/shapeshift'; -import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png'; +import { CancelIcon, DoneIcon } from '~/ui/Icons'; import AwaitingDepositStep from './AwaitingDepositStep'; import AwaitingExchangeStep from './AwaitingExchangeStep'; import CompletedStep from './CompletedStep'; import ErrorStep from './ErrorStep'; import OptionsStep from './OptionsStep'; +import Store, { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE } from './store'; import styles from './shapeshift.css'; -const shapeshift = initShapeshift(); - -const STAGE_NAMES = ['details', 'awaiting deposit', 'awaiting exchange', 'completed']; +const STAGE_TITLES = [ + , + , + , + +]; +const ERROR_TITLE = ( + +); +@observer export default class Shapeshift extends Component { static contextTypes = { store: PropTypes.object.isRequired @@ -44,46 +62,38 @@ export default class Shapeshift extends Component { onClose: PropTypes.func } - state = { - stage: 0, - coinSymbol: 'BTC', - coinPair: 'btc_eth', - coins: [], - depositAddress: '', - refundAddress: '', - price: null, - depositInfo: null, - exchangeInfo: null, - error: {}, - hasAccepted: false, - shifting: false - } + store = new Store(this.props.address); componentDidMount () { - this.retrieveCoins(); + this.store.retrieveCoins(); } componentWillUnmount () { - this.unsubscribe(); - } - - unsubscribe () { - // Unsubscribe from Shapeshit - const { depositAddress } = this.state; - shapeshift.unsubscribe(depositAddress); + this.store.unsubscribe(); } render () { - const { error, stage } = this.state; + const { error, stage } = this.store; return ( + steps={ + error + ? null + : STAGE_TITLES + } + title={ + error + ? ERROR_TITLE + : null + } + visible + waiting={ [ + STAGE_WAIT_DEPOSIT, + STAGE_WAIT_EXCHANGE + ] }> { this.renderPage() } ); @@ -91,7 +101,7 @@ export default class Shapeshift extends Component { renderDialogActions () { const { address } = this.props; - const { coins, error, stage, hasAccepted, shifting } = this.state; + const { coins, error, hasAcceptedTerms, stage } = this.store; const logo = ( @@ -100,12 +110,16 @@ export default class Shapeshift extends Component { ); const cancelBtn = (
+ ); + } + + renderInput () { + return ( + + } + onBlur={ this.hideInput } + onFocus={ this.showInput } + onSubmit={ this.inputOnSubmit } + style={ INPUT_STYLE } + /> + ); + } + + toggleInput = () => { + const { inputShown } = this.state; + this.setState({ + inputShown: !inputShown + }); + } + + hideInput = () => { + this.setState({ inputShown: false }); + } + + showInput = () => { + this.setState({ inputShown: true }); + } + + inputOnSubmit = (url) => { + const { router } = this.props; + + router.push(`/web/${encodeURIComponent(url)}`); + } +} + +export default withRouter(UrlButton); diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index fa7c44878..8ebbb602d 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -27,6 +27,7 @@ import PermissionStore from '~/modals/DappPermissions/store'; import { Actionbar, Button, Page } from '~/ui'; import { LockedIcon, VisibleIcon } from '~/ui/Icons'; +import UrlButton from './UrlButton'; import DappsStore from './dappsStore'; import Summary from './Summary'; @@ -88,6 +89,7 @@ class Dapps extends Component { defaultMessage='Decentralized Applications' /> } buttons={ [ + ,