Minor swap rates refactor (#1162)

* Refactor swaps:
* Remove references to bity fallback, it aint happening
* Consolidate sagas to single orchestrator function like other sagas
* Grab rates once, dont continuously poll

* tscheck

* Re-instate the auto-fetching behavior. This time, only notify the first time.

* Make CurrentRates responsible for redux actions. Fix up some typings.

* Remove commented code.

* Update snapshot.
This commit is contained in:
William O'Beirne 2018-02-24 20:29:34 -05:00 committed by Daniel Ternyak
parent b48617e95e
commit 33f5ede22a
16 changed files with 221 additions and 199 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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',

View File

@ -85,8 +85,9 @@ export default class CurrencySwap extends PureComponent<Props, State> {
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<Props, State> {
}
}
public componentWillUnmount() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}
public componentDidUpdate(prevProps: Props, prevState: State) {
const { origin, destination } = this.state;
const { options } = this.props;

View File

@ -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<Props> {
interface ActionProps {
loadShapeshiftRatesRequestedSwap: TLoadShapeshiftRatesRequestedSwap;
stopLoadShapeshiftRatesSwap: TStopLoadShapeshiftRatesSwap;
}
type Props = StateProps & ActionProps;
class CurrentRates extends PureComponent<Props> {
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<Props> {
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);

View File

@ -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<ReduxActionProps & ReduxStateProps & RouteComponentProps<{}>, {}> {
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<ReduxActionProps & ReduxStateProps & RouteComponent
bityRates
};
const CurrentRatesProps = { provider, bityRates, shapeshiftRates };
return (
<TabSection isUnavailableOffline={true}>
<section className="Tab-content swap-tab">
@ -246,7 +218,7 @@ class Swap extends Component<ReduxActionProps & ReduxStateProps & RouteComponent
path={`${currentPath}`}
render={() => (
<React.Fragment>
{step === 1 && <CurrentRates {...CurrentRatesProps} />}
{step === 1 && <CurrentRates />}
{step === 1 && <ShapeshiftBanner />}
{(step === 2 || step === 3) && <SwapInfoHeader {...SwapInfoHeaderProps} />}
<main className="Tab-content-pane">
@ -294,8 +266,6 @@ export default connect(mapStateToProps, {
initSwap: dInitSwap,
bityOrderCreateRequestedSwap: dBityOrderCreateRequestedSwap,
shapeshiftOrderCreateRequestedSwap: dShapeshiftOrderCreateRequestedSwap,
loadBityRatesRequestedSwap: dLoadBityRatesRequestedSwap,
loadShapeshiftRatesRequestedSwap: dLoadShapeshiftRatesRequestedSwap,
destinationAddressSwap: dDestinationAddressSwap,
restartSwap: dRestartSwap,
startOrderTimerSwap: dStartOrderTimerSwap,

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -45,8 +45,6 @@ exports[`render snapshot 1`] = `
isFetchingRates={null}
isOffline={false}
isPostingOrder={false}
loadBityRatesRequestedSwap={[Function]}
loadShapeshiftRatesRequestedSwap={[Function]}
location={
Object {
"hash": "",

View File

@ -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;

View File

@ -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)
);
});
});