-
Notifications
You must be signed in to change notification settings - Fork 200
Add thorough test cases for mobile app #752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
4bf259a
e980e47
1bb1dd2
76190b3
f1fdcd0
b5995dd
009aa14
5683c19
a619a98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 | ||
|
|
||
| import { useNavigation } from '@react-navigation/native'; | ||
| import { act, renderHook } from '@testing-library/react-native'; | ||
|
|
||
| jest.mock('@react-navigation/native', () => ({ | ||
| useNavigation: jest.fn(), | ||
| })); | ||
|
|
||
| jest.mock('react-native-check-version', () => ({ | ||
| checkVersion: jest.fn(), | ||
| })); | ||
|
|
||
| jest.mock('../../../src/utils/modalCallbackRegistry', () => ({ | ||
| registerModalCallbacks: jest.fn().mockReturnValue(1), | ||
| })); | ||
|
|
||
| jest.mock('../../../src/utils/analytics', () => () => ({ | ||
| trackEvent: jest.fn(), | ||
| })); | ||
|
|
||
| import { checkVersion } from 'react-native-check-version'; | ||
|
|
||
| import { useAppUpdates } from '../../../src/hooks/useAppUpdates'; | ||
| import { registerModalCallbacks } from '../../../src/utils/modalCallbackRegistry'; | ||
|
|
||
| const navigate = jest.fn(); | ||
| (useNavigation as jest.Mock).mockReturnValue({ navigate }); | ||
|
|
||
| describe('useAppUpdates', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('indicates update available', async () => { | ||
| (checkVersion as jest.Mock).mockResolvedValue({ | ||
| needsUpdate: true, | ||
| url: 'u', | ||
| }); | ||
| const { result } = renderHook(() => useAppUpdates()); | ||
| await act(async () => { | ||
| await Promise.resolve(); | ||
| }); | ||
| expect(result.current[0]).toBe(true); | ||
| }); | ||
|
|
||
| it('shows modal when triggered', () => { | ||
| (checkVersion as jest.Mock).mockResolvedValue({ | ||
| needsUpdate: true, | ||
| url: 'u', | ||
| }); | ||
| const { result } = renderHook(() => useAppUpdates()); | ||
| act(() => { | ||
| result.current[1](); | ||
| }); | ||
| expect(registerModalCallbacks).toHaveBeenCalled(); | ||
| expect(navigate).toHaveBeenCalledWith('Modal', expect.any(Object)); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 | ||
|
|
||
| import { act, renderHook } from '@testing-library/react-native'; | ||
|
|
||
| jest.useFakeTimers(); | ||
|
|
||
| jest.mock('../../../src/navigation', () => ({ | ||
| navigationRef: { isReady: jest.fn(() => true), navigate: jest.fn() }, | ||
| })); | ||
|
|
||
| jest.mock('../../../src/hooks/useModal'); | ||
| jest.mock('@react-native-community/netinfo', () => ({ | ||
| useNetInfo: jest | ||
| .fn() | ||
| .mockReturnValue({ isConnected: false, isInternetReachable: false }), | ||
| })); | ||
|
|
||
| import useConnectionModal from '../../../src/hooks/useConnectionModal'; | ||
| import { useModal } from '../../../src/hooks/useModal'; | ||
|
|
||
| const showModal = jest.fn(); | ||
| const dismissModal = jest.fn(); | ||
| (useModal as jest.Mock).mockReturnValue({ | ||
| showModal, | ||
| dismissModal, | ||
| visible: false, | ||
| }); | ||
|
|
||
| describe('useConnectionModal', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('shows modal when no connection', () => { | ||
| const { result } = renderHook(() => useConnectionModal()); | ||
| act(() => { | ||
| jest.advanceTimersByTime(2000); | ||
| }); | ||
| expect(showModal).toHaveBeenCalled(); | ||
| expect(result.current.visible).toBe(false); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 | ||
|
|
||
| import { useNavigation } from '@react-navigation/native'; | ||
| import { act, renderHook } from '@testing-library/react-native'; | ||
|
|
||
| import { | ||
| impactLight, | ||
| impactMedium, | ||
| selectionChange, | ||
| } from '../../../src/utils/haptic'; | ||
|
|
||
| jest.mock('@react-navigation/native', () => ({ | ||
| useNavigation: jest.fn(), | ||
| })); | ||
|
|
||
| jest.mock('../../../src/utils/haptic', () => ({ | ||
| impactLight: jest.fn(), | ||
| impactMedium: jest.fn(), | ||
| selectionChange: jest.fn(), | ||
| })); | ||
|
|
||
| const navigate = jest.fn(); | ||
| const popTo = jest.fn(); | ||
| (useNavigation as jest.Mock).mockReturnValue({ navigate, popTo }); | ||
|
|
||
| import useHapticNavigation from '../../../src/hooks/useHapticNavigation'; | ||
|
|
||
| describe('useHapticNavigation', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('navigates with light impact by default', () => { | ||
| const { result } = renderHook(() => useHapticNavigation('Home')); | ||
| act(() => { | ||
| result.current(); | ||
| }); | ||
| expect(impactLight).toHaveBeenCalled(); | ||
| expect(navigate).toHaveBeenCalledWith('Home'); | ||
| }); | ||
|
|
||
| it('uses cancel action', () => { | ||
| const { result } = renderHook(() => | ||
| useHapticNavigation('Home', { action: 'cancel' }), | ||
| ); | ||
| act(() => result.current()); | ||
| expect(selectionChange).toHaveBeenCalled(); | ||
| expect(popTo).toHaveBeenCalledWith('Home'); | ||
| }); | ||
|
|
||
| it('uses confirm action', () => { | ||
| const { result } = renderHook(() => | ||
| useHapticNavigation('Home', { action: 'confirm' }), | ||
| ); | ||
| act(() => result.current()); | ||
| expect(impactMedium).toHaveBeenCalled(); | ||
| expect(navigate).toHaveBeenCalledWith('Home'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 | ||
|
|
||
| import { act, renderHook } from '@testing-library/react-native'; | ||
|
|
||
| jest.mock('../../../src/providers/authProvider', () => ({ | ||
| useAuth: jest.fn(), | ||
| })); | ||
|
|
||
| import useMnemonic from '../../../src/hooks/useMnemonic'; | ||
| import { useAuth } from '../../../src/providers/authProvider'; | ||
|
|
||
| jest.mock('ethers', () => ({ | ||
| ethers: { | ||
| Mnemonic: { | ||
| fromEntropy: jest.fn().mockReturnValue({ phrase: 'one two three four' }), | ||
| }, | ||
| }, | ||
| })); | ||
|
|
||
| const getOrCreateMnemonic = jest.fn(); | ||
| (useAuth as jest.Mock).mockReturnValue({ getOrCreateMnemonic }); | ||
|
|
||
| describe('useMnemonic', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('loads mnemonic', async () => { | ||
| getOrCreateMnemonic.mockResolvedValue({ data: { entropy: '0x00' } }); | ||
| const { result } = renderHook(() => useMnemonic()); | ||
| await act(async () => { | ||
| await result.current.loadMnemonic(); | ||
| }); | ||
| expect(result.current.mnemonic).toEqual(['one', 'two', 'three', 'four']); | ||
| }); | ||
|
|
||
| it('handles missing mnemonic', async () => { | ||
| getOrCreateMnemonic.mockResolvedValue(null); | ||
| const { result } = renderHook(() => useMnemonic()); | ||
| await act(async () => { | ||
| await result.current.loadMnemonic(); | ||
| }); | ||
| expect(result.current.mnemonic).toBeUndefined(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 | ||
|
|
||
| import { Linking } from 'react-native'; | ||
|
|
||
| jest.mock('../../src/navigation', () => ({ | ||
| navigationRef: { navigate: jest.fn(), isReady: jest.fn(() => true) }, | ||
| })); | ||
|
|
||
| const mockSelfAppStore = { useSelfAppStore: { getState: jest.fn() } }; | ||
| jest.mock('../../src/stores/selfAppStore', () => mockSelfAppStore); | ||
|
|
||
| const mockUserStore = { default: { getState: jest.fn() } }; | ||
| jest.mock('../../src/stores/userStore', () => ({ | ||
| __esModule: true, | ||
| ...mockUserStore, | ||
| })); | ||
|
|
||
| let setSelfApp: jest.Mock, startAppListener: jest.Mock, cleanSelfApp: jest.Mock; | ||
| let setDeepLinkUserDetails: jest.Mock; | ||
|
|
||
| let handleUrl: (url: string) => void; | ||
| let setupUniversalLinkListenerInNavigation: () => () => void; | ||
|
|
||
| describe('deeplinks', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest.resetModules(); | ||
| ({ | ||
| handleUrl, | ||
| setupUniversalLinkListenerInNavigation, | ||
| } = require('../../src/utils/deeplinks')); | ||
| setSelfApp = jest.fn(); | ||
| startAppListener = jest.fn(); | ||
| cleanSelfApp = jest.fn(); | ||
| setDeepLinkUserDetails = jest.fn(); | ||
| jest.spyOn(Linking, 'getInitialURL').mockResolvedValue(null as any); | ||
| jest | ||
| .spyOn(Linking, 'addEventListener') | ||
| .mockReturnValue({ remove: jest.fn() } as any); | ||
| mockSelfAppStore.useSelfAppStore.getState.mockReturnValue({ | ||
| setSelfApp, | ||
| startAppListener, | ||
| cleanSelfApp, | ||
| }); | ||
| mockUserStore.default.getState.mockReturnValue({ | ||
| setDeepLinkUserDetails, | ||
| }); | ||
| }); | ||
|
|
||
| it('handles selfApp parameter', () => { | ||
| const selfApp = { sessionId: 'abc' }; | ||
| const url = `scheme://open?selfApp=${encodeURIComponent(JSON.stringify(selfApp))}`; | ||
| handleUrl(url); | ||
|
|
||
| expect(setSelfApp).toHaveBeenCalledWith(selfApp); | ||
| expect(startAppListener).toHaveBeenCalledWith('abc'); | ||
| const { navigationRef } = require('../../src/navigation'); | ||
| expect(navigationRef.navigate).toHaveBeenCalledWith('ProveScreen'); | ||
| }); | ||
|
|
||
| it('handles sessionId parameter', () => { | ||
| const url = 'scheme://open?sessionId=123'; | ||
| handleUrl(url); | ||
|
|
||
| expect(cleanSelfApp).toHaveBeenCalled(); | ||
| expect(startAppListener).toHaveBeenCalledWith('123'); | ||
| const { navigationRef } = require('../../src/navigation'); | ||
| expect(navigationRef.navigate).toHaveBeenCalledWith('ProveScreen'); | ||
| }); | ||
|
|
||
| it('handles mock_passport parameter', () => { | ||
| const mockData = { name: 'John', surname: 'Doe' }; | ||
| const url = `scheme://open?mock_passport=${encodeURIComponent(JSON.stringify(mockData))}`; | ||
| handleUrl(url); | ||
|
|
||
| expect(setDeepLinkUserDetails).toHaveBeenCalledWith({ | ||
| name: 'John', | ||
| surname: 'Doe', | ||
| nationality: undefined, | ||
| birthDate: undefined, | ||
| gender: undefined, | ||
| }); | ||
| const { navigationRef } = require('../../src/navigation'); | ||
| expect(navigationRef.navigate).toHaveBeenCalledWith('MockDataDeepLink'); | ||
| }); | ||
|
|
||
| it('navigates to QRCodeTrouble for invalid data', () => { | ||
| const url = 'scheme://open?selfApp=%7Binvalid'; | ||
| handleUrl(url); | ||
| const { navigationRef } = require('../../src/navigation'); | ||
| expect(navigationRef.navigate).toHaveBeenCalledWith('QRCodeTrouble'); | ||
| }); | ||
|
|
||
| it('setup listener registers and cleans up', () => { | ||
| const remove = jest.fn(); | ||
| (Linking.getInitialURL as jest.Mock).mockResolvedValue(undefined); | ||
| (Linking.addEventListener as jest.Mock).mockReturnValue({ remove }); | ||
|
|
||
| const cleanup = setupUniversalLinkListenerInNavigation(); | ||
| expect(Linking.addEventListener).toHaveBeenCalled(); | ||
| cleanup(); | ||
| expect(remove).toHaveBeenCalled(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||
| // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 | ||||||||||
|
|
||||||||||
| import { Platform } from 'react-native'; | ||||||||||
|
|
||||||||||
| import { parseScanResponse } from '../../src/utils/nfcScanner'; | ||||||||||
|
|
||||||||||
| describe('parseScanResponse', () => { | ||||||||||
| beforeEach(() => { | ||||||||||
| jest.clearAllMocks(); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| it('parses iOS response', () => { | ||||||||||
| Platform.OS = 'ios'; | ||||||||||
|
||||||||||
| const mrz = | ||||||||||
| 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<L898902C<3UTO6908061F9406236ZE184226B<<<<<14'; | ||||||||||
| const response = JSON.stringify({ | ||||||||||
| dataGroupHashes: JSON.stringify({ | ||||||||||
| DG1: { sodHash: 'abcd' }, | ||||||||||
| DG2: { sodHash: '1234' }, | ||||||||||
| }), | ||||||||||
| eContentBase64: Buffer.from('ec').toString('base64'), | ||||||||||
| signedAttributes: Buffer.from('sa').toString('base64'), | ||||||||||
| passportMRZ: mrz, | ||||||||||
| signatureBase64: Buffer.from([1, 2]).toString('base64'), | ||||||||||
| dataGroupsPresent: [1, 2], | ||||||||||
| passportPhoto: 'photo', | ||||||||||
| documentSigningCertificate: JSON.stringify({ PEM: 'CERT' }), | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| const result = parseScanResponse(response); | ||||||||||
| expect(result.mrz).toBe(mrz); | ||||||||||
| expect(result.documentType).toBe('passport'); | ||||||||||
| expect(result.dg1Hash).toEqual([171, 205]); | ||||||||||
| expect(result.dg2Hash).toEqual([18, 52]); | ||||||||||
| }); | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| it('parses Android response', () => { | ||||||||||
| Platform.OS = 'android'; | ||||||||||
|
||||||||||
| it('parses Android response', () => { | |
| Platform.OS = 'android'; | |
| it('parses Android response', () => { | |
| jest.spyOn(Platform, 'OS', 'get').mockReturnValue('android'); |
🤖 Prompt for AI Agents
In app/tests/utils/nfcScanner.test.ts around lines 37 to 38, avoid directly
mutating Platform.OS as it can cause test pollution. Instead, use jest.spyOn to
mock Platform.OS for the duration of the test. This involves spying on the
Platform module's OS property and providing a mock implementation that returns
'android', ensuring the original value is restored after the test.
Uh oh!
There was an error while loading. Please reload this page.