Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(trading): minor improvements #17097

Open
wants to merge 3 commits into
base: refactor/trading-reducer
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/suite/src/support/extraDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,12 @@ export const extraDependencies: ExtraDependencies = {
selectRouterApp: (state: AppState) => state.router.app,
selectRoute: (state: AppState) => state.router.route,
selectAddressDisplayType: (state: AppState) => state.suite.settings.addressDisplayType,
selectSelectedAccount: (state: AppState) => state.wallet.selectedAccount,
selectSelectedAccountStatus: (state: AppState) => state.wallet.selectedAccount.status,
selectSuiteSettings,
selectIsWindowVisible,
selectTradingEnvironment: (state: AppState) =>
state.suite.settings.debug.invityServerEnvironment,
},
actions: {
setAccountAddMetadata: metadataActions.setAccountAdd,
Expand Down
4 changes: 4 additions & 0 deletions suite-common/redux-utils/src/extraDependenciesType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,14 @@ export type ExtraDependencies = {
selectMetadata: SuiteCompatibleSelector<any>;
selectDeviceDiscovery: SuiteCompatibleSelector<Discovery | undefined>;
selectAddressDisplayType: SuiteCompatibleSelector<AddressDisplayOptions>;
selectSelectedAccount: SuiteCompatibleSelector<SelectedAccountStatus>;
selectSelectedAccountStatus: SuiteCompatibleSelector<SelectedAccountStatus['status']>;
selectSuiteSettings: SuiteCompatibleSelector<{
defaultWalletLoading: WalletType;
}>;
selectTradingEnvironment: SuiteCompatibleSelector<
'production' | 'staging' | 'dev' | 'localhost' | undefined
>;
};
// You should only use ActionCreatorWithPayload from redux-toolkit!
// That means you will need to convert actual action creators in packages/suite to use createAction from redux-toolkit,
Expand Down
7 changes: 6 additions & 1 deletion suite-common/test-utils/src/extraDependenciesMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createAction } from '@reduxjs/toolkit';
import { ExtraDependencies, createThunk } from '@suite-common/redux-utils';
import { BITCOIN_ONLY_SYMBOLS } from '@suite-common/suite-constants';
import { Route } from '@suite-common/suite-types';
import { AddressDisplayOptions } from '@suite-common/wallet-types';
import { AddressDisplayOptions, SelectedAccountLoaded } from '@suite-common/wallet-types';
import { PROTO } from '@trezor/connect';

import { testMocks } from './mocks';
Expand Down Expand Up @@ -98,11 +98,16 @@ export const extraDependenciesMock: ExtraDependencies = {
'selectAddressDisplayType',
AddressDisplayOptions.CHUNKED,
),
selectSelectedAccount: mockSelector('selectSelectedAccount', {
status: 'loaded',
account: testMocks.getWalletAccount(),
} as SelectedAccountLoaded),
selectSelectedAccountStatus: mockSelector('selectSelectedAccountStatus', 'loaded'),
selectSuiteSettings: mockSelector('selectSuiteSettings', {
defaultWalletLoading: 'standard',
}),
selectIsWindowVisible: mockSelector('selectIsWindowVisible', true),
selectTradingEnvironment: mockSelector('selectTradingEnvironment', 'localhost'),
},
actions: {
setAccountAddMetadata: mockAction('setAccountAddMetadata'),
Expand Down
40 changes: 29 additions & 11 deletions suite-common/trading/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import {
testnetToProdCryptoId,
} from '../utils';

describe('testing trading utils', () => {
it('getUnusedAddressFromAccount', () => {
describe('getUnusedAddressFromAccount', () => {
it('should return unused value from the passed account', () => {
expect(getUnusedAddressFromAccount(accountBtc as Account)).toStrictEqual({
address: '177BUDVZqTTzK1Fogqcrfbb5ketHEUDGSJ',
path: "m/44'/0'/3'/0/0",
Expand All @@ -33,15 +33,19 @@ describe('testing trading utils', () => {
path: "m/44'/60'/0'/0/1",
});
});
});

it('mapTestnetCryptoCurrency', () => {
describe('mapTestnetCryptoCurrency', () => {
it('should transform testnet network symbol to mainnet', () => {
expect(mapTestnetSymbol('btc')).toStrictEqual('btc');
expect(mapTestnetSymbol('eth')).toStrictEqual('eth');
expect(mapTestnetSymbol('test')).toStrictEqual('btc');
expect(mapTestnetSymbol('txrp')).toStrictEqual('xrp');
});
});

it('getTagAndInfoNote', () => {
describe('getTagAndInfoNote', () => {
it('should return tag and info not from passed data', () => {
expect(getTagAndInfoNote({})).toStrictEqual({ infoNote: '', tag: '' });
expect(getTagAndInfoNote({ infoNote: '' })).toStrictEqual({ infoNote: '', tag: '' });
expect(getTagAndInfoNote({ infoNote: 'Foo' })).toStrictEqual({ infoNote: 'Foo', tag: '' });
Expand Down Expand Up @@ -71,8 +75,10 @@ describe('testing trading utils', () => {
tag: 'Foo',
});
});
});

it('filterQuotesAccordingTags', () => {
describe('filterQuotesAccordingTags', () => {
it('should filter quotes', () => {
const quotes = [
...BUY_FIXTURE.MIN_MAX_QUOTES_OK,
...BUY_FIXTURE.ALTERNATIVE_QUOTES,
Expand All @@ -84,8 +90,10 @@ describe('testing trading utils', () => {
quotes.filter(q => !q.tags || !q.tags.includes('alternativeCurrency')).length,
);
});
});

it('addIdsToQuotes', () => {
describe('addIdsToQuotes', () => {
it('should add id to passed quotes according section', () => {
const quotes = [...BUY_FIXTURE.MIN_MAX_QUOTES_OK];
const quotesExchange = [...EXCHANGE_FIXTURE.MIN_MAX_QUOTES_OK];

Expand All @@ -97,8 +105,10 @@ describe('testing trading utils', () => {
quotesExchange.filter(q => q.orderId).length,
);
});
});

it('testnetToProdCryptoId', () => {
describe('testnetToProdCryptoId', () => {
it('should convert testnet CryptoId to mainnet CryptoId', () => {
expect(testnetToProdCryptoId('test-bitcoin' as CryptoId)).toEqual('bitcoin');
expect(testnetToProdCryptoId('bitcoin' as CryptoId)).toEqual('bitcoin');

Expand All @@ -116,8 +126,10 @@ describe('testing trading utils', () => {
),
).toEqual('ethereum--0x1234123412341234123412341234123412341236');
});
});

it('isCryptoIdForNativeToken - test if token is L2 native token', () => {
describe('isCryptoIdForNativeToken', () => {
it('should test if token is L2 native token', () => {
expect(isCryptoIdForNativeToken('ethereum' as CryptoId)).toEqual(false);
expect(
isCryptoIdForNativeToken(
Expand All @@ -135,8 +147,10 @@ describe('testing trading utils', () => {
),
).toEqual(true);
});
});

it('testing getTradingPaymentMethods', () => {
describe('getTradingPaymentMethods', () => {
it('should get payment methods from quotes', () => {
const paymentMethods = getTradingPaymentMethods([
...BUY_FIXTURE.MIN_MAX_QUOTES_OK,
BUY_FIXTURE.MIN_MAX_QUOTES_OK[1], // duplicate applePay
Expand All @@ -150,8 +164,10 @@ describe('testing trading utils', () => {
expect(paymentMethods.length).toBe(2);
expect(findApplePay).toBeDefined();
});
});

it('testing getTradingQuotesByPaymentMethod', () => {
describe('getTradingQuotesByPaymentMethod', () => {
it('should select quotes according to payment method', () => {
const quotes = getTradingQuotesByPaymentMethod(BUY_FIXTURE.MIN_MAX_QUOTES_OK, 'applePay');

const allQuotesApplePay = quotes?.find(quote => quote.paymentMethod === 'applePay');
Expand All @@ -162,8 +178,10 @@ describe('testing trading utils', () => {

expect(quotesUndefined).toBeUndefined();
});
});

it('testing getTradingNetworkDecimals', () => {
describe('getTradingNetworkDecimals', () => {
it('should select network decimals according to network or select', () => {
const network = getNetwork('base');
const decimals = getTradingNetworkDecimals({
network,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { combineReducers } from '@reduxjs/toolkit';
import { combineReducers, createReducer } from '@reduxjs/toolkit';

import { createReducerWithExtraDeps } from '@suite-common/redux-utils';
import { configureMockStore, extraDependenciesMock } from '@suite-common/test-utils';
import { Account } from '@suite-common/wallet-types';

Expand All @@ -15,7 +14,7 @@ import {
} from '../../reducers/tradingReducer';
import { regional } from '../../regional';
import { buyThunks } from '../../thunks/buy';
import { tradingMiddleware } from '../tradingMiddleware';
import { prepareTradingMiddleware } from '../tradingMiddleware';

jest.mock('../../invityAPI');
invityAPI.setInvityServersEnvironment = () => {};
Expand All @@ -27,15 +26,15 @@ type SelectedAccountStatus = {
account: Account;
};
type SelectedAccountState = SelectedAccountStatus;
const mockedSelectedAccountReducer = createReducerWithExtraDeps<SelectedAccountState>(
const mockedSelectedAccountReducer = createReducer<SelectedAccountState>(
{
status: 'none',
account: accountBtc as Account,
},
() => {},
);

const mockedSuiteReducer = createReducerWithExtraDeps(
const mockedSuiteReducer = createReducer(
{
settings: {
debug: {
Expand All @@ -46,16 +45,24 @@ const mockedSuiteReducer = createReducerWithExtraDeps(
() => {},
);

const tradingMiddleware = prepareTradingMiddleware({
...extraDependenciesMock,
selectors: {
...extraDependenciesMock.selectors,
selectSelectedAccount: () => ({ status: 'loaded', account: accountBtc }) as any,
},
});

const initStore = (localInitialState?: Partial<TradingState>) =>
configureMockStore({
middleware: [tradingMiddleware],
extra: {},
reducer: combineReducers({
wallet: combineReducers({
trading: tradingReducer,
selectedAccount: mockedSelectedAccountReducer(extraDependenciesMock),
selectedAccount: mockedSelectedAccountReducer,
}),
suite: mockedSuiteReducer(extraDependenciesMock),
suite: mockedSuiteReducer,
}),
preloadedState: {
wallet: {
Expand Down Expand Up @@ -139,20 +146,20 @@ const testUpdatedInfoData = async (type: 'outdated' | 'account-changed') => {
expect(setInvityServersEnvironmentMock).toHaveBeenCalledTimes(1);
};

describe('testing trading middleware', () => {
describe('tradingMiddleware', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('loadData - account changed and loading all necessary data', async () => {
it('should update when account is changed', async () => {
await testUpdatedInfoData('account-changed');
});

it('loadData - outdated data and loading all necessary data', async () => {
it('should update when data are outdated data ', async () => {
await testUpdatedInfoData('outdated');
});

it('loadData - keep current data without updating', async () => {
it('should keep same version of data without update', async () => {
invityAPI.getCurrentAccountDescriptor = () => accountBtc.descriptor;

const getCurrentAccountDescriptorMock = jest.spyOn(
Expand Down
75 changes: 40 additions & 35 deletions suite-common/trading/src/middlewares/tradingMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMiddleware } from '@suite-common/redux-utils';
import { ExtraDependencies, createMiddlewareWithExtraDeps } from '@suite-common/redux-utils';

import { INVITY_API_RELOAD_DATA_AFTER_MS } from '../constants';
import { invityAPI } from '../invityAPI';
Expand All @@ -9,56 +9,61 @@ import {
selectTradingBuy,
selectTradingInfo,
selectTradingLoadingAndTimestamp,
selectTradingSelectedAccount,
selectTradingSettingEnvironment,
} from '../selectors/tradingSelectors';
import { buyThunks } from '../thunks/buy';

export const getAccountAccordingToSection = (state: TradingRootState) => {
const { account } = selectTradingSelectedAccount(state);
export const getAccountAccordingToSection = (state: TradingRootState, extra: ExtraDependencies) => {
const { account } = extra.selectors.selectSelectedAccount(state);

return account;
};

export const tradingMiddleware = createMiddleware(async (action, { dispatch, next, getState }) => {
const { isLoading, lastLoadedTimestamp } = selectTradingLoadingAndTimestamp(getState());
export const prepareTradingMiddleware = createMiddlewareWithExtraDeps(
async (action, { dispatch, next, getState, extra }) => {
const { isLoading, lastLoadedTimestamp } = selectTradingLoadingAndTimestamp(getState());

if (action.type === tradingActions.loadInvityData.type) {
const account = getAccountAccordingToSection(getState());
const { platforms, coins } = selectTradingInfo(getState());
const { buyInfo } = selectTradingBuy(getState());
if (action.type === tradingActions.loadInvityData.type) {
const account = getAccountAccordingToSection(getState(), extra);
const { platforms, coins } = selectTradingInfo(getState());
const { buyInfo } = selectTradingBuy(getState());

const currentAccountDescriptor = invityAPI.getCurrentAccountDescriptor();
const isDifferentAccount = currentAccountDescriptor !== account?.descriptor;
const areDataOutdated = lastLoadedTimestamp + INVITY_API_RELOAD_DATA_AFTER_MS < Date.now();
const currentAccountDescriptor = invityAPI.getCurrentAccountDescriptor();
const isDifferentAccount = currentAccountDescriptor !== account?.descriptor;
const areDataOutdated =
lastLoadedTimestamp + INVITY_API_RELOAD_DATA_AFTER_MS < Date.now();

if (account && !isLoading && (isDifferentAccount || areDataOutdated)) {
dispatch(tradingActions.setLoading({ isLoading: true }));
if (account && !isLoading && (isDifferentAccount || areDataOutdated)) {
dispatch(tradingActions.setLoading({ isLoading: true }));

const invityServerEnvironment = selectTradingSettingEnvironment(getState());
if (invityServerEnvironment) {
invityAPI.setInvityServersEnvironment(invityServerEnvironment);
}
const invityServerEnvironment =
extra.selectors.selectTradingEnvironment(getState());
if (invityServerEnvironment) {
invityAPI.setInvityServersEnvironment(invityServerEnvironment);
}

invityAPI.createInvityAPIKey(account.descriptor);
invityAPI.createInvityAPIKey(account.descriptor);

if (isDifferentAccount || !platforms || !coins) {
const info = await invityAPI.getInfo();
if (isDifferentAccount || !platforms || !coins) {
const info = await invityAPI.getInfo();

dispatch(tradingActions.saveInfo(info));
}
dispatch(tradingActions.saveInfo(info));
}

if (isDifferentAccount || !buyInfo) {
const buyInfoData = await dispatch(buyThunks.loadInfoThunk()).unwrap();
if (isDifferentAccount || !buyInfo) {
const buyInfoData = await dispatch(buyThunks.loadInfoThunk()).unwrap();

dispatch(tradingBuyActions.saveBuyInfo(buyInfoData));
}
dispatch(tradingBuyActions.saveBuyInfo(buyInfoData));
}

dispatch(
tradingActions.setLoading({ isLoading: false, lastLoadedTimestamp: Date.now() }),
);
dispatch(
tradingActions.setLoading({
isLoading: false,
lastLoadedTimestamp: Date.now(),
}),
);
}
}
}

return next(action);
});
return next(action);
},
);
Loading
Loading