From f2cf66592934bacdc39354fca3c2114eae878001 Mon Sep 17 00:00:00 2001 From: Alejandro Garcia Anglada Date: Fri, 25 Apr 2025 10:17:23 +0200 Subject: [PATCH 1/7] feat: solana banner --- app/components/UI/Carousel/constants.ts | 32 ++++++++++ app/components/UI/Carousel/index.tsx | 64 ++++++++++++++++--- app/components/UI/Carousel/types.ts | 13 +++- app/images/banners/banner_image_solana.png | Bin 0 -> 3533 bytes app/lib/snaps/preinstalled-snaps.ts | 4 +- e2e/selectors/wallet/WalletView.selectors.js | 3 + locales/languages/en.json | 4 ++ metro.transform.js | 10 ++- 8 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 app/images/banners/banner_image_solana.png diff --git a/app/components/UI/Carousel/constants.ts b/app/components/UI/Carousel/constants.ts index 8808cc6146c7..975b020f3d4e 100644 --- a/app/components/UI/Carousel/constants.ts +++ b/app/components/UI/Carousel/constants.ts @@ -14,6 +14,11 @@ import aggregatedImage from '../../../images/banners/banner_image_aggregated.png ///: BEGIN:ONLY_INCLUDE_IF(multi-srp) import multiSrpImage from '../../../images/banners/banner_image_multisrp.png'; ///: END:ONLY_INCLUDE_IF +///: BEGIN:ONLY_INCLUDE_IF(solana) +import solanaImage from '../../../images/banners/banner_image_solana.png'; +import { WalletClientType } from '../../../core/SnapKeyring/MultichainWalletSnapClient'; +import { SolScope } from '@metamask/keyring-api'; +///: END:ONLY_INCLUDE_IF export const PREDEFINED_SLIDES: CarouselSlide[] = [ { @@ -84,6 +89,30 @@ export const PREDEFINED_SLIDES: CarouselSlide[] = [ testIDCloseButton: WalletViewSelectorsIDs.CAROUSEL_FIFTH_SLIDE_CLOSE_BUTTON, }, ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(solana) + { + id: 'solana', + title: strings('banner.solana.title'), + description: strings('banner.solana.subtitle'), + undismissable: false, + navigation: { + type: 'function', + navigate: () => [ + Routes.MODAL.ROOT_MODAL_FLOW, + { + screen: Routes.SHEET.ADD_ACCOUNT, + params: { + clientType: WalletClientType.Solana, + scope: SolScope.Mainnet, + }, + }, + ], + }, + testID: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE, + testIDTitle: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE_TITLE, + testIDCloseButton: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE_CLOSE_BUTTON, + }, + ///: END:ONLY_INCLUDE_IF ]; export const BANNER_IMAGES: Record = { @@ -94,4 +123,7 @@ export const BANNER_IMAGES: Record = { ///: BEGIN:ONLY_INCLUDE_IF(multi-srp) multisrp: multiSrpImage, ///: END:ONLY_INCLUDE_IF + ///: BEGIN:ONLY_INCLUDE_IF(solana) + solana: solanaImage as ImageSourcePropType, + ///: END:ONLY_INCLUDE_IF }; diff --git a/app/components/UI/Carousel/index.tsx b/app/components/UI/Carousel/index.tsx index 9b6e3c659bb8..d99ac18606ac 100644 --- a/app/components/UI/Carousel/index.tsx +++ b/app/components/UI/Carousel/index.tsx @@ -23,6 +23,12 @@ import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletV import { PREDEFINED_SLIDES, BANNER_IMAGES } from './constants'; import { useStyles } from '../../../component-library/hooks'; import { selectDismissedBanners } from '../../../selectors/banner'; +import { + selectSelectedInternalAccount, + selectLastSelectedSolanaAccount, +} from '../../../selectors/accountsController'; +import { SolAccountType } from '@metamask/keyring-api'; +import Engine from '../../../core/Engine'; export const Carousel: FC = ({ style }) => { const [selectedIndex, setSelectedIndex] = useState(0); @@ -34,6 +40,10 @@ export const Carousel: FC = ({ style }) => { const { navigate } = useNavigation(); const { styles } = useStyles(styleSheet, { style }); const dismissedBanners = useSelector(selectDismissedBanners); + const selectedAccount = useSelector(selectSelectedInternalAccount); + const lastSelectedSolanaAccount = useSelector( + selectLastSelectedSolanaAccount, + ); const isZeroBalance = selectedAccountMultichainBalance?.totalFiatBalance === 0; @@ -56,13 +66,32 @@ export const Carousel: FC = ({ style }) => { const visibleSlides = useMemo( () => - slidesConfig.filter((slide) => { - if (slide.id === 'fund' && isZeroBalance) { - return true; - } - return !dismissedBanners.includes(slide.id); - }), - [slidesConfig, isZeroBalance, dismissedBanners], + slidesConfig + .filter((slide) => { + ///: BEGIN:ONLY_INCLUDE_IF(solana) + if ( + slide.id === 'solana' && + selectedAccount?.type === SolAccountType.DataAccount + ) { + return false; + } + ///: END:ONLY_INCLUDE_IF + + if (slide.id === 'fund' && isZeroBalance) { + return true; + } + return !dismissedBanners.includes(slide.id); + }) + ///: BEGIN:ONLY_INCLUDE_IF(solana) + // prioritize Solana slide + .sort((a, b) => { + if (a.id === 'solana' && b.id !== 'solana') { + return -1; + } + return 1; + }), + ///: END:ONLY_INCLUDE_IF + [slidesConfig, isZeroBalance, dismissedBanners, selectedAccount], ); const isSingleSlide = visibleSlides.length === 1; @@ -76,15 +105,34 @@ export const Carousel: FC = ({ style }) => { const handleSlideClick = useCallback( (slideId: string, navigation: NavigationAction) => { + const extraProperties: Record = {}; + + const isSolanaBanner = slideId === 'solana'; + + ///: BEGIN:ONLY_INCLUDE_IF(solana) + if (isSolanaBanner && lastSelectedSolanaAccount) { + extraProperties.action = 'redirect-solana-account'; + } else if (isSolanaBanner && !lastSelectedSolanaAccount) { + extraProperties.action = 'create-solana-account'; + } + ///: END:ONLY_INCLUDE_IF + trackEvent( createEventBuilder({ category: 'Banner Select', properties: { name: slideId, + ...extraProperties, }, }).build(), ); + ///: BEGIN:ONLY_INCLUDE_IF(solana) + if (isSolanaBanner && lastSelectedSolanaAccount) { + return Engine.setSelectedAddress(lastSelectedSolanaAccount.address); + } + ///: END:ONLY_INCLUDE_IF + if (navigation.type === 'url') { return openUrl(navigation.href)(); } @@ -97,7 +145,7 @@ export const Carousel: FC = ({ style }) => { return navigate(navigation.route); } }, - [navigate, trackEvent, createEventBuilder], + [trackEvent, createEventBuilder, lastSelectedSolanaAccount, navigate], ); const handleClose = useCallback( diff --git a/app/components/UI/Carousel/types.ts b/app/components/UI/Carousel/types.ts index 3ea1a399f6ad..427f2cf9ffa0 100644 --- a/app/components/UI/Carousel/types.ts +++ b/app/components/UI/Carousel/types.ts @@ -1,12 +1,21 @@ import { ViewStyle } from 'react-native'; - -export type SlideId = 'card' | 'fund' | 'cashout' | 'aggregated' | 'multisrp'; +import { WalletClientType } from '../../../core/SnapKeyring/MultichainWalletSnapClient'; +import { CaipChainId } from '@metamask/utils'; +export type SlideId = + | 'card' + | 'fund' + | 'cashout' + | 'aggregated' + | 'multisrp' + | 'solana'; interface NavigationParams { address?: string; chainId?: string; amount?: string; currency?: string; + clientType?: WalletClientType; + scope?: CaipChainId; } interface NavigationScreen { diff --git a/app/images/banners/banner_image_solana.png b/app/images/banners/banner_image_solana.png new file mode 100644 index 0000000000000000000000000000000000000000..b7381e3593f69472a78bf9df3cba4115912d797d GIT binary patch literal 3533 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 literal 0 HcmV?d00001 diff --git a/app/lib/snaps/preinstalled-snaps.ts b/app/lib/snaps/preinstalled-snaps.ts index 452d61bf75e7..cb75ba148b5a 100644 --- a/app/lib/snaps/preinstalled-snaps.ts +++ b/app/lib/snaps/preinstalled-snaps.ts @@ -1,6 +1,6 @@ import type { PreinstalledSnap } from '@metamask/snaps-controllers'; import MessageSigningSnap from '@metamask/message-signing-snap/dist/preinstalled-snap.json'; -///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) +///: BEGIN:ONLY_INCLUDE_IF(solana) import SolanaWalletSnap from '@metamask/solana-wallet-snap/dist/preinstalled-snap.json'; ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) @@ -9,7 +9,7 @@ import BitcoinWalletSnap from '@metamask/bitcoin-wallet-snap/dist/preinstalled-s const PREINSTALLED_SNAPS: readonly PreinstalledSnap[] = Object.freeze([ MessageSigningSnap as unknown as PreinstalledSnap, - ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + ///: BEGIN:ONLY_INCLUDE_IF(solana) SolanaWalletSnap as unknown as PreinstalledSnap, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(bitcoin) diff --git a/e2e/selectors/wallet/WalletView.selectors.js b/e2e/selectors/wallet/WalletView.selectors.js index c6ee77f5838c..94d8eae65af0 100644 --- a/e2e/selectors/wallet/WalletView.selectors.js +++ b/e2e/selectors/wallet/WalletView.selectors.js @@ -79,6 +79,9 @@ export const WalletViewSelectorsIDs = { CAROUSEL_FIFTH_SLIDE: 'carousel-fifth-slide', CAROUSEL_FIFTH_SLIDE_TITLE: 'carousel-fifth-slide-title', CAROUSEL_FIFTH_SLIDE_CLOSE_BUTTON: 'carousel-fifth-slide-close-button', + CAROUSEL_SIXTH_SLIDE: 'carousel-sixth-slide', + CAROUSEL_SIXTH_SLIDE_TITLE: 'carousel-sixth-slide-title', + CAROUSEL_SIXTH_SLIDE_CLOSE_BUTTON: 'carousel-sixth-slide-close-button', CAROUSEL_PROGRESS_DOTS: 'progress-dots', CAROUSEL_SLIDE: 'carousel-slide', }; diff --git a/locales/languages/en.json b/locales/languages/en.json index e316b8856a55..17c97b926a16 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -446,6 +446,10 @@ "multisrp": { "title": "Add multiple Secret Recovery Phrases", "subtitle": "Import and use multiple wallets in MetaMask" + }, + "solana": { + "title": "Solana is now supported", + "subtitle": "Create a Solana account to get started" } }, "wallet": { diff --git a/metro.transform.js b/metro.transform.js index 3d01299fa2e0..4d755ddff7b2 100644 --- a/metro.transform.js +++ b/metro.transform.js @@ -20,10 +20,17 @@ const availableFeatures = new Set([ 'keyring-snaps', 'multi-srp', 'bitcoin', + 'solana', ]); const mainFeatureSet = new Set(['preinstalled-snaps', 'multi-srp']); -const betaFeatureSet = new Set(['beta', 'preinstalled-snaps', 'keyring-snaps', 'multi-srp']); +const betaFeatureSet = new Set([ + 'beta', + 'preinstalled-snaps', + 'keyring-snaps', + 'multi-srp', + 'solana', +]); const flaskFeatureSet = new Set([ 'flask', 'preinstalled-snaps', @@ -31,6 +38,7 @@ const flaskFeatureSet = new Set([ 'keyring-snaps', 'multi-srp', 'bitcoin', + 'solana', ]); /** From 1da30b5eaa10084a8d29bd1afdb80e315652cf31 Mon Sep 17 00:00:00 2001 From: Alejandro Garcia Anglada Date: Fri, 25 Apr 2025 10:48:16 +0200 Subject: [PATCH 2/7] fix: tests + review comments --- .../__snapshots__/index.test.tsx.snap | 430 ++++++++++++++++++ app/components/UI/Carousel/constants.ts | 48 +- app/components/UI/Carousel/index.tsx | 39 +- 3 files changed, 468 insertions(+), 49 deletions(-) diff --git a/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap b/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap index 87ea76f6bb50..5980eef69de2 100644 --- a/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap @@ -23,6 +23,19 @@ exports[`Carousel should only render fund banner when all banners are dismissed + + + + + + + + + + banner.solana.title + + + banner.solana.subtitle + + + + + +  + + + + + + `; @@ -484,6 +699,19 @@ exports[`Carousel should render correctly 1`] = ` + + + + + + + + + + banner.solana.title + + + banner.solana.subtitle + + + + + +  + + + + + + `; diff --git a/app/components/UI/Carousel/constants.ts b/app/components/UI/Carousel/constants.ts index 975b020f3d4e..2e082baa09aa 100644 --- a/app/components/UI/Carousel/constants.ts +++ b/app/components/UI/Carousel/constants.ts @@ -21,6 +21,30 @@ import { SolScope } from '@metamask/keyring-api'; ///: END:ONLY_INCLUDE_IF export const PREDEFINED_SLIDES: CarouselSlide[] = [ + ///: BEGIN:ONLY_INCLUDE_IF(solana) + { + id: 'solana', + title: strings('banner.solana.title'), + description: strings('banner.solana.subtitle'), + undismissable: false, + navigation: { + type: 'function', + navigate: () => [ + Routes.MODAL.ROOT_MODAL_FLOW, + { + screen: Routes.SHEET.ADD_ACCOUNT, + params: { + clientType: WalletClientType.Solana, + scope: SolScope.Mainnet, + }, + }, + ], + }, + testID: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE, + testIDTitle: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE_TITLE, + testIDCloseButton: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE_CLOSE_BUTTON, + }, + ///: END:ONLY_INCLUDE_IF { id: 'card', title: strings('banner.card.title'), @@ -89,30 +113,6 @@ export const PREDEFINED_SLIDES: CarouselSlide[] = [ testIDCloseButton: WalletViewSelectorsIDs.CAROUSEL_FIFTH_SLIDE_CLOSE_BUTTON, }, ///: END:ONLY_INCLUDE_IF - ///: BEGIN:ONLY_INCLUDE_IF(solana) - { - id: 'solana', - title: strings('banner.solana.title'), - description: strings('banner.solana.subtitle'), - undismissable: false, - navigation: { - type: 'function', - navigate: () => [ - Routes.MODAL.ROOT_MODAL_FLOW, - { - screen: Routes.SHEET.ADD_ACCOUNT, - params: { - clientType: WalletClientType.Solana, - scope: SolScope.Mainnet, - }, - }, - ], - }, - testID: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE, - testIDTitle: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE_TITLE, - testIDCloseButton: WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE_CLOSE_BUTTON, - }, - ///: END:ONLY_INCLUDE_IF ]; export const BANNER_IMAGES: Record = { diff --git a/app/components/UI/Carousel/index.tsx b/app/components/UI/Carousel/index.tsx index d99ac18606ac..453e83d88be5 100644 --- a/app/components/UI/Carousel/index.tsx +++ b/app/components/UI/Carousel/index.tsx @@ -66,34 +66,23 @@ export const Carousel: FC = ({ style }) => { const visibleSlides = useMemo( () => - slidesConfig - .filter((slide) => { - ///: BEGIN:ONLY_INCLUDE_IF(solana) - if ( - slide.id === 'solana' && - selectedAccount?.type === SolAccountType.DataAccount - ) { - return false; - } - ///: END:ONLY_INCLUDE_IF - - if (slide.id === 'fund' && isZeroBalance) { - return true; - } - return !dismissedBanners.includes(slide.id); - }) + slidesConfig.filter((slide) => { ///: BEGIN:ONLY_INCLUDE_IF(solana) - // prioritize Solana slide - .sort((a, b) => { - if (a.id === 'solana' && b.id !== 'solana') { - return -1; - } - return 1; - }), - ///: END:ONLY_INCLUDE_IF + if ( + slide.id === 'solana' && + selectedAccount?.type === SolAccountType.DataAccount + ) { + return false; + } + ///: END:ONLY_INCLUDE_IF + + if (slide.id === 'fund' && isZeroBalance) { + return true; + } + return !dismissedBanners.includes(slide.id); + }), [slidesConfig, isZeroBalance, dismissedBanners, selectedAccount], ); - const isSingleSlide = visibleSlides.length === 1; const openUrl = From ab7b534a5b8affa53447c472562aa3c898395931 Mon Sep 17 00:00:00 2001 From: Alejandro Garcia Anglada Date: Fri, 25 Apr 2025 11:23:19 +0200 Subject: [PATCH 3/7] fix: build --- app/components/UI/Carousel/index.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/components/UI/Carousel/index.tsx b/app/components/UI/Carousel/index.tsx index 453e83d88be5..a3a4af280565 100644 --- a/app/components/UI/Carousel/index.tsx +++ b/app/components/UI/Carousel/index.tsx @@ -27,8 +27,10 @@ import { selectSelectedInternalAccount, selectLastSelectedSolanaAccount, } from '../../../selectors/accountsController'; +///: BEGIN:ONLY_INCLUDE_IF(solana) import { SolAccountType } from '@metamask/keyring-api'; import Engine from '../../../core/Engine'; +///: END:ONLY_INCLUDE_IF export const Carousel: FC = ({ style }) => { const [selectedIndex, setSelectedIndex] = useState(0); @@ -96,9 +98,8 @@ export const Carousel: FC = ({ style }) => { (slideId: string, navigation: NavigationAction) => { const extraProperties: Record = {}; - const isSolanaBanner = slideId === 'solana'; - ///: BEGIN:ONLY_INCLUDE_IF(solana) + const isSolanaBanner = slideId === 'solana'; if (isSolanaBanner && lastSelectedSolanaAccount) { extraProperties.action = 'redirect-solana-account'; } else if (isSolanaBanner && !lastSelectedSolanaAccount) { @@ -134,7 +135,14 @@ export const Carousel: FC = ({ style }) => { return navigate(navigation.route); } }, - [trackEvent, createEventBuilder, lastSelectedSolanaAccount, navigate], + [ + trackEvent, + createEventBuilder, + navigate, + ///: BEGIN:ONLY_INCLUDE_IF(solana) + lastSelectedSolanaAccount, + ///: END:ONLY_INCLUDE_IF + ], ); const handleClose = useCallback( From ee20375c281ec8060d93d31bfb62e3c6dba624c4 Mon Sep 17 00:00:00 2001 From: Alejandro Garcia Anglada Date: Fri, 25 Apr 2025 11:58:51 +0200 Subject: [PATCH 4/7] fix: build --- app/components/UI/Carousel/index.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/components/UI/Carousel/index.tsx b/app/components/UI/Carousel/index.tsx index a3a4af280565..3ef0c4ee9528 100644 --- a/app/components/UI/Carousel/index.tsx +++ b/app/components/UI/Carousel/index.tsx @@ -23,11 +23,11 @@ import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletV import { PREDEFINED_SLIDES, BANNER_IMAGES } from './constants'; import { useStyles } from '../../../component-library/hooks'; import { selectDismissedBanners } from '../../../selectors/banner'; +///: BEGIN:ONLY_INCLUDE_IF(solana) import { selectSelectedInternalAccount, selectLastSelectedSolanaAccount, } from '../../../selectors/accountsController'; -///: BEGIN:ONLY_INCLUDE_IF(solana) import { SolAccountType } from '@metamask/keyring-api'; import Engine from '../../../core/Engine'; ///: END:ONLY_INCLUDE_IF @@ -42,10 +42,12 @@ export const Carousel: FC = ({ style }) => { const { navigate } = useNavigation(); const { styles } = useStyles(styleSheet, { style }); const dismissedBanners = useSelector(selectDismissedBanners); + ///: BEGIN:ONLY_INCLUDE_IF(solana) const selectedAccount = useSelector(selectSelectedInternalAccount); const lastSelectedSolanaAccount = useSelector( selectLastSelectedSolanaAccount, ); + ///: END:ONLY_INCLUDE_IF const isZeroBalance = selectedAccountMultichainBalance?.totalFiatBalance === 0; @@ -83,7 +85,14 @@ export const Carousel: FC = ({ style }) => { } return !dismissedBanners.includes(slide.id); }), - [slidesConfig, isZeroBalance, dismissedBanners, selectedAccount], + [ + slidesConfig, + isZeroBalance, + dismissedBanners, + ///: BEGIN:ONLY_INCLUDE_IF(solana) + selectedAccount, + ///: END:ONLY_INCLUDE_IF + ], ); const isSingleSlide = visibleSlides.length === 1; From 3d40a4162d478126e9c96ae4d30cdd35a4c74c51 Mon Sep 17 00:00:00 2001 From: Alejandro Garcia Anglada Date: Fri, 25 Apr 2025 14:19:34 +0200 Subject: [PATCH 5/7] chore: add unit test --- .../__snapshots__/index.test.tsx.snap | 1107 +++++++++++++++++ app/components/UI/Carousel/constants.ts | 2 +- app/components/UI/Carousel/index.test.tsx | 33 + 3 files changed, 1141 insertions(+), 1 deletion(-) diff --git a/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap b/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap index 5980eef69de2..9b0702550821 100644 --- a/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap @@ -1,5 +1,1112 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Carousel should not render solana banner if user has a solana account 1`] = ` + + + + + + + + + + + + + + banner.card.title + + + banner.card.subtitle + + + + + +  + + + + + + + + + + + + + + + banner.fund.title + + + banner.fund.subtitle + + + + + + + + + + + + + + + + banner.cashout.title + + + banner.cashout.subtitle + + + + + +  + + + + + + + + + + + + + + + banner.aggregated.title + + + banner.aggregated.subtitle + + + + + +  + + + + + + + + + + + + + + + banner.multisrp.title + + + banner.multisrp.subtitle + + + + + +  + + + + + + + + + + + + + + + + +`; + exports[`Carousel should only render fund banner when all banners are dismissed 1`] = ` = { multisrp: multiSrpImage, ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(solana) - solana: solanaImage as ImageSourcePropType, + solana: solanaImage, ///: END:ONLY_INCLUDE_IF }; diff --git a/app/components/UI/Carousel/index.test.tsx b/app/components/UI/Carousel/index.test.tsx index abc5e12302cc..e8a32261057e 100644 --- a/app/components/UI/Carousel/index.test.tsx +++ b/app/components/UI/Carousel/index.test.tsx @@ -5,6 +5,7 @@ import { Linking } from 'react-native'; import Carousel from './'; import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors'; import { backgroundState } from '../../../util/test/initial-root-state'; +import { SolAccountType } from '@metamask/keyring-api'; jest.mock('../../../core/Engine', () => ({ getTotalEvmFiatAccountBalance: jest.fn(), @@ -255,4 +256,36 @@ describe('Carousel', () => { expect(flatList).toBeTruthy(); }); + + it('should not render solana banner if user has a solana account', () => { + (useSelector as jest.Mock).mockImplementation((selector) => + selector({ + banners: { + dismissedBanners: [], + }, + engine: { + backgroundState: { + ...backgroundState, + AccountsController: { + internalAccounts: { + selectedAccount: '1', + accounts: { + '1': { + address: '0xSomeAddress', + type: SolAccountType.DataAccount, + }, + }, + }, + }, + }, + }, + }), + ); + + const { queryByTestId } = render(); + const solanaBanner = queryByTestId( + WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE, + ); + expect(solanaBanner).toBeNull(); + }); }); From 16c67cf3dfe0d01557dec2a921fa69e84135a328 Mon Sep 17 00:00:00 2001 From: Alejandro Garcia Anglada Date: Fri, 25 Apr 2025 15:06:00 +0200 Subject: [PATCH 6/7] chore: fix test --- .../__snapshots__/index.test.tsx.snap | 1107 ----------------- 1 file changed, 1107 deletions(-) diff --git a/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap b/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap index 9b0702550821..5980eef69de2 100644 --- a/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap +++ b/app/components/UI/Carousel/__snapshots__/index.test.tsx.snap @@ -1,1112 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Carousel should not render solana banner if user has a solana account 1`] = ` - - - - - - - - - - - - - - banner.card.title - - - banner.card.subtitle - - - - - -  - - - - - - - - - - - - - - - banner.fund.title - - - banner.fund.subtitle - - - - - - - - - - - - - - - - banner.cashout.title - - - banner.cashout.subtitle - - - - - -  - - - - - - - - - - - - - - - banner.aggregated.title - - - banner.aggregated.subtitle - - - - - -  - - - - - - - - - - - - - - - banner.multisrp.title - - - banner.multisrp.subtitle - - - - - -  - - - - - - - - - - - - - - - - -`; - exports[`Carousel should only render fund banner when all banners are dismissed 1`] = ` Date: Mon, 28 Apr 2025 09:55:37 +0200 Subject: [PATCH 7/7] chore: add more test --- app/components/UI/Carousel/index.test.tsx | 41 ++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/app/components/UI/Carousel/index.test.tsx b/app/components/UI/Carousel/index.test.tsx index e8a32261057e..ded8ca62e7cc 100644 --- a/app/components/UI/Carousel/index.test.tsx +++ b/app/components/UI/Carousel/index.test.tsx @@ -6,6 +6,7 @@ import Carousel from './'; import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors'; import { backgroundState } from '../../../util/test/initial-root-state'; import { SolAccountType } from '@metamask/keyring-api'; +import Engine from '../../../core/Engine'; jest.mock('../../../core/Engine', () => ({ getTotalEvmFiatAccountBalance: jest.fn(), @@ -48,6 +49,7 @@ jest.mock('@react-navigation/native', () => ({ jest.mock('../../../core/Engine', () => ({ getTotalEvmFiatAccountBalance: jest.fn(), + setSelectedAddress: jest.fn(), })); const selectShowFiatInTestnets = jest.fn(); @@ -257,7 +259,7 @@ describe('Carousel', () => { expect(flatList).toBeTruthy(); }); - it('should not render solana banner if user has a solana account', () => { + it('does not render solana banner if user has a solana account', () => { (useSelector as jest.Mock).mockImplementation((selector) => selector({ banners: { @@ -288,4 +290,41 @@ describe('Carousel', () => { ); expect(solanaBanner).toBeNull(); }); + + it('changes to a solana address if user has a solana account', async () => { + (useSelector as jest.Mock).mockImplementation((selector) => + selector({ + banners: { + dismissedBanners: [], + }, + engine: { + backgroundState: { + ...backgroundState, + AccountsController: { + internalAccounts: { + selectedAccount: '1', + accounts: { + '1': { + address: '0xSomeAddress', + }, + '2': { + address: 'SomeSolanaAddress', + type: SolAccountType.DataAccount, + }, + }, + }, + }, + }, + }, + }), + ); + + const { getByTestId } = render(); + const solanaBanner = getByTestId( + WalletViewSelectorsIDs.CAROUSEL_SIXTH_SLIDE, + ); + fireEvent.press(solanaBanner); + + expect(Engine.setSelectedAddress).toHaveBeenCalledWith('SomeSolanaAddress'); + }); });