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);
}