SELF-1938 sumsub integration (#1661)

* Sumsub: Update keychain and types

* sumsub: ProvingMachine changes - WIP

* fix: remove duplicate identifier

* update proving machine

* Refactor && Continue onchain registration if user left the app

* fix register flow

* Add hooks to KycSuccessScreen

* Integrate KycVerifiedScreen (#1686)

* Integrate KycVerifiedScreen & Fix race conditions

* yarn lint

* lint

* lint

* add mock kyc

* fix disclose flow

* yarn lint

* Feat/add kyc home screen card design (#1708)

* feat: add new designs to the kycIdCard

* refactor: Update KycIdCard design to match IdCard styling

* feat: update document cards + dev document

* feat: update empty id card for new design

* feat: update pending document card design

* feat: update expired doc + unregistered doc cards from new design

* fix: unregisted id card button links to continue registration screen

* fix: logo design on document cards

* feat: add 6 different backgrounds for ids

deterministically shows 1 of 6 backgrounds for each document | fix: fixed document designs not displaying correctly.

* chore: trigger CI rebuild

* feat: Integrate PendingIdCard to Homescreen

* fix KycIdCard.tsx

---------

Co-authored-by: seshanthS <seshanth@protonmail.com>

* lint

* fix tests

* fix: cleanup only on unmount

* coderabbit comments

* fix: cleanup unused code

* fix: edge case for German Passports with D<< nationality code

* fix tests

* review comments

* review comments

* lint

* Hide duplicated cards in Homescreen

* remove console.log

* fix patch

* remove unused vars

* agent updates

* agent feedback

* abstract colors and formatting

* agent feedback

* Regenerate Sumsub patch-package patch

* fix: handle malformed kyc payload in card background selector

* re-add for clean up

---------

Co-authored-by: Evi Nova <66773372+Tranquil-Flow@users.noreply.github.com>
Co-authored-by: Evi Nova <tranquil_flow@protonmail.com>
Co-authored-by: Justin Hernandez <justin.hernandez@self.xyz>
This commit is contained in:
Seshanth.S
2026-02-12 03:21:10 +05:30
committed by GitHub
parent 0bece5edd0
commit 886e02f53d
74 changed files with 3647 additions and 1606 deletions

View File

@@ -113,7 +113,6 @@ describe('navigation', () => {
'ShowRecoveryPhrase',
'Splash',
'StarfallPushCode',
'SumsubTest',
'WebView',
]);
});

View File

@@ -3,8 +3,7 @@
// 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 { fireEvent, render, waitFor } from '@testing-library/react-native';
import * as haptics from '@/integrations/haptics';
import KYCVerifiedScreen from '@/screens/kyc/KYCVerifiedScreen';
@@ -35,6 +34,9 @@ jest.mock('react-native-safe-area-context', () => ({
jest.mock('@react-navigation/native', () => ({
useNavigation: jest.fn(),
useRoute: jest.fn(() => ({
params: { documentId: 'test-document-id' },
})),
}));
// Mock Tamagui components
@@ -81,19 +83,33 @@ jest.mock('@/config/sentry', () => ({
captureException: jest.fn(),
}));
const mockUseNavigation = useNavigation as jest.MockedFunction<
typeof useNavigation
>;
const mockEmit = jest.fn();
const mockSelfClient = { emit: mockEmit };
jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
useSelfClient: jest.fn(() => mockSelfClient),
loadSelectedDocument: jest.fn(() =>
Promise.resolve({ documentCategory: 'kyc' }),
),
SdkEvents: {
DOCUMENT_OWNERSHIP_CONFIRMED: 'DOCUMENT_OWNERSHIP_CONFIRMED',
},
}));
jest.mock('@/stores/pendingKycStore', () => ({
usePendingKycStore: jest.fn(() => ({
pendingVerifications: [],
removePendingVerification: jest.fn(),
})),
}));
jest.mock('@/providers/passportDataProvider', () => ({
setSelectedDocument: jest.fn(() => Promise.resolve()),
}));
describe('KYCVerifiedScreen', () => {
const mockNavigate = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mockUseNavigation.mockReturnValue({
navigate: mockNavigate,
} as any);
});
it('should render the screen without errors', () => {
@@ -140,17 +156,23 @@ describe('KYCVerifiedScreen', () => {
expect(haptics.buttonTap).toHaveBeenCalledTimes(1);
});
it('should navigate to ProvingScreenRouter when "Generate proof" is pressed', () => {
it('should emit DOCUMENT_OWNERSHIP_CONFIRMED when "Generate proof" is pressed', async () => {
const { root } = render(<KYCVerifiedScreen />);
const button = root.findAllByType('button')[0];
fireEvent.press(button);
expect(mockNavigate).toHaveBeenCalledWith('ProvingScreenRouter');
await waitFor(() => {
expect(mockEmit).toHaveBeenCalledWith(
'DOCUMENT_OWNERSHIP_CONFIRMED',
expect.objectContaining({ documentCategory: 'kyc' }),
);
});
});
it('should have navigation available', () => {
render(<KYCVerifiedScreen />);
expect(mockUseNavigation).toHaveBeenCalled();
it('should use the documentId from route params', () => {
const { root } = render(<KYCVerifiedScreen />);
// Component should render without errors when documentId is provided
expect(root).toBeTruthy();
});
});

View File

@@ -25,12 +25,33 @@ jest.mock('react-native', () => ({
},
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
AppState: {
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
currentState: 'active',
},
NativeModules: {
NativeLoggerBridge: {},
RNPassportReader: {},
},
NativeEventEmitter: jest.fn(() => ({
addListener: jest.fn(() => ({ remove: jest.fn() })),
removeAllListeners: jest.fn(),
})),
requireNativeComponent: jest.fn(() => 'NativeComponent'),
}));
jest.mock('react-native-edge-to-edge', () => ({
SystemBars: () => null,
}));
jest.mock('@/hooks/useSumsubWebSocket', () => ({
useSumsubWebSocket: jest.fn(() => ({
subscribe: jest.fn(),
unsubscribe: jest.fn(),
unsubscribeAll: jest.fn(),
})),
}));
jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0 })),
}));
@@ -45,6 +66,7 @@ jest.mock('tamagui', () => ({
YStack: ({ children, ...props }: any) => <div {...props}>{children}</div>,
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
styled: (Component: any) => (props: any) => <Component {...props} />,
}));
jest.mock('@selfxyz/mobile-sdk-alpha/constants/colors', () => ({
@@ -108,7 +130,10 @@ jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
}));
jest.mock('@/stores/settingStore', () => ({
useSettingStore: jest.fn(),
useSettingStore: Object.assign(jest.fn(), {
getState: jest.fn(() => ({ loggingSeverity: 'info' })),
subscribe: jest.fn(() => jest.fn()),
}),
}));
const mockUseNavigation = useNavigation as jest.MockedFunction<

View File

@@ -0,0 +1,62 @@
// 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 type { IDDocument } from '@selfxyz/common';
import { serializeKycData } from '@selfxyz/common';
import { getBackgroundIndex } from '@/utils/cardBackgroundSelector';
const BACKGROUND_COUNT = 6;
function createKycDocument(serializedApplicantInfo: string): IDDocument {
return {
documentCategory: 'kyc',
documentType: 'drivers_licence',
mock: false,
serializedApplicantInfo,
signature: '',
pubkey: [],
};
}
describe('getBackgroundIndex', () => {
it('returns a deterministic index for a valid KYC payload', () => {
const serializedData = serializeKycData({
country: 'USA',
idType: 'passport',
idNumber: 'P1234567',
issuanceDate: '2020-01-01',
expiryDate: '2030-01-01',
fullName: 'Jane Doe',
dob: '1990-01-01',
photoHash: 'photohash',
phoneNumber: '+1234567890',
gender: 'F',
address: '123 Main St',
});
const serializedApplicantInfo = Buffer.from(
serializedData,
'utf-8',
).toString('base64');
const document = createKycDocument(serializedApplicantInfo);
const firstIndex = getBackgroundIndex(document);
const secondIndex = getBackgroundIndex(document);
expect(firstIndex).toBe(secondIndex);
expect(firstIndex).toBeGreaterThanOrEqual(1);
expect(firstIndex).toBeLessThanOrEqual(BACKGROUND_COUNT);
});
it('does not throw for malformed KYC payload and still returns a valid index', () => {
const document = createKycDocument(undefined as unknown as string);
expect(() => getBackgroundIndex(document)).not.toThrow();
const index = getBackgroundIndex(document);
expect(index).toBeGreaterThanOrEqual(1);
expect(index).toBeLessThanOrEqual(BACKGROUND_COUNT);
});
});