From 18963da5123738f97c22e8f08783e59e957f3361 Mon Sep 17 00:00:00 2001 From: Curtis David Date: Tue, 9 Dec 2025 19:16:16 -0500 Subject: [PATCH 1/2] test: fix flaky scroll to predictions tab on activity view (#23827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The purpose of this PR is to slow down the swipe to reveal the Predictions tab and make the $48.16 balance assertion run on all platforms in the claim positions test. ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- > [!NOTE] > Slow the swipe to the Predictions tab, assert `$48.16` on all platforms, and remove an unnecessary delay in the withdraw test. > > - **E2E Tests**: > - **Activities View** (`e2e/pages/Transactions/ActivitiesView.ts`): > - Slow down swipe to reveal `Predictions` tab by adding `speed: 'slow'` to `Gestures.swipe`. > - **Predict Claim Positions** (`e2e/specs/predict/predict-claim-positions.spec.ts`): > - Always assert final balance `$48.16` (remove iOS-only condition). > - **Predict Withdraw** (`e2e/specs/predict/predict-withdraw.spec.ts`): > - Remove unnecessary 9s delay; keep TODO for withdraw flow. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3376983f3006f4451ea08a2d4c1c666b7621c2c1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- e2e/pages/Transactions/ActivitiesView.ts | 1 + e2e/specs/predict/predict-claim-positions.spec.ts | 5 +---- e2e/specs/predict/predict-withdraw.spec.ts | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/e2e/pages/Transactions/ActivitiesView.ts b/e2e/pages/Transactions/ActivitiesView.ts index 0a37fa1e788..bc3fae18ce4 100644 --- a/e2e/pages/Transactions/ActivitiesView.ts +++ b/e2e/pages/Transactions/ActivitiesView.ts @@ -127,6 +127,7 @@ class ActivitiesView { // Swipe left on the tabs bar to reveal the Predictions tab (it may be off-screen) await Gestures.swipe(this.tabsBar, 'left', { percentage: 0.5, + speed: 'slow', elemDescription: 'Activity View Tabs Bar', }); await Gestures.waitAndTap(this.predictionsTab, { diff --git a/e2e/specs/predict/predict-claim-positions.spec.ts b/e2e/specs/predict/predict-claim-positions.spec.ts index 7760c1176bc..12fb3f22017 100644 --- a/e2e/specs/predict/predict-claim-positions.spec.ts +++ b/e2e/specs/predict/predict-claim-positions.spec.ts @@ -123,10 +123,7 @@ describe(SmokePredictions('Predictions'), () => { await TabBarComponent.tapWallet(); - // Verify balance on iOS only. Android balances take a while to refresh. - if (device.getPlatform() === 'ios') { - await Assertions.expectTextDisplayed('$48.16'); - } + await Assertions.expectTextDisplayed('$48.16'); }, ); }); diff --git a/e2e/specs/predict/predict-withdraw.spec.ts b/e2e/specs/predict/predict-withdraw.spec.ts index e44652b92b6..c90899ad26e 100644 --- a/e2e/specs/predict/predict-withdraw.spec.ts +++ b/e2e/specs/predict/predict-withdraw.spec.ts @@ -39,8 +39,6 @@ describe(SmokeTrade('Predictions'), () => { await loginToApp(); await TabBarComponent.tapActions(); await WalletActionsBottomSheet.tapPredictButton(); - await new Promise((resolve) => setTimeout(resolve, 9000)); - // TODO: Add withdraw flow }, ); From 581637f1d2df7f56bd1311314edf4f57e948c695 Mon Sep 17 00:00:00 2001 From: sophieqgu <37032128+sophieqgu@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:44:05 -0500 Subject: [PATCH 2/2] chore: Update card welcome splash screen (#23706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update nav options Update Signup Update ConfirmEmail Update ConfirmPhoneNumber Update Address Update VerifyIdentity Update PersonalDetails Update KYC success and failure ## **Description** Update card onboarding flow according to new design [CARD-213](https://consensyssoftware.atlassian.net/browse/CARD-213) [CARD-216](https://consensyssoftware.atlassian.net/browse/CARD-216) [CARD-217](https://consensyssoftware.atlassian.net/browse/CARD-217) [CARD-220](https://consensyssoftware.atlassian.net/browse/CARD-220) Half of [CARD-221](https://consensyssoftware.atlassian.net/browse/CARD-221) since address search and autocompletion requires a new library [CARD-222](https://consensyssoftware.atlassian.net/browse/CARD-222) [CARD-223](https://consensyssoftware.atlassian.net/browse/CARD-223) [CARD-224](https://consensyssoftware.atlassian.net/browse/CARD-224) ## **Changelog** CHANGELOG entry: Update card onboarding flow according to new design ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** Simulator Screenshot - E2E Test -
2025-12-05 at 14 10 51 Simulator Screenshot - E2E Test -
2025-12-05 at 14 10 56 Simulator Screenshot - E2E Test -
2025-12-05 at 14 12 07 Simulator Screenshot - E2E Test -
2025-12-08 at 10 36 23 Simulator Screenshot - E2E Test -
2025-12-05 at 14 15 16 Simulator Screenshot - E2E Test -
2025-12-08 at 10 42 57 Simulator Screenshot - E2E Test -
2025-12-09 at 12 11 02 Simulator Screenshot - E2E Test -
2025-12-05 at 14 39 46 Simulator Screenshot - E2E Test -
2025-12-05 at 14 34 16 Simulator Screenshot - E2E Test -
2025-12-05 at 14 34 28 Simulator Screenshot - E2E Test -
2025-12-08 at 10 44 00 Simulator Screenshot - E2E Test -
2025-12-05 at 14 35 40 Simulator Screenshot - E2E Test -
2025-12-05 at 14 35 50 ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. [CARD-213]: https://consensyssoftware.atlassian.net/browse/CARD-213?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-216]: https://consensyssoftware.atlassian.net/browse/CARD-216?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-217]: https://consensyssoftware.atlassian.net/browse/CARD-217?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-220]: https://consensyssoftware.atlassian.net/browse/CARD-220?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-221]: https://consensyssoftware.atlassian.net/browse/CARD-221?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-222]: https://consensyssoftware.atlassian.net/browse/CARD-222?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-223]: https://consensyssoftware.atlassian.net/browse/CARD-223?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CARD-224]: https://consensyssoftware.atlassian.net/browse/CARD-224?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- > [!NOTE] > Redesigns the Card onboarding and welcome experiences, updates navigation flow and copy, modernizes form inputs/UX, and adds country flag utility with comprehensive test updates. > > - **UI/UX**: > - **Welcome Screen**: Rebuilt `CardWelcome` with responsive styles, new imagery, CTA (“Apply now”), “Not now” to `Routes.WALLET.HOME`, and metrics. > - **OnboardingStep Layout**: Updated typography and removed legacy mockup image; adjusted title/description styling. > - **Screens**: Refreshed `VerifyIdentity`, `ValidatingKYC` (image, description, close button), `KYCFailed` (image, close to Home), and `Complete` (new content and confirm button). > - **Navigation/Flow**: > - Added `Routes.CARD.ONBOARDING.COMPLETE`; initial-route logic now finishes at `COMPLETE` (removed mailing-step branching). > - `ValidatingKYC` now resets to `COMPLETE` on success or `KYC_FAILED` on reject; KYC modal close goes to `PERSONAL_DETAILS`. > - Default nav options simplified (back-only), welcome header hidden; new `PersonalDetailsNavigationOptions` back to `VERIFY_IDENTITY`. > - **Forms/Inputs**: > - `ConfirmEmail`/`ConfirmPhoneNumber`: replaced code cells with `TextField`, numeric filtering, auto-submit on 6 digits, revised resend UX (“Didn’t receive… Resend”). > - `SetPhoneNumber`: country selector shows flag and `ISO-CC` value; updated legal/copy. > - `PhysicalAddress`: trimmed consents to e-sign only, added country select (read-only), assumes same mailing, navigates to `VALIDATING_KYC` when registered; action/errors moved below button. > - `PersonalDetails` and `SignUp`: copy tweaks, validation improvements (success icons, disabled logic, reset navigation on continue). > - **Utilities**: > - Added `countryCodeToFlag` helper with unit tests. > - **i18n**: > - Overhauled onboarding copy/labels/flows across locales. > - **Tests**: > - Broad updates to align with new UI, flows, metrics, and navigation; added new tests for flags, resend behavior, and navigation resets. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d314e6cc00d95ee0454fcc5f0a3f6da2b47d6293. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../Views/CardWelcome/CardWelcome.styles.ts | 122 ++- .../Views/CardWelcome/CardWelcome.test.tsx | 99 +- .../UI/Card/Views/CardWelcome/CardWelcome.tsx | 108 ++- .../components/Onboarding/Complete.test.tsx | 79 +- .../Card/components/Onboarding/Complete.tsx | 42 +- .../Onboarding/ConfirmEmail.test.tsx | 71 +- .../components/Onboarding/ConfirmEmail.tsx | 119 +-- .../Onboarding/ConfirmPhoneNumber.test.tsx | 279 +++--- .../Onboarding/ConfirmPhoneNumber.tsx | 166 ++-- .../components/Onboarding/KYCFailed.test.tsx | 177 ++-- .../Card/components/Onboarding/KYCFailed.tsx | 29 +- .../Card/components/Onboarding/KYCWebview.tsx | 2 +- .../Onboarding/MailingAddress.test.tsx | 12 +- .../Onboarding/OnboardingStep.test.tsx | 24 +- .../components/Onboarding/OnboardingStep.tsx | 19 +- .../Onboarding/PersonalDetails.test.tsx | 4 +- .../components/Onboarding/PersonalDetails.tsx | 37 +- .../Onboarding/PhysicalAddress.test.tsx | 864 +++++------------- .../components/Onboarding/PhysicalAddress.tsx | 422 ++------- .../Onboarding/SetPhoneNumber.test.tsx | 7 +- .../components/Onboarding/SetPhoneNumber.tsx | 51 +- .../UI/Card/components/Onboarding/SignUp.tsx | 50 +- .../Onboarding/ValidatingKYC.test.tsx | 715 +++++++++++++-- .../components/Onboarding/ValidatingKYC.tsx | 59 +- .../Onboarding/VerifyIdentity.test.tsx | 103 +++ .../components/Onboarding/VerifyIdentity.tsx | 48 +- .../Card/routes/OnboardingNavigator.test.tsx | 762 +++++---------- .../UI/Card/routes/OnboardingNavigator.tsx | 48 +- app/components/UI/Card/routes/index.tsx | 42 +- .../UI/Card/util/countryCodeToFlag.test.ts | 169 ++++ .../UI/Card/util/countryCodeToFlag.ts | 34 + app/images/mm-card-KYC-pending.png | Bin 0 -> 1680582 bytes app/images/mm-card-onboarding-failed.png | Bin 0 -> 722275 bytes app/images/mm-card-onboarding-success.png | Bin 0 -> 1022300 bytes app/images/mm-card-verify.png | Bin 0 -> 2354453 bytes app/images/mm-card-welcome.png | Bin 0 -> 1075710 bytes locales/languages/en.json | 109 +-- 37 files changed, 2515 insertions(+), 2357 deletions(-) create mode 100644 app/components/UI/Card/util/countryCodeToFlag.test.ts create mode 100644 app/components/UI/Card/util/countryCodeToFlag.ts create mode 100644 app/images/mm-card-KYC-pending.png create mode 100644 app/images/mm-card-onboarding-failed.png create mode 100644 app/images/mm-card-onboarding-success.png create mode 100644 app/images/mm-card-verify.png create mode 100644 app/images/mm-card-welcome.png diff --git a/app/components/UI/Card/Views/CardWelcome/CardWelcome.styles.ts b/app/components/UI/Card/Views/CardWelcome/CardWelcome.styles.ts index 8f2771b8dd6..8c99821fef3 100644 --- a/app/components/UI/Card/Views/CardWelcome/CardWelcome.styles.ts +++ b/app/components/UI/Card/Views/CardWelcome/CardWelcome.styles.ts @@ -1,30 +1,120 @@ -import { StyleSheet } from 'react-native'; +import { Platform, StyleSheet, Dimensions } from 'react-native'; +import { colors as importedColors } from '../../../../../styles/common'; import { Theme } from '@metamask/design-tokens'; -const createStyles = (theme: Theme, deviceWidth: number) => +// Responsive scaling utilities +const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); + +// Platform-specific base dimensions +const BASE_WIDTH = 375; +const BASE_HEIGHT_IOS = 812; // iPhone X/11/12/13/14/15 Pro base +const BASE_HEIGHT_ANDROID = 736; // Common Android base + +const MIN_SCREEN_HEIGHT_FOR_SMALL_SCREEN_STYLES = 750; + +// Calculate platform-aware scaling factors +const isIOS = Platform.OS === 'ios'; +const baseHeight = isIOS ? BASE_HEIGHT_IOS : BASE_HEIGHT_ANDROID; + +const widthScale = screenWidth / BASE_WIDTH; +const heightScale = screenHeight / baseHeight; + +// Use more conservative scaling to prevent excessive padding +const scale = Math.min(widthScale, heightScale); +const conservativeScale = Math.min(scale, 1.2); // Cap scaling at 120% + +// Platform-aware responsive scaling functions +const scaleSize = (size: number) => Math.ceil(size * conservativeScale); +const scaleFont = (size: number) => Math.ceil(size * conservativeScale); + +// For vertical spacing, use percentage of available height instead of pure scaling +const scaleVertical = (size: number) => { + // Use percentage of screen height for more consistent spacing + const percentage = size / baseHeight; + return Math.ceil(screenHeight * percentage); +}; + +const scaleHorizontal = (size: number) => Math.ceil(size * widthScale); + +const createStyles = (theme: Theme) => StyleSheet.create({ - safeAreaView: { + pageContainer: { flex: 1, + position: 'relative', + maxHeight: '100%', + width: '100%', + backgroundColor: theme.colors.accent03.dark, }, - container: { + imageContainer: { flex: 1, - backgroundColor: theme.colors.background.default, - paddingHorizontal: 16, - justifyContent: 'space-between', - }, - wrapper: { alignItems: 'center', + justifyContent: 'center', + marginBottom: scaleVertical(16), }, - imageWrapper: { + image: { + width: '100%', + height: '100%', + resizeMode: 'cover', + }, + contentContainer: { + flex: 1, + }, + headerContainer: { alignItems: 'center', + paddingHorizontal: scaleHorizontal(16), + paddingVertical: scaleVertical(16), }, - image: { - width: deviceWidth * 0.9, - height: deviceWidth, + title: { + fontFamily: Platform.OS === 'ios' ? 'MM Poly' : 'MM Poly Regular', + fontWeight: '400', + // make it smaller on smaller screens + fontSize: + screenHeight < MIN_SCREEN_HEIGHT_FOR_SMALL_SCREEN_STYLES ? 40 : 50, + lineHeight: + screenHeight < MIN_SCREEN_HEIGHT_FOR_SMALL_SCREEN_STYLES ? 40 : 50, // 100% of font size + letterSpacing: 0, + textAlign: 'center', + paddingTop: scaleVertical( + screenHeight < MIN_SCREEN_HEIGHT_FOR_SMALL_SCREEN_STYLES ? 8 : 12, + ), + color: theme.colors.accent03.light, + }, + titleDescription: { + // make it smaller on smaller screens + fontSize: + screenHeight < MIN_SCREEN_HEIGHT_FOR_SMALL_SCREEN_STYLES ? 14 : 16, + paddingTop: scaleVertical(10), + paddingHorizontal: scaleHorizontal(8), + textAlign: 'center', + fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto', // Default system font + fontWeight: '500', + lineHeight: 24, // Line Height BodyMd + letterSpacing: 0, + color: theme.colors.accent03.light, + }, + footerContainer: { + display: 'flex', + rowGap: scaleVertical(8), + paddingHorizontal: scaleHorizontal(30), + }, + getStartedButton: { + borderRadius: scaleSize(12), + backgroundColor: importedColors.white, + }, + getStartedButtonText: { + color: importedColors.btnBlack, + fontWeight: '600', + fontSize: scaleFont(16), + }, + notNowButton: { + borderRadius: scaleSize(12), + backgroundColor: importedColors.transparent, + borderWidth: 0, }, - button: { - marginTop: 48, - marginBottom: 32, + notNowButtonText: { + color: importedColors.white, + fontWeight: '500', + fontSize: scaleFont(16), }, }); diff --git a/app/components/UI/Card/Views/CardWelcome/CardWelcome.test.tsx b/app/components/UI/Card/Views/CardWelcome/CardWelcome.test.tsx index 7cac336f11f..86d686b565d 100644 --- a/app/components/UI/Card/Views/CardWelcome/CardWelcome.test.tsx +++ b/app/components/UI/Card/Views/CardWelcome/CardWelcome.test.tsx @@ -6,9 +6,16 @@ import CardWelcome from './CardWelcome'; import { CardWelcomeSelectors } from '../../../../../../e2e/selectors/Card/CardWelcome.selectors'; import { strings } from '../../../../../../locales/i18n'; import Routes from '../../../../../constants/navigation/Routes'; +import { MetaMetricsEvents } from '../../../../hooks/useMetrics'; // Mocks const mockNavigate = jest.fn(); +const mockTrackEvent = jest.fn(); +const mockBuild = jest.fn(); +const mockAddProperties = jest.fn(() => ({ build: mockBuild })); +const mockCreateEventBuilder = jest.fn(() => ({ + addProperties: mockAddProperties, +})); jest.mock('@react-navigation/native', () => { const actual = jest.requireActual('@react-navigation/native'); @@ -20,25 +27,31 @@ jest.mock('@react-navigation/native', () => { }; }); +jest.mock('../../../../hooks/useMetrics', () => ({ + useMetrics: () => ({ + trackEvent: mockTrackEvent, + createEventBuilder: mockCreateEventBuilder, + }), + MetaMetricsEvents: { + CARD_VIEWED: 'Card Viewed', + CARD_BUTTON_CLICKED: 'Card Button Clicked', + }, +})); + jest.mock('../../../../../../locales/i18n', () => ({ strings: (key: string) => { const map: Record = { 'card.card_onboarding.title': 'Enable MetaMask Card features', 'card.card_onboarding.description': 'Change your spending token and network by signing in with your Crypto Life email and password.', - 'card.card_onboarding.verify_account_button': 'Sign in', - 'card.card_onboarding.non_cardholder_title': 'Welcome to MetaMask Card', - 'card.card_onboarding.non_cardholder_description': - 'MetaMask Card is the free and easy way to spend your crypto, with rich onchain rewards.', - 'card.card_onboarding.non_cardholder_verify_account_button': - 'Get started', - 'card.card': 'Card', + 'card.card_onboarding.apply_now_button': 'Sign in', + 'predict.gtm_content.not_now': 'Not now', }; return map[key] || key; }, })); -jest.mock('../../../../../images/metal-card.png', () => 1); +jest.mock('../../../../../images/mm-card-welcome.png', () => 1); jest.mock('../../../../../util/theme', () => ({ useTheme: () => ({ colors: { background: { default: '#fff' } } }), @@ -57,9 +70,11 @@ describe('CardWelcome', () => { beforeEach(() => { jest.clearAllMocks(); mockNavigate.mockClear(); + mockTrackEvent.mockClear(); + mockCreateEventBuilder.mockClear(); }); - describe('Non-cardholder flow', () => { + describe('Render', () => { beforeEach(() => { store = createTestStore({ cardholderAccounts: [] }); }); @@ -81,9 +96,10 @@ describe('CardWelcome', () => { expect( getByTestId(CardWelcomeSelectors.VERIFY_ACCOUNT_BUTTON), ).toBeTruthy(); + expect(getByTestId('predict-gtm-not-now-button')).toBeTruthy(); }); - it('displays non-cardholder title when no cardholder accounts exist', () => { + it('displays correct title and description', () => { const { getByTestId } = render( @@ -92,69 +108,62 @@ describe('CardWelcome', () => { expect( getByTestId(CardWelcomeSelectors.WELCOME_TO_CARD_TITLE_TEXT), - ).toHaveTextContent(strings('card.card_onboarding.non_cardholder_title')); + ).toHaveTextContent(strings('card.card_onboarding.title')); + expect( + getByTestId(CardWelcomeSelectors.WELCOME_TO_CARD_DESCRIPTION_TEXT), + ).toHaveTextContent(strings('card.card_onboarding.description')); }); - it('displays non-cardholder description when no cardholder accounts exist', () => { - const { getByTestId } = render( + it('tracks view event on mount', () => { + render( , ); - expect( - getByTestId(CardWelcomeSelectors.WELCOME_TO_CARD_DESCRIPTION_TEXT), - ).toHaveTextContent( - strings('card.card_onboarding.non_cardholder_description'), + expect(mockCreateEventBuilder).toHaveBeenCalledWith( + MetaMetricsEvents.CARD_VIEWED, ); + expect(mockTrackEvent).toHaveBeenCalled(); }); + }); - it('navigates to onboarding root when verify account button pressed', () => { + describe('Interactions', () => { + it('navigates to wallet home when "Not Now" is pressed', () => { + store = createTestStore(); const { getByTestId } = render( , ); - fireEvent.press(getByTestId(CardWelcomeSelectors.VERIFY_ACCOUNT_BUTTON)); + fireEvent.press(getByTestId('predict-gtm-not-now-button')); - expect(mockNavigate).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenCalledWith(Routes.CARD.ONBOARDING.ROOT); + expect(mockNavigate).toHaveBeenCalledWith(Routes.WALLET.HOME); }); }); - describe('Cardholder flow', () => { - beforeEach(() => { - store = createTestStore({ - cardholderAccounts: ['0x1234567890abcdef'], - }); - }); - - it('displays cardholder title when cardholder accounts exist', () => { + describe('Navigation Flow', () => { + it('navigates to onboarding root when verify account button pressed (Non-cardholder)', () => { + store = createTestStore({ cardholderAccounts: [] }); const { getByTestId } = render( , ); - expect( - getByTestId(CardWelcomeSelectors.WELCOME_TO_CARD_TITLE_TEXT), - ).toHaveTextContent(strings('card.card_onboarding.title')); - }); + fireEvent.press(getByTestId(CardWelcomeSelectors.VERIFY_ACCOUNT_BUTTON)); - it('displays cardholder description when cardholder accounts exist', () => { - const { getByTestId } = render( - - - , + expect(mockNavigate).toHaveBeenCalledWith(Routes.CARD.ONBOARDING.ROOT); + expect(mockCreateEventBuilder).toHaveBeenCalledWith( + MetaMetricsEvents.CARD_BUTTON_CLICKED, ); - - expect( - getByTestId(CardWelcomeSelectors.WELCOME_TO_CARD_DESCRIPTION_TEXT), - ).toHaveTextContent(strings('card.card_onboarding.description')); }); - it('navigates to authentication when verify account button pressed', () => { + it('navigates to authentication when verify account button pressed (Cardholder)', () => { + store = createTestStore({ + cardholderAccounts: ['0x1234567890abcdef'], + }); const { getByTestId } = render( @@ -163,8 +172,10 @@ describe('CardWelcome', () => { fireEvent.press(getByTestId(CardWelcomeSelectors.VERIFY_ACCOUNT_BUTTON)); - expect(mockNavigate).toHaveBeenCalledTimes(1); expect(mockNavigate).toHaveBeenCalledWith(Routes.CARD.AUTHENTICATION); + expect(mockCreateEventBuilder).toHaveBeenCalledWith( + MetaMetricsEvents.CARD_BUTTON_CLICKED, + ); }); }); }); diff --git a/app/components/UI/Card/Views/CardWelcome/CardWelcome.tsx b/app/components/UI/Card/Views/CardWelcome/CardWelcome.tsx index e351f7c0fe3..22ea8d30001 100644 --- a/app/components/UI/Card/Views/CardWelcome/CardWelcome.tsx +++ b/app/components/UI/Card/Views/CardWelcome/CardWelcome.tsx @@ -1,6 +1,6 @@ import { useNavigation } from '@react-navigation/native'; -import React, { useCallback, useMemo, useEffect } from 'react'; -import { Image, useWindowDimensions, View } from 'react-native'; +import React, { useCallback, useEffect } from 'react'; +import { Image, View } from 'react-native'; import { strings } from '../../../../../../locales/i18n'; import Button, { @@ -9,10 +9,9 @@ import Button, { ButtonWidthTypes, } from '../../../../../component-library/components/Buttons/Button'; import Text, { - TextColor, TextVariant, } from '../../../../../component-library/components/Texts/Text'; -import MM_CARDS_MOCKUP from '../../../../../images/mm-cards-mockup.png'; +import MM_CARDS_WELCOME from '../../../../../images/mm-card-welcome.png'; import { useTheme } from '../../../../../util/theme'; import createStyles from './CardWelcome.styles'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -22,14 +21,14 @@ import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics'; import { CardActions, CardScreens } from '../../util/metrics'; import { selectHasCardholderAccounts } from '../../../../../core/redux/slices/card'; import { useSelector } from 'react-redux'; +import ButtonBase from '../../../../../component-library/components/Buttons/Button/foundation/ButtonBase'; const CardWelcome = () => { const { trackEvent, createEventBuilder } = useMetrics(); const { navigate } = useNavigation(); const hasCardholderAccounts = useSelector(selectHasCardholderAccounts); const theme = useTheme(); - const deviceWidth = useWindowDimensions().width; - const styles = createStyles(theme, deviceWidth); + const styles = createStyles(theme); useEffect(() => { trackEvent( @@ -41,25 +40,9 @@ const CardWelcome = () => { ); }, [trackEvent, createEventBuilder]); - const cardWelcomeCopies = useMemo(() => { - if (hasCardholderAccounts) { - return { - title: strings('card.card_onboarding.title'), - description: strings('card.card_onboarding.description'), - verify_account_button: strings( - 'card.card_onboarding.verify_account_button', - ), - }; - } - - return { - title: strings('card.card_onboarding.non_cardholder_title'), - description: strings('card.card_onboarding.non_cardholder_description'), - verify_account_button: strings( - 'card.card_onboarding.non_cardholder_verify_account_button', - ), - }; - }, [hasCardholderAccounts]); + const handleClose = useCallback(() => { + navigate(Routes.WALLET.HOME); + }, [navigate]); const handleButtonPress = useCallback(() => { trackEvent( @@ -78,43 +61,74 @@ const CardWelcome = () => { }, [hasCardholderAccounts, navigate, trackEvent, createEventBuilder]); return ( - - - - - - + + + {/* Header Section */} + - {cardWelcomeCopies.title} + {strings('card.card_onboarding.title')} - {cardWelcomeCopies.description} + {strings('card.card_onboarding.description')} + -