-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: update unit test in untp playground (#182)
* test: update unit test in untp playground * test: improve unit test in untp playground * test: update test in untp playground test result
- Loading branch information
Showing
9 changed files
with
800 additions
and
1 deletion.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
packages/untp-playground/__tests__/components/CredentialUploader.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// packages/untp-playground/src/components/CredentialUploader.test.tsx | ||
import '@testing-library/jest-dom'; | ||
import React, { act } from 'react'; | ||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'; | ||
|
||
import { CredentialUploader } from '@/components/CredentialUploader'; | ||
import { toast } from 'sonner'; | ||
|
||
// Mock the toast library | ||
jest.mock('sonner', () => ({ | ||
toast: { | ||
error: jest.fn(), | ||
}, | ||
})); | ||
|
||
describe('CredentialUploader component', () => { | ||
const mockOnCredentialUpload = jest.fn(); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
jest.resetAllMocks(); | ||
JSON.parse = JSON.parse; | ||
}); | ||
|
||
it('renders the CredentialUploader component', () => { | ||
render(<CredentialUploader onCredentialUpload={mockOnCredentialUpload} />); | ||
const dropzoneElement = screen.getByText(/drag and drop credentials here/i); | ||
expect(dropzoneElement).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls onCredentialUpload with valid JSON file', async () => { | ||
const returnValue = { | ||
key: 'value', | ||
}; | ||
|
||
render(<CredentialUploader onCredentialUpload={mockOnCredentialUpload} />); | ||
const inputElement = screen.getByRole('presentation').querySelector('input[type="file"]'); | ||
|
||
const validJsonFile = new File([JSON.stringify(returnValue)], 'valid.json', { | ||
type: 'application/json', | ||
}); | ||
|
||
await act(async () => { | ||
fireEvent.change(inputElement as Element, { target: { files: [validJsonFile] } }); | ||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
}); | ||
|
||
await waitFor(async () => { | ||
expect(mockOnCredentialUpload).toHaveBeenCalledWith(returnValue); | ||
}); | ||
}); | ||
|
||
it('displays an error for invalid JSON content', async () => { | ||
// JSON.parse = jest.fn().mockRejectedValue(new Error('Invalid JSON')); | ||
render(<CredentialUploader onCredentialUpload={mockOnCredentialUpload} />); | ||
const inputElement = screen.getByRole('presentation').querySelector('input[type="file"]'); | ||
|
||
const invalidJsonFile = new File(['invalid json'], 'invalid.json', { | ||
type: 'application/json', | ||
}); | ||
|
||
await act(async () => { | ||
fireEvent.change(inputElement as Element, { target: { files: [invalidJsonFile] } }); | ||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
}); | ||
|
||
expect(toast.error).toHaveBeenCalledWith('Invalid format - File must contain valid JSON'); | ||
}); | ||
|
||
it('displays an error for invalid file types', async () => { | ||
render(<CredentialUploader onCredentialUpload={mockOnCredentialUpload} />); | ||
// Find the input element of type file | ||
const inputElement = screen.getByRole('presentation').querySelector('input[type="file"]'); | ||
const invalidFile = new File(['content'], 'invalid.pdf', { type: 'application/pdf' }); | ||
|
||
await act(async () => { | ||
fireEvent.change(inputElement as Element, { target: { files: [invalidFile] } }); | ||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
}); | ||
expect(toast.error).toHaveBeenCalledWith('Invalid file format. Please upload only .json, .jwt, or .txt files.'); | ||
}); | ||
|
||
it('displays an error for invalid JWT content', async () => { | ||
render(<CredentialUploader onCredentialUpload={mockOnCredentialUpload} />); | ||
const inputElement = screen.getByRole('presentation').querySelector('input[type="file"]'); | ||
|
||
const invalidJwtFile = new File(['invalid jwt'], 'invalid.jwt', { | ||
type: 'text/plain', | ||
}); | ||
|
||
await act(async () => { | ||
fireEvent.change(inputElement as Element, { target: { files: [invalidJwtFile] } }); | ||
// await new Promise((resolve) => setTimeout(resolve, 0)); | ||
}); | ||
|
||
await waitFor(async () => { | ||
expect(toast.error).toHaveBeenCalledWith( | ||
'Invalid JWT format - Please provide a file containing a valid JWT token', | ||
); | ||
}); | ||
}); | ||
|
||
it('shows generic error when credential processing fails', async () => { | ||
// Mock onCredentialUpload to throw an error | ||
const mockOnCredentialUpload = jest.fn().mockImplementation(() => { | ||
throw new Error('Some unexpected error'); | ||
}); | ||
render(<CredentialUploader onCredentialUpload={mockOnCredentialUpload} />); | ||
|
||
const inputElement = screen.getByRole('presentation').querySelector('input[type="file"]'); | ||
|
||
const validJSON = JSON.stringify({ key: 'value' }); | ||
const file = new File([validJSON], 'test.json', { type: 'application/json' }); | ||
|
||
await act(async () => { | ||
fireEvent.change(inputElement as Element, { target: { files: [file] } }); | ||
await new Promise((resolve) => setTimeout(resolve, 0)); | ||
}); | ||
|
||
await waitFor(() => { | ||
expect(toast.error).toHaveBeenCalledWith( | ||
'Failed to process credential - Please ensure the file contains valid data', | ||
); | ||
}); | ||
}); | ||
}); |
67 changes: 67 additions & 0 deletions
67
packages/untp-playground/__tests__/components/DownloadCredential.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import React, { act } from 'react'; | ||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'; | ||
import { DownloadCredential } from '@/components/DownloadCredential'; | ||
|
||
// Mock the fetch function | ||
global.fetch = jest.fn(); | ||
// Mock URL.createObjectURL and URL.revokeObjectURL | ||
global.URL.createObjectURL = jest.fn(); | ||
global.URL.revokeObjectURL = jest.fn(); | ||
|
||
describe('DownloadCredential', () => { | ||
// Reset all mocks before each test | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
// Mock successful response | ||
(global.fetch as jest.Mock).mockResolvedValue({ | ||
json: () => Promise.resolve({ test: 'data' }), | ||
}); | ||
(global.URL.createObjectURL as jest.Mock).mockReturnValue('blob:test-url'); | ||
}); | ||
|
||
it('renders download button with correct text and icon', () => { | ||
render(<DownloadCredential />); | ||
|
||
const button = screen.getByRole('button', { name: /download test credential/i }); | ||
expect(button).toBeInTheDocument(); | ||
expect(button.querySelector('svg')).toBeInTheDocument(); | ||
}); | ||
|
||
it('handles download click successfully', async () => { | ||
render(<DownloadCredential />); | ||
|
||
await act(async () => { | ||
const button = screen.getByRole('button'); | ||
await fireEvent.click(button); | ||
}); | ||
|
||
await waitFor(() => { | ||
// Check if fetch was called with correct path | ||
expect(fetch).toHaveBeenCalledWith('/credentials/dpp.json'); | ||
|
||
// Verify Blob creation | ||
expect(window.URL.createObjectURL).toHaveBeenCalled(); | ||
const blobCall = (window.URL.createObjectURL as jest.Mock).mock.calls[0][0]; | ||
expect(blobCall instanceof Blob).toBeTruthy(); | ||
expect(blobCall.type).toBe('application/json'); | ||
|
||
// Verify cleanup | ||
expect(window.URL.revokeObjectURL).toHaveBeenCalledWith('blob:test-url'); | ||
}); | ||
}); | ||
|
||
it('handles download error gracefully', async () => { | ||
// Mock console.log to verify error logging | ||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); | ||
|
||
// Mock fetch to reject | ||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Download failed')); | ||
|
||
render(<DownloadCredential />); | ||
|
||
const button = screen.getByRole('button'); | ||
await fireEvent.click(button); | ||
|
||
expect(consoleSpy).toHaveBeenCalledWith('Error downloading credential:', expect.any(Error)); | ||
}); | ||
}); |
170 changes: 170 additions & 0 deletions
170
packages/untp-playground/__tests__/components/ErrorDialog.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
|
||
import React from 'react'; | ||
import { render, screen, fireEvent } from '@testing-library/react'; | ||
import { ErrorDialog } from '@/components/ErrorDialog'; | ||
|
||
// Mock clipboard API | ||
Object.assign(navigator, { | ||
clipboard: { | ||
writeText: jest.fn(), | ||
}, | ||
}); | ||
|
||
describe('ErrorDialog', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('returns null when no errors are provided', () => { | ||
const { container } = render(<ErrorDialog errors={[]} />); | ||
expect(container.firstChild).toBeNull(); | ||
}); | ||
|
||
it('returns null when errors is not an array', () => { | ||
// @ts-ignore - Testing invalid input | ||
const { container } = render(<ErrorDialog errors={{}} />); | ||
expect(container.firstChild).toBeNull(); | ||
}); | ||
|
||
it('displays validation errors correctly', () => { | ||
const errors = [ | ||
{ | ||
keyword: 'type', | ||
instancePath: '/data/field1', | ||
params: { type: 'string' }, | ||
}, | ||
{ | ||
keyword: 'required', | ||
instancePath: '/data', | ||
params: { missingProperty: 'requiredField' }, | ||
}, | ||
] as any; | ||
|
||
render(<ErrorDialog errors={errors} />); | ||
|
||
// Check if error count is displayed | ||
expect(screen.getByText(/we found 2 issues/i)).toBeInTheDocument(); | ||
|
||
// Check if error locations are displayed | ||
expect(screen.getByText(/data → field1/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/wrong type/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/missing field/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('displays warnings for additional properties', () => { | ||
const errors = [ | ||
{ | ||
keyword: 'additionalProperties', | ||
instancePath: '', | ||
params: { additionalProperty: 'extraField' }, | ||
}, | ||
] as any; | ||
|
||
render(<ErrorDialog errors={errors} />); | ||
|
||
expect(screen.getByText(/1 warning/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/additional property: "extraField"/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('handles expandable error details', async () => { | ||
const errors = [ | ||
{ | ||
keyword: 'enum', | ||
instancePath: '/data/status', | ||
params: { allowedValues: ['active', 'inactive'] }, | ||
}, | ||
] as any; | ||
|
||
render(<ErrorDialog errors={errors} />); | ||
|
||
// Click to expand error details | ||
const button = screen.getByRole('button', { name: /choose from allowed values/i }); | ||
fireEvent.click(button); | ||
|
||
// Check if expanded content is visible | ||
expect(screen.getByText(/must be one of:/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/active, inactive/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('handles copy functionality', async () => { | ||
const errors = [ | ||
{ | ||
keyword: 'const', | ||
instancePath: '/data/type', | ||
params: { allowedValue: 'user' }, | ||
}, | ||
] as any; | ||
|
||
render(<ErrorDialog errors={errors} />); | ||
|
||
// Expand the error details | ||
const expandButton = screen.getByRole('button', { name: /use the correct value/i }); | ||
fireEvent.click(expandButton); | ||
|
||
// Click copy button | ||
const copyButton = screen.getByRole('button', { name: /copy/i }); | ||
fireEvent.click(copyButton); | ||
|
||
// Verify clipboard API was called | ||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('"user"'); | ||
|
||
// Verify "Copied!" text appears | ||
expect(screen.getByText(/copied!/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('displays correct tips based on error type', () => { | ||
const errors = [ | ||
{ | ||
keyword: 'const', | ||
instancePath: '/data/type', | ||
params: { allowedValue: 'user' }, | ||
}, | ||
] as any; | ||
|
||
render(<ErrorDialog errors={errors} />); | ||
|
||
// Expand the error details | ||
const expandButton = screen.getByRole('button', { name: /use the correct value/i }); | ||
fireEvent.click(expandButton); | ||
|
||
// Verify tip content | ||
expect(screen.getByText(/this value must match exactly as shown above/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('groups multiple errors for the same path', () => { | ||
const errors = [ | ||
{ | ||
keyword: 'type', | ||
instancePath: '/data/field1', | ||
params: { type: 'string' }, | ||
}, | ||
{ | ||
keyword: 'minLength', | ||
instancePath: '/data/field1', | ||
params: { limit: 3 }, | ||
}, | ||
] as any; | ||
|
||
render(<ErrorDialog errors={errors} />); | ||
|
||
// Should show only one group for '/data/field1' | ||
expect(screen.getByText(/we found 1 issue/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/data → field1/i)).toBeInTheDocument(); | ||
}); | ||
|
||
it('applies custom className when provided', () => { | ||
const errors = [ | ||
{ | ||
keyword: 'type', | ||
instancePath: '/data/field1', | ||
params: { type: 'string' }, | ||
}, | ||
] as any; | ||
|
||
const { container } = render(<ErrorDialog errors={errors} className='custom-class' />); | ||
expect(container.firstChild).toHaveClass('custom-class'); | ||
}); | ||
}); |
Oops, something went wrong.