diff --git a/packages/untp-playground/__tests__/components/TestResults.test.tsx b/packages/untp-playground/__tests__/components/TestResults.test.tsx index ad3dbc66..543e40f5 100644 --- a/packages/untp-playground/__tests__/components/TestResults.test.tsx +++ b/packages/untp-playground/__tests__/components/TestResults.test.tsx @@ -1,9 +1,9 @@ -import { render, screen, waitFor, fireEvent, act } from '@testing-library/react'; -import { TestResults } from '@/components/TestResults'; -import { verifyCredential } from '@/lib/verificationService'; +import { TestResults, confettiConfig } from '@/components/TestResults'; import { detectExtension, validateCredentialSchema, validateExtension } from '@/lib/schemaValidation'; +import { verifyCredential } from '@/lib/verificationService'; import { Credential } from '@/types/credential'; -import { isEnvelopedProof } from '@/lib/credentialService'; +import { act, render, screen, waitFor } from '@testing-library/react'; +import confetti from 'canvas-confetti'; // Mock the external dependencies jest.mock('@/lib/verificationService'); @@ -18,31 +18,15 @@ jest.mock('sonner', () => ({ // Mock sample credentials const mockBasicCredential = { - original: { - proof: { - type: 'Ed25519Signature2020', + DigitalProductPassport: { + original: { + proof: { type: 'Ed25519Signature2020' }, }, + decoded: { + '@context': ['https://vocabulary.uncefact.org/2.0.0/context.jsonld'], + type: ['VerifiableCredential', 'DigitalProductPassport'], + } as Credential, }, - decoded: { - '@context': ['https://vocabulary.uncefact.org/2.0.0/context.jsonld'], - type: ['VerifiableCredential', 'DigitalProductPassport'], - } as Credential, -}; - -const mockExtensionCredential = { - original: { - proof: { - type: 'Ed25519Signature2020', - }, - }, - decoded: { - '@context': ['https://vocabulary.uncefact.org/2.0.0/context.jsonld'], - type: ['VerifiableCredential', 'DigitalProductPassport'], - credentialSubject: { - type: 'ExtensionType', - version: '1.0.0', - }, - } as Credential, }; describe('TestResults Component', () => { @@ -66,13 +50,13 @@ describe('TestResults Component', () => { expect(screen.getByText('DigitalTraceabilityEvent')).toBeInTheDocument(); }); - it('enders credential section with correct type and version', async () => { + it('renders credential section with correct type and version', async () => { (detectExtension as jest.Mock).mockReturnValue({ core: { type: 'DigitalProductPassport', version: '2.0.0' }, extension: { type: 'DigitalProductPassport', version: '2.0.0' }, }); - render(); + render(); // Check for the credential type heading const credentialSection = screen.getByRole('heading', { @@ -81,4 +65,60 @@ describe('TestResults Component', () => { }); expect(credentialSection).toBeInTheDocument(); }); + + describe('Confetti Behavior', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('shows confetti when all validations pass', async () => { + (verifyCredential as jest.Mock).mockResolvedValue({ verified: true }); + (validateCredentialSchema as jest.Mock).mockResolvedValue({ valid: true }); + (detectExtension as jest.Mock).mockReturnValue(undefined); + + render(); + + await waitFor(() => { + expect(confetti).toHaveBeenCalledTimes(1); + expect(confetti).toHaveBeenCalledWith(expect.objectContaining(confettiConfig)); + }); + }); + + it('does not show confetti when schema validation fails', async () => { + (verifyCredential as jest.Mock).mockResolvedValue({ verified: true }); + (validateCredentialSchema as jest.Mock).mockResolvedValue({ valid: false }); + (detectExtension as jest.Mock).mockReturnValue(undefined); + + await act(async () => { + render(); + }); + + expect(confetti).not.toHaveBeenCalled(); + }); + + it('does not show confetti when extension validation fails', async () => { + (verifyCredential as jest.Mock).mockResolvedValue({ verified: true }); + (validateCredentialSchema as jest.Mock).mockResolvedValue({ valid: true }); + (detectExtension as jest.Mock).mockReturnValue('someExtension'); + (validateExtension as jest.Mock).mockResolvedValue({ valid: false }); + + await act(async () => { + render(); + }); + + expect(confetti).not.toHaveBeenCalled(); + }); + + it('does not show confetti when verification fails', async () => { + (verifyCredential as jest.Mock).mockResolvedValue({ verified: false }); + (validateCredentialSchema as jest.Mock).mockResolvedValue({ valid: true }); + (detectExtension as jest.Mock).mockReturnValue(undefined); + + await act(async () => { + render(); + }); + + expect(confetti).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/untp-playground/src/components/TestResults.tsx b/packages/untp-playground/src/components/TestResults.tsx index 6432c941..dfe43f82 100644 --- a/packages/untp-playground/src/components/TestResults.tsx +++ b/packages/untp-playground/src/components/TestResults.tsx @@ -42,6 +42,12 @@ interface TestGroupProps { onToggle: () => void; } +export const confettiConfig = { + particleCount: 200, + spread: 90, + origin: { y: 0.7 }, +}; + const TestGroup = ({ credentialType, version, @@ -314,11 +320,14 @@ export function TestResults({ validated: true, }; + let allChecksPass = verificationResult.verified; const extension = detectExtension(credential.decoded); // Schema validation try { const validationResult = await validateCredentialSchema(credential.decoded); + allChecksPass = allChecksPass && validationResult.valid; + setTestResults((prev) => ({ ...prev, [type as CredentialType]: prev[type as CredentialType]?.map((step) => @@ -331,22 +340,8 @@ export function TestResults({ : step, ), })); - - if (!extension && validationResult.valid) { - if (!validatedCredentialsRef.current[credentialType]?.confettiShown) { - confetti({ - particleCount: 200, - spread: 90, - origin: { y: 0.7 }, - }); - - validatedCredentialsRef.current[credentialType] = { - ...validatedCredentialsRef.current[credentialType]!, - confettiShown: true, - }; - } - } } catch (error) { + allChecksPass = false; console.log('Schema validation error:', error); toast.error('Failed to fetch schema. Please try again.'); @@ -377,6 +372,8 @@ export function TestResults({ if (extension) { try { const extensionValidationResult = await validateExtension(credential.decoded); + allChecksPass = allChecksPass && extensionValidationResult.valid; + setTestResults((prev) => ({ ...prev, [type as CredentialType]: prev[type as CredentialType]?.map((step) => @@ -389,21 +386,8 @@ export function TestResults({ : step, ), })); - if (extensionValidationResult.valid) { - if (!validatedCredentialsRef.current[credentialType]?.confettiShown) { - confetti({ - particleCount: 200, - spread: 90, - origin: { y: 0.7 }, - }); - - validatedCredentialsRef.current[credentialType] = { - ...validatedCredentialsRef.current[credentialType]!, - confettiShown: true, - }; - } - } } catch (error) { + allChecksPass = false; console.log('Extension schema validation error:', error); toast.error('Failed to fetch extension schema. Please try again.'); @@ -430,6 +414,16 @@ export function TestResults({ })); } } + + // Trigger confetti only if all checks pass + if (allChecksPass && !validatedCredentialsRef.current[credentialType]?.confettiShown) { + confetti(confettiConfig); + + validatedCredentialsRef.current[credentialType] = { + ...validatedCredentialsRef.current[credentialType]!, + confettiShown: true, + }; + } } catch (error) { console.log('Error processing credential:', error); }