Skip to content

Commit 43d85eb

Browse files
committed
feat: implement new asset picker for swap
1 parent 6af545e commit 43d85eb

File tree

126 files changed

+2291
-1480
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+2291
-1480
lines changed

packages/product-components/src/components/AssetLogo/AssetLogo.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,8 @@ export const AssetLogo = ({
136136
shouldTryToFetch = true,
137137
placeholder,
138138
placeholderWithTooltip = true,
139-
<<<<<<< HEAD
140139
showNetworkIcon = false,
141-
=======
142-
showNetworkIcon = shouldShowNetworkIcon(symbol, contractAddress),
143-
>>>>>>> d9b6ecb0cb (feat: add TopAssets & NetworkIcon components)
144140
'data-testid': dataTest,
145-
className,
146141
...rest
147142
}: AssetLogoProps) => {
148143
const contractAddressArray = useMemo(
@@ -251,7 +246,7 @@ export const AssetLogo = ({
251246
};
252247

253248
return (
254-
<Container $size={size} {...frameProps} className={className}>
249+
<Container $size={size} {...frameProps}>
255250
{showPlaceholder && (
256251
<AssetInitials size={size} withTooltip={placeholderWithTooltip}>
257252
{placeholder}
@@ -270,14 +265,15 @@ export const AssetLogo = ({
270265
onLoad={handleOnLoad}
271266
onError={handleLoadError}
272267
/>
273-
{showNetworkIcon && (
274-
<StyledNetworkIcon
275-
networkSymbol={symbol as NetworkSymbol | LegacyNetworkSymbol}
276-
size={size * 0.375}
277-
/>
278-
)}
279268
</ElevationUp>
280269
)}
270+
271+
{showNetworkIcon && (
272+
<StyledNetworkIcon
273+
networkSymbol={symbol as NetworkSymbol | LegacyNetworkSymbol}
274+
size={size * 0.375}
275+
/>
276+
)}
281277
</Container>
282278
);
283279
};

packages/product-components/src/components/SelectAssetModal/SearchAsset.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,14 @@ export const SearchAsset = ({
7171
value={selectedOption}
7272
onChange={option => selectConfig.onChange(option.value)}
7373
size="small"
74-
formatOptionLabel={option => (
75-
<Option>
74+
formatOptionLabel={(option, meta) => (
75+
<Option
76+
data-testid={
77+
meta.context === 'menu'
78+
? `${dataTestId}/select-option/${option.value ?? 'all-networks'}`
79+
: `${dataTestId}/select-option-value/${option.value ?? 'all-networks'}`
80+
}
81+
>
7682
{option.value && (
7783
<CoinLogo size={20} symbol={option.value} type="network" />
7884
)}
@@ -81,7 +87,9 @@ export const SearchAsset = ({
8187
)}
8288
width="auto"
8389
minValueWidth="170px"
90+
data-testid={`${dataTestId}/select`}
8491
isRenderedInModal
92+
openMenuOnFocus={false}
8593
/>
8694
</SelectWrapper>
8795
) : undefined;

packages/product-components/src/components/TopAssets/TopAssets.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ import styled from 'styled-components';
33
import { Text } from '@trezor/components';
44
import { borders, mapElevationToBorder, spacings, spacingsPx } from '@trezor/theme';
55

6-
import { AssetLogo, AssetLogoProps } from '../AssetLogo/AssetLogo';
6+
import { AssetLogo, AssetLogoProps, shouldShowNetworkIcon } from '../AssetLogo/AssetLogo';
77
import { CoinLogo } from '../CoinLogo/CoinLogo';
88

99
const Container = styled.div<{ $itemsCount: number }>`
1010
display: grid;
1111
grid-template-columns: repeat(${({ $itemsCount }) => $itemsCount}, 1fr);
1212
border: 1px solid ${({ theme }) => mapElevationToBorder({ $elevation: 1, theme })};
13-
border-radius: ${borders.radii.sm};
13+
border-radius: ${borders.radii.md};
1414
align-items: center;
1515
width: 100%;
16+
overflow: hidden;
17+
position: relative;
1618
`;
1719

1820
const Item = styled('button')<{ $isLast: boolean }>`
@@ -30,6 +32,11 @@ const Item = styled('button')<{ $isLast: boolean }>`
3032
gap: ${spacingsPx.xxs};
3133
flex-direction: column;
3234
padding: ${spacings.xs * 1.25}px ${spacings.xs * 1.5}px ${spacings.xs * 0.75}px;
35+
transition: background-color 150ms ease-in-out;
36+
37+
&:hover {
38+
background-color: ${({ theme }) => theme.backgroundTertiaryPressedOnElevation1};
39+
}
3340
`;
3441

3542
export type Asset = {
@@ -43,12 +50,12 @@ export interface TopAssetsProps {
4350
assets: Asset[];
4451
onAssetClick: (asset: Asset) => void;
4552
logoSize?: AssetLogoProps['size'];
46-
className?: string;
53+
dataTestId?: string;
4754
}
4855

49-
export function TopAssets({ assets, logoSize = 40, onAssetClick, className }: TopAssetsProps) {
56+
export function TopAssets({ assets, logoSize = 40, onAssetClick, dataTestId }: TopAssetsProps) {
5057
return (
51-
<Container $itemsCount={assets.length} className={className}>
58+
<Container $itemsCount={assets.length}>
5259
{assets.map((asset, index) => {
5360
const displaySymbol = asset.symbol.toUpperCase();
5461

@@ -57,6 +64,7 @@ export function TopAssets({ assets, logoSize = 40, onAssetClick, className }: To
5764
key={asset.id}
5865
$isLast={index === assets.length - 1}
5966
onClick={() => onAssetClick(asset)}
67+
data-testid={`${dataTestId}/${asset.id}`}
6068
>
6169
{asset.isNativeToken ? (
6270
<CoinLogo
@@ -72,6 +80,10 @@ export function TopAssets({ assets, logoSize = 40, onAssetClick, className }: To
7280
symbol={asset.symbol}
7381
contractAddress={asset.contractAddress}
7482
placeholder={displaySymbol}
83+
showNetworkIcon={shouldShowNetworkIcon(
84+
asset.symbol,
85+
asset.contractAddress,
86+
)}
7587
/>
7688
)}
7789

packages/suite/e2e/support/pageObjects/trading/tradingPage.ts

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Locator, Page } from '@playwright/test';
2+
import { CryptoId } from 'invity-api';
23

34
import { TradingCountryCode } from '@suite-common/trading';
4-
import { NetworkSymbol } from '@suite-common/wallet-config';
5+
import { NetworkConfigWithoutTestnets, NetworkSymbol } from '@suite-common/wallet-config';
56
import type { BaseCurrencyCode } from '@trezor/blockchain-link-types';
67

78
import { Fees } from './fees';
@@ -23,16 +24,7 @@ const paymentMethodNameMap: Record<string, PaymentMethods> = {
2324
'Revolut Pay': 'revolutPay',
2425
};
2526

26-
type AccountTabFilter =
27-
| 'all-networks'
28-
| 'eth'
29-
| 'pol'
30-
| 'bsc'
31-
| 'arb'
32-
| 'base'
33-
| 'op'
34-
| 'sol'
35-
| 'avax';
27+
type AccountTabFilter = 'all-networks' | NetworkConfigWithoutTestnets['symbol'];
3628

3729
export class TradingPage {
3830
readonly fees: Fees;
@@ -58,15 +50,24 @@ export class TradingPage {
5850
readonly countryOfResidenceDropdown: Locator;
5951
readonly countryOfResidenceOption = (countryCode: string) =>
6052
this.page.getByTestId(`@trading/form/country-select/option/${countryCode}`);
61-
readonly accountDropdown: Locator;
62-
readonly accountSearchInput: Locator;
63-
readonly accountTabFilter = (tab: AccountTabFilter) =>
64-
this.page.getByTestId(`@trading/form/select-crypto/network-tab/${tab}`);
65-
readonly accountOption = (cryptoName: string, symbol?: NetworkSymbol) => {
66-
const suffix = symbol ? `${cryptoName}-${symbol}` : cryptoName;
67-
68-
return this.page.getByTestId(`@trading/form/select-crypto/option/${suffix}`);
69-
};
53+
54+
readonly assetPickerInput: Locator;
55+
readonly assetPickerSearchInput: Locator;
56+
readonly assetPickerNetworkFilter: Locator;
57+
readonly assetPickerDisplaySymbol: Locator;
58+
59+
readonly assetPickerNetworkFilterOption = (tab: AccountTabFilter) =>
60+
this.page.getByTestId(`@trading/form/select-crypto/search/select-option/${tab}`);
61+
62+
readonly assetPickerTokenOption = (networkSymbol?: NetworkSymbol, tokenSymbol?: string) =>
63+
this.page.getByTestId(`@trading/form/select-crypto/token/${networkSymbol}/${tokenSymbol}`);
64+
readonly assetPickerAccountOption = (networkSymbol?: NetworkSymbol) =>
65+
this.page.getByTestId(`@trading/form/select-crypto/account/${networkSymbol}`);
66+
readonly assetPickerTopFiveOption = (id: CryptoId) =>
67+
this.page.getByTestId(`@trading/form/select-crypto/top-five-assets/asset/${id}`);
68+
readonly assetPickerAssetOption = (id: CryptoId) =>
69+
this.page.getByTestId(`@trading/form/select-crypto/asset/${id}`);
70+
7071
readonly paymentMethodDropdown: Locator;
7172
readonly paymentMethodOption = (method: PaymentMethods) =>
7273
this.page.getByTestId(`@trading/form/payment-method-select/option/${method}`);
@@ -171,8 +172,15 @@ export class TradingPage {
171172
this.countryOfResidenceDropdown = this.page.getByTestId(
172173
'@trading/form/country-select/input',
173174
);
174-
this.accountDropdown = this.page.getByTestId('@trading/form/select-crypto/input');
175-
this.accountSearchInput = this.page.getByTestId('@trading/form/select-crypto/search-input');
175+
this.assetPickerInput = this.page.getByTestId('@trading/form/select-crypto/input');
176+
this.assetPickerDisplaySymbol = this.page.getByTestId(
177+
'@trading/form/select-crypto/display-symbol',
178+
);
179+
this.assetPickerSearchInput = this.page.getByTestId('@trading/form/select-crypto/search');
180+
this.assetPickerNetworkFilter = this.page.getByTestId(
181+
'@trading/form/select-crypto/search/select/input',
182+
);
183+
176184
this.paymentMethodDropdown = this.page.getByTestId(
177185
'@trading/form/payment-method-select/input',
178186
);
@@ -302,11 +310,36 @@ export class TradingPage {
302310
}
303311

304312
@step()
305-
async selectAccount(cryptoName: string, symbol: NetworkSymbol) {
306-
await this.accountDropdown.click();
307-
await this.accountTabFilter('all-networks').click();
308-
await this.accountSearchInput.fill(cryptoName);
309-
await this.accountOption(cryptoName, symbol).click();
313+
async selectReceiveAssetInAssetPicker({
314+
searchFilter,
315+
networkFilter,
316+
receiveAsset,
317+
receiveAccount,
318+
}: {
319+
searchFilter?: string;
320+
networkFilter?: AccountTabFilter;
321+
receiveAsset?: CryptoId;
322+
receiveAccount?: NetworkSymbol;
323+
}) {
324+
await this.assetPickerInput.click();
325+
326+
if (networkFilter) {
327+
await this.assetPickerNetworkFilter.click();
328+
await this.assetPickerNetworkFilterOption(networkFilter).click();
329+
}
330+
331+
if (searchFilter) {
332+
await this.assetPickerSearchInput.pressSequentially(searchFilter, { delay: 250 });
333+
await this.assetPickerSearchInput.blur();
334+
}
335+
336+
if (receiveAsset) {
337+
await this.assetPickerAssetOption(receiveAsset).click();
338+
}
339+
340+
if (receiveAccount) {
341+
await this.assetPickerAccountOption(receiveAccount).click();
342+
}
310343
}
311344

312345
@step()
@@ -467,8 +500,7 @@ export class TradingPage {
467500
sendCurrency: string;
468501
accountIndex?: number;
469502
sendTicker: string;
470-
receiveCurrency: string;
471-
receiveSymbol: NetworkSymbol;
503+
receiveAsset: Parameters<TradingPage['selectReceiveAssetInAssetPicker']>[0];
472504
receiveNetwork: string;
473505
receiveAddress?: string;
474506
fromAddress?: string;
@@ -478,7 +510,7 @@ export class TradingPage {
478510
this.swapFromAccountInput,
479511
this.swapFromAccountOption(params.sendCurrency).nth(params.accountIndex ?? 0),
480512
);
481-
await this.selectAccount(params.receiveCurrency, params.receiveSymbol);
513+
await this.selectReceiveAssetInAssetPicker(params.receiveAsset);
482514
// We should not fill in amount until account change takes effect = correct ticker is displayed
483515
await expect(this.swapAmountInputCurrencyTicker).toHaveText(params.sendTicker);
484516

@@ -598,13 +630,13 @@ export class TradingPage {
598630

599631
@step()
600632
async verifyBuyFormOpened(cryptoName: RegExp) {
601-
await expect(this.accountDropdown).toHaveText(cryptoName);
633+
await expect(this.assetPickerDisplaySymbol).toHaveText(cryptoName);
602634
await expect(this.page.getByText('You buy')).toBeVisible();
603635
}
604636

605637
@step()
606638
async verifySellFormOpened(cryptoName: RegExp) {
607-
await expect(this.accountDropdown).toHaveText(cryptoName);
639+
await expect(this.assetPickerInput).toHaveText(cryptoName);
608640
await expect(this.page.getByText('You sell')).toBeVisible();
609641
}
610642

packages/suite/e2e/tests/trading/buy-ethereum.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CryptoId } from 'invity-api';
2+
13
import { localizeNumber } from '@suite-common/wallet-utils';
24
import { capitalizeFirstLetter } from '@trezor/utils';
35

@@ -22,7 +24,11 @@ test.describe('Trading - Buy Ethereum', { tag: ['@group=trading', '@webOnly'] },
2224
test('Enable Ethereum on account by buying it', async ({ page, walletPage, tradingPage }) => {
2325
await test.step('Request to buy Ethereum', async () => {
2426
await walletPage.openTradingGlobalButton.click();
25-
await tradingPage.selectAccount('Ethereum', 'eth');
27+
await tradingPage.selectReceiveAssetInAssetPicker({
28+
searchFilter: 'Ethereum',
29+
networkFilter: 'eth',
30+
receiveAsset: 'ethereum' as CryptoId,
31+
});
2632
await tradingPage.fillBuyForm({
2733
amount: fiatAmount,
2834
cryptoCurrency: 'ethereum',

packages/suite/e2e/tests/trading/buy-solana-token.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CryptoId } from 'invity-api';
2+
13
import { localizeNumber } from '@suite-common/wallet-utils';
24
import { capitalizeFirstLetter } from '@trezor/utils';
35

@@ -32,13 +34,18 @@ test.describe('Trading - Buy Solana', { tag: ['@group=trading', '@webOnly'] }, (
3234

3335
test('Buy Solana Jupiter token - amount specified in crypto', async ({ page, tradingPage }) => {
3436
await test.step('Request a specific crypto amount of Jupiter token to buy', async () => {
35-
await tradingPage.selectAccount('Jupiter', 'sol');
37+
const cryptoId = 'solana--JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN' as CryptoId;
38+
await tradingPage.selectReceiveAssetInAssetPicker({
39+
networkFilter: 'sol',
40+
searchFilter: 'Jupiter',
41+
receiveAsset: cryptoId,
42+
});
3643
await tradingPage.waitForOffersSync();
3744
await tradingPage.youPayFiatCryptoSwitchButton.click();
3845
const isCryptoInput = true;
3946
await tradingPage.fillBuyForm({
4047
amount: cryptoAmount,
41-
cryptoCurrency: 'solana--JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN',
48+
cryptoCurrency: cryptoId,
4249
wantCrypto: isCryptoInput,
4350
selectReceiveAddress: async () => {
4451
await tradingPage.selectSuiteReceiveAccount(0);

packages/suite/e2e/tests/trading/swap-coin-to-token.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CryptoId } from 'invity-api';
2+
13
import { localizeNumber } from '@suite-common/wallet-utils';
24

35
import {
@@ -46,8 +48,11 @@ test.describe('Trading - Swap coin to token', { tag: ['@group=trading', '@webOnl
4648
amount: sendAmount,
4749
sendCurrency: 'solana',
4850
sendTicker: 'SOL',
49-
receiveCurrency: 'USDC',
50-
receiveSymbol: 'eth',
51+
receiveAsset: {
52+
searchFilter: 'USDC',
53+
networkFilter: 'eth',
54+
receiveAsset: usdcMint as CryptoId,
55+
},
5156
receiveNetwork: usdcMint,
5257
receiveAddress,
5358
selectReceiveAddress: async () => {

packages/suite/e2e/tests/trading/swap-coins.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CryptoId } from 'invity-api';
2+
13
import { localizeNumber } from '@suite-common/wallet-utils';
24

35
import type { TranslationKey } from '../../../src/components/suite/Translation';
@@ -65,8 +67,11 @@ test.describe('Trading - Swap coins', { tag: ['@group=trading', '@webOnly'] }, (
6567
amount: sendAmount,
6668
sendCurrency: 'solana',
6769
sendTicker: 'SOL',
68-
receiveCurrency: 'Bitcoin',
69-
receiveSymbol: 'btc',
70+
receiveAsset: {
71+
searchFilter: 'Bitcoin',
72+
networkFilter: 'btc',
73+
receiveAsset: 'bitcoin' as CryptoId,
74+
},
7075
receiveNetwork: 'bitcoin',
7176
receiveAddress,
7277
selectReceiveAddress: async () => {

0 commit comments

Comments
 (0)