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 = (