[SELF-1952] UI: Create KYC verified screen; prompt to enter proving flow (#1681)

* first pass at kyc verified screen

* finalize kyc verified design

* add queue buffer
This commit is contained in:
Justin Hernandez
2026-02-02 16:34:08 -08:00
committed by GitHub
parent ebdc639c88
commit 8b87ba36ab
7 changed files with 866 additions and 0 deletions

View File

@@ -80,6 +80,7 @@ export type RootStackParamList = Omit<
| 'IDPicker'
| 'IdDetails'
| 'KycSuccess'
| 'KYCVerified'
| 'RegistrationFallback'
| 'Loading'
| 'Modal'
@@ -207,6 +208,12 @@ export type RootStackParamList = Omit<
userId?: string;
}
| undefined;
KYCVerified:
| {
status?: string;
userId?: string;
}
| undefined;
// Dev screens
CreateMock: undefined;

View File

@@ -5,6 +5,7 @@
import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';
import KycSuccessScreen from '@/screens/kyc/KycSuccessScreen';
import KYCVerifiedScreen from '@/screens/kyc/KYCVerifiedScreen';
import AccountVerifiedSuccessScreen from '@/screens/onboarding/AccountVerifiedSuccessScreen';
import DisclaimerScreen from '@/screens/onboarding/DisclaimerScreen';
import SaveRecoveryPhraseScreen from '@/screens/onboarding/SaveRecoveryPhraseScreen';
@@ -38,6 +39,13 @@ const onboardingScreens = {
animation: 'slide_from_bottom',
} as NativeStackNavigationOptions,
},
KYCVerified: {
screen: KYCVerifiedScreen,
options: {
headerShown: false,
animation: 'slide_from_bottom',
} as NativeStackNavigationOptions,
},
};
export default onboardingScreens;

View File

@@ -4,16 +4,89 @@
import type { PropsWithChildren } from 'react';
import React, { useEffect } from 'react';
import type { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
import messaging from '@react-native-firebase/messaging';
import { NotificationEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import { navigationRef } from '@/navigation';
import { trackEvent } from '@/services/analytics';
// Queue for pending navigation actions that need to wait for navigation to be ready
let pendingNavigation: FirebaseMessagingTypes.RemoteMessage | null = null;
/**
* Execute navigation for a notification
* @returns true if navigation was executed, false if it needs to be queued
*/
const executeNotificationNavigation = (
remoteMessage: FirebaseMessagingTypes.RemoteMessage,
): boolean => {
if (!navigationRef.isReady()) {
return false;
}
const notificationType = remoteMessage.data?.type;
const status = remoteMessage.data?.status;
// Handle KYC result notifications
if (notificationType === 'kyc_result' && status === 'approved') {
navigationRef.navigate('KYCVerified', {
status: String(status),
userId: remoteMessage.data?.user_id
? String(remoteMessage.data.user_id)
: undefined,
});
return true;
}
// Add handling for other notification types here as needed
// For retry/rejected statuses, could navigate to appropriate screens in future
return true; // Navigation handled (or not applicable)
};
/**
* Handle navigation based on notification type and data
* Queues navigation if navigationRef is not ready yet
*/
const handleNotificationNavigation = (
remoteMessage: FirebaseMessagingTypes.RemoteMessage,
) => {
const executed = executeNotificationNavigation(remoteMessage);
if (!executed) {
// Navigation not ready yet - queue for later
pendingNavigation = remoteMessage;
if (__DEV__) {
console.log(
'Navigation not ready, queuing notification navigation:',
remoteMessage.data?.type,
);
}
}
};
/**
* Process any pending navigation once navigation is ready
*/
const processPendingNavigation = () => {
if (pendingNavigation && navigationRef.isReady()) {
if (__DEV__) {
console.log(
'Processing pending notification navigation:',
pendingNavigation.data?.type,
);
}
executeNotificationNavigation(pendingNavigation);
pendingNavigation = null;
}
};
export const NotificationTrackingProvider: React.FC<PropsWithChildren> = ({
children,
}) => {
useEffect(() => {
// Handle notification tap when app is in background
const unsubscribe = messaging().onNotificationOpenedApp(remoteMessage => {
trackEvent(NotificationEvents.BACKGROUND_NOTIFICATION_OPENED, {
messageId: remoteMessage.messageId,
@@ -22,8 +95,12 @@ export const NotificationTrackingProvider: React.FC<PropsWithChildren> = ({
// Track if user interacted with any actions
actionId: remoteMessage.data?.actionId,
});
// Handle navigation based on notification type
handleNotificationNavigation(remoteMessage);
});
// Handle notification tap when app is completely closed (cold start)
messaging()
.getInitialNotification()
.then(remoteMessage => {
@@ -35,11 +112,34 @@ export const NotificationTrackingProvider: React.FC<PropsWithChildren> = ({
// Track if user interacted with any actions
actionId: remoteMessage.data?.actionId,
});
// Handle navigation based on notification type
handleNotificationNavigation(remoteMessage);
}
});
return unsubscribe;
}, []);
// Monitor navigation readiness and process pending navigation
useEffect(() => {
// Check immediately if navigation is already ready
if (navigationRef.isReady()) {
processPendingNavigation();
return;
}
// Poll for navigation readiness if not ready yet
const checkInterval = setInterval(() => {
if (navigationRef.isReady()) {
processPendingNavigation();
clearInterval(checkInterval);
}
}, 100); // Check every 100ms
// Cleanup interval on unmount
return () => clearInterval(checkInterval);
}, []);
return <>{children}</>;
};

View File

@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { YStack } from 'tamagui';
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import {
AbstractButton,
Description,
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import { black, white } from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { buttonTap } from '@/integrations/haptics';
import type { RootStackParamList } from '@/navigation';
const KYCVerifiedScreen: React.FC = () => {
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const insets = useSafeAreaInsets();
const handleGenerateProof = () => {
buttonTap();
navigation.navigate('ProvingScreenRouter');
};
return (
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
<View style={styles.spacer} />
<YStack
paddingHorizontal={24}
justifyContent="center"
alignItems="center"
gap={12}
marginBottom={64}
>
<Title style={styles.title}>Your ID has been verified</Title>
<Description style={styles.description}>
Next Self will generate a zk proof specifically for this device that
you can use to proof your identity.
</Description>
</YStack>
<YStack gap={12} paddingHorizontal={20} paddingBottom={24}>
<AbstractButton
bgColor={white}
color={black}
onPress={handleGenerateProof}
>
Generate proof
</AbstractButton>
</YStack>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: black,
},
spacer: {
flex: 1,
},
title: {
color: white,
textAlign: 'center',
fontSize: 28,
letterSpacing: 1,
},
description: {
color: white,
textAlign: 'center',
fontSize: 18,
lineHeight: 27,
},
});
export default KYCVerifiedScreen;

View File

@@ -85,6 +85,7 @@ describe('navigation', () => {
'Home',
'IDPicker',
'IdDetails',
'KYCVerified',
'KycSuccess',
'Loading',
'ManageDocuments',

View File

@@ -0,0 +1,511 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import React from 'react';
import type { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
import { render, waitFor } from '@testing-library/react-native';
import { navigationRef } from '@/navigation';
import { NotificationTrackingProvider } from '@/providers/notificationTrackingProvider';
import * as analytics from '@/services/analytics';
// Mock Firebase messaging
const mockOnNotificationOpenedApp = jest.fn();
const mockGetInitialNotification = jest.fn();
jest.mock('@react-native-firebase/messaging', () => {
return jest.fn(() => ({
onNotificationOpenedApp: mockOnNotificationOpenedApp,
getInitialNotification: mockGetInitialNotification,
}));
});
// Mock navigation
jest.mock('@/navigation', () => ({
navigationRef: {
isReady: jest.fn(),
navigate: jest.fn(),
},
}));
// Mock analytics
jest.mock('@/services/analytics', () => ({
trackEvent: jest.fn(),
}));
// Mock analytics constants
jest.mock('@selfxyz/mobile-sdk-alpha/constants/analytics', () => ({
NotificationEvents: {
BACKGROUND_NOTIFICATION_OPENED: 'BACKGROUND_NOTIFICATION_OPENED',
COLD_START_NOTIFICATION_OPENED: 'COLD_START_NOTIFICATION_OPENED',
},
}));
const mockNavigationRef = navigationRef as jest.Mocked<typeof navigationRef>;
describe('NotificationTrackingProvider', () => {
const mockUserId = '19f21362-856a-4606-88e1-fa306036978f';
beforeEach(() => {
jest.clearAllMocks();
mockNavigationRef.isReady.mockReturnValue(true);
});
it('should render children without errors', () => {
mockGetInitialNotification.mockResolvedValue(null);
const { getByTestId } = render(
<NotificationTrackingProvider>
<mock-text testID="child">Test Child</mock-text>
</NotificationTrackingProvider>,
);
expect(getByTestId('child')).toHaveTextContent('Test Child');
});
describe('Background notification (onNotificationOpenedApp)', () => {
it('should navigate to KYCVerified when notification type is kyc_result and status is approved', async () => {
let notificationHandler:
| ((message: FirebaseMessagingTypes.RemoteMessage) => void)
| null = null;
mockOnNotificationOpenedApp.mockImplementation(handler => {
notificationHandler = handler;
return jest.fn(); // Return unsubscribe function
});
mockGetInitialNotification.mockResolvedValue(null);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
expect(mockOnNotificationOpenedApp).toHaveBeenCalled();
// Simulate notification tap
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'approved',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
if (notificationHandler) {
notificationHandler(remoteMessage);
}
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalledWith(
'BACKGROUND_NOTIFICATION_OPENED',
{
messageId: 'test-message-id',
type: 'kyc_result',
actionId: undefined,
},
);
expect(mockNavigationRef.navigate).toHaveBeenCalledWith('KYCVerified', {
status: 'approved',
userId: mockUserId,
});
});
});
it('should not navigate when status is retry', async () => {
let notificationHandler:
| ((message: FirebaseMessagingTypes.RemoteMessage) => void)
| null = null;
mockOnNotificationOpenedApp.mockImplementation(handler => {
notificationHandler = handler;
return jest.fn();
});
mockGetInitialNotification.mockResolvedValue(null);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'retry',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
if (notificationHandler) {
notificationHandler(remoteMessage);
}
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalled();
});
// Should not navigate for retry status
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
it('should not navigate when status is rejected', async () => {
let notificationHandler:
| ((message: FirebaseMessagingTypes.RemoteMessage) => void)
| null = null;
mockOnNotificationOpenedApp.mockImplementation(handler => {
notificationHandler = handler;
return jest.fn();
});
mockGetInitialNotification.mockResolvedValue(null);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'rejected',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
if (notificationHandler) {
notificationHandler(remoteMessage);
}
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalled();
});
// Should not navigate for rejected status
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
it('should handle missing notification data gracefully', async () => {
let notificationHandler:
| ((message: FirebaseMessagingTypes.RemoteMessage) => void)
| null = null;
mockOnNotificationOpenedApp.mockImplementation(handler => {
notificationHandler = handler;
return jest.fn();
});
mockGetInitialNotification.mockResolvedValue(null);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
const remoteMessage = {
messageId: 'test-message-id',
data: undefined,
} as FirebaseMessagingTypes.RemoteMessage;
if (notificationHandler) {
notificationHandler(remoteMessage);
}
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalled();
});
// Should not navigate when data is missing
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
it('should not navigate when navigation is not ready', async () => {
mockNavigationRef.isReady.mockReturnValue(false);
let notificationHandler:
| ((message: FirebaseMessagingTypes.RemoteMessage) => void)
| null = null;
mockOnNotificationOpenedApp.mockImplementation(handler => {
notificationHandler = handler;
return jest.fn();
});
mockGetInitialNotification.mockResolvedValue(null);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'approved',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
if (notificationHandler) {
notificationHandler(remoteMessage);
}
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalled();
});
// Should not navigate when navigationRef is not ready
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
});
describe('Cold start notification (getInitialNotification)', () => {
it('should navigate to KYCVerified when notification type is kyc_result and status is approved', async () => {
mockOnNotificationOpenedApp.mockReturnValue(jest.fn());
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'approved',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
mockGetInitialNotification.mockResolvedValue(remoteMessage);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalledWith(
'COLD_START_NOTIFICATION_OPENED',
{
messageId: 'test-message-id',
type: 'kyc_result',
actionId: undefined,
},
);
expect(mockNavigationRef.navigate).toHaveBeenCalledWith('KYCVerified', {
status: 'approved',
userId: mockUserId,
});
});
});
it('should not navigate when getInitialNotification returns null', async () => {
mockOnNotificationOpenedApp.mockReturnValue(jest.fn());
mockGetInitialNotification.mockResolvedValue(null);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
await waitFor(() => {
expect(mockGetInitialNotification).toHaveBeenCalled();
});
// Should not track or navigate when there's no initial notification
expect(analytics.trackEvent).not.toHaveBeenCalledWith(
'COLD_START_NOTIFICATION_OPENED',
expect.anything(),
);
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
it('should not navigate when status is retry on cold start', async () => {
mockOnNotificationOpenedApp.mockReturnValue(jest.fn());
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'retry',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
mockGetInitialNotification.mockResolvedValue(remoteMessage);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalledWith(
'COLD_START_NOTIFICATION_OPENED',
expect.anything(),
);
});
// Should not navigate for retry status
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
it('should queue navigation when navigationRef is not ready on cold start', async () => {
// Start with navigation not ready
mockNavigationRef.isReady.mockReturnValue(false);
mockOnNotificationOpenedApp.mockReturnValue(jest.fn());
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'approved',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
mockGetInitialNotification.mockResolvedValue(remoteMessage);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
// Wait for initial notification to be processed
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalledWith(
'COLD_START_NOTIFICATION_OPENED',
expect.anything(),
);
});
// Navigation should not have been called yet
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
// Simulate navigation becoming ready
mockNavigationRef.isReady.mockReturnValue(true);
// Wait for the polling interval to detect navigation is ready
await waitFor(
() => {
expect(mockNavigationRef.navigate).toHaveBeenCalledWith(
'KYCVerified',
{
status: 'approved',
userId: mockUserId,
},
);
},
{ timeout: 2000 },
);
});
it('should process pending navigation immediately if navigation becomes ready', async () => {
// Start with navigation not ready
mockNavigationRef.isReady.mockReturnValue(false);
mockOnNotificationOpenedApp.mockReturnValue(jest.fn());
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'kyc_result',
status: 'approved',
user_id: mockUserId,
},
} as FirebaseMessagingTypes.RemoteMessage;
mockGetInitialNotification.mockResolvedValue(remoteMessage);
const { rerender } = render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
// Wait for initial notification to be processed
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalledWith(
'COLD_START_NOTIFICATION_OPENED',
expect.anything(),
);
});
// Navigation should not have been called yet
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
// Make navigation ready
mockNavigationRef.isReady.mockReturnValue(true);
// Trigger a re-render to simulate React's update cycle
rerender(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
// Navigation should be called after navigation becomes ready
await waitFor(
() => {
expect(mockNavigationRef.navigate).toHaveBeenCalledWith(
'KYCVerified',
{
status: 'approved',
userId: mockUserId,
},
);
},
{ timeout: 2000 },
);
});
it('should not queue navigation for non-KYC notifications when navigation is not ready', async () => {
mockNavigationRef.isReady.mockReturnValue(false);
mockOnNotificationOpenedApp.mockReturnValue(jest.fn());
const remoteMessage = {
messageId: 'test-message-id',
data: {
type: 'other_notification',
status: 'some_status',
},
} as FirebaseMessagingTypes.RemoteMessage;
mockGetInitialNotification.mockResolvedValue(remoteMessage);
render(
<NotificationTrackingProvider>
<mock-text testID="child">Test</mock-text>
</NotificationTrackingProvider>,
);
await waitFor(() => {
expect(analytics.trackEvent).toHaveBeenCalledWith(
'COLD_START_NOTIFICATION_OPENED',
expect.anything(),
);
});
// Make navigation ready
mockNavigationRef.isReady.mockReturnValue(true);
// Wait a bit to ensure no navigation happens
await new Promise(resolve => setTimeout(resolve, 300));
// Should not navigate for non-KYC notifications
expect(mockNavigationRef.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import React from 'react';
import { useNavigation } from '@react-navigation/native';
import { fireEvent, render } from '@testing-library/react-native';
import * as haptics from '@/integrations/haptics';
import KYCVerifiedScreen from '@/screens/kyc/KYCVerifiedScreen';
// Note: While jest.setup.js provides comprehensive React Native mocking,
// react-test-renderer requires component-based mocks (functions) rather than
// string-based mocks for proper rendering. This minimal mock provides the
// specific components needed for this test without using requireActual to
// avoid memory issues (see .cursor/rules/test-memory-optimization.mdc).
jest.mock('react-native', () => ({
__esModule: true,
Platform: { OS: 'ios', select: jest.fn() },
StyleSheet: {
create: (styles: any) => styles,
flatten: (style: any) => style,
},
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
}));
jest.mock('react-native-edge-to-edge', () => ({
SystemBars: () => null,
}));
jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0 })),
}));
jest.mock('@react-navigation/native', () => ({
useNavigation: jest.fn(),
}));
// Mock Tamagui components
jest.mock('tamagui', () => ({
__esModule: true,
YStack: ({ children, ...props }: any) => <div {...props}>{children}</div>,
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
}));
jest.mock('@selfxyz/mobile-sdk-alpha/constants/colors', () => ({
black: '#000000',
white: '#FFFFFF',
}));
jest.mock('@selfxyz/mobile-sdk-alpha/components', () => ({
AbstractButton: ({ children, onPress, testID, ...props }: any) => (
<button
onClick={onPress}
type="button"
data-testid={testID || 'generate-proof-button'}
{...props}
>
{children}
</button>
),
Title: ({ children, style, testID, ...props }: any) => (
<div data-testid={testID || 'title'} style={style} {...props}>
{children}
</div>
),
Description: ({ children, style, testID, ...props }: any) => (
<div data-testid={testID || 'description'} style={style} {...props}>
{children}
</div>
),
}));
jest.mock('@/integrations/haptics', () => ({
buttonTap: jest.fn(),
}));
jest.mock('@/config/sentry', () => ({
captureException: jest.fn(),
}));
const mockUseNavigation = useNavigation as jest.MockedFunction<
typeof useNavigation
>;
describe('KYCVerifiedScreen', () => {
const mockNavigate = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mockUseNavigation.mockReturnValue({
navigate: mockNavigate,
} as any);
});
it('should render the screen without errors', () => {
const { root } = render(<KYCVerifiedScreen />);
expect(root).toBeTruthy();
});
it('should display the correct title', () => {
const { root } = render(<KYCVerifiedScreen />);
// Title is the first div child
const titleElement = root.findAll(
node =>
node.type === 'div' &&
node.props.children === 'Your ID has been verified',
)[0];
expect(titleElement).toBeTruthy();
});
it('should display the correct description text', () => {
const { root } = render(<KYCVerifiedScreen />);
// Description is a div with the description text
const descriptionElement = root.findAll(
node =>
node.type === 'div' &&
node.props.children ===
'Next Self will generate a zk proof specifically for this device that you can use to proof your identity.',
)[0];
expect(descriptionElement).toBeTruthy();
});
it('should have a "Generate proof" button that is visible', () => {
const { root } = render(<KYCVerifiedScreen />);
const buttons = root.findAllByType('button');
expect(buttons.length).toBeGreaterThan(0);
expect(buttons[0].props.children).toBe('Generate proof');
});
it('should trigger haptic feedback when "Generate proof" is pressed', () => {
const { root } = render(<KYCVerifiedScreen />);
const button = root.findAllByType('button')[0];
fireEvent.press(button);
expect(haptics.buttonTap).toHaveBeenCalledTimes(1);
});
it('should navigate to ProvingScreenRouter when "Generate proof" is pressed', () => {
const { root } = render(<KYCVerifiedScreen />);
const button = root.findAllByType('button')[0];
fireEvent.press(button);
expect(mockNavigate).toHaveBeenCalledWith('ProvingScreenRouter');
});
it('should have navigation available', () => {
render(<KYCVerifiedScreen />);
expect(mockUseNavigation).toHaveBeenCalled();
});
});