mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Add tests for merge
This commit is contained in:
parent
5b38c0c31a
commit
5070eb8a3f
182
frontend/src/components/tools/merge/MergeFileSorter.test.tsx
Normal file
182
frontend/src/components/tools/merge/MergeFileSorter.test.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import MergeFileSorter from './MergeFileSorter';
|
||||
|
||||
// Mock useTranslation with predictable return values
|
||||
const mockT = vi.fn((key: string) => `mock-${key}`);
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: mockT })
|
||||
}));
|
||||
|
||||
// Wrapper component to provide Mantine context
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider>{children}</MantineProvider>
|
||||
);
|
||||
|
||||
describe('MergeFileSorter', () => {
|
||||
const mockOnSortFiles = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should render sort options dropdown, direction toggle, and sort button', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Should have a select dropdown (Mantine Select uses textbox role)
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
||||
|
||||
// Should have direction toggle button
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons).toHaveLength(2); // ActionIcon + Sort Button
|
||||
|
||||
// Should have sort button with text
|
||||
expect(screen.getByText('mock-merge.sortBy.sort')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render description text', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
expect(screen.getByText('mock-merge.sortBy.description')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should have filename selected by default', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const select = screen.getByRole('textbox');
|
||||
expect(select).toHaveValue('mock-merge.sortBy.filename');
|
||||
});
|
||||
|
||||
test('should show ascending direction by default', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Should show ascending arrow icon
|
||||
const directionButton = screen.getAllByRole('button')[0];
|
||||
expect(directionButton).toHaveAttribute('title', 'mock-merge.sortBy.ascending');
|
||||
});
|
||||
|
||||
test('should toggle direction when direction button is clicked', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const directionButton = screen.getAllByRole('button')[0];
|
||||
|
||||
// Initially ascending
|
||||
expect(directionButton).toHaveAttribute('title', 'mock-merge.sortBy.ascending');
|
||||
|
||||
// Click to toggle to descending
|
||||
fireEvent.click(directionButton);
|
||||
expect(directionButton).toHaveAttribute('title', 'mock-merge.sortBy.descending');
|
||||
|
||||
// Click again to toggle back to ascending
|
||||
fireEvent.click(directionButton);
|
||||
expect(directionButton).toHaveAttribute('title', 'mock-merge.sortBy.ascending');
|
||||
});
|
||||
|
||||
test('should call onSortFiles with correct parameters when sort button is clicked', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const sortButton = screen.getByText('mock-merge.sortBy.sort');
|
||||
fireEvent.click(sortButton);
|
||||
|
||||
// Should be called with default values (filename, ascending)
|
||||
expect(mockOnSortFiles).toHaveBeenCalledWith('filename', true);
|
||||
});
|
||||
|
||||
test('should call onSortFiles with dateModified when dropdown is changed', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Open the dropdown by clicking on the current selected value
|
||||
const currentSelection = screen.getByText('mock-merge.sortBy.filename');
|
||||
fireEvent.mouseDown(currentSelection);
|
||||
|
||||
// Click on the dateModified option
|
||||
const dateModifiedOption = screen.getByText('mock-merge.sortBy.dateModified');
|
||||
fireEvent.click(dateModifiedOption);
|
||||
|
||||
const sortButton = screen.getByText('mock-merge.sortBy.sort');
|
||||
fireEvent.click(sortButton);
|
||||
|
||||
expect(mockOnSortFiles).toHaveBeenCalledWith('dateModified', true);
|
||||
});
|
||||
|
||||
test('should call onSortFiles with descending direction when toggled', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const directionButton = screen.getAllByRole('button')[0];
|
||||
const sortButton = screen.getByText('mock-merge.sortBy.sort');
|
||||
|
||||
// Toggle to descending
|
||||
fireEvent.click(directionButton);
|
||||
|
||||
// Click sort
|
||||
fireEvent.click(sortButton);
|
||||
|
||||
expect(mockOnSortFiles).toHaveBeenCalledWith('filename', false);
|
||||
});
|
||||
|
||||
test('should handle complex user interaction sequence', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeFileSorter onSortFiles={mockOnSortFiles} />
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const directionButton = screen.getAllByRole('button')[0];
|
||||
const sortButton = screen.getByText('mock-merge.sortBy.sort');
|
||||
|
||||
// 1. Change to dateModified
|
||||
const currentSelection = screen.getByText('mock-merge.sortBy.filename');
|
||||
fireEvent.mouseDown(currentSelection);
|
||||
const dateModifiedOption = screen.getByText('mock-merge.sortBy.dateModified');
|
||||
fireEvent.click(dateModifiedOption);
|
||||
|
||||
// 2. Toggle to descending
|
||||
fireEvent.click(directionButton);
|
||||
|
||||
// 3. Click sort
|
||||
fireEvent.click(sortButton);
|
||||
|
||||
expect(mockOnSortFiles).toHaveBeenCalledWith('dateModified', false);
|
||||
|
||||
// 4. Toggle back to ascending
|
||||
fireEvent.click(directionButton);
|
||||
|
||||
// 5. Sort again
|
||||
fireEvent.click(sortButton);
|
||||
|
||||
expect(mockOnSortFiles).toHaveBeenCalledWith('dateModified', true);
|
||||
});
|
||||
});
|
100
frontend/src/components/tools/merge/MergeSettings.test.tsx
Normal file
100
frontend/src/components/tools/merge/MergeSettings.test.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import MergeSettings from './MergeSettings';
|
||||
import { MergeParameters } from '../../../hooks/tools/merge/useMergeParameters';
|
||||
|
||||
// Mock useTranslation with predictable return values
|
||||
const mockT = vi.fn((key: string) => `mock-${key}`);
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: mockT })
|
||||
}));
|
||||
|
||||
// Wrapper component to provide Mantine context
|
||||
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MantineProvider>{children}</MantineProvider>
|
||||
);
|
||||
|
||||
describe('MergeSettings', () => {
|
||||
const defaultParameters: MergeParameters = {
|
||||
removeDigitalSignature: false,
|
||||
generateTableOfContents: false,
|
||||
};
|
||||
|
||||
const mockOnParameterChange = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should render both merge option checkboxes', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeSettings
|
||||
parameters={defaultParameters}
|
||||
onParameterChange={mockOnParameterChange}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Should render one checkbox for each parameter
|
||||
const expectedCheckboxCount = Object.keys(defaultParameters).length;
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
expect(checkboxes).toHaveLength(expectedCheckboxCount);
|
||||
});
|
||||
|
||||
test('should show correct initial checkbox states based on parameters', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeSettings
|
||||
parameters={defaultParameters}
|
||||
onParameterChange={mockOnParameterChange}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
|
||||
// Both checkboxes should be unchecked initially
|
||||
checkboxes.forEach(checkbox => {
|
||||
expect(checkbox).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
test('should call onParameterChange with correct parameters when checkboxes are clicked', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeSettings
|
||||
parameters={defaultParameters}
|
||||
onParameterChange={mockOnParameterChange}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
|
||||
// Click the first checkbox (removeDigitalSignature - should toggle from false to true)
|
||||
fireEvent.click(checkboxes[0]);
|
||||
expect(mockOnParameterChange).toHaveBeenCalledWith('removeDigitalSignature', true);
|
||||
|
||||
// Click the second checkbox (generateTableOfContents - should toggle from false to true)
|
||||
fireEvent.click(checkboxes[1]);
|
||||
expect(mockOnParameterChange).toHaveBeenCalledWith('generateTableOfContents', true);
|
||||
});
|
||||
|
||||
test('should call translation function with correct keys', () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
<MergeSettings
|
||||
parameters={defaultParameters}
|
||||
onParameterChange={mockOnParameterChange}
|
||||
/>
|
||||
</TestWrapper>
|
||||
);
|
||||
|
||||
// Verify that translation keys are being called
|
||||
expect(mockT).toHaveBeenCalledWith('merge.removeDigitalSignature', 'Remove digital signature in the merged file?');
|
||||
expect(mockT).toHaveBeenCalledWith('merge.generateTableOfContents', 'Generate table of contents in the merged file?');
|
||||
});
|
||||
|
||||
});
|
131
frontend/src/hooks/tools/merge/useMergeOperation.test.ts
Normal file
131
frontend/src/hooks/tools/merge/useMergeOperation.test.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useMergeOperation } from './useMergeOperation';
|
||||
import type { MergeParameters } from './useMergeParameters';
|
||||
|
||||
// Mock the useToolOperation hook
|
||||
vi.mock('../shared/useToolOperation', () => ({
|
||||
useToolOperation: vi.fn()
|
||||
}));
|
||||
|
||||
// Mock the translation hook
|
||||
const mockT = vi.fn((key: string) => `translated-${key}`);
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: mockT })
|
||||
}));
|
||||
|
||||
// Mock the error handler
|
||||
vi.mock('../../../utils/toolErrorHandler', () => ({
|
||||
createStandardErrorHandler: vi.fn(() => 'error-handler-function')
|
||||
}));
|
||||
|
||||
// Import the mocked function
|
||||
import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
|
||||
|
||||
describe('useMergeOperation', () => {
|
||||
const mockUseToolOperation = vi.mocked(useToolOperation);
|
||||
|
||||
const getToolConfig = (): ToolOperationConfig<MergeParameters> => mockUseToolOperation.mock.calls[0][0];
|
||||
|
||||
const mockToolOperationReturn: ToolOperationHook<unknown> = {
|
||||
files: [],
|
||||
thumbnails: [],
|
||||
downloadUrl: null,
|
||||
downloadFilename: '',
|
||||
isLoading: false,
|
||||
errorMessage: null,
|
||||
status: '',
|
||||
isGeneratingThumbnails: false,
|
||||
progress: null,
|
||||
executeOperation: vi.fn(),
|
||||
resetResults: vi.fn(),
|
||||
clearError: vi.fn(),
|
||||
cancelOperation: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseToolOperation.mockReturnValue(mockToolOperationReturn);
|
||||
});
|
||||
|
||||
test('should build FormData correctly', () => {
|
||||
renderHook(() => useMergeOperation());
|
||||
|
||||
const config = getToolConfig();
|
||||
const mockFiles = [
|
||||
new File(['content1'], 'file1.pdf', { type: 'application/pdf' }),
|
||||
new File(['content2'], 'file2.pdf', { type: 'application/pdf' })
|
||||
];
|
||||
const parameters: MergeParameters = {
|
||||
removeDigitalSignature: true,
|
||||
generateTableOfContents: false
|
||||
};
|
||||
|
||||
const formData = config.buildFormData(parameters, mockFiles as any /* FIX ME */);
|
||||
|
||||
// Verify files are appended
|
||||
expect(formData.getAll('fileInput')).toHaveLength(2);
|
||||
expect(formData.getAll('fileInput')[0]).toBe(mockFiles[0]);
|
||||
expect(formData.getAll('fileInput')[1]).toBe(mockFiles[1]);
|
||||
|
||||
// Verify parameters are appended correctly
|
||||
expect(formData.get('sortType')).toBe('orderProvided');
|
||||
expect(formData.get('removeCertSign')).toBe('true');
|
||||
expect(formData.get('generateToc')).toBe('false');
|
||||
});
|
||||
|
||||
test('should handle response correctly', () => {
|
||||
renderHook(() => useMergeOperation());
|
||||
|
||||
const config = getToolConfig();
|
||||
const mockBlob = new Blob(['merged content'], { type: 'application/pdf' });
|
||||
const mockFiles = [
|
||||
new File(['content1'], 'file1.pdf', { type: 'application/pdf' }),
|
||||
new File(['content2'], 'file2.pdf', { type: 'application/pdf' })
|
||||
];
|
||||
|
||||
const result = config.responseHandler!(mockBlob, mockFiles) as File[];
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('merged_file1.pdf');
|
||||
expect(result[0].type).toBe('application/pdf');
|
||||
expect(result[0].size).toBe(mockBlob.size);
|
||||
});
|
||||
|
||||
test('should return the hook result from useToolOperation', () => {
|
||||
const { result } = renderHook(() => useMergeOperation());
|
||||
|
||||
expect(result.current).toBe(mockToolOperationReturn);
|
||||
});
|
||||
|
||||
test('should use correct translation keys for error handling', () => {
|
||||
renderHook(() => useMergeOperation());
|
||||
|
||||
expect(mockT).toHaveBeenCalledWith('merge.error.failed', 'An error occurred while merging the PDFs.');
|
||||
});
|
||||
|
||||
test('should build FormData with different parameter combinations', () => {
|
||||
renderHook(() => useMergeOperation());
|
||||
|
||||
const config = getToolConfig();
|
||||
const mockFiles = [new File(['test'], 'test.pdf', { type: 'application/pdf' })];
|
||||
|
||||
// Test case 1: All options disabled
|
||||
const params1: MergeParameters = {
|
||||
removeDigitalSignature: false,
|
||||
generateTableOfContents: false
|
||||
};
|
||||
const formData1 = config.buildFormData(params1, mockFiles as any /* FIX ME */);
|
||||
expect(formData1.get('removeCertSign')).toBe('false');
|
||||
expect(formData1.get('generateToc')).toBe('false');
|
||||
|
||||
// Test case 2: All options enabled
|
||||
const params2: MergeParameters = {
|
||||
removeDigitalSignature: true,
|
||||
generateTableOfContents: true
|
||||
};
|
||||
const formData2 = config.buildFormData(params2, mockFiles as any /* FIX ME */);
|
||||
expect(formData2.get('removeCertSign')).toBe('true');
|
||||
expect(formData2.get('generateToc')).toBe('true');
|
||||
});
|
||||
});
|
68
frontend/src/hooks/tools/merge/useMergeParameters.test.ts
Normal file
68
frontend/src/hooks/tools/merge/useMergeParameters.test.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useMergeParameters, defaultMergeParameters } from './useMergeParameters';
|
||||
|
||||
describe('useMergeParameters', () => {
|
||||
test('should initialize with default parameters', () => {
|
||||
const { result } = renderHook(() => useMergeParameters());
|
||||
|
||||
expect(result.current.parameters).toStrictEqual(defaultMergeParameters);
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ paramName: 'removeDigitalSignature' as const, value: true },
|
||||
{ paramName: 'removeDigitalSignature' as const, value: false },
|
||||
{ paramName: 'generateTableOfContents' as const, value: true },
|
||||
{ paramName: 'generateTableOfContents' as const, value: false }
|
||||
])('should update parameter $paramName to $value', ({ paramName, value }) => {
|
||||
const { result } = renderHook(() => useMergeParameters());
|
||||
|
||||
act(() => {
|
||||
result.current.updateParameter(paramName, value);
|
||||
});
|
||||
|
||||
expect(result.current.parameters[paramName]).toBe(value);
|
||||
});
|
||||
|
||||
test('should reset parameters to defaults', () => {
|
||||
const { result } = renderHook(() => useMergeParameters());
|
||||
|
||||
// First, change some parameters
|
||||
act(() => {
|
||||
result.current.updateParameter('removeDigitalSignature', true);
|
||||
result.current.updateParameter('generateTableOfContents', true);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.removeDigitalSignature).toBe(true);
|
||||
expect(result.current.parameters.generateTableOfContents).toBe(true);
|
||||
|
||||
// Then reset
|
||||
act(() => {
|
||||
result.current.resetParameters();
|
||||
});
|
||||
|
||||
expect(result.current.parameters).toStrictEqual(defaultMergeParameters);
|
||||
});
|
||||
|
||||
test('should validate parameters correctly - always returns true', () => {
|
||||
const { result } = renderHook(() => useMergeParameters());
|
||||
|
||||
// Default state should be valid
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
|
||||
// Change parameters and validate again
|
||||
act(() => {
|
||||
result.current.updateParameter('removeDigitalSignature', true);
|
||||
result.current.updateParameter('generateTableOfContents', true);
|
||||
});
|
||||
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
|
||||
// Reset and validate again
|
||||
act(() => {
|
||||
result.current.resetParameters();
|
||||
});
|
||||
|
||||
expect(result.current.validateParameters()).toBe(true);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user