From 1bce16a758b767f053a40f7398efa3472e2cd96e Mon Sep 17 00:00:00 2001 From: jpuri Date: Tue, 11 Feb 2025 15:36:12 +0530 Subject: [PATCH] Add support for ledger wallet in new signature designs --- .../LedgerMessageSignModal.test.tsx | 91 ++- .../LedgerModals/LedgerMessageSignModal.tsx | 20 +- .../LedgerMessageSignModal.test.tsx.snap | 606 ------------------ app/components/UI/LedgerModals/styles.ts | 1 - .../components/Confirm/Footer/Footer.test.tsx | 23 + .../components/Confirm/Footer/Footer.tsx | 21 +- .../components/Confirm/Info/Info.test.tsx | 12 +- .../QRHardwareContext.test.tsx | 7 + .../hooks/useConfirmActions.test.ts | 108 +++- .../confirmations/hooks/useConfirmActions.ts | 57 +- .../hooks/useLedgerWallet.test.ts | 47 ++ .../confirmations/hooks/useLedgerWallet.ts | 70 ++ locales/languages/en.json | 1 + 13 files changed, 391 insertions(+), 673 deletions(-) delete mode 100644 app/components/UI/LedgerModals/__snapshots__/LedgerMessageSignModal.test.tsx.snap create mode 100644 app/components/Views/confirmations/hooks/useLedgerWallet.test.ts create mode 100644 app/components/Views/confirmations/hooks/useLedgerWallet.ts diff --git a/app/components/UI/LedgerModals/LedgerMessageSignModal.test.tsx b/app/components/UI/LedgerModals/LedgerMessageSignModal.test.tsx index 358b1717b69f..3e294e0de567 100644 --- a/app/components/UI/LedgerModals/LedgerMessageSignModal.test.tsx +++ b/app/components/UI/LedgerModals/LedgerMessageSignModal.test.tsx @@ -1,18 +1,85 @@ -import { renderScreen } from '../../../util/test/renderWithProvider'; +import React from 'react'; +import { Button, Text, View } from 'react-native'; +import { fireEvent } from '@testing-library/react-native'; + +import renderWithProvider from '../../../util/test/renderWithProvider'; +// eslint-disable-next-line import/no-namespace +import * as NavUtils from '../../../util/navigation/navUtils'; +// eslint-disable-next-line import/no-namespace +import * as rpcEventsFuncs from '../../../actions/rpcEvents'; +import { personalSignatureConfirmationState } from '../../../util/test/confirm-data-helpers'; import LedgerMessageSignModal from './LedgerMessageSignModal'; -import { RPCStageTypes } from '../../../reducers/rpcEvents'; -const initialState = { - rpcEvents: { signingEvent: RPCStageTypes.IDLE }, -}; +const MockView = View; +const MockText = Text; +const MockButton = Button; +jest.mock('./LedgerConfirmationModal', () => ({ + __esModule: true, + default: ({ + onConfirmation, + onRejection, + deviceId, + }: { + onConfirmation: () => void; + onRejection: () => void; + deviceId: string; + }) => ( + + Mock LedgerConfirmationModal + {deviceId} + + + + ), +})); + +const DummyDeviceId = 'DummyDeviceId'; describe('LedgerMessageSignModal', () => { - it('should render correctly', () => { - const { toJSON } = renderScreen( - LedgerMessageSignModal, - { name: 'LederMessageSignModal' }, - { state: initialState }, - ); - expect(toJSON()).toMatchSnapshot(); + it('should render LedgerConfirmationModal correctly', () => { + jest + .spyOn(NavUtils, 'useParams') + .mockReturnValue({ deviceId: DummyDeviceId }); + const { getByText } = renderWithProvider(, { + state: personalSignatureConfirmationState, + }); + expect(getByText('Mock LedgerConfirmationModal')).toBeTruthy(); + expect(getByText(DummyDeviceId)).toBeTruthy(); + }); + + it('should call onConfirmationComplete when request is confirmed', () => { + const mockOnConfirmationComplete = jest.fn(); + jest + .spyOn(NavUtils, 'useParams') + .mockReturnValue({ + onConfirmationComplete: mockOnConfirmationComplete, + deviceId: DummyDeviceId, + }); + const { getByText } = renderWithProvider(, { + state: personalSignatureConfirmationState, + }); + fireEvent.press(getByText('onConfirmation')); + expect(mockOnConfirmationComplete).toHaveBeenCalledTimes(1); + expect(mockOnConfirmationComplete).toHaveBeenCalledWith(true); + }); + + it('should call onConfirmationComplete when request is rejected', () => { + const mockOnConfirmationComplete = jest.fn(); + jest + .spyOn(NavUtils, 'useParams') + .mockReturnValue({ + onConfirmationComplete: mockOnConfirmationComplete, + deviceId: DummyDeviceId, + }); + jest + .spyOn(rpcEventsFuncs, 'resetEventStage') + .mockImplementation(() => ({ rpcName: 'dummy', type: 'DUMMY' })); + const { getByText } = renderWithProvider(, { + state: personalSignatureConfirmationState, + }); + fireEvent.press(getByText('onRejection')); + expect(mockOnConfirmationComplete).toHaveBeenCalledTimes(1); + expect(mockOnConfirmationComplete).toHaveBeenCalledWith(false); + expect(rpcEventsFuncs.resetEventStage).toHaveBeenCalledTimes(1); }); }); diff --git a/app/components/UI/LedgerModals/LedgerMessageSignModal.tsx b/app/components/UI/LedgerModals/LedgerMessageSignModal.tsx index 0a2ef706ee9e..edf13a8bab8d 100644 --- a/app/components/UI/LedgerModals/LedgerMessageSignModal.tsx +++ b/app/components/UI/LedgerModals/LedgerMessageSignModal.tsx @@ -1,6 +1,7 @@ -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import Modal from 'react-native-modal'; + import LedgerConfirmationModal from './LedgerConfirmationModal'; -import ReusableModal, { ReusableModalRef } from '../ReusableModal'; import { createStyles } from './styles'; import { createNavigationDetails, @@ -28,9 +29,6 @@ export interface LedgerMessageSignModalParams { ) => Promise; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any - version: any; - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any type: any; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -44,7 +42,7 @@ export const createLedgerMessageSignModalNavDetails = const LedgerMessageSignModal = () => { const dispatch = useDispatch(); - const modalRef = useRef(null); + const [requestCompleted, setRequestCompleted] = useState(false); const { colors } = useAppThemeFromContext() || mockTheme; const styles = createStyles(colors); const { signingEvent }: iEventGroup = useSelector( @@ -55,7 +53,7 @@ const LedgerMessageSignModal = () => { useParams(); const dismissModal = useCallback(() => { - modalRef?.current?.dismissModal(); + setRequestCompleted(false); dispatch(resetEventStage(signingEvent.rpcName)); }, [dispatch, signingEvent.rpcName]); @@ -85,8 +83,12 @@ const LedgerMessageSignModal = () => { dismissModal(); }, [dismissModal, onConfirmationComplete]); + if (requestCompleted) { + return null; + } + return ( - + { deviceId={deviceId} /> - + ); }; diff --git a/app/components/UI/LedgerModals/__snapshots__/LedgerMessageSignModal.test.tsx.snap b/app/components/UI/LedgerModals/__snapshots__/LedgerMessageSignModal.test.tsx.snap deleted file mode 100644 index 420066eb41b0..000000000000 --- a/app/components/UI/LedgerModals/__snapshots__/LedgerMessageSignModal.test.tsx.snap +++ /dev/null @@ -1,606 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LedgerMessageSignModal should render correctly 1`] = ` - - - - - - - - - - - - - LederMessageSignModal - - - - - - - - - - - - - - - - - - - - - - - - - - - - Looking for device - - - - - - - - Please make sure your Ledger device is: - - - 1. Unlock your Ledger device - - - 2. Install and open the Ethereum app - - - 3. Enable Bluetooth - - - 5. Do not disturb must be turned off - - - 6. Enable "blind signing" on your Ledger device. - - - - - How to install the Ethereum app on a Ledger device - - - - - - - - - - - - - - - - - - -`; diff --git a/app/components/UI/LedgerModals/styles.ts b/app/components/UI/LedgerModals/styles.ts index 6de560c41a70..efd154fd5c41 100644 --- a/app/components/UI/LedgerModals/styles.ts +++ b/app/components/UI/LedgerModals/styles.ts @@ -5,7 +5,6 @@ import { Colors } from '../../../util/theme/models'; export const createStyles = (colors: Colors) => StyleSheet.create({ modal: { - justifyContent: 'flex-end', height: 600, margin: 0, zIndex: 1000, diff --git a/app/components/Views/confirmations/components/Confirm/Footer/Footer.test.tsx b/app/components/Views/confirmations/components/Confirm/Footer/Footer.test.tsx index 5790532acd22..e29201f9de1b 100644 --- a/app/components/Views/confirmations/components/Confirm/Footer/Footer.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Footer/Footer.test.tsx @@ -6,8 +6,20 @@ import renderWithProvider from '../../../../../../util/test/renderWithProvider'; import { personalSignatureConfirmationState } from '../../../../../../util/test/confirm-data-helpers'; // eslint-disable-next-line import/no-namespace import * as QRHardwareHook from '../../../context/QRHardwareContext/QRHardwareContext'; +// eslint-disable-next-line import/no-namespace +import * as LedgerWalletHook from '../../../hooks/useLedgerWallet'; import Footer from './index'; +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useNavigation: () => ({ + navigate: jest.fn(), + }), + }; +}); + const mockConfirmSpy = jest.fn(); const mockRejectSpy = jest.fn(); jest.mock('../../../hooks/useConfirmActions', () => ({ @@ -53,6 +65,17 @@ describe('Footer', () => { expect(getByText('Get Signature')).toBeTruthy(); }); + it('renders confirm button text "Sign with Ledger" if account used for signing is ledger account', () => { + jest.spyOn(LedgerWalletHook, 'useLedgerWallet').mockReturnValue({ + isLedgerAccount: true, + openLedgerSignModal: () => Promise.resolve(), + }); + const { getByText } = renderWithProvider(