Skip to content

Commit 634041f

Browse files
authored
Merge branch 'main' into rn-upgrade/0.81.5-no-unit-tests
2 parents 5bf025e + b22424a commit 634041f

41 files changed

Lines changed: 2871 additions & 952 deletions

File tree

Some content is hidden

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

.github/actions/setup-e2e-env/action.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@ runs:
116116
if: ${{ inputs.platform == 'android' && inputs.setup-simulator == 'true' && runner.os == 'Linux' }}
117117
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
118118
with:
119-
timeout_minutes: 3
119+
timeout_minutes: 5
120120
max_attempts: 3
121121
retry_wait_seconds: 30
122+
retry_on: error
122123
on_retry_command: sudo apt-get clean
123124
command: |
124125
set -euo pipefail

app/components/UI/Assets/components/AssetLogo/AssetLogo.test.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@ import AssetLogo from './AssetLogo';
44
import AvatarToken from '../../../../../component-library/components/Avatars/Avatar/variants/AvatarToken';
55
import { AvatarSize } from '../../../../../component-library/components/Avatars/Avatar';
66
import NetworkAssetLogo from '../../../NetworkAssetLogo';
7+
import { getAssetImageUrl } from '../../../Bridge/hooks/useAssetMetadata/utils';
8+
9+
jest.mock('../../../Bridge/hooks/useAssetMetadata/utils', () => ({
10+
getAssetImageUrl: jest.fn(),
11+
}));
712

813
describe('AssetLogo', () => {
14+
const mockedGetAssetImageUrl = jest.mocked(getAssetImageUrl);
15+
916
const mockState = {
1017
engine: {
1118
backgroundState: {
@@ -19,6 +26,10 @@ describe('AssetLogo', () => {
1926
},
2027
};
2128

29+
beforeEach(() => {
30+
mockedGetAssetImageUrl.mockReset();
31+
});
32+
2233
it('renders asset logo for non-native assets', () => {
2334
const asset = {
2435
decimals: 18,
@@ -87,4 +98,77 @@ describe('AssetLogo', () => {
8798
}),
8899
);
89100
});
101+
102+
it('uses fallback image URL when image is an empty string', () => {
103+
const fallbackImageUrl = 'https://example.com/fallback.png';
104+
mockedGetAssetImageUrl.mockReturnValue(fallbackImageUrl);
105+
106+
const asset = {
107+
decimals: 18,
108+
address: '0x456',
109+
chainId: '0x1',
110+
symbol: 'TEST',
111+
name: 'Test Token',
112+
balance: '1.23',
113+
balanceFiat: '$123.00',
114+
isNative: false,
115+
isETH: false,
116+
image: '',
117+
logo: 'https://example.com/logo.png',
118+
aggregators: [],
119+
};
120+
121+
const { UNSAFE_getByType } = renderWithProvider(
122+
<AssetLogo asset={asset} />,
123+
{
124+
state: mockState,
125+
},
126+
);
127+
128+
expect(mockedGetAssetImageUrl).toHaveBeenCalledWith('0x456', '0x1');
129+
130+
const assetAvatar = UNSAFE_getByType(AvatarToken);
131+
expect(assetAvatar.props).toStrictEqual({
132+
name: 'TEST',
133+
imageSource: {
134+
uri: fallbackImageUrl,
135+
},
136+
size: AvatarSize.Lg,
137+
});
138+
});
139+
140+
it('does not call fallback image utility for unsupported chainId', () => {
141+
const asset = {
142+
decimals: 18,
143+
address: '0x456',
144+
chainId: '1',
145+
symbol: 'TEST',
146+
name: 'Test Token',
147+
balance: '1.23',
148+
balanceFiat: '$123.00',
149+
isNative: false,
150+
isETH: false,
151+
image: '',
152+
logo: 'https://example.com/logo.png',
153+
aggregators: [],
154+
};
155+
156+
const { UNSAFE_getByType } = renderWithProvider(
157+
<AssetLogo asset={asset} />,
158+
{
159+
state: mockState,
160+
},
161+
);
162+
163+
expect(mockedGetAssetImageUrl).not.toHaveBeenCalled();
164+
165+
const assetAvatar = UNSAFE_getByType(AvatarToken);
166+
expect(assetAvatar.props).toStrictEqual({
167+
name: 'TEST',
168+
imageSource: {
169+
uri: undefined,
170+
},
171+
size: AvatarSize.Lg,
172+
});
173+
});
90174
});

app/components/UI/Assets/components/AssetLogo/AssetLogo.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
import React from 'react';
2+
import { isCaipChainId, isStrictHexString } from '@metamask/utils';
23
import { AvatarSize } from '../../../../../component-library/components/Avatars/Avatar';
34
import NetworkAssetLogo from '../../../NetworkAssetLogo';
45
import AvatarToken from '../../../../../component-library/components/Avatars/Avatar/variants/AvatarToken';
56
import { TokenI } from '../../../Tokens/types';
67
import { useStyles } from '../../../../../component-library/hooks/useStyles';
8+
import { getAssetImageUrl } from '../../../Bridge/hooks/useAssetMetadata/utils';
79
import styleSheet from './AssetLogo.styles';
810

11+
const getFallbackAssetImageUrl = (asset: TokenI): string | undefined => {
12+
if (!asset.chainId) {
13+
return undefined;
14+
}
15+
16+
if (!isCaipChainId(asset.chainId) && !isStrictHexString(asset.chainId)) {
17+
return undefined;
18+
}
19+
20+
return getAssetImageUrl(asset.address, asset.chainId);
21+
};
22+
923
const AssetLogo = ({ asset }: { asset: TokenI }) => {
1024
const { styles } = useStyles(styleSheet, {});
1125

@@ -22,10 +36,12 @@ const AssetLogo = ({ asset }: { asset: TokenI }) => {
2236
);
2337
}
2438

39+
const imageUri = asset.image || getFallbackAssetImageUrl(asset);
40+
2541
return (
2642
<AvatarToken
2743
name={asset.symbol}
28-
imageSource={{ uri: asset.image }}
44+
imageSource={{ uri: imageUri }}
2945
size={AvatarSize.Lg}
3046
/>
3147
);
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import '../../../../tests/component-view/mocks';
2+
import React from 'react';
3+
import { FlatList } from 'react-native';
4+
import { act, fireEvent } from '@testing-library/react-native';
5+
import type { GroupedDeFiPositions } from '@metamask/assets-controllers';
6+
7+
import DeFiProtocolPositionDetails, {
8+
DEFI_PROTOCOL_POSITION_DETAILS_BALANCE_TEST_ID,
9+
} from './DeFiProtocolPositionDetails';
10+
import { WalletViewSelectorsIDs } from '../../Views/Wallet/WalletView.testIds';
11+
import { renderComponentViewScreen } from '../../../../tests/component-view/render';
12+
import { describeForPlatforms } from '../../../../tests/component-view/platform';
13+
import { backgroundState } from '../../../util/test/initial-root-state';
14+
15+
/**
16+
* Mirrors smoke `view-defi-details`: tap Aave V3 → read-only position details with
17+
* Supplied tokens and fiat balances (no transaction).
18+
*/
19+
const aaveV3PositionAggregate: GroupedDeFiPositions['protocols'][number] = {
20+
protocolDetails: {
21+
name: 'Aave V3',
22+
iconUrl: '',
23+
},
24+
aggregatedMarketValue: 14.74,
25+
positionTypes: {
26+
supply: {
27+
aggregatedMarketValue: 14.74,
28+
positions: [
29+
[
30+
{
31+
address: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a',
32+
name: 'Aave Ethereum USDT',
33+
symbol: 'aEthUSDT',
34+
decimals: 6,
35+
balance: 0.300112,
36+
balanceRaw: '300112',
37+
marketValue: 14.74,
38+
type: 'protocol',
39+
tokens: [
40+
{
41+
address: '0xdac17f958d2ee523a2206206994597c13d831ec7',
42+
name: 'Tether USD',
43+
symbol: 'USDT',
44+
decimals: 6,
45+
balance: 0.300112,
46+
balanceRaw: '300112',
47+
marketValue: 14.74,
48+
price: 0.99994,
49+
type: 'underlying',
50+
iconUrl: '',
51+
},
52+
],
53+
},
54+
],
55+
[
56+
{
57+
address: '0xfa1fdbbd71b0aa16162d76914d69cd8cb3ef92da',
58+
name: 'Aave Ethereum Lido WETH',
59+
symbol: 'aEthLidoWETH',
60+
decimals: 18,
61+
balance: 1e-5,
62+
balanceRaw: '9030902767263172',
63+
marketValue: 0.3,
64+
type: 'protocol',
65+
tokens: [
66+
{
67+
address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
68+
name: 'Wrapped Ether',
69+
symbol: 'WETH',
70+
decimals: 18,
71+
balance: 1e-5,
72+
balanceRaw: '10000000000000',
73+
marketValue: 0.3,
74+
price: 1599.45,
75+
type: 'underlying',
76+
iconUrl: '',
77+
},
78+
],
79+
},
80+
],
81+
],
82+
},
83+
},
84+
};
85+
86+
const defiDetailsState = {
87+
engine: {
88+
backgroundState: {
89+
...backgroundState,
90+
PreferencesController: {
91+
...backgroundState.PreferencesController,
92+
privacyMode: false,
93+
},
94+
},
95+
},
96+
};
97+
98+
describeForPlatforms('DeFi position details (read-only)', () => {
99+
it('shows Aave V3 supplied assets with token symbols and fiat amounts', () => {
100+
const { getByTestId, getByText, getAllByText, UNSAFE_getByType } =
101+
renderComponentViewScreen(
102+
DeFiProtocolPositionDetails,
103+
{ name: 'DeFiProtocolPositionDetails' },
104+
{ state: defiDetailsState },
105+
{
106+
protocolAggregate: aaveV3PositionAggregate,
107+
networkIconAvatar: undefined,
108+
},
109+
);
110+
111+
expect(
112+
getByTestId(WalletViewSelectorsIDs.DEFI_POSITIONS_DETAILS_CONTAINER),
113+
).toBeOnTheScreen();
114+
115+
expect(getByText('Aave V3')).toBeOnTheScreen();
116+
expect(
117+
getByTestId(DEFI_PROTOCOL_POSITION_DETAILS_BALANCE_TEST_ID),
118+
).toHaveTextContent('$14.74');
119+
120+
// Smoke parity for details checks: Supplied + USDT + WETH + $14.74 + $0.30.
121+
expect(getAllByText('Supplied')).toHaveLength(2);
122+
expect(getAllByText('USDT')).toHaveLength(1);
123+
expect(getAllByText('$14.74').length).toBeGreaterThanOrEqual(2);
124+
125+
const list = UNSAFE_getByType(FlatList);
126+
act(() => {
127+
fireEvent.scroll(list, {
128+
nativeEvent: {
129+
contentOffset: { y: 150 },
130+
contentSize: { height: 500, width: 400 },
131+
layoutMeasurement: { height: 400, width: 400 },
132+
},
133+
});
134+
});
135+
136+
expect(getByText('WETH')).toBeOnTheScreen();
137+
expect(getByText('$0.30')).toBeOnTheScreen();
138+
});
139+
});

0 commit comments

Comments
 (0)