Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __mocks__/viem.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,5 @@ module.exports = {
formatEther: jest.fn((wei) => Number(wei) / 1e18),
parseEther: jest.fn((eth) => BigInt(Math.floor(Number(eth) * 1e18))),
parseUnits: jest.fn((val, decimals) => BigInt(Number(val) * Math.pow(10, decimals))),
defineChain: jest.fn((chain) => chain),
};
14 changes: 6 additions & 8 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,13 @@ const sessionStorageMock = {
}
global.sessionStorage = sessionStorageMock

// Polyfill crypto.randomUUID for jsdom (Node.js <19)
// Polyfill crypto.randomUUID for jsdom (Node.js <19 / jsdom without randomUUID)
if (typeof globalThis.crypto !== 'undefined' && !globalThis.crypto.randomUUID) {
let counter = BigInt(0);
globalThis.crypto.randomUUID = function randomUUID() {
counter++;
const hex = (counter + BigInt(Date.now()) * BigInt(100000)).toString(16);
return hex.slice(0, 36).replace(
/^(.{8})(.{4})(.{4})(.{4})(.{12})$/,
'$1-$2-4$3-a$4-$5'
);
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/components/kyc/KycVerificationCenter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export function KycVerificationCenter() {
</p>
<div className="mt-3">
<Input
data-testid="kyc-file-input"
type="file"
multiple
accept="image/*,application/pdf"
Expand Down
100 changes: 100 additions & 0 deletions src/components/kyc/__tests__/ComplianceAuditLog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ComplianceAuditLog } from '../ComplianceAuditLog';
import { useKycStore } from '@/store/kycStore';
import type { ComplianceLogEntry } from '@/types/kyc';

describe('ComplianceAuditLog', () => {
beforeEach(() => {
useKycStore.getState().resetKyc();
});

it('shows an empty state when there is no compliance activity', () => {
render(<ComplianceAuditLog />);

expect(
screen.getByText('No compliance activity yet.')
).toBeInTheDocument();
});

it('renders a human-readable label for each audit entry', () => {
useKycStore.setState({
auditLog: [
{
id: 'log-1',
timestamp: '2026-08-01T10:00:00.000Z',
event: 'document_uploaded',
details: { name: 'passport.pdf' },
},
{
id: 'log-2',
timestamp: '2026-08-01T10:05:00.000Z',
event: 'verification_approved',
details: { provider: 'TrustLayer Mock' },
},
],
});

render(<ComplianceAuditLog />);

expect(screen.getByText('Document uploaded')).toBeInTheDocument();
expect(screen.getByText('Verification approved')).toBeInTheDocument();
expect(screen.queryByText('No compliance activity yet.')).not.toBeInTheDocument();
});

it('renders the event details for each entry', () => {
useKycStore.setState({
auditLog: [
{
id: 'log-1',
timestamp: '2026-08-01T10:00:00.000Z',
event: 'transaction_blocked',
details: { valueEth: 25, allowed: false },
},
],
});

render(<ComplianceAuditLog />);

expect(screen.getByText('Transaction blocked')).toBeInTheDocument();
expect(screen.getByText('valueEth: 25')).toBeInTheDocument();
expect(screen.getByText('allowed: false')).toBeInTheDocument();
});

it('covers all known event types with labels', () => {
const events: ComplianceLogEntry['event'][] = [
'threshold_updated',
'document_uploaded',
'liveness_started',
'liveness_passed',
'liveness_failed',
'verification_submitted',
'verification_approved',
'verification_rejected',
'transaction_screened',
'transaction_blocked',
];

useKycStore.setState({
auditLog: events.map((event, index) => ({
id: `log-${index}`,
timestamp: `2026-08-01T10:0${index}:00.000Z`,
event,
details: {},
})),
});

render(<ComplianceAuditLog />);

expect(screen.getByText('Threshold updated')).toBeInTheDocument();
expect(screen.getByText('Document uploaded')).toBeInTheDocument();
expect(screen.getByText('Liveness started')).toBeInTheDocument();
expect(screen.getByText('Liveness passed')).toBeInTheDocument();
expect(screen.getByText('Liveness failed')).toBeInTheDocument();
expect(screen.getByText('Verification submitted')).toBeInTheDocument();
expect(screen.getByText('Verification approved')).toBeInTheDocument();
expect(screen.getByText('Verification rejected')).toBeInTheDocument();
expect(screen.getByText('Transaction screened')).toBeInTheDocument();
expect(screen.getByText('Transaction blocked')).toBeInTheDocument();
});
});
61 changes: 61 additions & 0 deletions src/components/kyc/__tests__/KycStatusBadge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { KycStatusBadge } from '../KycStatusBadge';

describe('KycStatusBadge', () => {
it('renders the KYC required label for unverified status', () => {
render(<KycStatusBadge status="unverified" thresholdEth={10} />);

expect(screen.getByText('KYC required')).toBeInTheDocument();
});

it('renders the KYC pending label for pending status', () => {
render(<KycStatusBadge status="pending" thresholdEth={10} />);

expect(screen.getByText('KYC pending')).toBeInTheDocument();
});

it('renders the KYC verified label for verified status', () => {
render(<KycStatusBadge status="verified" thresholdEth={10} />);

expect(screen.getByText('KYC verified')).toBeInTheDocument();
});

it('renders the review needed label for rejected status', () => {
render(<KycStatusBadge status="rejected" thresholdEth={10} />);

expect(screen.getByText('KYC review needed')).toBeInTheDocument();
});

it('annotates the threshold in the title attribute', () => {
render(<KycStatusBadge status="unverified" thresholdEth={25} />);

expect(
screen.getByTitle('High-value transactions above 25 ETH require KYC review')
).toBeInTheDocument();
});

it('uses a different title when already verified', () => {
render(<KycStatusBadge status="verified" thresholdEth={25} />);

expect(
screen.getByTitle('High-value transactions above 25 ETH are allowed')
).toBeInTheDocument();
});

it('renders the compact variant without the full label', () => {
const { rerender } = render(
<KycStatusBadge status="verified" thresholdEth={10} compact />
);

expect(screen.getByText('Verified')).toBeInTheDocument();
expect(screen.queryByText('KYC verified')).not.toBeInTheDocument();

rerender(
<KycStatusBadge status="unverified" thresholdEth={10} compact />
);

expect(screen.getByText('KYC')).toBeInTheDocument();
expect(screen.queryByText('KYC required')).not.toBeInTheDocument();
});
});
Loading
Loading