diff --git a/src/services/walletconnect/WalletConnectContext.test.tsx b/src/services/walletconnect/WalletConnectContext.test.tsx deleted file mode 100644 index 2719b3eb21..0000000000 --- a/src/services/walletconnect/WalletConnectContext.test.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { hexZeroPad } from 'ethers/lib/utils' -import { useContext } from 'react' -import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' - -import { fireEvent, render, waitFor } from '@/tests/test-utils' -import { WalletConnectContext, WalletConnectProvider } from './WalletConnectContext' -import WalletConnectWallet from './WalletConnectWallet' -import { safeInfoSlice } from '@/store/safeInfoSlice' -import { useAppDispatch } from '@/store' - -jest.mock('./WalletConnectWallet') - -const TestComponent = () => { - const { walletConnect, error } = useContext(WalletConnectContext) - return ( - <> - {walletConnect &&

WalletConnect initialized

} - {error &&

{error.message}

} - - ) -} - -describe('WalletConnectProvider', () => { - it('sets the walletConnect state', async () => { - jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) - jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) - - const { getByText } = render( - - - , - { - initialReduxState: { - safeInfo: { - loading: false, - data: { - address: { - value: hexZeroPad('0x123', 20), - }, - chainId: '5', - } as SafeInfo, - }, - }, - }, - ) - - await waitFor(() => { - expect(getByText('WalletConnect initialized')).toBeInTheDocument() - }) - }) - - it('sets the error state', async () => { - jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.reject(new Error('Test init failed'))) - jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) - - const { getByText } = render( - - - , - { - initialReduxState: { - safeInfo: { - loading: false, - data: { - address: { - value: hexZeroPad('0x123', 20), - }, - chainId: '5', - } as SafeInfo, - }, - }, - }, - ) - - await waitFor(() => { - expect(getByText('Test init failed')).toBeInTheDocument() - }) - }) - - describe('updateSessions', () => { - const getUpdateSafeInfoComponent = (safeInfo: SafeInfo) => { - // eslint-disable-next-line react/display-name - return () => { - const dispatch = useAppDispatch() - const updateSafeInfo = () => { - dispatch( - safeInfoSlice.actions.set({ - loading: false, - data: safeInfo, - }), - ) - } - - return - } - } - - it('updates sessions when the chainId changes', async () => { - jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) - jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) - - const ChainUpdater = getUpdateSafeInfoComponent({ - address: { value: hexZeroPad('0x123', 20) }, - chainId: '1', - } as SafeInfo) - - const { getByText } = render( - - - - , - { - initialReduxState: { - safeInfo: { - loading: false, - data: { - address: { - value: hexZeroPad('0x123', 20), - }, - chainId: '5', - } as SafeInfo, - }, - }, - }, - ) - - await waitFor(() => { - expect(getByText('WalletConnect initialized')).toBeInTheDocument() - expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('5', hexZeroPad('0x123', 20)) - }) - - fireEvent.click(getByText('update')) - - await waitFor(() => { - expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('1', hexZeroPad('0x123', 20)) - }) - }) - - it('updates sessions when the safeAddress changes', async () => { - jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) - jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) - - const AddressUpdater = getUpdateSafeInfoComponent({ - address: { value: hexZeroPad('0x456', 20) }, - chainId: '5', - } as SafeInfo) - - const { getByText } = render( - - - - , - { - initialReduxState: { - safeInfo: { - loading: false, - data: { - address: { - value: hexZeroPad('0x123', 20), - }, - chainId: '5', - } as SafeInfo, - }, - }, - }, - ) - - await waitFor(() => { - expect(getByText('WalletConnect initialized')).toBeInTheDocument() - expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('5', hexZeroPad('0x123', 20)) - }) - - fireEvent.click(getByText('update')) - - await waitFor(() => { - expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('5', hexZeroPad('0x456', 20)) - }) - }) - - it('sets the error state', async () => { - jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) - jest - .spyOn(WalletConnectWallet, 'updateSessions') - .mockImplementation(() => Promise.reject(new Error('Test updateSessions failed'))) - - const { getByText } = render( - - - , - { - initialReduxState: { - safeInfo: { - loading: false, - data: { - address: { - value: hexZeroPad('0x123', 20), - }, - chainId: '5', - } as SafeInfo, - }, - }, - }, - ) - - await waitFor(() => { - expect(getByText('Test updateSessions failed')).toBeInTheDocument() - }) - }) - }) - - describe('onRequest', () => { - it.todo('does not continue with the request if there is no matching topic') - - it.todo('does not continue with the request if there is no matching chainId') - - it.todo('passes the request onto the Safe Wallet Provider and sends the response to WalletConnect') - - it.todo('sets the error state if there is an error requesting the response from the Safe Wallet Provider') - }) -}) diff --git a/src/services/walletconnect/WalletConnectContext.tsx b/src/services/walletconnect/WalletConnectContext.tsx index ec95c6eb30..7848cc2cc7 100644 --- a/src/services/walletconnect/WalletConnectContext.tsx +++ b/src/services/walletconnect/WalletConnectContext.tsx @@ -60,6 +60,8 @@ export const WalletConnectProvider = ({ children }: { children: ReactNode }) => iconUrl: session.peer.metadata.icons[0], }) + console.log('============', response) + // Send response to WalletConnect await walletConnect.sendSessionResponse(topic, response) } catch (e) { diff --git a/src/services/walletconnect/__tests__/WalletConnectContext.test.tsx b/src/services/walletconnect/__tests__/WalletConnectContext.test.tsx new file mode 100644 index 0000000000..9a3e108c88 --- /dev/null +++ b/src/services/walletconnect/__tests__/WalletConnectContext.test.tsx @@ -0,0 +1,476 @@ +import { hexZeroPad } from 'ethers/lib/utils' +import { useContext } from 'react' +import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' +import type { Web3WalletTypes } from '@walletconnect/web3wallet' +import type { SessionTypes } from '@walletconnect/types' + +import { fireEvent, render, waitFor } from '@/tests/test-utils' +import { WalletConnectContext, WalletConnectProvider } from '../WalletConnectContext' +import WalletConnectWallet from '../WalletConnectWallet' +import { safeInfoSlice } from '@/store/safeInfoSlice' +import { useAppDispatch } from '@/store' +import * as useSafeWalletProvider from '@/services/safe-wallet-provider/useSafeWalletProvider' + +jest.mock('../WalletConnectWallet') +jest.mock('@/services/safe-wallet-provider/useSafeWalletProvider') + +const TestComponent = () => { + const { walletConnect, error } = useContext(WalletConnectContext) + return ( + <> + {walletConnect &&

WalletConnect initialized

} + {error &&

{error.message}

} + + ) +} + +describe('WalletConnectProvider', () => { + beforeEach(() => { + jest.resetAllMocks() + }) + + it('sets the walletConnect state', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + + const { getByText } = render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(getByText('WalletConnect initialized')).toBeInTheDocument() + }) + }) + + it('sets the error state', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.reject(new Error('Test init failed'))) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + + const { getByText } = render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(getByText('Test init failed')).toBeInTheDocument() + }) + }) + + describe('updateSessions', () => { + const getUpdateSafeInfoComponent = (safeInfo: SafeInfo) => { + // eslint-disable-next-line react/display-name + return () => { + const dispatch = useAppDispatch() + const updateSafeInfo = () => { + dispatch( + safeInfoSlice.actions.set({ + loading: false, + data: safeInfo, + }), + ) + } + + return + } + } + + it('updates sessions when the chainId changes', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + + const ChainUpdater = getUpdateSafeInfoComponent({ + address: { value: hexZeroPad('0x123', 20) }, + chainId: '1', + } as SafeInfo) + + const { getByText } = render( + + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(getByText('WalletConnect initialized')).toBeInTheDocument() + expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('5', hexZeroPad('0x123', 20)) + }) + + fireEvent.click(getByText('update')) + + await waitFor(() => { + expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('1', hexZeroPad('0x123', 20)) + }) + }) + + it('updates sessions when the safeAddress changes', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + + const AddressUpdater = getUpdateSafeInfoComponent({ + address: { value: hexZeroPad('0x456', 20) }, + chainId: '5', + } as SafeInfo) + + const { getByText } = render( + + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(getByText('WalletConnect initialized')).toBeInTheDocument() + expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('5', hexZeroPad('0x123', 20)) + }) + + fireEvent.click(getByText('update')) + + await waitFor(() => { + expect(WalletConnectWallet.updateSessions).toHaveBeenCalledWith('5', hexZeroPad('0x456', 20)) + }) + }) + + it('sets the error state', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest + .spyOn(WalletConnectWallet, 'updateSessions') + .mockImplementation(() => Promise.reject(new Error('Test updateSessions failed'))) + + const { getByText } = render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(getByText('Test updateSessions failed')).toBeInTheDocument() + }) + }) + }) + + describe('onRequest', () => { + it('does not continue with the request if there is no matching topic', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'getActiveSessions').mockImplementation(() => []) + + const onRequestSpy = jest.spyOn(WalletConnectWallet, 'onRequest') + const sendSessionResponseSpy = jest.spyOn(WalletConnectWallet, 'sendSessionResponse') + + const mockRequest = jest.fn() + jest.spyOn(useSafeWalletProvider, 'default').mockImplementation( + () => + ({ + request: mockRequest, + } as unknown as ReturnType), + ) + + render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(onRequestSpy).toHaveBeenCalled() + }) + + const onRequestHandler = onRequestSpy.mock.calls[0][0] + + onRequestHandler({ + id: 1, + topic: 'topic', + params: { + request: {}, + chainId: 'eip155:5', // Goerli + }, + } as unknown as Web3WalletTypes.SessionRequest) + + expect(mockRequest).not.toHaveBeenCalled() + expect(sendSessionResponseSpy).not.toHaveBeenCalled() + }) + + it('does not continue with the request if there is no matching chainId', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + jest + .spyOn(WalletConnectWallet, 'getActiveSessions') + .mockImplementation(() => [{ topic: 'topic' } as unknown as SessionTypes.Struct]) + + const onRequestSpy = jest.spyOn(WalletConnectWallet, 'onRequest') + const sendSessionResponseSpy = jest.spyOn(WalletConnectWallet, 'sendSessionResponse') + + const mockRequest = jest.fn() + jest.spyOn(useSafeWalletProvider, 'default').mockImplementation( + () => + ({ + request: mockRequest, + } as unknown as ReturnType), + ) + + render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(onRequestSpy).toHaveBeenCalled() + }) + + const onRequestHandler = onRequestSpy.mock.calls[0][0] + + onRequestHandler({ + id: 1, + topic: 'topic', + params: { + request: {}, + chainId: 'eip155:1', // Mainnet + }, + } as unknown as Web3WalletTypes.SessionRequest) + + expect(mockRequest).not.toHaveBeenCalled() + expect(sendSessionResponseSpy).not.toHaveBeenCalled() + }) + + it('passes the request onto the Safe Wallet Provider and sends the response to WalletConnect', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'getActiveSessions').mockImplementation(() => [ + { + topic: 'topic', + peer: { + metadata: { + name: 'name', + description: 'description', + url: 'url', + icons: ['iconUrl'], + }, + }, + } as unknown as SessionTypes.Struct, + ]) + + const onRequestSpy = jest.spyOn(WalletConnectWallet, 'onRequest') + const sendSessionResponseSpy = jest.spyOn(WalletConnectWallet, 'sendSessionResponse') + + const mockRequest = jest.fn().mockImplementation(() => Promise.resolve({})) + jest.spyOn(useSafeWalletProvider, 'default').mockImplementation( + () => + ({ + request: mockRequest, + } as unknown as ReturnType), + ) + + render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(onRequestSpy).toHaveBeenCalled() + }) + + const onRequestHandler = onRequestSpy.mock.calls[0][0] + + onRequestHandler({ + id: 1, + topic: 'topic', + params: { + request: { method: 'fake', params: [] }, + chainId: 'eip155:5', // Goerli + }, + } as unknown as Web3WalletTypes.SessionRequest) + + expect(mockRequest).toHaveBeenCalledWith( + 1, + { method: 'fake', params: [] }, + { + name: 'name', + description: 'description', + url: 'url', + iconUrl: 'iconUrl', + }, + ) + + await waitFor(() => { + expect(sendSessionResponseSpy).toHaveBeenCalledWith('topic', {}) + }) + }) + + it('sets the error state if there is an error requesting', async () => { + jest.spyOn(WalletConnectWallet, 'init').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'updateSessions').mockImplementation(() => Promise.resolve()) + jest.spyOn(WalletConnectWallet, 'getActiveSessions').mockImplementation(() => [ + { + topic: 'topic', + peer: { + metadata: { + name: 'name', + description: 'description', + url: 'url', + icons: ['iconUrl'], + }, + }, + } as unknown as SessionTypes.Struct, + ]) + + jest.spyOn(useSafeWalletProvider, 'default').mockImplementation( + () => + ({ + request: () => Promise.reject(new Error('Test request failed')), + } as unknown as ReturnType), + ) + + const onRequestSpy = jest.spyOn(WalletConnectWallet, 'onRequest') + const sendSessionResponseSpy = jest.spyOn(WalletConnectWallet, 'sendSessionResponse') + + const { getByText } = render( + + + , + { + initialReduxState: { + safeInfo: { + loading: false, + data: { + address: { + value: hexZeroPad('0x123', 20), + }, + chainId: '5', + } as SafeInfo, + }, + }, + }, + ) + + await waitFor(() => { + expect(onRequestSpy).toHaveBeenCalled() + }) + + const onRequestHandler = onRequestSpy.mock.calls[0][0] + + onRequestHandler({ + id: 1, + topic: 'topic', + params: { + request: {}, + chainId: 'eip155:5', // Goerli + }, + } as unknown as Web3WalletTypes.SessionRequest) + + expect(sendSessionResponseSpy).not.toHaveBeenCalled() + + await waitFor(() => { + expect(getByText('Test request failed')).toBeInTheDocument() + }) + }) + }) +}) diff --git a/src/services/walletconnect/WalletConnectWallet.test.ts b/src/services/walletconnect/__tests__/WalletConnectWallet.test.ts similarity index 99% rename from src/services/walletconnect/WalletConnectWallet.test.ts rename to src/services/walletconnect/__tests__/WalletConnectWallet.test.ts index 9655de631d..47e7203b29 100644 --- a/src/services/walletconnect/WalletConnectWallet.test.ts +++ b/src/services/walletconnect/__tests__/WalletConnectWallet.test.ts @@ -2,7 +2,7 @@ import { hexZeroPad } from 'ethers/lib/utils' import type { ProposalTypes, SessionTypes, SignClientTypes, Verify } from '@walletconnect/types' import type { IWeb3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet' -import type WalletConnectWallet from './WalletConnectWallet' +import type WalletConnectWallet from '../WalletConnectWallet' jest.mock('@walletconnect/core', () => ({ Core: jest.fn(), @@ -49,7 +49,7 @@ describe('WalletConnectWallet', () => { beforeEach(async () => { // Reset import - wallet = (await import('./WalletConnectWallet')).default + wallet = (await import('../WalletConnectWallet')).default await wallet.init() }) diff --git a/src/services/walletconnect/useWalletConnectSessions.test.tsx b/src/services/walletconnect/__tests__/useWalletConnectSessions.test.tsx similarity index 96% rename from src/services/walletconnect/useWalletConnectSessions.test.tsx rename to src/services/walletconnect/__tests__/useWalletConnectSessions.test.tsx index 8754bf36fa..aaf3d30aa6 100644 --- a/src/services/walletconnect/useWalletConnectSessions.test.tsx +++ b/src/services/walletconnect/__tests__/useWalletConnectSessions.test.tsx @@ -1,7 +1,7 @@ import { renderHook, waitFor } from '@/tests/test-utils' import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext' -import useWalletConnectSessions from './useWalletConnectSessions' -import type WalletConnectWallet from './WalletConnectWallet' +import useWalletConnectSessions from '../useWalletConnectSessions' +import type WalletConnectWallet from '../WalletConnectWallet' describe('useWalletConnectSessions', () => { it('should return an array of active sessions', () => {