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

chore: cherry-pick-10163-10227-10219-10067 #10217

Merged
merged 4 commits into from
Jul 3, 2024
Merged
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
5 changes: 3 additions & 2 deletions app/components/UI/AddressInputs/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const initialState = {
name: 'Account 2',
},
},
useTokenDetection: false,
},
AddressBookController: {
addressBook: {
Expand Down Expand Up @@ -61,7 +62,7 @@ describe('AddressInputs', () => {
fromAccountBalance="0x5"
fromAccountName="DUMMY_ACCOUNT"
/>,
{},
{ state: initialState },
);
expect(container).toMatchSnapshot();
});
Expand All @@ -74,7 +75,7 @@ describe('AddressInputs', () => {
fromAccountName="DUMMY_ACCOUNT"
layout="vertical"
/>,
{},
{ state: initialState },
);
expect(container).toMatchSnapshot();
});
Expand Down
20 changes: 20 additions & 0 deletions app/components/UI/Identicon/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Identicon should render correctly when provided address found in tokenList and iconUrl is available 1`] = `
<Image
source={
{
"uri": "https://example.com/icon.png",
}
}
style={
[
{
"borderRadius": 23,
"height": 46,
"width": 46,
},
undefined,
]
}
/>
`;

exports[`Identicon should render correctly when useBlockieIcon is false 1`] = `
<ContextProvider
value={
Expand Down
29 changes: 29 additions & 0 deletions app/components/UI/Identicon/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import React from 'react';
import { shallow } from 'enzyme';
import { render } from '@testing-library/react-native';
import Identicon from './';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import useTokenList from '../../../components/hooks/DisplayName/useTokenList';

jest.mock('../../../components/hooks/DisplayName/useTokenList');

describe('Identicon', () => {
const mockStore = configureMockStore();
const mockUseTokenList = jest
.mocked(useTokenList)
.mockImplementation(() => ({}));

it('should render correctly when provided address found in tokenList and iconUrl is available', () => {
const addressMock = '0x0439e60f02a8900a951603950d8d4527f400c3f1';
mockUseTokenList.mockImplementation(() => [
{
address: addressMock,
iconUrl: 'https://example.com/icon.png',
},
]);

const initialState = {
settings: { useBlockieIcon: true },
};
const store = mockStore(initialState);

const wrapper = render(
<Provider store={store}>
<Identicon address={addressMock} />
</Provider>,
);
expect(wrapper).toMatchSnapshot();
});
it('should render correctly when useBlockieIcon is true', () => {
const initialState = {
settings: { useBlockieIcon: true },
Expand Down
36 changes: 25 additions & 11 deletions app/components/UI/Identicon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import FadeIn from 'react-native-fade-in-image';
import Jazzicon from 'react-native-jazzicon';
import { connect } from 'react-redux';
import { useTheme } from '../../../util/theme';
import { useTokenListEntry } from '../../../components/hooks/DisplayName/useTokenListEntry';
import { NameType } from '../../UI/Name/Name.types';

interface IdenticonProps {
/**
Expand Down Expand Up @@ -43,23 +45,35 @@ const Identicon: React.FC<IdenticonProps> = ({
useBlockieIcon = true,
}) => {
const { colors } = useTheme();
const tokenListIcon = useTokenListEntry(
address || '',
NameType.EthereumAddress,
)?.iconUrl;

if (!address) return null;

const uri = useBlockieIcon && toDataUrl(address);

const styleForBlockieAndTokenIcon = [
{
height: diameter,
width: diameter,
borderRadius: diameter / 2,
},
customStyle,
];

if (tokenListIcon) {
return (
<Image
source={{ uri: tokenListIcon }}
style={styleForBlockieAndTokenIcon}
/>
);
}

const image = useBlockieIcon ? (
<Image
source={{ uri }}
style={[
{
height: diameter,
width: diameter,
borderRadius: diameter / 2,
},
customStyle,
]}
/>
<Image source={{ uri }} style={styleForBlockieAndTokenIcon} />
) : (
<View style={customStyle}>
<Jazzicon size={diameter} address={address} />
Expand Down
5 changes: 5 additions & 0 deletions app/components/UI/Name/Name.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ jest.mock('../../hooks/DisplayName/useDisplayName', () => ({
default: jest.fn(),
}));

jest.mock('../Identicon', () => ({
__esModule: true,
default: () => 'Identicon',
}));

const UNKNOWN_ADDRESS_CHECKSUMMED =
'0x299007B3F9E23B8d432D5f545F8a4a2B3E9A5B4e';
const EXPECTED_UNKNOWN_ADDRESS_CHECKSUMMED = '0x29900...A5B4e';
Expand Down
61 changes: 2 additions & 59 deletions app/components/UI/Name/__snapshots__/Name.test.tsx.snap

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions app/components/Views/confirmations/Send/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const initialState = {
TokenBalancesController: {
contractBalances: {},
},
TokenListController: {
tokenList: [],
},
PreferencesController: {
featureFlags: {},
identities: {
Expand Down
30 changes: 16 additions & 14 deletions app/components/hooks/DisplayName/useTokenList.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React from 'react';
import { type TokenListMap } from '@metamask/assets-controllers';
import { type TokenListToken } from '@metamask/assets-controllers';
import { selectChainId } from '../../../selectors/networkController';
import { selectUseTokenDetection } from '../../../selectors/preferencesController';
import { selectTokenList } from '../../../selectors/tokenListController';
import { selectTokenListArray } from '../../../selectors/tokenListController';
import { isMainnetByChainId } from '../../../util/networks';

import useTokenList from './useTokenList';

const MAINNET_TOKEN_ADDRESS_MOCK = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
const MAINNET_TOKEN_NAME_MOCK = 'Tether USD';
const normalizedMainnetTokenListMock = {
[MAINNET_TOKEN_ADDRESS_MOCK.toLowerCase()]: {
const normalizedMainnetTokenListMock = [
{
name: MAINNET_TOKEN_NAME_MOCK,
},
};
];
jest.mock('@metamask/contract-metadata', () => ({
__esModule: true,
default: {
Expand All @@ -36,7 +36,7 @@ jest.mock('../../../selectors/preferencesController', () => ({
}));

jest.mock('../../../selectors/tokenListController', () => ({
selectTokenList: jest.fn(),
selectTokenListArray: jest.fn(),
}));

jest.mock('../../../util/networks', () => ({
Expand All @@ -46,27 +46,29 @@ jest.mock('../../../util/networks', () => ({
const CHAIN_ID_MOCK = '0x1';
const TOKEN_NAME_MOCK = 'MetaMask Token';
const TOKEN_ADDRESS_MOCK = '0x0439e60F02a8900a951603950d8D4527f400C3f1';
const TOKEN_LIST_MOCK = {
[TOKEN_ADDRESS_MOCK]: {
const TOKEN_LIST_ARRAY_MOCK = [
{
name: TOKEN_NAME_MOCK,
address: TOKEN_ADDRESS_MOCK,
},
} as unknown as TokenListMap;
const normalizedTokenListMock = {
[TOKEN_ADDRESS_MOCK.toLowerCase()]: {
] as unknown as TokenListToken[];
const normalizedTokenListMock = [
{
address: TOKEN_ADDRESS_MOCK,
name: TOKEN_NAME_MOCK,
},
};
];

describe('useTokenList', () => {
const selectChainIdMock = jest.mocked(selectChainId);
const selectUseTokenDetectionMock = jest.mocked(selectUseTokenDetection);
const selectTokenListMock = jest.mocked(selectTokenList);
const selectTokenListArrayMock = jest.mocked(selectTokenListArray);
const isMainnetByChainIdMock = jest.mocked(isMainnetByChainId);
beforeEach(() => {
jest.resetAllMocks();
selectChainIdMock.mockReturnValue(CHAIN_ID_MOCK);
selectUseTokenDetectionMock.mockReturnValue(true);
selectTokenListMock.mockReturnValue(TOKEN_LIST_MOCK);
selectTokenListArrayMock.mockReturnValue(TOKEN_LIST_ARRAY_MOCK);
isMainnetByChainIdMock.mockReturnValue(true);

const memoizedValues = new Map();
Expand Down
31 changes: 10 additions & 21 deletions app/components/hooks/DisplayName/useTokenList.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@
import { useMemo } from 'react';
import { type TokenListMap } from '@metamask/assets-controllers';
import contractMap from '@metamask/contract-metadata';

import { TokenListToken } from '@metamask/assets-controllers';
import { useSelector } from 'react-redux';
import { selectChainId } from '../../../selectors/networkController';
import { selectUseTokenDetection } from '../../../selectors/preferencesController';
import { selectTokenList } from '../../../selectors/tokenListController';
import { selectTokenListArray } from '../../../selectors/tokenListController';
import { isMainnetByChainId } from '../../../util/networks';

function normalizeTokenAddresses(tokenMap: TokenListMap) {
return Object.keys(tokenMap).reduce((acc, address) => {
const tokenMetadata = tokenMap[address];
return {
...acc,
[address.toLowerCase()]: {
...tokenMetadata,
},
};
}, {});
}

const NORMALIZED_MAINNET_TOKEN_LIST = normalizeTokenAddresses(contractMap);
const NORMALIZED_MAINNET_TOKEN_ARRAY = Object.values(
contractMap,
) as TokenListToken[];

export default function useTokenList(): TokenListMap {
export default function useTokenList(): TokenListToken[] {
const chainId = useSelector(selectChainId);
const isMainnet = isMainnetByChainId(chainId);
const isTokenDetectionEnabled = useSelector(selectUseTokenDetection);
const tokenList = useSelector(selectTokenList);
const tokenListArray = useSelector(selectTokenListArray);
const shouldUseStaticList = !isTokenDetectionEnabled && isMainnet;

return useMemo(() => {
if (shouldUseStaticList) {
return NORMALIZED_MAINNET_TOKEN_LIST;
return NORMALIZED_MAINNET_TOKEN_ARRAY;
}
return normalizeTokenAddresses(tokenList);
}, [shouldUseStaticList, tokenList]);
return tokenListArray;
}, [shouldUseStaticList, tokenListArray]);
}
9 changes: 5 additions & 4 deletions app/components/hooks/DisplayName/useTokenListEntry.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TokenListMap } from '@metamask/assets-controllers';
import { TokenListToken } from '@metamask/assets-controllers';
import { NameType } from '../../UI/Name/Name.types';
import { useTokenListEntry } from './useTokenListEntry';
import useTokenList from './useTokenList';
Expand All @@ -18,12 +18,13 @@ describe('useTokenListEntry', () => {
beforeEach(() => {
jest.resetAllMocks();

useTokenListMock.mockReturnValue({
[TOKEN_ADDRESS_MOCK.toLowerCase()]: {
useTokenListMock.mockReturnValue([
{
address: TOKEN_ADDRESS_MOCK.toLowerCase(),
name: TOKEN_NAME_MOCK,
symbol: TOKEN_SYMBOL_MOCK,
},
} as TokenListMap);
] as unknown as TokenListToken[]);
});

it('returns undefined if no token found', () => {
Expand Down
9 changes: 6 additions & 3 deletions app/components/hooks/DisplayName/useTokenListEntry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TokenListToken } from '@metamask/assets-controllers';
import { NameType } from '../../UI/Name/Name.types';
import useTokenList from './useTokenList';

Expand All @@ -7,16 +8,18 @@ export interface UseTokenListEntriesRequest {
}

export function useTokenListEntries(requests: UseTokenListEntriesRequest[]) {
const tokenList = useTokenList();
const tokenListArray = useTokenList();

return requests.map(({ value, type }) => {
if (type !== NameType.EthereumAddress) {
if (type !== NameType.EthereumAddress || !value) {
return null;
}

const normalizedValue = value.toLowerCase();

return tokenList[normalizedValue];
return tokenListArray.find(
(token: TokenListToken) => token.address === normalizedValue,
);
});
}

Expand Down
3 changes: 2 additions & 1 deletion app/selectors/tokenListController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createSelector } from 'reselect';
import { TokenListState } from '@metamask/assets-controllers';
import { RootState } from '../reducers';
import { tokenListToArray } from '../util/tokens';
import { createDeepEqualSelector } from '../selectors/util';

const selectTokenLIstConstrollerState = (state: RootState) =>
state.engine.backgroundState.TokenListController;
Expand All @@ -20,7 +21,7 @@ export const selectTokenList = createSelector(
* Return token list array from TokenListController.
* Can pass directly into useSelector.
*/
export const selectTokenListArray = createSelector(
export const selectTokenListArray = createDeepEqualSelector(
selectTokenList,
tokenListToArray,
);
20 changes: 20 additions & 0 deletions bitrise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ stages:
workflows:
- run_tag_smoke_accounts_ios: {}
- run_tag_smoke_accounts_android: {}
- run_tag_smoke_assets_ios: {}
- run_tag_smoke_assets_android: {}
- run_tag_smoke_confirmations_ios: {}
- run_tag_smoke_confirmations_android: {}
- run_tag_smoke_swaps_ios: {}
Expand All @@ -139,6 +141,8 @@ stages:
- run_tag_smoke_confirmations_android: {}
- run_tag_smoke_accounts_ios: {}
- run_tag_smoke_accounts_android: {}
- run_tag_smoke_assets_ios: {}
- run_tag_smoke_assets_android: {}
- run_tag_smoke_swaps_ios: {}
- run_tag_smoke_swaps_android: {}
- run_tag_smoke_core_ios: {}
Expand Down Expand Up @@ -470,6 +474,22 @@ workflows:
- TEST_SUITE_TAG: '.*SmokeAccounts.*'
after_run:
- android_e2e_test
run_tag_smoke_assets_ios:
envs:
- TEST_SUITE_FOLDER: './e2e/specs/assets/*'
- TEST_SUITE_TAG: '.*SmokeAssets.*'
after_run:
- ios_e2e_test
run_tag_smoke_assets_android:
meta:
bitrise.io:
stack: linux-docker-android-20.04
machine_type_id: elite-xl
envs:
- TEST_SUITE_FOLDER: './e2e/specs/assets/*'
- TEST_SUITE_TAG: '.*SmokeAssets.*'
after_run:
- android_e2e_test
run_tag_smoke_confirmations_ios:
envs:
- TEST_SUITE_FOLDER: './e2e/specs/confirmations/*'
Expand Down
Loading
Loading