1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Add more unit / integration tests for Swaps (#16040)

This commit is contained in:
Daniel 2022-10-04 18:55:05 +02:00 committed by GitHub
parent 22f07aefe3
commit c8067e9351
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1800 additions and 120 deletions

View File

@ -11,7 +11,7 @@ module.exports = {
coverageThreshold: {
global: {
branches: 44,
functions: 42,
functions: 46.8,
lines: 52,
statements: 52,
},

View File

@ -120,6 +120,13 @@ export const createSwapsMockStore = () => {
},
},
fromToken: 'ETH',
toToken: {
symbol: 'USDC',
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
occurances: 4,
},
currentSmartTransactionsErrorMessageDismissed: false,
swapsSTXLoading: false,
},
metamask: {
networkDetails: {
@ -314,10 +321,10 @@ export const createSwapsMockStore = () => {
fetchParams: {
metaData: {
sourceTokenInfo: {
symbol: 'BAT',
symbol: 'ETH',
},
destinationTokenInfo: {
symbol: 'ETH',
symbol: 'USDC',
},
},
},
@ -326,6 +333,10 @@ export const createSwapsMockStore = () => {
quotesLastFetched: 1519211809934,
swapsQuoteRefreshTime: 60000,
swapsQuotePrefetchingRefreshTime: 60000,
swapsStxBatchStatusRefreshTime: 5000,
swapsStxGetTransactionsRefreshTime: 5000,
swapsStxMaxFeeMultiplier: 1.5,
swapsStxStatusDeadline: 150000,
customMaxGas: '',
customGasPrice: null,
selectedAggId: 'TEST_AGG_2',
@ -412,6 +423,7 @@ export const createSwapsMockStore = () => {
{
uuid: 'uuid2',
status: 'success',
cancellable: false,
statusMetadata: {
cancellationFeeWei: 36777567771000,
cancellationReason: 'not_cancelled',
@ -424,6 +436,7 @@ export const createSwapsMockStore = () => {
{
uuid: 'uuid2',
status: 'pending',
cancellable: true,
statusMetadata: {
cancellationFeeWei: 36777567771000,
cancellationReason: 'not_cancelled',

View File

@ -92,7 +92,7 @@ import {
hexWEIToDecGWEI,
} from '../../../shared/lib/transactions-controller-utils';
const GAS_PRICES_LOADING_STATES = {
export const GAS_PRICES_LOADING_STATES = {
INITIAL: 'INITIAL',
LOADING: 'LOADING',
FAILED: 'FAILED',

View File

@ -1,10 +1,14 @@
import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { MOCKS, createSwapsMockStore } from '../../../test/jest';
import { setSwapsLiveness, setSwapsFeatureFlags } from '../../store/actions';
import { CHAIN_IDS } from '../../../shared/constants/network';
import { setStorageItem } from '../../../shared/lib/storage-helpers';
import * as swaps from './swaps';
import swapsReducer, * as swaps from './swaps';
const middleware = [thunk];
jest.mock('../../store/actions.js', () => ({
setSwapsLiveness: jest.fn(),
@ -176,24 +180,6 @@ describe('Ducks - Swaps', () => {
});
});
describe('getCustomSwapsGas', () => {
it('returns "customMaxGas"', () => {
const state = createSwapsMockStore();
const customMaxGas = '29000';
state.metamask.swapsState.customMaxGas = customMaxGas;
expect(swaps.getCustomSwapsGas(state)).toBe(customMaxGas);
});
});
describe('getCustomMaxFeePerGas', () => {
it('returns "customMaxFeePerGas"', () => {
const state = createSwapsMockStore();
const customMaxFeePerGas = '20';
state.metamask.swapsState.customMaxFeePerGas = customMaxFeePerGas;
expect(swaps.getCustomMaxFeePerGas(state)).toBe(customMaxFeePerGas);
});
});
describe('getCustomMaxPriorityFeePerGas', () => {
it('returns "customMaxPriorityFeePerGas"', () => {
const state = createSwapsMockStore();
@ -206,6 +192,188 @@ describe('Ducks - Swaps', () => {
});
});
describe('getAggregatorMetadata', () => {
it('returns agg metadata', () => {
const state = createSwapsMockStore();
expect(swaps.getAggregatorMetadata(state)).toBe(
state.swaps.aggregatorMetadata,
);
});
});
describe('getBalanceError', () => {
it('returns a balance error', () => {
const state = createSwapsMockStore();
state.swaps.balanceError = 'balanceError';
expect(swaps.getBalanceError(state)).toBe(state.swaps.balanceError);
});
});
describe('getFromToken', () => {
it('returns fromToken', () => {
const state = createSwapsMockStore();
expect(swaps.getFromToken(state)).toBe(state.swaps.fromToken);
});
});
describe('getFromTokenError', () => {
it('returns fromTokenError', () => {
const state = createSwapsMockStore();
state.swaps.fromTokenError = 'fromTokenError';
expect(swaps.getFromTokenError(state)).toBe(state.swaps.fromTokenError);
});
});
describe('getFromTokenInputValue', () => {
it('returns fromTokenInputValue', () => {
const state = createSwapsMockStore();
expect(swaps.getFromTokenInputValue(state)).toBe(
state.swaps.fromTokenInputValue,
);
});
});
describe('getIsFeatureFlagLoaded', () => {
it('returns isFeatureFlagLoaded', () => {
const state = createSwapsMockStore();
expect(swaps.getIsFeatureFlagLoaded(state)).toBe(
state.swaps.isFeatureFlagLoaded,
);
});
});
describe('getSwapsSTXLoading', () => {
it('returns swapsSTXLoading', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsSTXLoading(state)).toBe(state.swaps.swapsSTXLoading);
});
});
describe('getMaxSlippage', () => {
it('returns maxSlippage', () => {
const state = createSwapsMockStore();
expect(swaps.getMaxSlippage(state)).toBe(state.swaps.maxSlippage);
});
});
describe('getTopAssets', () => {
it('returns topAssets', () => {
const state = createSwapsMockStore();
expect(swaps.getTopAssets(state)).toBe(state.swaps.topAssets);
});
});
describe('getToToken', () => {
it('returns toToken', () => {
const state = createSwapsMockStore();
expect(swaps.getToToken(state)).toBe(state.swaps.toToken);
});
});
describe('getFetchingQuotes', () => {
it('returns fetchingQuotes', () => {
const state = createSwapsMockStore();
expect(swaps.getFetchingQuotes(state)).toBe(state.swaps.fetchingQuotes);
});
});
describe('getQuotesFetchStartTime', () => {
it('returns quotesFetchStartTime', () => {
const state = createSwapsMockStore();
expect(swaps.getQuotesFetchStartTime(state)).toBe(
state.swaps.quotesFetchStartTime,
);
});
});
describe('getReviewSwapClickedTimestamp', () => {
it('returns reviewSwapClickedTimestamp', () => {
const state = createSwapsMockStore();
expect(swaps.getReviewSwapClickedTimestamp(state)).toBe(
state.swaps.reviewSwapClickedTimestamp,
);
});
});
describe('getSwapsCustomizationModalPrice', () => {
it('returns customGas.price', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsCustomizationModalPrice(state)).toBe(
state.swaps.customGas.price,
);
});
});
describe('getSwapsCustomizationModalLimit', () => {
it('returns customGas.limit', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsCustomizationModalLimit(state)).toBe(
state.swaps.customGas.limit,
);
});
});
describe('swapGasPriceEstimateIsLoading', () => {
it('returns true for swapGasPriceEstimateIsLoading', () => {
const state = createSwapsMockStore();
state.swaps.customGas.loading = swaps.GAS_PRICES_LOADING_STATES.LOADING;
expect(swaps.swapGasPriceEstimateIsLoading(state)).toBe(true);
});
});
describe('swapGasEstimateLoadingHasFailed', () => {
it('returns true for swapGasEstimateLoadingHasFailed', () => {
const state = createSwapsMockStore();
state.swaps.customGas.loading = swaps.GAS_PRICES_LOADING_STATES.INITIAL;
expect(swaps.swapGasEstimateLoadingHasFailed(state)).toBe(true);
});
});
describe('getSwapGasPriceEstimateData', () => {
it('returns customGas.priceEstimates', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapGasPriceEstimateData(state)).toBe(
state.swaps.customGas.priceEstimates,
);
});
});
describe('getSwapsFallbackGasPrice', () => {
it('returns customGas.fallBackPrice', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsFallbackGasPrice(state)).toBe(
state.swaps.customGas.fallBackPrice,
);
});
});
describe('getCurrentSmartTransactionsError', () => {
it('returns currentSmartTransactionsError', () => {
const state = createSwapsMockStore();
state.swaps.currentSmartTransactionsError =
'currentSmartTransactionsError';
expect(swaps.getCurrentSmartTransactionsError(state)).toBe(
state.swaps.currentSmartTransactionsError,
);
});
});
describe('getCurrentSmartTransactionsErrorMessageDismissed', () => {
it('returns currentSmartTransactionsErrorMessageDismissed', () => {
const state = createSwapsMockStore();
expect(
swaps.getCurrentSmartTransactionsErrorMessageDismissed(state),
).toBe(state.swaps.currentSmartTransactionsErrorMessageDismissed);
});
});
describe('shouldShowCustomPriceTooLowWarning', () => {
it('returns false for showCustomPriceTooLowWarning', () => {
const state = createSwapsMockStore();
expect(swaps.shouldShowCustomPriceTooLowWarning(state)).toBe(false);
});
});
describe('getSwapsFeatureIsLive', () => {
it('returns true for "swapsFeatureIsLive"', () => {
const state = createSwapsMockStore();
@ -222,19 +390,22 @@ describe('Ducks - Swaps', () => {
});
});
describe('getUsedQuote', () => {
it('returns selected quote', () => {
describe('getSmartTransactionsError', () => {
it('returns smartTransactionsError', () => {
const state = createSwapsMockStore();
expect(swaps.getUsedQuote(state)).toMatchObject(
state.metamask.swapsState.quotes.TEST_AGG_2,
state.appState.smartTransactionsError = 'stxError';
expect(swaps.getSmartTransactionsError(state)).toBe(
state.appState.smartTransactionsError,
);
});
});
it('returns best quote', () => {
describe('getSmartTransactionsErrorMessageDismissed', () => {
it('returns smartTransactionsErrorMessageDismissed', () => {
const state = createSwapsMockStore();
state.metamask.swapsState.selectedAggId = null;
expect(swaps.getUsedQuote(state)).toMatchObject(
state.metamask.swapsState.quotes.TEST_AGG_BEST,
state.appState.smartTransactionsErrorMessageDismissed = true;
expect(swaps.getSmartTransactionsErrorMessageDismissed(state)).toBe(
state.appState.smartTransactionsErrorMessageDismissed,
);
});
});
@ -288,6 +459,234 @@ describe('Ducks - Swaps', () => {
});
});
describe('getCurrentSmartTransactionsEnabled', () => {
it('returns true if STX are enabled and there is no current STX error', () => {
const state = createSwapsMockStore();
expect(swaps.getCurrentSmartTransactionsEnabled(state)).toBe(true);
});
it('returns false if STX are enabled and there is an current STX error', () => {
const state = createSwapsMockStore();
state.swaps.currentSmartTransactionsError =
'currentSmartTransactionsError';
expect(swaps.getCurrentSmartTransactionsEnabled(state)).toBe(false);
});
});
describe('getSwapsQuoteRefreshTime', () => {
it('returns swapsQuoteRefreshTime', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsQuoteRefreshTime(state)).toBe(
state.metamask.swapsState.swapsQuoteRefreshTime,
);
});
});
describe('getSwapsQuotePrefetchingRefreshTime', () => {
it('returns swapsQuotePrefetchingRefreshTime', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsQuotePrefetchingRefreshTime(state)).toBe(
state.metamask.swapsState.swapsQuotePrefetchingRefreshTime,
);
});
});
describe('getBackgroundSwapRouteState', () => {
it('returns routeState', () => {
const state = createSwapsMockStore();
expect(swaps.getBackgroundSwapRouteState(state)).toBe(
state.metamask.swapsState.routeState,
);
});
});
describe('getCustomSwapsGas', () => {
it('returns "customMaxGas"', () => {
const state = createSwapsMockStore();
const customMaxGas = '29000';
state.metamask.swapsState.customMaxGas = customMaxGas;
expect(swaps.getCustomSwapsGas(state)).toBe(customMaxGas);
});
});
describe('getCustomSwapsGasPrice', () => {
it('returns customGasPrice', () => {
const state = createSwapsMockStore();
expect(swaps.getCustomSwapsGasPrice(state)).toBe(
state.metamask.swapsState.customGasPrice,
);
});
});
describe('getCustomMaxFeePerGas', () => {
it('returns "customMaxFeePerGas"', () => {
const state = createSwapsMockStore();
const customMaxFeePerGas = '20';
state.metamask.swapsState.customMaxFeePerGas = customMaxFeePerGas;
expect(swaps.getCustomMaxFeePerGas(state)).toBe(customMaxFeePerGas);
});
});
describe('getSwapsUserFeeLevel', () => {
it('returns swapsUserFeeLevel', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsUserFeeLevel(state)).toBe(
state.metamask.swapsState.swapsUserFeeLevel,
);
});
});
describe('getFetchParams', () => {
it('returns fetchParams', () => {
const state = createSwapsMockStore();
expect(swaps.getFetchParams(state)).toBe(
state.metamask.swapsState.fetchParams,
);
});
});
describe('getQuotes', () => {
it('returns quotes for Swaps', () => {
const state = createSwapsMockStore();
expect(swaps.getQuotes(state)).toBe(state.metamask.swapsState.quotes);
});
});
describe('getQuotesLastFetched', () => {
it('returns quotesLastFetched', () => {
const state = createSwapsMockStore();
expect(swaps.getQuotesLastFetched(state)).toBe(
state.metamask.swapsState.quotesLastFetched,
);
});
});
describe('getSelectedQuote', () => {
it('returns selected quote', () => {
const state = createSwapsMockStore();
expect(swaps.getSelectedQuote(state)).toBe(
state.metamask.swapsState.quotes.TEST_AGG_2,
);
});
});
describe('getSwapsErrorKey', () => {
it('returns errorKey', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsErrorKey(state)).toBe(
state.metamask.swapsState.errorKey,
);
});
});
describe('getShowQuoteLoadingScreen', () => {
it('returns showQuoteLoadingScreen', () => {
const state = createSwapsMockStore();
expect(swaps.getShowQuoteLoadingScreen(state)).toBe(
state.swaps.showQuoteLoadingScreen,
);
});
});
describe('getSwapsTokens', () => {
it('returns tokens', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsTokens(state)).toBe(
state.metamask.swapsState.tokens,
);
});
});
describe('getSwapsWelcomeMessageSeenStatus', () => {
it('returns', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapsWelcomeMessageSeenStatus(state)).toBe(
state.metamask.swapsState.swapsWelcomeMessageHasBeenShown,
);
});
});
describe('getTopQuote', () => {
it('returns a top quote', () => {
const state = createSwapsMockStore();
expect(swaps.getTopQuote(state)).toBe(
state.metamask.swapsState.quotes.TEST_AGG_BEST,
);
});
});
describe('getApproveTxId', () => {
it('returns approveTxId', () => {
const state = createSwapsMockStore();
expect(swaps.getApproveTxId(state)).toBe(
state.metamask.swapsState.approveTxId,
);
});
});
describe('getTradeTxId', () => {
it('returns tradeTxId', () => {
const state = createSwapsMockStore();
expect(swaps.getTradeTxId(state)).toBe(
state.metamask.swapsState.tradeTxId,
);
});
});
describe('getUsedQuote', () => {
it('returns selected quote', () => {
const state = createSwapsMockStore();
expect(swaps.getUsedQuote(state)).toMatchObject(
state.metamask.swapsState.quotes.TEST_AGG_2,
);
});
it('returns best quote', () => {
const state = createSwapsMockStore();
state.metamask.swapsState.selectedAggId = null;
expect(swaps.getUsedQuote(state)).toMatchObject(
state.metamask.swapsState.quotes.TEST_AGG_BEST,
);
});
});
describe('getDestinationTokenInfo', () => {
it('returns destinationTokenInfo', () => {
const state = createSwapsMockStore();
expect(swaps.getDestinationTokenInfo(state)).toBe(
state.metamask.swapsState.fetchParams.metaData.destinationTokenInfo,
);
});
});
describe('getUsedSwapsGasPrice', () => {
it('returns customGasPrice', () => {
const state = createSwapsMockStore();
state.metamask.swapsState.customGasPrice = 5;
expect(swaps.getUsedSwapsGasPrice(state)).toBe(
state.metamask.swapsState.customGasPrice,
);
});
});
describe('getApproveTxParams', () => {
it('returns approveTxParams', () => {
const state = createSwapsMockStore();
state.metamask.swapsState.quotes.TEST_AGG_2.approvalNeeded = {
data: '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00',
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
value: '0x0',
from: '0x2369267687A84ac7B494daE2f1542C40E37f4455',
gas: '0x12',
gasPrice: '0x34',
};
expect(swaps.getApproveTxParams(state)).toMatchObject({
...state.metamask.swapsState.quotes.TEST_AGG_2.approvalNeeded,
gasPrice: 5,
});
});
});
describe('getSmartTransactionsOptInStatus', () => {
it('returns STX opt in status', () => {
const state = createSwapsMockStore();
@ -325,4 +724,261 @@ describe('Ducks - Swaps', () => {
);
});
});
describe('getSmartTransactionEstimatedGas', () => {
it('returns unsigned transactions and estimates', () => {
const state = createSwapsMockStore();
const smartTransactionFees = swaps.getSmartTransactionEstimatedGas(state);
expect(smartTransactionFees).toBe(
state.metamask.smartTransactionsState.estimatedGas,
);
});
});
describe('getSwapsNetworkConfig', () => {
it('returns Swaps network config', () => {
const state = createSwapsMockStore();
const {
swapsQuoteRefreshTime,
swapsQuotePrefetchingRefreshTime,
swapsStxGetTransactionsRefreshTime,
swapsStxBatchStatusRefreshTime,
swapsStxStatusDeadline,
swapsStxMaxFeeMultiplier,
} = state.metamask.swapsState;
const swapsNetworkConfig = swaps.getSwapsNetworkConfig(state);
expect(swapsNetworkConfig).toMatchObject({
quoteRefreshTime: swapsQuoteRefreshTime,
quotePrefetchingRefreshTime: swapsQuotePrefetchingRefreshTime,
stxGetTransactionsRefreshTime: swapsStxGetTransactionsRefreshTime,
stxBatchStatusRefreshTime: swapsStxBatchStatusRefreshTime,
stxStatusDeadline: swapsStxStatusDeadline,
stxMaxFeeMultiplier: swapsStxMaxFeeMultiplier,
});
});
});
describe('actions + reducers', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
afterEach(() => {
store.clearActions();
});
describe('clearSwapsState', () => {
it('calls the "swaps/clearSwapsState" action', () => {
store.dispatch(swaps.clearSwapsState());
const actions = store.getActions();
expect(actions).toHaveLength(1);
expect(actions[0].type).toBe('swaps/clearSwapsState');
});
});
describe('dismissCurrentSmartTransactionsErrorMessage', () => {
it('calls the "swaps/dismissCurrentSmartTransactionsErrorMessage" action', () => {
const state = store.getState().swaps;
store.dispatch(swaps.dismissCurrentSmartTransactionsErrorMessage());
const actions = store.getActions();
expect(actions[0].type).toBe(
'swaps/dismissCurrentSmartTransactionsErrorMessage',
);
const newState = swapsReducer(state, actions[0]);
expect(newState.currentSmartTransactionsErrorMessageDismissed).toBe(
true,
);
});
});
describe('setAggregatorMetadata', () => {
it('calls the "swaps/setAggregatorMetadata" action', () => {
const state = store.getState().swaps;
const actionPayload = {
name: 'agg1',
};
store.dispatch(swaps.setAggregatorMetadata(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setAggregatorMetadata');
const newState = swapsReducer(state, actions[0]);
expect(newState.aggregatorMetadata).toBe(actionPayload);
});
});
describe('setBalanceError', () => {
it('calls the "swaps/setBalanceError" action', () => {
const state = store.getState().swaps;
const actionPayload = 'balanceError';
store.dispatch(swaps.setBalanceError(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setBalanceError');
const newState = swapsReducer(state, actions[0]);
expect(newState.balanceError).toBe(actionPayload);
});
});
describe('setFetchingQuotes', () => {
it('calls the "swaps/setFetchingQuotes" action', () => {
const state = store.getState().swaps;
const actionPayload = true;
store.dispatch(swaps.setFetchingQuotes(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setFetchingQuotes');
const newState = swapsReducer(state, actions[0]);
expect(newState.fetchingQuotes).toBe(actionPayload);
});
});
describe('setSwapsFromToken', () => {
it('calls the "swaps/setFromToken" action', () => {
const state = store.getState().swaps;
const actionPayload = 'ETH';
store.dispatch(swaps.setSwapsFromToken(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setFromToken');
const newState = swapsReducer(state, actions[0]);
expect(newState.fromToken).toBe(actionPayload);
});
});
describe('setFromTokenError', () => {
it('calls the "swaps/setFromTokenError" action', () => {
const state = store.getState().swaps;
const actionPayload = 'fromTokenError';
store.dispatch(swaps.setFromTokenError(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setFromTokenError');
const newState = swapsReducer(state, actions[0]);
expect(newState.fromTokenError).toBe(actionPayload);
});
});
describe('setFromTokenInputValue', () => {
it('calls the "swaps/setFromTokenInputValue" action', () => {
const state = store.getState().swaps;
const actionPayload = '5';
store.dispatch(swaps.setFromTokenInputValue(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setFromTokenInputValue');
const newState = swapsReducer(state, actions[0]);
expect(newState.fromTokenInputValue).toBe(actionPayload);
});
});
describe('setIsFeatureFlagLoaded', () => {
it('calls the "swaps/setIsFeatureFlagLoaded" action', () => {
const state = store.getState().swaps;
const actionPayload = true;
store.dispatch(swaps.setIsFeatureFlagLoaded(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setIsFeatureFlagLoaded');
const newState = swapsReducer(state, actions[0]);
expect(newState.isFeatureFlagLoaded).toBe(actionPayload);
});
});
describe('setMaxSlippage', () => {
it('calls the "swaps/setMaxSlippage" action', () => {
const state = store.getState().swaps;
const actionPayload = 3;
store.dispatch(swaps.setMaxSlippage(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setMaxSlippage');
const newState = swapsReducer(state, actions[0]);
expect(newState.maxSlippage).toBe(actionPayload);
});
});
describe('setSwapQuotesFetchStartTime', () => {
it('calls the "swaps/setQuotesFetchStartTime" action', () => {
const state = store.getState().swaps;
const actionPayload = '1664461886';
store.dispatch(swaps.setSwapQuotesFetchStartTime(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setQuotesFetchStartTime');
const newState = swapsReducer(state, actions[0]);
expect(newState.quotesFetchStartTime).toBe(actionPayload);
});
});
describe('setReviewSwapClickedTimestamp', () => {
it('calls the "swaps/setReviewSwapClickedTimestamp" action', () => {
const state = store.getState().swaps;
const actionPayload = '1664461886';
store.dispatch(swaps.setReviewSwapClickedTimestamp(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setReviewSwapClickedTimestamp');
const newState = swapsReducer(state, actions[0]);
expect(newState.reviewSwapClickedTimestamp).toBe(actionPayload);
});
});
describe('setTopAssets', () => {
it('calls the "swaps/setTopAssets" action', () => {
const state = store.getState().swaps;
const actionPayload = {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
index: '0',
},
'0x04fa0d235c4abf4bcf4787af4cf447de572ef828': {
index: '1',
},
'0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e': {
index: '2',
},
};
store.dispatch(swaps.setTopAssets(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setTopAssets');
const newState = swapsReducer(state, actions[0]);
expect(newState.topAssets).toBe(actionPayload);
});
});
describe('setSwapToToken', () => {
it('calls the "swaps/setToToken" action', () => {
const state = store.getState().swaps;
const actionPayload = 'USDC';
store.dispatch(swaps.setSwapToToken(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/setToToken');
const newState = swapsReducer(state, actions[0]);
expect(newState.toToken).toBe(actionPayload);
});
});
describe('swapCustomGasModalPriceEdited', () => {
it('calls the "swaps/swapCustomGasModalPriceEdited" action', () => {
const state = store.getState().swaps;
const actionPayload = 5;
store.dispatch(swaps.swapCustomGasModalPriceEdited(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/swapCustomGasModalPriceEdited');
const newState = swapsReducer(state, actions[0]);
expect(newState.customGas.price).toBe(actionPayload);
});
});
describe('swapCustomGasModalLimitEdited', () => {
it('calls the "swaps/swapCustomGasModalLimitEdited" action', () => {
const state = store.getState().swaps;
const actionPayload = 100;
store.dispatch(swaps.swapCustomGasModalLimitEdited(actionPayload));
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/swapCustomGasModalLimitEdited');
const newState = swapsReducer(state, actions[0]);
expect(newState.customGas.limit).toBe(actionPayload);
});
});
describe('swapCustomGasModalClosed', () => {
it('calls the "swaps/swapCustomGasModalClosed" action', () => {
const state = store.getState().swaps;
store.dispatch(swaps.swapCustomGasModalClosed());
const actions = store.getActions();
expect(actions[0].type).toBe('swaps/swapCustomGasModalClosed');
const newState = swapsReducer(state, actions[0]);
expect(newState.customGas.price).toBe(null);
expect(newState.customGas.limit).toBe(null);
});
});
});
});

View File

@ -10,7 +10,7 @@ exports[`AwaitingSwap renders the component with initial props 1`] = `
<span
class="awaiting-swap__amount-and-symbol"
>
ETH
USDC
</span>
will be added to your account once this transaction has processed.

View File

@ -283,6 +283,7 @@ export default function AwaitingSwap({
) : null}
<SwapsFooter
onSubmit={async () => {
/* istanbul ignore next */
if (errorKey === OFFLINE_FOR_MAINTENANCE) {
await dispatch(prepareToLeaveSwaps());
history.push(DEFAULT_ROUTE);

View File

@ -1,13 +1,26 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
fireEvent,
} from '../../../../test/jest';
import { SLIPPAGE } from '../../../../shared/constants/swaps';
import {
SLIPPAGE,
QUOTES_EXPIRED_ERROR,
SWAP_FAILED_ERROR,
ERROR_FETCHING_QUOTES,
QUOTES_NOT_AVAILABLE_ERROR,
CONTRACT_DATA_DISABLED_ERROR,
OFFLINE_FOR_MAINTENANCE,
} from '../../../../shared/constants/swaps';
import AwaitingSwap from '.';
const middleware = [thunk];
const createProps = (customProps = {}) => {
return {
swapComplete: false,
@ -20,20 +33,25 @@ const createProps = (customProps = {}) => {
};
};
setBackgroundConnection({
stopPollingForQuotes: jest.fn(),
});
describe('AwaitingSwap', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const { getByText } = renderWithProvider(
const { getByText, getByTestId } = renderWithProvider(
<AwaitingSwap {...createProps()} />,
store,
);
expect(getByText('Processing')).toBeInTheDocument();
expect(getByText('ETH')).toBeInTheDocument();
expect(getByText('USDC')).toBeInTheDocument();
expect(getByText('View in activity')).toBeInTheDocument();
expect(
document.querySelector('.awaiting-swap__main-description'),
).toMatchSnapshot();
expect(getByText('View in activity')).toBeInTheDocument();
expect(getByTestId('page-container-footer-next')).toBeInTheDocument();
});
it('renders the component with for completed swap', () => {
@ -43,8 +61,110 @@ describe('AwaitingSwap', () => {
store,
);
expect(getByText('Transaction complete')).toBeInTheDocument();
expect(getByText('tokens received: ETH')).toBeInTheDocument();
expect(getByText('tokens received: USDC')).toBeInTheDocument();
expect(getByText('View Swap at etherscan.io')).toBeInTheDocument();
expect(getByText('Create a new swap')).toBeInTheDocument();
});
it('renders the component with the "OFFLINE_FOR_MAINTENANCE" error', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
errorKey: OFFLINE_FOR_MAINTENANCE,
});
const { getByText } = renderWithProvider(
<AwaitingSwap {...props} />,
store,
);
expect(getByText('Offline for maintenance')).toBeInTheDocument();
expect(
getByText(
'MetaMask Swaps is undergoing maintenance. Please check back later.',
),
).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('renders the component with the "SWAP_FAILED_ERROR" error', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
errorKey: SWAP_FAILED_ERROR,
});
const { getByText } = renderWithProvider(
<AwaitingSwap {...props} />,
store,
);
expect(getByText('Swap failed')).toBeInTheDocument();
fireEvent.click(getByText('metamask-flask.zendesk.com'));
expect(getByText('Try again')).toBeInTheDocument();
});
it('renders the component with the "QUOTES_EXPIRED_ERROR" error', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
errorKey: QUOTES_EXPIRED_ERROR,
});
const { getByText } = renderWithProvider(
<AwaitingSwap {...props} />,
store,
);
expect(getByText('Quotes timeout')).toBeInTheDocument();
expect(
getByText('Please request new quotes to get the latest rates.'),
).toBeInTheDocument();
expect(getByText('Try again')).toBeInTheDocument();
});
it('renders the component with the "ERROR_FETCHING_QUOTES" error', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
errorKey: ERROR_FETCHING_QUOTES,
});
const { getByText } = renderWithProvider(
<AwaitingSwap {...props} />,
store,
);
expect(getByText('Error fetching quotes')).toBeInTheDocument();
expect(
getByText(
'Hmmm... something went wrong. Try again, or if errors persist, contact customer support.',
),
).toBeInTheDocument();
expect(getByText('Back')).toBeInTheDocument();
});
it('renders the component with the "QUOTES_NOT_AVAILABLE_ERROR" error', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
errorKey: QUOTES_NOT_AVAILABLE_ERROR,
});
const { getByText } = renderWithProvider(
<AwaitingSwap {...props} />,
store,
);
expect(getByText('No quotes available')).toBeInTheDocument();
expect(
getByText('Try adjusting the amount or slippage settings and try again.'),
).toBeInTheDocument();
expect(getByText('Try again')).toBeInTheDocument();
});
it('renders the component with the "CONTRACT_DATA_DISABLED_ERROR" error', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
errorKey: CONTRACT_DATA_DISABLED_ERROR,
});
const { getByText } = renderWithProvider(
<AwaitingSwap {...props} />,
store,
);
expect(
getByText('Contract data is not enabled on your Ledger'),
).toBeInTheDocument();
expect(
getByText(
'In the Ethereum app on your Ledger, go to "Settings" and allow contract data. Then, try your swap again.',
),
).toBeInTheDocument();
expect(getByText('Try again')).toBeInTheDocument();
});
});

View File

@ -481,6 +481,7 @@ export default function BuildQuote({
className="build-quote__token-etherscan-link build-quote__underline"
key="build-quote-etherscan-link"
onClick={() => {
/* istanbul ignore next */
trackEvent({
event: EVENT_NAMES.EXTERNAL_LINK_CLICKED,
category: EVENT.CATEGORIES.SWAPS,
@ -678,6 +679,7 @@ export default function BuildQuote({
onSelect={onFromSelect}
itemsToSearch={tokensToSearchSwapFrom}
onInputChange={(value) => {
/* istanbul ignore next */
onInputChange(value, fromTokenBalance);
}}
inputValue={fromTokenInputValue}
@ -732,6 +734,7 @@ export default function BuildQuote({
<div className="build-quote__swap-arrows-row">
<button
className="build-quote__swap-arrows"
data-testid="build-quote__swap-arrows"
onClick={() => {
onToSelect(selectedFromToken);
onFromSelect(selectedToToken);
@ -781,6 +784,7 @@ export default function BuildQuote({
</div>
}
primaryAction={
/* istanbul ignore next */
verificationClicked
? null
: {
@ -809,6 +813,7 @@ export default function BuildQuote({
className="build-quote__token-etherscan-link"
key="build-quote-etherscan-link"
onClick={() => {
/* istanbul ignore next */
trackEvent({
event: 'Clicked Block Explorer Link',
category: EVENT.CATEGORIES.SWAPS,
@ -861,30 +866,33 @@ export default function BuildQuote({
)}
</div>
<SwapsFooter
onSubmit={async () => {
// We need this to know how long it took to go from clicking on the Review swap button to rendered View Quote page.
dispatch(setReviewSwapClickedTimestamp(Date.now()));
// In case that quotes prefetching is waiting to be executed, but hasn't started yet,
// we want to cancel it and fetch quotes from here.
if (timeoutIdForQuotesPrefetching) {
clearTimeout(timeoutIdForQuotesPrefetching);
dispatch(
fetchQuotesAndSetQuoteState(
history,
fromTokenInputValue,
maxSlippage,
trackEvent,
),
);
} else if (areQuotesPresent) {
// If there are prefetched quotes already, go directly to the View Quote page.
history.push(VIEW_QUOTE_ROUTE);
} else {
// If the "Review swap" button was clicked while quotes are being fetched, go to the Loading Quotes page.
await dispatch(setBackgroundSwapRouteState('loading'));
history.push(LOADING_QUOTES_ROUTE);
onSubmit={
/* istanbul ignore next */
async () => {
// We need this to know how long it took to go from clicking on the Review swap button to rendered View Quote page.
dispatch(setReviewSwapClickedTimestamp(Date.now()));
// In case that quotes prefetching is waiting to be executed, but hasn't started yet,
// we want to cancel it and fetch quotes from here.
if (timeoutIdForQuotesPrefetching) {
clearTimeout(timeoutIdForQuotesPrefetching);
dispatch(
fetchQuotesAndSetQuoteState(
history,
fromTokenInputValue,
maxSlippage,
trackEvent,
),
);
} else if (areQuotesPresent) {
// If there are prefetched quotes already, go directly to the View Quote page.
history.push(VIEW_QUOTE_ROUTE);
} else {
// If the "Review swap" button was clicked while quotes are being fetched, go to the Loading Quotes page.
await dispatch(setBackgroundSwapRouteState('loading'));
history.push(LOADING_QUOTES_ROUTE);
}
}
}}
}
submitText={t('swapReviewSwap')}
disabled={isReviewSwapButtonDisabled}
hideCancel

View File

@ -6,7 +6,13 @@ import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
fireEvent,
} from '../../../../test/jest';
import {
setSwapsFromToken,
setSwapToToken,
setFromTokenInputValue,
} from '../../../ducks/swaps/swaps';
import BuildQuote from '.';
const middleware = [thunk];
@ -27,16 +33,46 @@ setBackgroundConnection({
clearSwapsQuotes: jest.fn(),
stopPollingForQuotes: jest.fn(),
clearSmartTransactionFees: jest.fn(),
setSwapsFromToken: jest.fn(),
setSwapToToken: jest.fn(),
setFromTokenInputValue: jest.fn(),
});
jest.mock('../../../ducks/swaps/swaps', () => {
const actual = jest.requireActual('../../../ducks/swaps/swaps');
return {
...actual,
setSwapsFromToken: jest.fn(),
setSwapToToken: jest.fn(),
setFromTokenInputValue: jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
}),
};
});
jest.mock('../swaps.util', () => {
const actual = jest.requireActual('../swaps.util');
return {
...actual,
fetchTokenBalance: jest.fn(() => Promise.resolve()),
fetchTokenPrice: jest.fn(() => Promise.resolve()),
};
});
describe('BuildQuote', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders the component with initial props', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps();
const { getByText } = renderWithProvider(<BuildQuote {...props} />, store);
expect(getByText('Swap from')).toBeInTheDocument();
expect(getByText('Swap to')).toBeInTheDocument();
expect(getByText('ETH')).toBeInTheDocument();
expect(getByText('Select')).toBeInTheDocument();
expect(getByText('Slippage tolerance')).toBeInTheDocument();
expect(getByText('2%')).toBeInTheDocument();
expect(getByText('3%')).toBeInTheDocument();
@ -45,4 +81,86 @@ describe('BuildQuote', () => {
document.querySelector('.slippage-buttons__button-group'),
).toMatchSnapshot();
});
it('switches swap from and to tokens', () => {
const setSwapFromTokenMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setSwapsFromToken.mockImplementation(setSwapFromTokenMock);
const setSwapToTokenMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setSwapToToken.mockImplementation(setSwapToTokenMock);
const mockStore = createSwapsMockStore();
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText, getByTestId } = renderWithProvider(
<BuildQuote {...props} />,
store,
);
expect(getByText('Swap from')).toBeInTheDocument();
fireEvent.click(getByTestId('build-quote__swap-arrows'));
expect(setSwapsFromToken).toHaveBeenCalledWith(mockStore.swaps.toToken);
expect(setSwapToToken).toHaveBeenCalled();
});
it('renders the block explorer link, only 1 verified source', () => {
const mockStore = createSwapsMockStore();
mockStore.swaps.toToken.occurances = 1;
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(<BuildQuote {...props} />, store);
expect(getByText('Swap from')).toBeInTheDocument();
expect(getByText('Only verified on 1 source.')).toBeInTheDocument();
expect(getByText('Etherscan')).toBeInTheDocument();
});
it('renders the block explorer link, 0 verified sources', () => {
const mockStore = createSwapsMockStore();
mockStore.swaps.toToken.occurances = 0;
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(<BuildQuote {...props} />, store);
expect(getByText('Swap from')).toBeInTheDocument();
expect(
getByText('This token has been added manually.'),
).toBeInTheDocument();
expect(getByText('Etherscan')).toBeInTheDocument();
});
it('clicks on a block explorer link', () => {
global.platform = { openTab: jest.fn() };
const mockStore = createSwapsMockStore();
mockStore.swaps.toToken.occurances = 1;
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(<BuildQuote {...props} />, store);
const blockExplorer = getByText('Etherscan');
expect(blockExplorer).toBeInTheDocument();
fireEvent.click(blockExplorer);
expect(global.platform.openTab).toHaveBeenCalledWith({
url: 'https://etherscan.io/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
});
});
it('clicks on the "max" link', () => {
const setFromTokenInputValueMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setFromTokenInputValue.mockImplementation(setFromTokenInputValueMock);
const mockStore = createSwapsMockStore();
mockStore.swaps.fromToken = 'DAI';
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(<BuildQuote {...props} />, store);
const maxLink = getByText('Max');
fireEvent.click(maxLink);
expect(setFromTokenInputValue).toHaveBeenCalled();
});
});

View File

@ -1,12 +1,20 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
renderWithProvider,
createSwapsMockStore,
fireEvent,
setBackgroundConnection,
} from '../../../../test/jest';
import {
setSwapsFromToken,
navigateBackToBuildQuote,
} from '../../../ducks/swaps/swaps';
import CreateNewSwap from '.';
const middleware = [thunk];
const createProps = (customProps = {}) => {
return {
sensitiveProperties: {},
@ -14,7 +22,28 @@ const createProps = (customProps = {}) => {
};
};
const backgroundConnection = {
navigateBackToBuildQuote: jest.fn(),
setBackgroundSwapRouteState: jest.fn(),
navigatedBackToBuildQuote: jest.fn(),
};
setBackgroundConnection(backgroundConnection);
jest.mock('../../../ducks/swaps/swaps', () => {
const actual = jest.requireActual('../../../ducks/swaps/swaps');
return {
...actual,
setSwapsFromToken: jest.fn(),
navigateBackToBuildQuote: jest.fn(),
};
});
describe('CreateNewSwap', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
@ -24,4 +53,28 @@ describe('CreateNewSwap', () => {
);
expect(getByText('Create a new swap')).toBeInTheDocument();
});
it('clicks on the Make another swap link', async () => {
const setSwapFromTokenMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setSwapsFromToken.mockImplementation(setSwapFromTokenMock);
const navigateBackToBuildQuoteMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
navigateBackToBuildQuote.mockImplementation(navigateBackToBuildQuoteMock);
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps();
const { getByText } = renderWithProvider(
<CreateNewSwap {...props} />,
store,
);
await fireEvent.click(getByText('Create a new swap'));
expect(setSwapFromTokenMock).toHaveBeenCalledTimes(1);
expect(navigateBackToBuildQuoteMock).toHaveBeenCalledTimes(1);
});
});

View File

@ -4,11 +4,13 @@ import configureMockStore from 'redux-mock-store';
import {
renderWithProvider,
createSwapsMockStore,
fireEvent,
} from '../../../../test/jest';
import DropdownInputPair from '.';
const createProps = (customProps = {}) => {
return {
onInputChange: jest.fn(),
...customProps,
};
};
@ -26,4 +28,17 @@ describe('DropdownInputPair', () => {
document.querySelector('.dropdown-input-pair__input'),
).toMatchSnapshot();
});
it('changes the input field', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByPlaceholderText } = renderWithProvider(
<DropdownInputPair {...props} />,
store,
);
fireEvent.change(getByPlaceholderText('0'), {
target: { value: 1.1 },
});
expect(props.onInputChange).toHaveBeenCalledWith('1.1');
});
});

View File

@ -4,6 +4,7 @@ exports[`DropdownSearchList renders the component with initial props 1`] = `
<div>
<div
class="dropdown-search-list"
data-testid="dropdown-search-list"
tabindex="0"
>
<div

View File

@ -91,6 +91,7 @@ export default function DropdownSearchList({
setIsImportTokenModalOpen(true);
};
/* istanbul ignore next */
const onImportTokenClick = () => {
trackEvent({
event: 'Token Imported',
@ -167,6 +168,7 @@ export default function DropdownSearchList({
return (
<div
className={classnames('dropdown-search-list', className)}
data-testid="dropdown-search-list"
onClick={onClickSelector}
onKeyUp={onKeyUp}
tabIndex="0"
@ -216,6 +218,7 @@ export default function DropdownSearchList({
<SearchableItemList
itemsToSearch={loading ? [] : itemsToSearch}
Placeholder={() =>
/* istanbul ignore next */
loading ? (
<div className="dropdown-search-list__loading-item">
<PulseLoader />
@ -286,6 +289,7 @@ export default function DropdownSearchList({
/>
<div
className="dropdown-search-list__close-area"
data-testid="dropdown-search-list__close-area"
onClick={(event) => {
event.stopPropagation();
setIsOpen(false);

View File

@ -4,6 +4,7 @@ import configureMockStore from 'redux-mock-store';
import {
renderWithProvider,
createSwapsMockStore,
fireEvent,
} from '../../../../test/jest';
import DropdownSearchList from '.';
@ -17,6 +18,8 @@ const createProps = (customProps = {}) => {
};
};
jest.mock('../searchable-item-list', () => jest.fn(() => null));
describe('DropdownSearchList', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
@ -28,4 +31,20 @@ describe('DropdownSearchList', () => {
expect(container).toMatchSnapshot();
expect(getByText('symbol')).toBeInTheDocument();
});
it('renders the component, opens the list and closes it', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByTestId } = renderWithProvider(
<DropdownSearchList {...props} />,
store,
);
const dropdownSearchList = getByTestId('dropdown-search-list');
expect(dropdownSearchList).toBeInTheDocument();
fireEvent.click(dropdownSearchList);
const closeButton = getByTestId('dropdown-search-list__close-area');
expect(closeButton).toBeInTheDocument();
fireEvent.click(closeButton);
expect(closeButton).not.toBeInTheDocument();
});
});

View File

@ -10,6 +10,7 @@ exports[`ExchangeRateDisplay renders the component with initial props 1`] = `
</span>
<span
class="exchange-rate-display__bold"
data-testid="exchange-rate-display__base-symbol"
>
ETH
</span>
@ -26,6 +27,7 @@ exports[`ExchangeRateDisplay renders the component with initial props 1`] = `
</span>
<div
class="exchange-rate-display__switch-arrows"
data-testid="exchange-rate-display__switch-arrows"
>
<svg
fill="none"

View File

@ -66,6 +66,7 @@ export default function ExchangeRateDisplay({
<span>1</span>
<span
className={classnames({ 'exchange-rate-display__bold': boldSymbols })}
data-testid="exchange-rate-display__base-symbol"
>
{baseSymbol}
</span>
@ -80,6 +81,7 @@ export default function ExchangeRateDisplay({
className={classnames('exchange-rate-display__switch-arrows', {
'exchange-rate-display__switch-arrows-rotate': rotating,
})}
data-testid="exchange-rate-display__switch-arrows"
onClick={() => {
setShowPrimaryToSecondary(!showPrimaryToSecondary);
setRotating(true);

View File

@ -1,6 +1,6 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import { renderWithProvider, fireEvent } from '../../../../test/jest';
import ExchangeRateDisplay from '.';
const createProps = (customProps = {}) => {
@ -25,4 +25,18 @@ describe('ExchangeRateDisplay', () => {
expect(getByText(props.secondaryTokenSymbol)).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it('clicks on the switch link', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(
<ExchangeRateDisplay {...props} />,
);
expect(getByTestId('exchange-rate-display__base-symbol')).toHaveTextContent(
'ETH',
);
fireEvent.click(getByTestId('exchange-rate-display__switch-arrows'));
expect(getByTestId('exchange-rate-display__base-symbol')).toHaveTextContent(
'BAT',
);
});
});

View File

@ -3,3 +3,32 @@
exports[`FeeCard renders the component with EIP-1559 enabled 1`] = `null`;
exports[`FeeCard renders the component with initial props 1`] = `null`;
exports[`FeeCard renders the component with initial props 2`] = `
<div
class="info-tooltip"
>
<div
class="fee-card__row-label fee-card__info-tooltip-container"
>
<div
aria-describedby="tippy-tooltip-1"
class="info-tooltip__tooltip-container fee-card__info-tooltip-content-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
`;

View File

@ -31,6 +31,7 @@ export default function FeeCard({
}) {
const t = useContext(I18nContext);
/* istanbul ignore next */
const getTranslatedNetworkName = () => {
switch (chainId) {
case CHAIN_IDS.MAINNET:
@ -84,6 +85,7 @@ export default function FeeCard({
<a
className="fee-card__link"
onClick={() => {
/* istanbul ignore next */
trackEvent({
event: 'Clicked "Gas Fees: Learn More" Link',
category: EVENT.CATEGORIES.SWAPS,

View File

@ -8,6 +8,7 @@ import {
createSwapsMockStore,
setBackgroundConnection,
MOCKS,
fireEvent,
} from '../../../../test/jest';
import { CHAIN_IDS } from '../../../../shared/constants/network';
@ -104,6 +105,8 @@ describe('FeeCard', () => {
expect(
document.querySelector('.fee-card__top-bordered-row'),
).toMatchSnapshot();
expect(document.querySelector('.info-tooltip')).toMatchSnapshot();
expect(getByText('Edit limit')).toBeInTheDocument();
});
it('renders the component with EIP-1559 enabled', () => {
@ -145,4 +148,23 @@ describe('FeeCard', () => {
expect(getByText(`: ${props.secondaryFee.maxFee}`)).toBeInTheDocument();
expect(queryByTestId('fee-card__edit-link')).not.toBeInTheDocument();
});
it('renders the component with hidden token approval row', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
hideTokenApprovalRow: true,
});
const { queryByText } = renderWithProvider(<FeeCard {...props} />, store);
expect(queryByText('Edit limit')).not.toBeInTheDocument();
});
it('approves a token', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
onTokenApprovalClick: jest.fn(),
});
const { queryByText } = renderWithProvider(<FeeCard {...props} />, store);
fireEvent.click(queryByText('Edit limit'));
expect(props.onTokenApprovalClick).toHaveBeenCalled();
});
});

View File

@ -77,6 +77,7 @@ exports[`MainQuoteSummary renders the component with initial props 4`] = `
</span>
<span
class=""
data-testid="exchange-rate-display__base-symbol"
>
ETH
</span>
@ -93,6 +94,7 @@ exports[`MainQuoteSummary renders the component with initial props 4`] = `
</span>
<div
class="exchange-rate-display__switch-arrows"
data-testid="exchange-rate-display__switch-arrows"
>
<svg
fill="none"

View File

@ -39,6 +39,7 @@ exports[`SearchableItemList renders the component with initial props 1`] = `
exports[`SearchableItemList renders the component with initial props 2`] = `
<div
class="searchable-item-list__item searchable-item-list__item--selected"
data-testid="searchable-item-list__item"
tabindex="0"
>
<img

View File

@ -0,0 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ItemList renders the component with initial props 1`] = `
<div
class="searchable-item-list__item searchable-item-list__item--selected"
data-testid="searchable-item-list__item"
tabindex="0"
>
<img
alt="primaryLabel"
class="url-icon"
src="iconUrl"
/>
<div
class="searchable-item-list__labels"
>
<div
class="searchable-item-list__item-labels"
>
<span
class="searchable-item-list__primary-label"
>
primaryLabel
</span>
<span
class="searchable-item-list__secondary-label"
>
secondaryLabel
</span>
</div>
<div
class="searchable-item-list__right-labels"
>
<span
class="searchable-item-list__right-primary-label"
>
rightPrimaryLabel
</span>
<span
class="searchable-item-list__right-secondary-label"
>
rightSecondaryLabel
</span>
</div>
</div>
</div>
`;

View File

@ -89,6 +89,7 @@ export default function ItemList({
'searchable-item-list__item--selected': selected,
'searchable-item-list__item--disabled': disabled,
})}
data-testid="searchable-item-list__item"
onClick={onClick}
onKeyUp={(e) => e.key === 'Enter' && onClick()}
key={`searchable-item-list-item-${i}`}
@ -150,6 +151,7 @@ export default function ItemList({
<a
key="searchable-item-list__etherscan-link"
onClick={() => {
/* istanbul ignore next */
trackEvent({
event: 'Clicked Block Explorer Link',
category: EVENT.CATEGORIES.SWAPS,

View File

@ -0,0 +1,82 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import {
renderWithProvider,
createSwapsMockStore,
fireEvent,
} from '../../../../../test/jest';
import ItemList from '.';
const createProps = (customProps = {}) => {
return {
defaultToAll: true,
listTitle: 'listTitle',
onClickItem: jest.fn(),
results: [
{
iconUrl: 'iconUrl',
selected: true,
primaryLabel: 'primaryLabel',
secondaryLabel: 'secondaryLabel',
rightPrimaryLabel: 'rightPrimaryLabel',
rightSecondaryLabel: 'rightSecondaryLabel',
},
],
fuseSearchKeys: [
{
name: 'name',
weight: 0.499,
},
{
name: 'symbol',
weight: 0.499,
},
{
name: 'address',
weight: 0.002,
},
],
...customProps,
};
};
describe('ItemList', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByText } = renderWithProvider(<ItemList {...props} />, store);
expect(getByText(props.listTitle)).toBeInTheDocument();
expect(
document.querySelector('.searchable-item-list__item'),
).toMatchSnapshot();
});
it('clicks on a list item', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByText, getByTestId } = renderWithProvider(
<ItemList {...props} />,
store,
);
expect(getByText(props.listTitle)).toBeInTheDocument();
fireEvent.click(getByTestId('searchable-item-list__item'));
expect(props.onClickItem).toHaveBeenCalledWith(props.results[0]);
});
it('presses the "Enter" key on a list item', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByText, getByTestId } = renderWithProvider(
<ItemList {...props} />,
store,
);
expect(getByText(props.listTitle)).toBeInTheDocument();
fireEvent.keyUp(getByTestId('searchable-item-list__item'), {
key: 'Enter',
code: 'Enter',
charCode: 13,
});
expect(props.onClickItem).toHaveBeenCalledWith(props.results[0]);
});
});

View File

@ -0,0 +1,107 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
renderWithProvider,
createSwapsMockStore,
fireEvent,
} from '../../../../../test/jest';
import ListItemSearch from './list-item-search.component';
const token = {
erc20: true,
symbol: 'BAT',
decimals: 18,
address: '0x0D8775F648430679A709E98d2b0Cb6250d2887EF',
};
jest.mock('../../swaps.util', () => {
const original = jest.requireActual('../../swaps.util');
return {
...original,
fetchToken: jest.fn(() => {
return token;
}),
};
});
const createProps = (customProps = {}) => {
return {
onSearch: jest.fn(),
setSearchQuery: jest.fn(),
listToSearch: [
{
iconUrl: 'iconUrl',
selected: true,
primaryLabel: 'primaryLabel',
secondaryLabel: 'secondaryLabel',
rightPrimaryLabel: 'rightPrimaryLabel',
rightSecondaryLabel: 'rightSecondaryLabel',
},
],
fuseSearchKeys: [
{
name: 'name',
weight: 0.499,
},
{
name: 'symbol',
weight: 0.499,
},
{
name: 'address',
weight: 0.002,
},
],
searchPlaceholderText: 'Search token',
defaultToAll: true,
...customProps,
};
};
const middleware = [thunk];
describe('ListItemSearch', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByTestId } = renderWithProvider(
<ListItemSearch {...props} />,
store,
);
expect(getByTestId('search-list-items')).toBeInTheDocument();
});
it('changes the search query', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps();
const { getByTestId } = renderWithProvider(
<ListItemSearch {...props} />,
store,
);
const input = getByTestId('search-list-items');
fireEvent.change(input, { target: { value: 'USD' } });
expect(props.setSearchQuery).toHaveBeenCalledWith('USD');
expect(props.onSearch).toHaveBeenCalledWith({
searchQuery: 'USD',
results: [],
});
});
it('imports a token', async () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({ shouldSearchForImports: true });
const { getByTestId } = renderWithProvider(
<ListItemSearch {...props} />,
store,
);
const input = getByTestId('search-list-items');
await fireEvent.change(input, { target: { value: token.address } });
expect(props.setSearchQuery).toHaveBeenCalledWith(token.address);
expect(props.onSearch).toHaveBeenCalledWith({
searchQuery: token.address,
results: [token],
});
});
});

View File

@ -0,0 +1,33 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import {
renderWithProvider,
createSwapsMockStore,
} from '../../../../../test/jest';
import quoteDataRows from '../mock-quote-data';
import QuoteDetails from './quote-details';
const createProps = (customProps = {}) => {
return {
...quoteDataRows[0],
slippage: 2,
liquiditySourceKey: 'swapAggregator',
minimumAmountReceived: '2',
feeInEth: '0.0003',
metaMaskFee: 0.0205,
...customProps,
};
};
describe('ListItemSearch', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByText } = renderWithProvider(
<QuoteDetails {...props} />,
store,
);
expect(getByText('Rate')).toBeInTheDocument();
});
});

View File

@ -3,6 +3,7 @@
exports[`SortList renders the component with initial props 1`] = `
<div
class="select-quote-popover__column-header select-quote-popover__receiving"
data-testid="select-quote-popover__receiving"
>
<span
class="select-quote-popover__receiving-symbol"
@ -50,6 +51,7 @@ exports[`SortList renders the component with initial props 1`] = `
exports[`SortList renders the component with initial props 2`] = `
<div
class="select-quote-popover__column-header select-quote-popover__network-fees select-quote-popover__network-fees-header"
data-testid="select-quote-popover__network-fees-header"
>
<span>
Estimated network fees
@ -88,6 +90,7 @@ exports[`SortList renders the component with initial props 2`] = `
exports[`SortList renders the component with initial props 3`] = `
<div
class="select-quote-popover__row select-quote-popover__row--selected"
data-testid="select-quote-popover-row-1"
>
<div
class="select-quote-popover__receiving"
@ -124,6 +127,7 @@ exports[`SortList renders the component with initial props 3`] = `
</div>
<div
class="select-quote-popover__caret-right"
data-testid="select-quote-popover__caret-right-1"
>
<i
class="fa fa-angle-up"

View File

@ -80,6 +80,7 @@ export default function SortList({
<div className="select-quote-popover__column-headers">
<div
className="select-quote-popover__column-header select-quote-popover__receiving"
data-testid="select-quote-popover__receiving"
onClick={() => onColumnHeaderClick('destinationTokenValue')}
>
<span className="select-quote-popover__receiving-symbol">
@ -96,6 +97,7 @@ export default function SortList({
</div>
<div
className="select-quote-popover__column-header select-quote-popover__network-fees select-quote-popover__network-fees-header"
data-testid="select-quote-popover__network-fees-header"
onClick={() => onColumnHeaderClick('rawNetworkFees')}
>
{!hideEstimatedGasFee && (
@ -111,6 +113,7 @@ export default function SortList({
</div>
<div
className="select-quote-popover__column-header select-quote-popover__quote-source"
data-testid="select-quote-popover__quote-source"
onClick={() => onColumnHeaderClick('quoteSource')}
>
{t('swapQuoteSource')}
@ -137,6 +140,7 @@ export default function SortList({
})}
onClick={() => onSelect(aggId)}
key={`select-quote-popover-row-${i}`}
data-testid={`select-quote-popover-row-${i}`}
>
<div className="select-quote-popover__receiving">
<div className="select-quote-popover__receiving-value">
@ -178,6 +182,7 @@ export default function SortList({
</div>
<div
className="select-quote-popover__caret-right"
data-testid={`select-quote-popover__caret-right-${i}`}
onClick={(event) => {
event.stopPropagation();
onCaretClick(aggId);

View File

@ -1,6 +1,6 @@
import React from 'react';
import { renderWithProvider } from '../../../../../test/jest';
import { renderWithProvider, fireEvent } from '../../../../../test/jest';
import SortList from './sort-list';
jest.mock(
@ -86,4 +86,32 @@ describe('SortList', () => {
document.querySelector('.select-quote-popover__row--selected'),
).toMatchSnapshot();
});
it('clicks on the "destinationTokenValue" header', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
fireEvent.click(getByTestId('select-quote-popover__receiving'));
expect(props.setSortColumn).toHaveBeenCalledWith('destinationTokenValue');
});
it('clicks on the "rawNetworkFees" header', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
fireEvent.click(getByTestId('select-quote-popover__network-fees-header'));
expect(props.setSortColumn).toHaveBeenCalledWith('rawNetworkFees');
});
it('clicks on the first aggregator', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
fireEvent.click(getByTestId('select-quote-popover-row-0'));
expect(props.onSelect).toHaveBeenCalledWith('Agg1');
});
it('clicks on a caret for the first aggregator', () => {
const props = createProps();
const { getByTestId } = renderWithProvider(<SortList {...props} />);
fireEvent.click(getByTestId('select-quote-popover__caret-right-0'));
expect(props.onCaretClick).toHaveBeenCalledWith('Agg1');
});
});

View File

@ -47,7 +47,7 @@ exports[`SlippageButtons renders the component with initial props 2`] = `
</div>
`;
exports[`SlippageButtons renders the component with the smart transaction opt-in button available 1`] = `
exports[`SlippageButtons renders the component with the smart transaction opt-in button available, opt into STX 1`] = `
<button
class="slippage-buttons__header slippage-buttons__header--open"
>
@ -62,7 +62,7 @@ exports[`SlippageButtons renders the component with the smart transaction opt-in
</button>
`;
exports[`SlippageButtons renders the component with the smart transaction opt-in button available 2`] = `
exports[`SlippageButtons renders the component with the smart transaction opt-in button available, opt into STX 2`] = `
<div
class="button-group slippage-buttons__button-group radio-button-group"
role="radiogroup"

View File

@ -170,6 +170,7 @@ export default function SlippageButtons({
)}
>
<input
data-testid="slippage-buttons__custom-slippage"
onChange={(event) => {
const { value } = event.target;
const isValueNumeric = !isNaN(Number(value));

View File

@ -1,13 +1,14 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import { renderWithProvider, fireEvent } from '../../../../test/jest';
import { SLIPPAGE } from '../../../../shared/constants/swaps';
import SlippageButtons from '.';
const createProps = (customProps = {}) => {
return {
onSelect: jest.fn(),
maxAllowedSlippage: 15,
currentSlippage: 3,
currentSlippage: SLIPPAGE.HIGH,
smartTransactionsEnabled: false,
...customProps,
};
@ -15,7 +16,7 @@ const createProps = (customProps = {}) => {
describe('SlippageButtons', () => {
it('renders the component with initial props', () => {
const { getByText, queryByText } = renderWithProvider(
const { getByText, queryByText, getByTestId } = renderWithProvider(
<SlippageButtons {...createProps()} />,
);
expect(getByText('2%')).toBeInTheDocument();
@ -29,11 +30,21 @@ describe('SlippageButtons', () => {
document.querySelector('.slippage-buttons__button-group'),
).toMatchSnapshot();
expect(queryByText('Smart transaction')).not.toBeInTheDocument();
expect(getByTestId('button-group__button1')).toHaveAttribute(
'aria-checked',
'true',
);
});
it('renders the component with the smart transaction opt-in button available', () => {
it('renders the component with the smart transaction opt-in button available, opt into STX', () => {
const setSmartTransactionsOptInStatus = jest.fn();
const { getByText } = renderWithProvider(
<SlippageButtons {...createProps({ smartTransactionsEnabled: true })} />,
<SlippageButtons
{...createProps({
smartTransactionsEnabled: true,
setSmartTransactionsOptInStatus,
})}
/>,
);
expect(getByText('2%')).toBeInTheDocument();
expect(getByText('3%')).toBeInTheDocument();
@ -46,5 +57,69 @@ describe('SlippageButtons', () => {
document.querySelector('.slippage-buttons__button-group'),
).toMatchSnapshot();
expect(getByText('Smart transaction')).toBeInTheDocument();
expect(document.querySelector('.toggle-button--off')).toBeInTheDocument();
fireEvent.click(document.querySelector('.toggle-button'));
expect(setSmartTransactionsOptInStatus).toHaveBeenCalledWith(true, false);
});
it('renders slippage with a custom value', () => {
const { getByText } = renderWithProvider(
<SlippageButtons {...createProps({ currentSlippage: 2.5 })} />,
);
expect(getByText('2.5')).toBeInTheDocument();
});
it('renders the default slippage with Advanced options hidden', () => {
const { getByText, queryByText } = renderWithProvider(
<SlippageButtons
{...createProps({ currentSlippage: SLIPPAGE.DEFAULT })}
/>,
);
expect(getByText('Advanced options')).toBeInTheDocument();
expect(document.querySelector('.fa-angle-down')).toBeInTheDocument();
expect(queryByText('2%')).not.toBeInTheDocument();
});
it('opens the Advanced options section and sets a default slippage', () => {
const { getByText, getByTestId } = renderWithProvider(
<SlippageButtons
{...createProps({ currentSlippage: SLIPPAGE.DEFAULT })}
/>,
);
fireEvent.click(getByText('Advanced options'));
fireEvent.click(getByTestId('button-group__button0'));
expect(getByTestId('button-group__button0')).toHaveAttribute(
'aria-checked',
'true',
);
});
it('opens the Advanced options section and sets a high slippage', () => {
const { getByText, getByTestId } = renderWithProvider(
<SlippageButtons
{...createProps({ currentSlippage: SLIPPAGE.DEFAULT })}
/>,
);
fireEvent.click(getByText('Advanced options'));
fireEvent.click(getByTestId('button-group__button1'));
expect(getByTestId('button-group__button1')).toHaveAttribute(
'aria-checked',
'true',
);
});
it('sets a custom slippage value', () => {
const { getByTestId } = renderWithProvider(
<SlippageButtons {...createProps()} />,
);
fireEvent.click(getByTestId('button-group__button2'));
expect(getByTestId('button-group__button2')).toHaveAttribute(
'aria-checked',
'true',
);
const input = getByTestId('slippage-buttons__custom-slippage');
fireEvent.change(input, { target: { value: 5 } });
fireEvent.click(document);
expect(input).toHaveAttribute('value', '5');
});
});

View File

@ -6,7 +6,9 @@ import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
fireEvent,
} from '../../../../test/jest';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import SmartTransactionStatus from '.';
const middleware = [thunk];
@ -15,6 +17,28 @@ setBackgroundConnection({
setBackgroundSwapRouteState: jest.fn(),
});
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
return {
...original,
useHistory: () => ({
push: jest.fn(),
}),
};
});
jest.mock('../../../ducks/swaps/swaps', () => {
const original = jest.requireActual('../../../ducks/swaps/swaps');
return {
...original,
prepareToLeaveSwaps: jest.fn(() => {
return {
type: 'MOCK_TYPE',
};
}),
};
});
describe('SmartTransactionStatus', () => {
it('renders the component with initial props', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
@ -22,4 +46,109 @@ describe('SmartTransactionStatus', () => {
expect(getByText('Publicly submitting your Swap...')).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('renders the "success" STX status', () => {
const mockStore = createSwapsMockStore();
const latestSmartTransaction =
mockStore.metamask.smartTransactionsState.smartTransactions[
CHAIN_IDS.MAINNET
][1];
latestSmartTransaction.status = 'success';
const store = configureMockStore(middleware)(mockStore);
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Swap complete!')).toBeInTheDocument();
expect(getByText('Your USDC is now available.')).toBeInTheDocument();
expect(getByText('Create a new swap')).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('renders the "reverted" STX status', () => {
const mockStore = createSwapsMockStore();
const latestSmartTransaction =
mockStore.metamask.smartTransactionsState.smartTransactions[
CHAIN_IDS.MAINNET
][1];
latestSmartTransaction.status = 'reverted';
const store = configureMockStore(middleware)(mockStore);
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Swap failed')).toBeInTheDocument();
expect(getByText('customer support')).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('renders the "cancelled_user_cancelled" STX status', () => {
const mockStore = createSwapsMockStore();
const latestSmartTransaction =
mockStore.metamask.smartTransactionsState.smartTransactions[
CHAIN_IDS.MAINNET
][1];
latestSmartTransaction.status = 'cancelled_user_cancelled';
const store = configureMockStore(middleware)(mockStore);
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Swap cancelled')).toBeInTheDocument();
expect(
getByText(
'Your transaction has been cancelled and you did not pay any unnecessary gas fees.',
),
).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('renders the "deadline_missed" STX status', () => {
const mockStore = createSwapsMockStore();
const latestSmartTransaction =
mockStore.metamask.smartTransactionsState.smartTransactions[
CHAIN_IDS.MAINNET
][1];
latestSmartTransaction.status = 'deadline_missed';
const store = configureMockStore(middleware)(mockStore);
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Swap would have failed')).toBeInTheDocument();
expect(
getByText(
'Your transaction would have failed and was cancelled to protect you from paying unnecessary gas fees.',
),
).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('renders the "unknown" STX status', () => {
const mockStore = createSwapsMockStore();
const latestSmartTransaction =
mockStore.metamask.smartTransactionsState.smartTransactions[
CHAIN_IDS.MAINNET
][1];
latestSmartTransaction.status = 'unknown';
const store = configureMockStore(middleware)(mockStore);
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Status unknown')).toBeInTheDocument();
expect(
getByText(
'A transaction has been successful but were unsure what it is. This may be due to submitting another transaction while this swap was processing.',
),
).toBeInTheDocument();
expect(getByText('Close')).toBeInTheDocument();
});
it('cancels a transaction', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Publicly submitting your Swap...')).toBeInTheDocument();
const cancelLink = getByText('Cancel swap for ~0');
expect(cancelLink).toBeInTheDocument();
fireEvent.click(cancelLink);
expect(
getByText('Trying to cancel your transaction...'),
).toBeInTheDocument();
expect(cancelLink).not.toBeInTheDocument();
});
it('clicks on the Close button', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store);
expect(getByText('Publicly submitting your Swap...')).toBeInTheDocument();
const closeButton = getByText('Close');
expect(closeButton).toBeInTheDocument();
fireEvent.click(closeButton);
});
});

View File

@ -1,6 +1,6 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import { renderWithProvider, fireEvent } from '../../../../test/jest';
import SwapsFooter from '.';
const createProps = (customProps = {}) => {
@ -25,4 +25,15 @@ describe('SwapsFooter', () => {
expect(getByText('Terms of service')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it('clicks on a block explorer link', () => {
global.platform = { openTab: jest.fn() };
const props = createProps();
const { getByText } = renderWithProvider(<SwapsFooter {...props} />);
expect(getByText(props.submitText)).toBeInTheDocument();
fireEvent.click(getByText('Terms of service'));
expect(global.platform.openTab).toHaveBeenCalledWith({
url: 'https://metamask.io/terms.html',
});
});
});

View File

@ -34,6 +34,8 @@ import {
getSwapsLivenessForNetwork,
countDecimals,
showRemainingTimeInMinAndSec,
getFeeForSmartTransaction,
formatSwapsValueForDisplay,
} from './swaps.util';
jest.mock('../../../shared/lib/storage-helpers', () => ({
@ -534,6 +536,10 @@ describe('Swaps Util', () => {
shouldEnableDirectWrapping(CHAIN_IDS.MAINNET, WETH_CONTRACT_ADDRESS),
).toBe(false);
});
it('returns false if source and destination tokens are undefined', () => {
expect(shouldEnableDirectWrapping(CHAIN_IDS.MAINNET)).toBe(false);
});
});
describe('showRemainingTimeInMinAndSec', () => {
@ -551,9 +557,48 @@ describe('Swaps Util', () => {
});
describe('getFeeForSmartTransaction', () => {
it('returns estimated for for STX', () => {
// TODO: Implement tests for this function.
expect(true).toBe(true);
it('returns estimated fee for STX', () => {
const expected = {
feeInUsd: '0.02',
feeInFiat: '$0.02',
feeInEth: '0.00323 ETH',
rawEthFee: '0.00323',
};
const actual = getFeeForSmartTransaction({
chainId: CHAIN_IDS.MAINNET,
currentCurrency: 'usd',
conversionRate: 5,
USDConversionRate: 5,
nativeCurrencySymbol: 'ETH',
feeInWeiDec: 3225623412028924,
});
expect(actual).toMatchObject(expected);
});
it('returns estimated fee for STX for JPY currency', () => {
const expected = {
feeInUsd: '0.02',
feeInFiat: '£0.02',
feeInEth: '0.00323 ETH',
rawEthFee: '0.00323',
};
const actual = getFeeForSmartTransaction({
chainId: CHAIN_IDS.MAINNET,
currentCurrency: 'gbp',
conversionRate: 5,
USDConversionRate: 5,
nativeCurrencySymbol: 'ETH',
feeInWeiDec: 3225623412028924,
});
expect(actual).toMatchObject(expected);
});
});
describe('formatSwapsValueForDisplay', () => {
it('gets swaps value for display', () => {
expect(formatSwapsValueForDisplay('39.6493201125465000000')).toBe(
'39.6493201125',
);
});
});

View File

@ -1,6 +1,6 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import { renderWithProvider, fireEvent } from '../../../../test/jest';
import ViewOnBlockExplorer from '.';
const createProps = (customProps = {}) => {
@ -20,4 +20,17 @@ describe('ViewOnBlockExplorer', () => {
expect(getByText('View Swap at etherscan.io')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it('clicks on the block explorer link', () => {
global.platform = { openTab: jest.fn() };
const { getByText } = renderWithProvider(
<ViewOnBlockExplorer {...createProps()} />,
);
const link = getByText('View Swap at etherscan.io');
expect(link).toBeInTheDocument();
fireEvent.click(link);
expect(global.platform.openTab).toHaveBeenCalledWith({
url: 'https://etherscan.io',
});
});
});

View File

@ -38,6 +38,7 @@ exports[`ViewQuote renders the component with EIP-1559 enabled 2`] = `
</span>
<span
class=""
data-testid="exchange-rate-display__base-symbol"
>
DAI
</span>
@ -54,6 +55,7 @@ exports[`ViewQuote renders the component with EIP-1559 enabled 2`] = `
</span>
<div
class="exchange-rate-display__switch-arrows"
data-testid="exchange-rate-display__switch-arrows"
>
<svg
fill="none"
@ -110,6 +112,7 @@ exports[`ViewQuote renders the component with initial props 2`] = `
</span>
<span
class=""
data-testid="exchange-rate-display__base-symbol"
>
DAI
</span>
@ -126,6 +129,7 @@ exports[`ViewQuote renders the component with initial props 2`] = `
</span>
<div
class="exchange-rate-display__switch-arrows"
data-testid="exchange-rate-display__switch-arrows"
>
<svg
fill="none"

View File

@ -659,6 +659,7 @@ export default function ViewQuote() {
const metaMaskFee = usedQuote.fee;
/* istanbul ignore next */
const onFeeCardTokenApprovalClick = () => {
trackEditSpendLimitOpened();
dispatch(
@ -891,19 +892,22 @@ export default function ViewQuote() {
'view-quote__content_modal': disableSubmissionDueToPriceWarning,
})}
>
{selectQuotePopoverShown && (
<SelectQuotePopover
quoteDataRows={renderablePopoverData}
onClose={() => setSelectQuotePopoverShown(false)}
onSubmit={(aggId) => dispatch(swapsQuoteSelected(aggId))}
swapToSymbol={destinationTokenSymbol}
initialAggId={usedQuote.aggregator}
onQuoteDetailsIsOpened={trackQuoteDetailsOpened}
hideEstimatedGasFee={
smartTransactionsEnabled && smartTransactionsOptInStatus
}
/>
)}
{
/* istanbul ignore next */
selectQuotePopoverShown && (
<SelectQuotePopover
quoteDataRows={renderablePopoverData}
onClose={() => setSelectQuotePopoverShown(false)}
onSubmit={(aggId) => dispatch(swapsQuoteSelected(aggId))}
swapToSymbol={destinationTokenSymbol}
initialAggId={usedQuote.aggregator}
onQuoteDetailsIsOpened={trackQuoteDetailsOpened}
hideEstimatedGasFee={
smartTransactionsEnabled && smartTransactionsOptInStatus
}
/>
)
}
<div
className={classnames('view-quote__warning-wrapper', {
@ -914,7 +918,10 @@ export default function ViewQuote() {
{(showInsufficientWarning || tokenBalanceUnavailable) && (
<ActionableMessage
message={actionableBalanceErrorMessage}
onClose={() => setWarningHidden(true)}
onClose={
/* istanbul ignore next */
() => setWarningHidden(true)
}
/>
)}
</div>
@ -971,10 +978,13 @@ export default function ViewQuote() {
onTokenApprovalClick={onFeeCardTokenApprovalClick}
metaMaskFee={String(metaMaskFee)}
numberOfQuotes={Object.values(quotes).length}
onQuotesClick={() => {
trackAllAvailableQuotesOpened();
setSelectQuotePopoverShown(true);
}}
onQuotesClick={
/* istanbul ignore next */
() => {
trackAllAvailableQuotesOpened();
setSelectQuotePopoverShown(true);
}
}
chainId={chainId}
isBestQuote={isBestQuote}
maxPriorityFeePerGasDecGWEI={hexWEIToDecGWEI(
@ -986,37 +996,39 @@ export default function ViewQuote() {
)}
</div>
<SwapsFooter
onSubmit={() => {
setSubmitClicked(true);
if (!balanceError) {
if (
currentSmartTransactionsEnabled &&
smartTransactionsOptInStatus &&
smartTransactionFees?.tradeTxFees
) {
dispatch(
signAndSendSwapsSmartTransaction({
unsignedTransaction,
trackEvent,
history,
additionalTrackingParams,
}),
);
onSubmit={
/* istanbul ignore next */ () => {
setSubmitClicked(true);
if (!balanceError) {
if (
currentSmartTransactionsEnabled &&
smartTransactionsOptInStatus &&
smartTransactionFees?.tradeTxFees
) {
dispatch(
signAndSendSwapsSmartTransaction({
unsignedTransaction,
trackEvent,
history,
additionalTrackingParams,
}),
);
} else {
dispatch(
signAndSendTransactions(
history,
trackEvent,
additionalTrackingParams,
),
);
}
} else if (destinationToken.symbol === defaultSwapsToken.symbol) {
history.push(DEFAULT_ROUTE);
} else {
dispatch(
signAndSendTransactions(
history,
trackEvent,
additionalTrackingParams,
),
);
history.push(`${ASSET_ROUTE}/${destinationToken.address}`);
}
} else if (destinationToken.symbol === defaultSwapsToken.symbol) {
history.push(DEFAULT_ROUTE);
} else {
history.push(`${ASSET_ROUTE}/${destinationToken.address}`);
}
}}
}
submitText={
currentSmartTransactionsEnabled &&
smartTransactionsOptInStatus &&