From 76bb5c8edbf66cc6b09bd638abff76a8a66e6e83 Mon Sep 17 00:00:00 2001 From: Wei Sun Date: Thu, 22 May 2025 15:33:56 -0700 Subject: [PATCH] feat: 15379 update minimum version modal UI update changelog --- CHANGELOG.md | 3 + .../UI/UpdateNeeded/UpdateNeeded.test.tsx | 59 ++++++- .../UI/UpdateNeeded/UpdateNeeded.tsx | 45 ++++-- .../__snapshots__/UpdateNeeded.test.tsx.snap | 147 +++++++++++++----- app/components/UI/UpdateNeeded/styles.ts | 12 ++ locales/languages/en.json | 28 ++-- 6 files changed, 225 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fef14b55de9f..12b8802635c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix(bridge): prevent quote polling when "quote expired" modal is open ([#15602](https://github.com/MetaMask/metamask-mobile/issues/15602)) - fix(browser): fix browser PermissionsSummary origin spoofing when browser redirects issue ([#13394](https://github.com/MetaMask/metamask-mobile/pull/13394)) +### Changed +- feat: update minimum version modal UI ([#15567](https://github.com/MetaMask/metamask-mobile/pull/15567)) + ### Added - feat(bridge): improve bridge screen layout and user experience ([#15425](https://github.com/MetaMask/metamask-mobile/pull/15425)) diff --git a/app/components/UI/UpdateNeeded/UpdateNeeded.test.tsx b/app/components/UI/UpdateNeeded/UpdateNeeded.test.tsx index aafb4e1d6603..6657c29a9f0a 100644 --- a/app/components/UI/UpdateNeeded/UpdateNeeded.test.tsx +++ b/app/components/UI/UpdateNeeded/UpdateNeeded.test.tsx @@ -1,8 +1,23 @@ import { renderScreen } from '../../..//util/test/renderWithProvider'; import { UpdateNeeded } from './'; +import { fireEvent } from '@testing-library/react-native'; +import { MM_APP_STORE_LINK, MM_PLAY_STORE_LINK } from '../../../constants/urls'; +import { Platform } from 'react-native'; + +const mockCanOpenURL = jest.fn(() => Promise.resolve(true)); +const mockOpenURL = jest.fn(() => Promise.resolve()); +const mockAddEventListener = jest.fn(); +const mockRemoveEventListener = jest.fn(); + +jest.mock('react-native/Libraries/Linking/Linking', () => ({ + openURL: mockOpenURL, + canOpenURL: mockCanOpenURL, + addEventListener: mockAddEventListener, + removeEventListener: mockRemoveEventListener, +})); describe('UpdateNeeded', () => { - it('should render correctly', () => { + it('should render snapshot correctly', () => { const { toJSON } = renderScreen( UpdateNeeded, { name: 'UpdateNeeded' }, @@ -10,4 +25,46 @@ describe('UpdateNeeded', () => { ); expect(toJSON()).toMatchSnapshot(); }); + it('should render correctly', () => { + const { getByText, getByTestId } = renderScreen( + UpdateNeeded, + { name: 'UpdateNeeded' }, + { state: {} }, + ); + const title = getByText('Get the newest features') + expect(title).toBeDefined(); + + const description = getByText('We’ve made your wallet safer, smoother, and added some new features. Update now to stay protected and use our latest improvements.'); + expect(description).toBeDefined(); + + const closeButton = getByTestId('update-needed-modal-close-button'); + expect(closeButton).toBeDefined(); + + const primaryButton = getByText('Update to latest version'); + expect(primaryButton).toBeDefined(); + }); + it('should open iOS App Store on primary button press', () => { + Platform.OS = 'ios'; + const { getByText } = renderScreen( + UpdateNeeded, + { name: 'UpdateNeeded' }, + { state: {} }, + ); + const primaryButton = getByText('Update to latest version'); + fireEvent.press(primaryButton); + expect(mockCanOpenURL).toHaveBeenCalled(); + expect(mockCanOpenURL).toHaveBeenCalledWith(MM_APP_STORE_LINK); + }); + it('should open Google Play Store on primary button press', () => { + Platform.OS = 'android'; + const { getByText } = renderScreen( + UpdateNeeded, + { name: 'UpdateNeeded' }, + { state: {} }, + ); + const primaryButton = getByText('Update to latest version'); + fireEvent.press(primaryButton); + expect(mockCanOpenURL).toHaveBeenCalled(); + expect(mockCanOpenURL).toHaveBeenCalledWith(MM_PLAY_STORE_LINK); + }); }); diff --git a/app/components/UI/UpdateNeeded/UpdateNeeded.tsx b/app/components/UI/UpdateNeeded/UpdateNeeded.tsx index 3113dcc93872..3673581439f7 100644 --- a/app/components/UI/UpdateNeeded/UpdateNeeded.tsx +++ b/app/components/UI/UpdateNeeded/UpdateNeeded.tsx @@ -11,10 +11,16 @@ import { useTheme } from '../../../util/theme'; import ReusableModal, { ReusableModalRef } from '../ReusableModal'; import Logger from '../../../util/Logger'; import Button, { - ButtonSize, ButtonVariants, ButtonWidthTypes, } from '../../../component-library/components/Buttons/Button'; +import ButtonIcon, { +} from '../../../component-library/components/Buttons/ButtonIcon'; +import HeaderBase from '../../../component-library/components/HeaderBase'; +import { + IconColor, + IconName, +} from '../../../component-library/components/Icons/Icon'; import { MM_APP_STORE_LINK, MM_PLAY_STORE_LINK } from '../../../constants/urls'; import { MetaMetricsEvents } from '../../../core/Analytics'; @@ -23,7 +29,8 @@ import generateDeviceAnalyticsMetaData from '../../../util/metrics'; import { useMetrics } from '../../../components/hooks/useMetrics'; /* eslint-disable import/no-commonjs, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */ -const onboardingDeviceImage = require('../../../images/swaps_onboard_device.png'); +const foxLogo = require('../../../images/branding/fox.png'); +const metamaskName = require('../../../images/branding/metamask-name.png'); export const createUpdateNeededNavDetails = createNavigationDetails( Routes.MODAL.ROOT_MODAL_FLOW, @@ -91,11 +98,32 @@ const UpdateNeeded = () => { return ( + } + > + + - + - + {strings('update_needed.title')} @@ -110,15 +138,8 @@ const UpdateNeeded = () => { onPress={onUpdatePressed} style={styles.actionButton} /> -