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
76 changes: 40 additions & 36 deletions frontend/features/verification/__tests__/spv-step.test.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,72 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import SPVStep from '../spv-step'; // Adjust path to your component
import { useVerificationStore } from '@/store/verificationStore'; // Adjust path to your Zustand store
import SPVPrivacyStep from '../components/steps/SPVPrivacyStep';
import { useWizardStore } from '../store/wizard.store';

// 1. Mock the Zustand store
jest.mock('@/store/verificationStore', () => ({
useVerificationStore: jest.fn(),
// 1. Mock the specific Wizard Zustand store
jest.mock('../store/wizard.store', () => ({
useWizardStore: jest.fn(),
}));

describe('SPVStep Component', () => {
const mockSetPrivacyOption = jest.fn();
describe('SPVPrivacyStep Component', () => {
const mockSetEncryptionEnabled = jest.fn();

beforeEach(() => {
jest.clearAllMocks();

// Set up the default mock return value for the Zustand store hook
(useVerificationStore as unknown as jest.Mock).mockReturnValue({
privacyOption: null, // Initial state
setPrivacyOption: mockSetPrivacyOption,
// Set up default store mock state (KMS Encrypted / true by default)
(useWizardStore as unknown as jest.Mock).mockReturnValue({
formData: {
content: {
encryptionEnabled: true,
}
},
setEncryptionEnabled: mockSetEncryptionEnabled,
});
});

it('renders the SPV privacy options correctly', () => {
render(<SPVStep />);
render(<SPVPrivacyStep />);

// Adjust these queries based on your actual UI text/roles
expect(screen.getByText(/Privacy Options/i)).toBeInTheDocument();

// Assuming you have buttons or radio inputs for the options
const publicOption = screen.getByRole('button', { name: /Public/i });
const privateOption = screen.getByRole('button', { name: /Private/i });
const publicOption = screen.getByText('Public');
const privateOption = screen.getByText('KMS Encrypted');

expect(publicOption).toBeInTheDocument();
expect(privateOption).toBeInTheDocument();
});

it('updates the Zustand store when a privacy option is clicked', async () => {
it('calls setEncryptionEnabled with false when Public is clicked', async () => {
const user = userEvent.setup();
render(<SPVStep />);
render(<SPVPrivacyStep />);

// Find the option element to interact with
const privateOption = screen.getByRole('button', { name: /Private/i });
// Grab the interactive element (the div holding the Public option)
const publicOptionContainer = screen.getByText('Public').closest('div[role="button"]');

// Simulate the user click event
await user.click(privateOption);
await user.click(publicOptionContainer!);

// Verify the store action was called with the expected value
// Adjust 'private' to whatever value your state actually expects
expect(mockSetPrivacyOption).toHaveBeenCalledTimes(1);
expect(mockSetPrivacyOption).toHaveBeenCalledWith('private');
expect(mockSetEncryptionEnabled).toHaveBeenCalledTimes(1);
expect(mockSetEncryptionEnabled).toHaveBeenCalledWith(false);
});

it('displays the correct active state based on the store value', () => {
// Override the mock for this specific test to simulate an already selected option
(useVerificationStore as unknown as jest.Mock).mockReturnValue({
privacyOption: 'private',
setPrivacyOption: mockSetPrivacyOption,
it('displays the correct active styling based on store boolean', () => {
// Override mock to test the Public (false) active state
(useWizardStore as unknown as jest.Mock).mockReturnValue({
formData: {
content: {
encryptionEnabled: false,
}
},
setEncryptionEnabled: mockSetEncryptionEnabled,
});

render(<SPVStep />);
render(<SPVPrivacyStep />);

// Example assertion: Check if the selected button has a specific class or aria attribute
// This depends heavily on how your UI library handles selected states
const privateOption = screen.getByRole('button', { name: /Private/i });
expect(privateOption).toHaveAttribute('aria-pressed', 'true');
const publicOptionContainer = screen.getByText('Public').closest('div[role="button"]');

// Check if the Tailwind active classes applied correctly
expect(publicOptionContainer).toHaveClass('bg-blue-50');
expect(publicOptionContainer).toHaveClass('border-blue-500');
});
});
97 changes: 97 additions & 0 deletions frontend/features/verification/components/steps/SPVPrivacyStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';
import { Info } from 'lucide-react';
import { useWizardStore } from '../../store/wizard.store';
import clsx from 'clsx';

const SPVPrivacyStep = () => {
const { formData, setEncryptionEnabled } = useWizardStore();

// Default to true as defined in your defaultContent store configuration
const isEncrypted = formData.content?.encryptionEnabled ?? true;

return (
<div className="flex flex-col gap-6 p-6">
<div className="space-y-2">
<h2 className="text-xl font-semibold text-gray-900">Privacy Options</h2>
<p className="text-sm text-gray-500">
Select how your verification data is handled on the Stellar network.
</p>
</div>

<Tooltip.Provider delayDuration={200}>
<div className="flex flex-col gap-4 sm:flex-row">

{/* Public Option (Encryption Disabled) */}
<div
role="button"
tabIndex={0}
onClick={() => setEncryptionEnabled(false)}
onKeyDown={(e) => e.key === 'Enter' && setEncryptionEnabled(false)}
className={clsx(
"relative flex flex-1 cursor-pointer flex-col gap-2 rounded-xl border p-4 transition-all hover:border-blue-500 hover:bg-blue-50/50",
!isEncrypted ? "border-blue-500 bg-blue-50 ring-1 ring-blue-500" : "border-gray-200"
)}
>
<div className="flex items-center justify-between">
<span className="font-medium text-gray-900">Public</span>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button type="button" className="text-gray-400 hover:text-gray-600 focus:outline-none">
<Info className="h-4 w-4" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="z-50 max-w-xs rounded-md bg-gray-900 px-3 py-2 text-sm text-white shadow-md animate-in fade-in zoom-in-95"
sideOffset={5}
>
Data is stored plainly on the ledger. Best for standard verifications where transparency is preferred.
<Tooltip.Arrow className="fill-gray-900" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
<p className="text-sm text-gray-500">Standard transparent verification</p>
</div>

{/* KMS Encrypted Option (Encryption Enabled) */}
<div
role="button"
tabIndex={0}
onClick={() => setEncryptionEnabled(true)}
onKeyDown={(e) => e.key === 'Enter' && setEncryptionEnabled(true)}
className={clsx(
"relative flex flex-1 cursor-pointer flex-col gap-2 rounded-xl border p-4 transition-all hover:border-blue-500 hover:bg-blue-50/50",
isEncrypted ? "border-blue-500 bg-blue-50 ring-1 ring-blue-500" : "border-gray-200"
)}
>
<div className="flex items-center justify-between">
<span className="font-medium text-gray-900">KMS Encrypted</span>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button type="button" className="text-gray-400 hover:text-gray-600 focus:outline-none">
<Info className="h-4 w-4" />
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="z-50 max-w-xs rounded-md bg-gray-900 px-3 py-2 text-sm text-white shadow-md animate-in fade-in zoom-in-95"
sideOffset={5}
>
Payloads are secured via Key Management Service before broadcasting. Required for sensitive transit permits.
<Tooltip.Arrow className="fill-gray-900" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
<p className="text-sm text-gray-500">Maximum security for sensitive data</p>
</div>

</div>
</Tooltip.Provider>
</div>
);
};

export default SPVPrivacyStep;
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@dnd-kit/utilities": "^3.2.2",
"@heroicons/react": "^2.2.0",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-tooltip": "^1.2.16",
"@react-pdf/renderer": "^4.3.2",
"@stellar/freighter-api": "^6.0.1",
"@stellar/stellar-sdk": "^15.1.0",
Expand Down
Loading
Loading