mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-02 02:25:21 +00:00
fix tests, removed excess logging and warnings
This commit is contained in:
parent
f01e3081a7
commit
78e5594cc3
@ -43,7 +43,6 @@ const ConvertSettings = ({
|
||||
const { setSelectedFiles } = useFileSelectionActions();
|
||||
const { setSelectedFiles: setContextSelectedFiles } = useFileContext();
|
||||
|
||||
// Get all possible conversion endpoints to check their availability
|
||||
const allEndpoints = useMemo(() => {
|
||||
const endpoints = new Set<string>();
|
||||
Object.values(EXTENSION_TO_ENDPOINT).forEach(toEndpoints => {
|
||||
@ -56,7 +55,6 @@ const ConvertSettings = ({
|
||||
|
||||
const { endpointStatus } = useMultipleEndpointsEnabled(allEndpoints);
|
||||
|
||||
// Function to check if a conversion is available based on endpoint
|
||||
const isConversionAvailable = (fromExt: string, toExt: string): boolean => {
|
||||
const endpointKey = EXTENSION_TO_ENDPOINT[fromExt]?.[toExt];
|
||||
if (!endpointKey) return false;
|
||||
@ -100,7 +98,6 @@ const ConvertSettings = ({
|
||||
const autoTarget = availableToOptions.length === 1 ? availableToOptions[0].value : '';
|
||||
onParameterChange('toExtension', autoTarget);
|
||||
|
||||
// Reset format-specific options
|
||||
onParameterChange('imageOptions', {
|
||||
colorType: COLOR_TYPES.COLOR,
|
||||
dpi: 300,
|
||||
@ -118,30 +115,23 @@ const ConvertSettings = ({
|
||||
onParameterChange('pdfaOptions', {
|
||||
outputFormat: 'pdfa-1',
|
||||
});
|
||||
// Disable smart detection when manually changing source format
|
||||
onParameterChange('isSmartDetection', false);
|
||||
onParameterChange('smartDetectionType', 'none');
|
||||
|
||||
// Deselect files that don't match the new source format
|
||||
if (selectedFiles.length > 0 && value !== 'any') {
|
||||
const matchingFiles = selectedFiles.filter(file => {
|
||||
const extension = file.name.split('.').pop()?.toLowerCase() || '';
|
||||
|
||||
// For 'image' source format, check if it's an image
|
||||
if (value === 'image') {
|
||||
return isImageFormat(extension);
|
||||
}
|
||||
|
||||
// For specific extensions, match exactly
|
||||
return extension === value;
|
||||
});
|
||||
|
||||
// Only update selection if files were filtered out
|
||||
if (matchingFiles.length !== selectedFiles.length) {
|
||||
// Update both selection contexts
|
||||
setSelectedFiles(matchingFiles);
|
||||
|
||||
// Update File Context selection with file IDs
|
||||
const matchingFileIds = matchingFiles.map(file => (file as any).id || file.name);
|
||||
setContextSelectedFiles(matchingFileIds);
|
||||
}
|
||||
@ -150,7 +140,6 @@ const ConvertSettings = ({
|
||||
|
||||
const handleToExtensionChange = (value: string) => {
|
||||
onParameterChange('toExtension', value);
|
||||
// Reset format-specific options when target extension changes
|
||||
onParameterChange('imageOptions', {
|
||||
colorType: COLOR_TYPES.COLOR,
|
||||
dpi: 300,
|
||||
|
@ -32,7 +32,6 @@ const GroupedFormatDropdown = ({
|
||||
const theme = useMantineTheme();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
||||
// Group options by category
|
||||
const groupedOptions = useMemo(() => {
|
||||
const groups: Record<string, FormatOption[]> = {};
|
||||
|
||||
@ -46,7 +45,6 @@ const GroupedFormatDropdown = ({
|
||||
return groups;
|
||||
}, [options]);
|
||||
|
||||
// Get selected option label for display in format "Group (EXTENSION)"
|
||||
const selectedLabel = useMemo(() => {
|
||||
if (!value) return placeholder;
|
||||
const selected = options.find(opt => opt.value === value);
|
||||
|
@ -31,11 +31,6 @@ export interface ConvertOperationHook {
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
// Utility functions for better maintainability
|
||||
|
||||
/**
|
||||
* Determines if multiple files should be processed separately
|
||||
*/
|
||||
const shouldProcessFilesSeparately = (
|
||||
selectedFiles: File[],
|
||||
parameters: ConvertParameters
|
||||
@ -58,9 +53,6 @@ const shouldProcessFilesSeparately = (
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a file from API response with fallback naming
|
||||
*/
|
||||
const createFileFromResponse = (
|
||||
responseData: any,
|
||||
headers: any,
|
||||
@ -73,9 +65,6 @@ const createFileFromResponse = (
|
||||
return createFileFromApiResponse(responseData, headers, fallbackFilename);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates thumbnails for multiple files
|
||||
*/
|
||||
const generateThumbnailsForFiles = async (files: File[]): Promise<string[]> => {
|
||||
const thumbnails: string[] = [];
|
||||
|
||||
@ -84,7 +73,6 @@ const generateThumbnailsForFiles = async (files: File[]): Promise<string[]> => {
|
||||
const thumbnail = await generateThumbnailForFile(file);
|
||||
thumbnails.push(thumbnail);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to generate thumbnail for ${file.name}:`, error);
|
||||
thumbnails.push('');
|
||||
}
|
||||
}
|
||||
@ -92,16 +80,11 @@ const generateThumbnailsForFiles = async (files: File[]): Promise<string[]> => {
|
||||
return thumbnails;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates download URL and filename for single or multiple files
|
||||
*/
|
||||
const createDownloadInfo = async (files: File[]): Promise<{ url: string; filename: string }> => {
|
||||
if (files.length === 1) {
|
||||
// Single file - direct download
|
||||
const url = window.URL.createObjectURL(files[0]);
|
||||
return { url, filename: files[0].name };
|
||||
} else {
|
||||
// Multiple files - create ZIP for convenient download
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
@ -125,7 +108,6 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
addFiles
|
||||
} = useFileContext();
|
||||
|
||||
// Internal state management
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [thumbnails, setThumbnails] = useState<string[]>([]);
|
||||
const [isGeneratingThumbnails, setIsGeneratingThumbnails] = useState(false);
|
||||
@ -147,7 +129,6 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
|
||||
const { fromExtension, toExtension, imageOptions, htmlOptions, emailOptions, pdfaOptions } = parameters;
|
||||
|
||||
// Add conversion-specific parameters
|
||||
if (isImageFormat(toExtension)) {
|
||||
formData.append("imageFormat", toExtension);
|
||||
formData.append("colorType", imageOptions.colorType);
|
||||
@ -164,19 +145,15 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
formData.append("colorType", imageOptions.colorType);
|
||||
formData.append("autoRotate", imageOptions.autoRotate.toString());
|
||||
} else if ((fromExtension === 'html' || fromExtension === 'zip') && toExtension === 'pdf') {
|
||||
// HTML to PDF conversion with zoom level (includes ZIP files with HTML)
|
||||
formData.append("zoom", htmlOptions.zoomLevel.toString());
|
||||
} else if (fromExtension === 'eml' && toExtension === 'pdf') {
|
||||
// Email to PDF conversion with email-specific options
|
||||
formData.append("includeAttachments", emailOptions.includeAttachments.toString());
|
||||
formData.append("maxAttachmentSizeMB", emailOptions.maxAttachmentSizeMB.toString());
|
||||
formData.append("downloadHtml", emailOptions.downloadHtml.toString());
|
||||
formData.append("includeAllRecipients", emailOptions.includeAllRecipients.toString());
|
||||
} else if (fromExtension === 'pdf' && toExtension === 'pdfa') {
|
||||
// PDF to PDF/A conversion with output format
|
||||
formData.append("outputFormat", pdfaOptions.outputFormat);
|
||||
} else if (fromExtension === 'pdf' && toExtension === 'csv') {
|
||||
// CSV extraction - always process all pages for simplified workflow
|
||||
formData.append("pageNumbers", "all");
|
||||
}
|
||||
|
||||
@ -250,12 +227,9 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use utility function to determine processing strategy
|
||||
if (shouldProcessFilesSeparately(selectedFiles, parameters)) {
|
||||
// Process each file separately with appropriate endpoint
|
||||
await executeMultipleSeparateFiles(parameters, selectedFiles);
|
||||
} else {
|
||||
// Process all files together (default behavior)
|
||||
await executeSingleCombinedOperation(parameters, selectedFiles);
|
||||
}
|
||||
}, [t]);
|
||||
@ -276,14 +250,9 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
const file = selectedFiles[i];
|
||||
setStatus(t("convert.processingFile", `Processing file ${i + 1} of ${selectedFiles.length}...`));
|
||||
|
||||
// Detect the specific file type for this file using the shared utility
|
||||
const fileExtension = detectFileExtension(file.name);
|
||||
|
||||
// Determine the best endpoint for this specific file type
|
||||
let endpoint = getEndpointUrl(fileExtension, parameters.toExtension);
|
||||
let fileSpecificParams = { ...parameters, fromExtension: fileExtension };
|
||||
|
||||
// Fallback to file-to-pdf if specific endpoint doesn't exist
|
||||
if (!endpoint && parameters.toExtension === 'pdf') {
|
||||
endpoint = '/api/v1/convert/file/pdf';
|
||||
console.log(`Using file-to-pdf fallback for ${fileExtension} file: ${file.name}`);
|
||||
@ -291,10 +260,9 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
|
||||
if (!endpoint) {
|
||||
console.error(`No endpoint available for ${fileExtension} to ${parameters.toExtension}`);
|
||||
continue; // Skip this file
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create individual operation for this file
|
||||
const { operation, operationId, fileId } = createOperation(fileSpecificParams, [file]);
|
||||
const formData = buildFormData(fileSpecificParams, [file]);
|
||||
|
||||
@ -316,32 +284,24 @@ export const useConvertOperation = (): ConvertOperationHook => {
|
||||
} catch (error: any) {
|
||||
console.error(`Error converting file ${file.name}:`, error);
|
||||
markOperationFailed(fileId, operationId);
|
||||
// Continue with other files even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length > 0) {
|
||||
console.log(`Multi-file conversion completed: ${results.length} files processed from ${selectedFiles.length} input files`);
|
||||
console.log('Result files:', results.map(f => f.name));
|
||||
|
||||
// Use utility function to generate thumbnails
|
||||
const generatedThumbnails = await generateThumbnailsForFiles(results);
|
||||
|
||||
// Set results for multiple files
|
||||
setFiles(results);
|
||||
setThumbnails(generatedThumbnails);
|
||||
|
||||
// Add all converted files to FileContext
|
||||
await addFiles(results);
|
||||
|
||||
// Use utility function to create download info
|
||||
try {
|
||||
const { url, filename } = await createDownloadInfo(results);
|
||||
setDownloadUrl(url);
|
||||
setDownloadFilename(filename);
|
||||
} catch (error) {
|
||||
console.error('Failed to create download info:', error);
|
||||
// Fallback to first file only
|
||||
const url = window.URL.createObjectURL(results[0]);
|
||||
setDownloadUrl(url);
|
||||
setDownloadFilename(results[0].name);
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useConvertParameters } from './useConvertParameters';
|
||||
|
||||
describe('useConvertParameters - Auto Detection & Smart Conversion', () => {
|
||||
@ -53,23 +53,7 @@ describe('useConvertParameters - Auto Detection & Smart Conversion', () => {
|
||||
expect(result.current.parameters.toExtension).toBe('pdf'); // Fallback to file-to-pdf
|
||||
});
|
||||
|
||||
test('should reset parameters when no files provided', () => {
|
||||
const { result } = renderHook(() => useConvertParameters());
|
||||
|
||||
// First set some parameters
|
||||
act(() => {
|
||||
result.current.analyzeFileTypes([{ name: 'test.pdf' }]);
|
||||
});
|
||||
|
||||
// Then analyze empty file list
|
||||
act(() => {
|
||||
result.current.analyzeFileTypes([]);
|
||||
});
|
||||
|
||||
expect(result.current.parameters.fromExtension).toBe('');
|
||||
expect(result.current.parameters.toExtension).toBe('');
|
||||
expect(result.current.parameters.isSmartDetection).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Multiple Identical Files', () => {
|
||||
|
@ -30,6 +30,56 @@ vi.mock('i18next-http-backend', () => ({
|
||||
global.URL.createObjectURL = vi.fn(() => 'mocked-url')
|
||||
global.URL.revokeObjectURL = vi.fn()
|
||||
|
||||
// Mock File and Blob API methods that aren't available in jsdom
|
||||
if (!globalThis.File.prototype.arrayBuffer) {
|
||||
globalThis.File.prototype.arrayBuffer = function() {
|
||||
// Return a simple ArrayBuffer with some mock data
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const view = new Uint8Array(buffer);
|
||||
view.set([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
return Promise.resolve(buffer);
|
||||
};
|
||||
}
|
||||
|
||||
if (!globalThis.Blob.prototype.arrayBuffer) {
|
||||
globalThis.Blob.prototype.arrayBuffer = function() {
|
||||
// Return a simple ArrayBuffer with some mock data
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const view = new Uint8Array(buffer);
|
||||
view.set([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
return Promise.resolve(buffer);
|
||||
};
|
||||
}
|
||||
|
||||
// Mock crypto.subtle for hashing in tests - force override even if exists
|
||||
const mockHashBuffer = new ArrayBuffer(32);
|
||||
const mockHashView = new Uint8Array(mockHashBuffer);
|
||||
// Fill with predictable mock hash data
|
||||
for (let i = 0; i < 32; i++) {
|
||||
mockHashView[i] = i;
|
||||
}
|
||||
|
||||
// Force override crypto.subtle to avoid Node.js native implementation
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
value: {
|
||||
subtle: {
|
||||
digest: vi.fn().mockImplementation(async (algorithm: string, data: any) => {
|
||||
// Always return the mock hash buffer regardless of input
|
||||
return mockHashBuffer.slice();
|
||||
}),
|
||||
},
|
||||
getRandomValues: vi.fn().mockImplementation((array: any) => {
|
||||
// Mock getRandomValues if needed
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
array[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
return array;
|
||||
}),
|
||||
} as Crypto,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
// Mock Worker for tests (Web Workers not available in test environment)
|
||||
global.Worker = vi.fn().mockImplementation(() => ({
|
||||
postMessage: vi.fn(),
|
||||
|
@ -27,12 +27,10 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const convertParams = useConvertParameters();
|
||||
const convertOperation = useConvertOperation();
|
||||
|
||||
// Endpoint validation
|
||||
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(
|
||||
convertParams.getEndpointName()
|
||||
);
|
||||
|
||||
// Auto-scroll to bottom when content grows
|
||||
const scrollToBottom = () => {
|
||||
if (scrollContainerRef.current) {
|
||||
scrollContainerRef.current.scrollTo({
|
||||
@ -42,13 +40,11 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate state variables first
|
||||
const hasFiles = selectedFiles.length > 0;
|
||||
const hasResults = convertOperation.downloadUrl !== null;
|
||||
const filesCollapsed = hasFiles;
|
||||
const settingsCollapsed = hasResults;
|
||||
|
||||
// Auto-detect extension when files change - now with smart detection
|
||||
useEffect(() => {
|
||||
if (selectedFiles.length > 0) {
|
||||
convertParams.analyzeFileTypes(selectedFiles);
|
||||
@ -62,17 +58,15 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
onPreviewFile?.(null);
|
||||
}, [convertParams.parameters, selectedFiles]);
|
||||
|
||||
// Auto-scroll when settings step becomes visible (files selected)
|
||||
useEffect(() => {
|
||||
if (hasFiles) {
|
||||
setTimeout(scrollToBottom, 100); // Small delay to ensure DOM update
|
||||
setTimeout(scrollToBottom, 100);
|
||||
}
|
||||
}, [hasFiles]);
|
||||
|
||||
// Auto-scroll when results appear
|
||||
useEffect(() => {
|
||||
if (hasResults) {
|
||||
setTimeout(scrollToBottom, 100); // Small delay to ensure DOM update
|
||||
setTimeout(scrollToBottom, 100);
|
||||
}
|
||||
}, [hasResults]);
|
||||
|
||||
@ -116,7 +110,6 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
<div className="h-full max-h-screen overflow-y-auto" ref={scrollContainerRef}>
|
||||
<ToolStepContainer>
|
||||
<Stack gap="sm" p="sm">
|
||||
{/* Files Step */}
|
||||
<ToolStep
|
||||
title={t("convert.files", "Files")}
|
||||
isVisible={true}
|
||||
@ -130,7 +123,6 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
/>
|
||||
</ToolStep>
|
||||
|
||||
{/* Settings Step */}
|
||||
<ToolStep
|
||||
title={t("convert.settings", "Settings")}
|
||||
isVisible={true}
|
||||
@ -161,7 +153,6 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
</Stack>
|
||||
</ToolStep>
|
||||
|
||||
{/* Results Step */}
|
||||
<ToolStep
|
||||
title={t("convert.results", "Results")}
|
||||
isVisible={hasResults}
|
||||
|
Loading…
x
Reference in New Issue
Block a user