diff --git a/common/actions/swap/actionCreators.ts b/common/actions/swap/actionCreators.ts index a2d0c108..e638044f 100644 --- a/common/actions/swap/actionCreators.ts +++ b/common/actions/swap/actionCreators.ts @@ -27,10 +27,10 @@ export function loadBityRatesSucceededSwap( }; } -export type TLoadShapeshiftSucceededSwap = typeof loadShapeshiftRatesSucceededSwap; +export type TLoadShapeshiftRatesSucceededSwap = typeof loadShapeshiftRatesSucceededSwap; export function loadShapeshiftRatesSucceededSwap( payload -): interfaces.LoadShapshiftRatesSucceededSwapAction { +): interfaces.LoadShapeshiftRatesSucceededSwapAction { return { type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED, payload @@ -59,13 +59,27 @@ export function loadBityRatesRequestedSwap(): interfaces.LoadBityRatesRequestedS }; } -export type TLoadShapeshiftRequestedSwap = typeof loadShapeshiftRatesRequestedSwap; -export function loadShapeshiftRatesRequestedSwap(): interfaces.LoadShapeshiftRequestedSwapAction { +export type TLoadShapeshiftRatesRequestedSwap = typeof loadShapeshiftRatesRequestedSwap; +export function loadShapeshiftRatesRequestedSwap(): interfaces.LoadShapeshiftRatesRequestedSwapAction { return { type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED }; } +export type TLoadBityRatesFailedSwap = typeof loadBityRatesFailedSwap; +export function loadBityRatesFailedSwap(): interfaces.LoadBityRatesFailedSwapAction { + return { + type: TypeKeys.SWAP_LOAD_BITY_RATES_FAILED + }; +} + +export type TLoadShapeshiftFailedSwap = typeof loadShapeshiftRatesFailedSwap; +export function loadShapeshiftRatesFailedSwap(): interfaces.LoadShapeshiftRatesFailedSwapAction { + return { + type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_FAILED + }; +} + export type TStopLoadBityRatesSwap = typeof stopLoadBityRatesSwap; export function stopLoadBityRatesSwap(): interfaces.StopLoadBityRatesSwapAction { return { diff --git a/common/actions/swap/actionTypes.ts b/common/actions/swap/actionTypes.ts index f22e8143..2ef859dd 100644 --- a/common/actions/swap/actionTypes.ts +++ b/common/actions/swap/actionTypes.ts @@ -43,7 +43,7 @@ export interface LoadBityRatesSucceededSwapAction { payload: ApiResponse; } -export interface LoadShapshiftRatesSucceededSwapAction { +export interface LoadShapeshiftRatesSucceededSwapAction { type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED; payload: ApiResponse; } @@ -59,12 +59,18 @@ export interface RestartSwapAction { export interface LoadBityRatesRequestedSwapAction { type: TypeKeys.SWAP_LOAD_BITY_RATES_REQUESTED; - payload?: null; } -export interface LoadShapeshiftRequestedSwapAction { +export interface LoadShapeshiftRatesRequestedSwapAction { type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED; - payload?: null; +} + +export interface LoadBityRatesFailedSwapAction { + type: TypeKeys.SWAP_LOAD_BITY_RATES_FAILED; +} + +export interface LoadShapeshiftRatesFailedSwapAction { + type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_FAILED; } export interface ChangeStepSwapAction { @@ -240,12 +246,14 @@ export interface ShowLiteSendAction { export type SwapAction = | ChangeStepSwapAction | InitSwap - | LoadBityRatesSucceededSwapAction - | LoadShapshiftRatesSucceededSwapAction | DestinationAddressSwapAction | RestartSwapAction | LoadBityRatesRequestedSwapAction - | LoadShapeshiftRequestedSwapAction + | LoadBityRatesSucceededSwapAction + | LoadBityRatesFailedSwapAction + | LoadShapeshiftRatesRequestedSwapAction + | LoadShapeshiftRatesSucceededSwapAction + | LoadShapeshiftRatesFailedSwapAction | StopLoadBityRatesSwapAction | StopLoadShapeshiftRatesSwapAction | BityOrderCreateRequestedSwapAction diff --git a/common/actions/swap/constants.ts b/common/actions/swap/constants.ts index 11d4ee34..1fdfc4f3 100644 --- a/common/actions/swap/constants.ts +++ b/common/actions/swap/constants.ts @@ -7,6 +7,8 @@ export enum TypeKeys { SWAP_RESTART = 'SWAP_RESTART', SWAP_LOAD_BITY_RATES_REQUESTED = 'SWAP_LOAD_BITY_RATES_REQUESTED', SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED = 'SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED', + SWAP_LOAD_BITY_RATES_FAILED = 'SWAP_LOAD_BITY_RATES_FAILED', + SWAP_LOAD_SHAPESHIFT_RATES_FAILED = 'SWAP_LOAD_SHAPESHIFT_RATES_FAILED', SWAP_STOP_LOAD_BITY_RATES = 'SWAP_STOP_LOAD_BITY_RATES', SWAP_STOP_LOAD_SHAPESHIFT_RATES = 'SWAP_STOP_LOAD_SHAPESHIFT_RATES', SWAP_ORDER_TIME = 'SWAP_ORDER_TIME', diff --git a/common/containers/Tabs/Swap/components/CurrencySwap.tsx b/common/containers/Tabs/Swap/components/CurrencySwap.tsx index 0c116756..b6215f14 100644 --- a/common/containers/Tabs/Swap/components/CurrencySwap.tsx +++ b/common/containers/Tabs/Swap/components/CurrencySwap.tsx @@ -85,8 +85,9 @@ export default class CurrencySwap extends PureComponent { this.setErrorMessages(originErr, destinationErr); }, 1000); + private timeoutId: NodeJS.Timer | null; public componentDidMount() { - setTimeout(() => { + this.timeoutId = setTimeout(() => { this.setState({ timeout: true }); @@ -108,6 +109,12 @@ export default class CurrencySwap extends PureComponent { } } + public componentWillUnmount() { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + } + public componentDidUpdate(prevProps: Props, prevState: State) { const { origin, destination } = this.state; const { options } = this.props; diff --git a/common/containers/Tabs/Swap/components/CurrentRates.tsx b/common/containers/Tabs/Swap/components/CurrentRates.tsx index 76e1f112..50440189 100644 --- a/common/containers/Tabs/Swap/components/CurrentRates.tsx +++ b/common/containers/Tabs/Swap/components/CurrentRates.tsx @@ -1,3 +1,7 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'react-redux'; +import sample from 'lodash/sample'; +import times from 'lodash/times'; import { NormalizedBityRates, NormalizedShapeshiftRates, @@ -7,24 +11,57 @@ import bityLogoWhite from 'assets/images/logo-bity-white.svg'; import shapeshiftLogoWhite from 'assets/images/logo-shapeshift.svg'; import Spinner from 'components/ui/Spinner'; import { bityReferralURL, shapeshiftReferralURL } from 'config'; -import React, { PureComponent } from 'react'; import translate from 'translations'; -import './CurrentRates.scss'; import { SHAPESHIFT_WHITELIST } from 'api/shapeshift'; -import { ProviderName } from 'actions/swap'; -import sample from 'lodash/sample'; -import times from 'lodash/times'; +import { + loadShapeshiftRatesRequestedSwap, + TLoadShapeshiftRatesRequestedSwap, + stopLoadShapeshiftRatesSwap, + TStopLoadShapeshiftRatesSwap, + ProviderName +} from 'actions/swap'; +import { getOffline } from 'selectors/config'; import Rates from './Rates'; +import { AppState } from 'reducers'; +import './CurrentRates.scss'; -interface Props { +interface StateProps { + isOffline: boolean; provider: ProviderName; bityRates: NormalizedBityRates; shapeshiftRates: NormalizedShapeshiftRates; } -export default class CurrentRates extends PureComponent { +interface ActionProps { + loadShapeshiftRatesRequestedSwap: TLoadShapeshiftRatesRequestedSwap; + stopLoadShapeshiftRatesSwap: TStopLoadShapeshiftRatesSwap; +} + +type Props = StateProps & ActionProps; + +class CurrentRates extends PureComponent { private shapeShiftRateCache = null; + public componentDidMount() { + if (!this.props.isOffline) { + this.loadRates(); + } + } + + public componentWillReceiveProps(nextProps: Props) { + if (this.props.isOffline && !nextProps.isOffline) { + this.loadRates(); + } + } + + public componentWillUnmount() { + this.props.stopLoadShapeshiftRatesSwap(); + } + + public loadRates() { + this.props.loadShapeshiftRatesRequestedSwap(); + } + public getRandomSSPairData = ( shapeshiftRates: NormalizedShapeshiftRates ): NormalizedShapeshiftRate => { @@ -139,3 +176,17 @@ export default class CurrentRates extends PureComponent { return this.swapEl(providerURL, providerLogo, children); } } + +function mapStateToProps(state: AppState): StateProps { + return { + isOffline: getOffline(state), + provider: state.swap.provider, + bityRates: state.swap.bityRates, + shapeshiftRates: state.swap.shapeshiftRates + }; +} + +export default connect(mapStateToProps, { + loadShapeshiftRatesRequestedSwap, + stopLoadShapeshiftRatesSwap +})(CurrentRates); diff --git a/common/containers/Tabs/Swap/index.tsx b/common/containers/Tabs/Swap/index.tsx index 729a83c5..bd895717 100644 --- a/common/containers/Tabs/Swap/index.tsx +++ b/common/containers/Tabs/Swap/index.tsx @@ -5,8 +5,6 @@ import { shapeshiftOrderCreateRequestedSwap as dShapeshiftOrderCreateRequestedSwap, changeStepSwap as dChangeStepSwap, destinationAddressSwap as dDestinationAddressSwap, - loadBityRatesRequestedSwap as dLoadBityRatesRequestedSwap, - loadShapeshiftRatesRequestedSwap as dLoadShapeshiftRatesRequestedSwap, restartSwap as dRestartSwap, startOrderTimerSwap as dStartOrderTimerSwap, startPollBityOrderStatus as dStartPollBityOrderStatus, @@ -21,9 +19,7 @@ import { TBityOrderCreateRequestedSwap, TChangeStepSwap, TDestinationAddressSwap, - TLoadBityRatesRequestedSwap, TShapeshiftOrderCreateRequestedSwap, - TLoadShapeshiftRequestedSwap, TRestartSwap, TStartOrderTimerSwap, TStartPollBityOrderStatus, @@ -81,8 +77,6 @@ interface ReduxStateProps { interface ReduxActionProps { changeStepSwap: TChangeStepSwap; - loadBityRatesRequestedSwap: TLoadBityRatesRequestedSwap; - loadShapeshiftRatesRequestedSwap: TLoadShapeshiftRequestedSwap; destinationAddressSwap: TDestinationAddressSwap; restartSwap: TRestartSwap; stopLoadBityRatesSwap: TStopLoadBityRatesSwap; @@ -101,26 +95,6 @@ interface ReduxActionProps { } class Swap extends Component, {}> { - public componentDidMount() { - if (!this.props.isOffline) { - this.loadRates(); - } - } - - public componentWillReceiveProps(nextProps: ReduxStateProps) { - if (this.props.isOffline && !nextProps.isOffline) { - this.loadRates(); - } - } - - public componentWillUnmount() { - this.props.stopLoadShapeshiftRatesSwap(); - } - - public loadRates() { - this.props.loadShapeshiftRatesRequestedSwap(); - } - public render() { const { // STATE @@ -235,8 +209,6 @@ class Swap extends Component
@@ -246,7 +218,7 @@ class Swap extends Component ( - {step === 1 && } + {step === 1 && } {step === 1 && } {(step === 2 || step === 3) && }
@@ -294,8 +266,6 @@ export default connect(mapStateToProps, { initSwap: dInitSwap, bityOrderCreateRequestedSwap: dBityOrderCreateRequestedSwap, shapeshiftOrderCreateRequestedSwap: dShapeshiftOrderCreateRequestedSwap, - loadBityRatesRequestedSwap: dLoadBityRatesRequestedSwap, - loadShapeshiftRatesRequestedSwap: dLoadShapeshiftRatesRequestedSwap, destinationAddressSwap: dDestinationAddressSwap, restartSwap: dRestartSwap, startOrderTimerSwap: dStartOrderTimerSwap, diff --git a/common/reducers/swap/index.ts b/common/reducers/swap/index.ts index bdadd707..43ba8cd1 100644 --- a/common/reducers/swap/index.ts +++ b/common/reducers/swap/index.ts @@ -11,12 +11,13 @@ export interface State { options: stateTypes.NormalizedOptions; bityRates: stateTypes.NormalizedBityRates; // Change this - shapeshiftRates: stateTypes.NormalizedBityRates; - provider: string; + shapeshiftRates: stateTypes.NormalizedShapeshiftRates; + provider: stateTypes.ProviderName; bityOrder: any; shapeshiftOrder: any; destinationAddress: string; isFetchingRates: boolean | null; + hasNotifiedRatesFailure: boolean; secondsRemaining: number | null; outputTx: string | null; isPostingOrder: boolean; @@ -50,6 +51,7 @@ export const INITIAL_STATE: State = { bityOrder: {}, shapeshiftOrder: {}, isFetchingRates: null, + hasNotifiedRatesFailure: false, secondsRemaining: null, outputTx: null, isPostingOrder: false, @@ -209,14 +211,17 @@ export function swap(state: State = INITIAL_STATE, action: actionTypes.SwapActio }; case TypeKeys.SWAP_LOAD_BITY_RATES_REQUESTED: - return { - ...state, - isFetchingRates: true - }; case TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED: return { ...state, - isFetchingRates: true + isFetchingRates: true, + hasNotifiedRatesFailure: false + }; + case TypeKeys.SWAP_LOAD_BITY_RATES_FAILED: + case TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_FAILED: + return { + ...state, + hasNotifiedRatesFailure: true }; case TypeKeys.SWAP_STOP_LOAD_BITY_RATES: return { diff --git a/common/reducers/swap/types.ts b/common/reducers/swap/types.ts index 863304e5..e3407431 100644 --- a/common/reducers/swap/types.ts +++ b/common/reducers/swap/types.ts @@ -1,6 +1,8 @@ -import { Option } from 'actions/swap/actionTypes'; +import { Option, ProviderName } from 'actions/swap/actionTypes'; import { WhitelistedCoins } from 'config'; +export type ProviderName = ProviderName; + export interface SwapInput { id: WhitelistedCoins; amount: number | string; diff --git a/common/sagas/index.ts b/common/sagas/index.ts index 4c716762..a9769525 100644 --- a/common/sagas/index.ts +++ b/common/sagas/index.ts @@ -2,16 +2,9 @@ import configSaga from './config'; import deterministicWallets from './deterministicWallets'; import notifications from './notifications'; import rates from './rates'; -import { - swapTimerSaga, - pollBityOrderStatusSaga, - postBityOrderSaga, - postShapeshiftOrderSaga, - pollShapeshiftOrderStatusSaga, - restartSwapSaga -} from './swap/orders'; -import { liteSend } from './swap/liteSend'; -import { getBityRatesSaga, getShapeShiftRatesSaga, swapProviderSaga } from './swap/rates'; +import swapOrders from './swap/orders'; +import swapLiteSend from './swap/liteSend'; +import swapRates from './swap/rates'; import wallet from './wallet'; import { ens } from './ens'; import { transaction } from './transaction'; @@ -20,21 +13,14 @@ import gas from './gas'; export default { ens, - liteSend, configSaga, - postBityOrderSaga, - postShapeshiftOrderSaga, - pollBityOrderStatusSaga, - pollShapeshiftOrderStatusSaga, - getBityRatesSaga, - getShapeShiftRatesSaga, - swapTimerSaga, - restartSwapSaga, + swapOrders, + swapRates, + swapLiteSend, notifications, wallet, transaction, deterministicWallets, - swapProviderSaga, rates, transactions, gas diff --git a/common/sagas/swap/liteSend.ts b/common/sagas/swap/liteSend.ts index 8779480d..dc28492f 100644 --- a/common/sagas/swap/liteSend.ts +++ b/common/sagas/swap/liteSend.ts @@ -108,6 +108,6 @@ export function* fetchPaymentAddress(): SagaIterator { return false; } -export function* liteSend(): SagaIterator { +export default function* swapLiteSend(): SagaIterator { yield takeEvery(SwapTK.SWAP_CONFIGURE_LITE_SEND, handleConfigureLiteSend); } diff --git a/common/sagas/swap/orders.ts b/common/sagas/swap/orders.ts index 89ea84d4..4fffb392 100644 --- a/common/sagas/swap/orders.ts +++ b/common/sagas/swap/orders.ts @@ -198,14 +198,6 @@ export function* postShapeshiftOrderCreate( } } -export function* postBityOrderSaga(): SagaIterator { - yield takeEvery(TypeKeys.SWAP_BITY_ORDER_CREATE_REQUESTED, postBityOrderCreate); -} - -export function* postShapeshiftOrderSaga(): SagaIterator { - yield takeEvery(TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED, postShapeshiftOrderCreate); -} - export function* restartSwap() { yield put(reset()); yield put(resetWallet()); @@ -214,10 +206,6 @@ export function* restartSwap() { yield put(loadShapeshiftRatesRequestedSwap()); } -export function* restartSwapSaga(): SagaIterator { - yield takeEvery(TypeKeys.SWAP_RESTART, restartSwap); -} - export function* bityOrderTimeRemaining(): SagaIterator { while (true) { let hasShownNotification = false; @@ -364,6 +352,12 @@ export function* handleOrderTimeRemaining(): SagaIterator { yield cancel(orderTimeRemainingTask); } -export function* swapTimerSaga(): SagaIterator { +export default function* swapOrders(): SagaIterator { + yield fork(handleOrderTimeRemaining); + yield fork(pollShapeshiftOrderStatusSaga); + yield fork(pollBityOrderStatusSaga); + yield takeEvery(TypeKeys.SWAP_BITY_ORDER_CREATE_REQUESTED, postBityOrderCreate); + yield takeEvery(TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED, postShapeshiftOrderCreate); yield takeEvery(TypeKeys.SWAP_ORDER_START_TIMER, handleOrderTimeRemaining); + yield takeEvery(TypeKeys.SWAP_RESTART, restartSwap); } diff --git a/common/sagas/swap/rates.ts b/common/sagas/swap/rates.ts index 04509b0b..d8548a89 100644 --- a/common/sagas/swap/rates.ts +++ b/common/sagas/swap/rates.ts @@ -2,18 +2,21 @@ import { showNotification } from 'actions/notifications'; import { loadBityRatesSucceededSwap, loadShapeshiftRatesSucceededSwap, + loadBityRatesFailedSwap, + loadShapeshiftRatesFailedSwap, changeSwapProvider, ChangeProviderSwapAcion } from 'actions/swap'; import { TypeKeys } from 'actions/swap/constants'; import { getAllRates } from 'api/bity'; import { delay, SagaIterator } from 'redux-saga'; -import { call, select, cancel, fork, put, take, takeLatest, race } from 'redux-saga/effects'; +import { call, select, put, takeLatest, race, take, cancel, fork } from 'redux-saga/effects'; import shapeshift from 'api/shapeshift'; import { getSwap } from 'sagas/swap/orders'; +import { getHasNotifiedRatesFailure } from 'selectors/swap'; -const POLLING_CYCLE = 30000; export const SHAPESHIFT_TIMEOUT = 10000; +export const POLLING_CYCLE = 30000; export function* loadBityRates(): SagaIterator { while (true) { @@ -21,12 +24,23 @@ export function* loadBityRates(): SagaIterator { const data = yield call(getAllRates); yield put(loadBityRatesSucceededSwap(data)); } catch (error) { - yield put(showNotification('danger', error.message)); + const hasNotified = yield select(getHasNotifiedRatesFailure); + if (!hasNotified) { + console.error('Failed to load rates from Bity:', error); + yield put(showNotification('danger', error.message)); + } + yield put(loadBityRatesFailedSwap()); } yield call(delay, POLLING_CYCLE); } } +export function* handleBityRates(): SagaIterator { + const loadBityRatesTask = yield fork(loadBityRates); + yield take(TypeKeys.SWAP_STOP_LOAD_BITY_RATES); + yield cancel(loadBityRatesTask); +} + export function* loadShapeshiftRates(): SagaIterator { while (true) { try { @@ -40,17 +54,31 @@ export function* loadShapeshiftRates(): SagaIterator { if (tokens) { yield put(loadShapeshiftRatesSucceededSwap(tokens)); } else { - yield put( - showNotification('danger', 'Error loading ShapeShift tokens - reverting to Bity') - ); + throw new Error('ShapeShift rates request timed out.'); } } catch (error) { - yield put(showNotification('danger', `Error loading ShapeShift tokens - ${error}`)); + const hasNotified = yield select(getHasNotifiedRatesFailure); + if (!hasNotified) { + console.error('Failed to fetch rates from shapeshift:', error); + yield put( + showNotification( + 'danger', + 'Failed to load swap rates from ShapeShift, please try again later' + ) + ); + } + yield put(loadShapeshiftRatesFailedSwap()); } yield call(delay, POLLING_CYCLE); } } +export function* handleShapeshiftRates(): SagaIterator { + const loadShapeshiftRatesTask = yield fork(loadShapeshiftRates); + yield take(TypeKeys.SWAP_STOP_LOAD_SHAPESHIFT_RATES); + yield cancel(loadShapeshiftRatesTask); +} + export function* swapProvider(action: ChangeProviderSwapAcion): SagaIterator { const swap = yield select(getSwap); if (swap.provider !== action.payload) { @@ -58,31 +86,8 @@ export function* swapProvider(action: ChangeProviderSwapAcion): SagaIterator { } } -// Fork our recurring API call, watch for the need to cancel. -export function* handleBityRates(): SagaIterator { - const loadBityRatesTask = yield fork(loadBityRates); - yield take(TypeKeys.SWAP_STOP_LOAD_BITY_RATES); - yield cancel(loadBityRatesTask); -} - -// Watch for latest SWAP_LOAD_BITY_RATES_REQUESTED action. -export function* getBityRatesSaga(): SagaIterator { +export default function* swapRates(): SagaIterator { yield takeLatest(TypeKeys.SWAP_LOAD_BITY_RATES_REQUESTED, handleBityRates); -} - -// Fork our API call -export function* handleShapeShiftRates(): SagaIterator { - const loadShapeShiftRatesTask = yield fork(loadShapeshiftRates); - yield take(TypeKeys.SWAP_STOP_LOAD_SHAPESHIFT_RATES); - yield cancel(loadShapeShiftRatesTask); -} - -// Watch for SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED action. -export function* getShapeShiftRatesSaga(): SagaIterator { - yield takeLatest(TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED, handleShapeShiftRates); -} - -// Watch for provider swaps -export function* swapProviderSaga(): SagaIterator { + yield takeLatest(TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED, handleShapeshiftRates); yield takeLatest(TypeKeys.SWAP_CHANGE_PROVIDER, swapProvider); } diff --git a/common/selectors/swap.ts b/common/selectors/swap.ts index a135de7e..f7069a22 100644 --- a/common/selectors/swap.ts +++ b/common/selectors/swap.ts @@ -4,3 +4,5 @@ const getSwap = (state: AppState) => state.swap; export const getOrigin = (state: AppState) => getSwap(state).origin; export const getPaymentAddress = (state: AppState) => getSwap(state).paymentAddress; export const shouldDisplayLiteSend = (state: AppState) => getSwap(state).showLiteSend; +export const getHasNotifiedRatesFailure = (state: AppState) => + getSwap(state).hasNotifiedRatesFailure; diff --git a/spec/pages/__snapshots__/Swap.spec.tsx.snap b/spec/pages/__snapshots__/Swap.spec.tsx.snap index ff8bb522..b110abf8 100644 --- a/spec/pages/__snapshots__/Swap.spec.tsx.snap +++ b/spec/pages/__snapshots__/Swap.spec.tsx.snap @@ -45,8 +45,6 @@ exports[`render snapshot 1`] = ` isFetchingRates={null} isOffline={false} isPostingOrder={false} - loadBityRatesRequestedSwap={[Function]} - loadShapeshiftRatesRequestedSwap={[Function]} location={ Object { "hash": "", diff --git a/spec/sagas/swap/orders.spec.ts b/spec/sagas/swap/orders.spec.ts index 9f4a45b4..0b1bd8c3 100644 --- a/spec/sagas/swap/orders.spec.ts +++ b/spec/sagas/swap/orders.spec.ts @@ -30,26 +30,14 @@ import { getOrderStatus, postOrder } from 'api/bity'; import shapeshift from 'api/shapeshift'; import { State as SwapState, INITIAL_STATE as INITIAL_SWAP_STATE } from 'reducers/swap'; import { delay } from 'redux-saga'; -import { - call, - cancel, - apply, - cancelled, - fork, - put, - select, - take, - takeEvery -} from 'redux-saga/effects'; +import { call, cancel, apply, cancelled, fork, put, select, take } from 'redux-saga/effects'; import { getSwap, pollBityOrderStatus, pollBityOrderStatusSaga, postBityOrderCreate, - postBityOrderSaga, pollShapeshiftOrderStatus, pollShapeshiftOrderStatusSaga, - postShapeshiftOrderSaga, shapeshiftOrderTimeRemaining, bityOrderTimeRemaining, ORDER_TIMEOUT_MESSAGE, @@ -455,26 +443,6 @@ describe('postShapeshiftOrderCreate*', () => { }); }); -describe('postBityOrderSaga*', () => { - const gen = postBityOrderSaga(); - - it('should takeEvery SWAP_BITY_ORDER_CREATE_REQUESTED', () => { - expect(gen.next().value).toEqual( - takeEvery(TypeKeys.SWAP_BITY_ORDER_CREATE_REQUESTED, postBityOrderCreate) - ); - }); -}); - -describe('postShapeshiftOrderSaga*', () => { - const gen = postShapeshiftOrderSaga(); - - it('should takeEvery SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED', () => { - expect(gen.next().value).toEqual( - takeEvery(TypeKeys.SWAP_SHAPESHIFT_ORDER_CREATE_REQUESTED, postShapeshiftOrderCreate) - ); - }); -}); - describe('bityOrderTimeRemaining*', () => { const orderTime = new Date().toISOString(); const orderTimeExpired = new Date().getTime() - ELEVEN_SECONDS; diff --git a/spec/sagas/swap/rates.spec.ts b/spec/sagas/swap/rates.spec.ts index 5e8399c5..7a29d696 100644 --- a/spec/sagas/swap/rates.spec.ts +++ b/spec/sagas/swap/rates.spec.ts @@ -1,24 +1,28 @@ import { showNotification } from 'actions/notifications'; -import { loadBityRatesSucceededSwap, loadShapeshiftRatesSucceededSwap } from 'actions/swap'; +import { + loadBityRatesSucceededSwap, + loadShapeshiftRatesSucceededSwap, + loadShapeshiftRatesFailedSwap, + loadBityRatesFailedSwap +} from 'actions/swap'; import { getAllRates } from 'api/bity'; import { delay } from 'redux-saga'; -import { call, cancel, fork, put, race, take, takeLatest } from 'redux-saga/effects'; +import { call, cancel, fork, put, race, take, select } from 'redux-saga/effects'; import { createMockTask } from 'redux-saga/utils'; import { loadBityRates, - handleBityRates, - getBityRatesSaga, loadShapeshiftRates, - getShapeShiftRatesSaga, + handleBityRates, + handleShapeshiftRates, SHAPESHIFT_TIMEOUT, - handleShapeShiftRates + POLLING_CYCLE } from 'sagas/swap/rates'; import shapeshift from 'api/shapeshift'; import { TypeKeys } from 'actions/swap/constants'; +import { getHasNotifiedRatesFailure } from 'selectors/swap'; describe('loadBityRates*', () => { const gen1 = loadBityRates(); - const gen2 = loadBityRates(); const apiResponse = { BTCETH: { id: 'BTCETH', @@ -31,6 +35,7 @@ describe('loadBityRates*', () => { rate: 0.042958 } }; + const err = { message: 'error' }; let random; beforeAll(() => { @@ -50,20 +55,30 @@ describe('loadBityRates*', () => { expect(gen1.next(apiResponse).value).toEqual(put(loadBityRatesSucceededSwap(apiResponse))); }); - it('should call delay for 5 seconds', () => { - expect(gen1.next().value).toEqual(call(delay, 30000)); + it(`should delay for ${POLLING_CYCLE}ms`, () => { + expect(gen1.next().value).toEqual(call(delay, POLLING_CYCLE)); }); it('should handle an exception', () => { - const err = { message: 'error' }; - gen2.next(); - expect((gen2 as any).throw(err).value).toEqual(put(showNotification('danger', err.message))); + const errGen = loadBityRates(); + errGen.next(); + expect((errGen as any).throw(err).value).toEqual(select(getHasNotifiedRatesFailure)); + expect(errGen.next(false).value).toEqual(put(showNotification('danger', err.message))); + expect(errGen.next().value).toEqual(put(loadBityRatesFailedSwap())); + expect(errGen.next().value).toEqual(call(delay, POLLING_CYCLE)); + }); + + it('should not notify on subsequent exceptions', () => { + const noNotifyErrGen = loadBityRates(); + noNotifyErrGen.next(); + expect((noNotifyErrGen as any).throw(err).value).toEqual(select(getHasNotifiedRatesFailure)); + expect(noNotifyErrGen.next(true).value).toEqual(put(loadBityRatesFailedSwap())); + expect(noNotifyErrGen.next().value).toEqual(call(delay, POLLING_CYCLE)); }); }); describe('loadShapeshiftRates*', () => { const gen1 = loadShapeshiftRates(); - const gen2 = loadShapeshiftRates(); const apiResponse = { ['1SSTANT']: { @@ -87,6 +102,7 @@ describe('loadShapeshiftRates*', () => { min: 7.86382979 } }; + const err = 'error'; let random; beforeAll(() => { @@ -113,16 +129,30 @@ describe('loadShapeshiftRates*', () => { ); }); - it('should call delay for 30 seconds', () => { - expect(gen1.next().value).toEqual(call(delay, 30000)); + it(`should delay for ${POLLING_CYCLE}ms`, () => { + expect(gen1.next().value).toEqual(call(delay, POLLING_CYCLE)); }); it('should handle an exception', () => { - const err = 'error'; - gen2.next(); - expect((gen2 as any).throw(err).value).toEqual( - put(showNotification('danger', `Error loading ShapeShift tokens - ${err}`)) + const errGen = loadShapeshiftRates(); + errGen.next(); + expect((errGen as any).throw(err).value).toEqual(select(getHasNotifiedRatesFailure)); + expect(errGen.next(false).value).toEqual( + put( + showNotification( + 'danger', + 'Failed to load swap rates from ShapeShift, please try again later' + ) + ) ); + expect(errGen.next().value).toEqual(put(loadShapeshiftRatesFailedSwap())); + }); + + it('should not notify on subsequent exceptions', () => { + const noNotifyErrGen = loadShapeshiftRates(); + noNotifyErrGen.next(); + expect((noNotifyErrGen as any).throw(err).value).toEqual(select(getHasNotifiedRatesFailure)); + expect(noNotifyErrGen.next(true).value).toEqual(put(loadShapeshiftRatesFailedSwap())); }); }); @@ -148,7 +178,7 @@ describe('handleBityRates*', () => { }); describe('handleShapeshiftRates*', () => { - const gen = handleShapeShiftRates(); + const gen = handleShapeshiftRates(); const mockTask = createMockTask(); it('should fork loadShapeshiftRates', () => { @@ -167,23 +197,3 @@ describe('handleShapeshiftRates*', () => { expect(gen.next().done).toEqual(true); }); }); - -describe('getBityRatesSaga*', () => { - const gen = getBityRatesSaga(); - - it('should takeLatest SWAP_LOAD_BITY_RATES_REQUESTED', () => { - expect(gen.next().value).toEqual( - takeLatest(TypeKeys.SWAP_LOAD_BITY_RATES_REQUESTED, handleBityRates) - ); - }); -}); - -describe('getShapeshiftRatesSaga*', () => { - const gen = getShapeShiftRatesSaga(); - - it('should takeLatest SWAP_LOAD_BITY_RATES_REQUESTED', () => { - expect(gen.next().value).toEqual( - takeLatest(TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_REQUESTED, handleShapeShiftRates) - ); - }); -});