mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
V2: Convert Tool (#3828)
🔄 Dynamic Processing Strategies - Adaptive routing: Same tool uses different backend endpoints based on file analysis - Combined vs separate processing: Intelligently chooses between merge operations and individual file processing - Cross-format workflows: Enable complex conversions like "mixed files → PDF" that other tools can't handle ⚙️ Format-Specific Intelligence Each conversion type gets tailored options: - HTML/ZIP → PDF: Zoom controls (0.1-3.0 increments) with live preview - Email → PDF: Attachment handling, size limits, recipient control - PDF → PDF/A: Digital signature detection with warnings - Images → PDF: Smart combining vs individual file options File Architecture Core Implementation: ├── Convert.tsx # Main stepped workflow UI ├── ConvertSettings.tsx # Centralized settings with smart detection ├── GroupedFormatDropdown.tsx # Enhanced format selector with grouping ├── useConvertParameters.ts # Smart detection & parameter management ├── useConvertOperation.ts # Multi-strategy processing logic └── Settings Components: ├── ConvertFromWebSettings.tsx # HTML zoom controls ├── ConvertFromEmailSettings.tsx # Email attachment options ├── ConvertToPdfaSettings.tsx # PDF/A with signature detection ├── ConvertFromImageSettings.tsx # Image PDF options └── ConvertToImageSettings.tsx # PDF to image options Utility Layer Utils & Services: ├── convertUtils.ts # Format detection & endpoint routing ├── fileResponseUtils.ts # Generic API response handling └── setupTests.ts # Enhanced test environment with crypto mocks Testing & Quality Comprehensive Test Coverage Test Suite: ├── useConvertParameters.test.ts # Parameter logic & smart detection ├── useConvertParametersAutoDetection.test.ts # File type analysis ├── ConvertIntegration.test.tsx # End-to-end conversion workflows ├── ConvertSmartDetectionIntegration.test.tsx # Mixed file scenarios ├── ConvertE2E.spec.ts # Playwright browser tests ├── convertUtils.test.ts # Utility function validation └── fileResponseUtils.test.ts # API response handling Advanced Test Features - Crypto API mocking: Proper test environment for file hashing - File.arrayBuffer() polyfills: Complete browser API simulation - Multi-file scenario testing: Complex batch processing validation - CI/CD integration: Vitest runs in GitHub Actions with proper artifacts --------- Co-authored-by: Connor Yoh <connor@stirlingpdf.com> Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
429
frontend/src/tests/convert/ConvertE2E.spec.ts
Normal file
429
frontend/src/tests/convert/ConvertE2E.spec.ts
Normal file
@@ -0,0 +1,429 @@
|
||||
/**
|
||||
* End-to-End Tests for Convert Tool
|
||||
*
|
||||
* These tests dynamically discover available conversion endpoints and test them.
|
||||
* Tests are automatically skipped if the backend endpoint is not available.
|
||||
*
|
||||
* Run with: npm run test:e2e or npx playwright test
|
||||
*/
|
||||
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import {
|
||||
conversionDiscovery,
|
||||
type ConversionEndpoint
|
||||
} from '../helpers/conversionEndpointDiscovery';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Test configuration
|
||||
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8080';
|
||||
|
||||
/**
|
||||
* Resolves test fixture paths dynamically based on current working directory.
|
||||
* Works from both top-level project directory and frontend subdirectory.
|
||||
*/
|
||||
function resolveTestFixturePath(filename: string): string {
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Try frontend/src/tests/test-fixtures/ first (from top-level)
|
||||
const topLevelPath = path.join(cwd, 'frontend', 'src', 'tests', 'test-fixtures', filename);
|
||||
if (fs.existsSync(topLevelPath)) {
|
||||
return topLevelPath;
|
||||
}
|
||||
|
||||
// Try src/tests/test-fixtures/ (from frontend directory)
|
||||
const frontendPath = path.join(cwd, 'src', 'tests', 'test-fixtures', filename);
|
||||
if (fs.existsSync(frontendPath)) {
|
||||
return frontendPath;
|
||||
}
|
||||
|
||||
// Try relative path from current test file location
|
||||
const relativePath = path.join(__dirname, '..', 'test-fixtures', filename);
|
||||
if (fs.existsSync(relativePath)) {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
// Fallback to the original path format (should work from top-level)
|
||||
return path.join('.', 'frontend', 'src', 'tests', 'test-fixtures', filename);
|
||||
}
|
||||
|
||||
// Test file paths (dynamically resolved based on current working directory)
|
||||
const TEST_FILES = {
|
||||
pdf: resolveTestFixturePath('sample.pdf'),
|
||||
docx: resolveTestFixturePath('sample.docx'),
|
||||
doc: resolveTestFixturePath('sample.doc'),
|
||||
pptx: resolveTestFixturePath('sample.pptx'),
|
||||
ppt: resolveTestFixturePath('sample.ppt'),
|
||||
xlsx: resolveTestFixturePath('sample.xlsx'),
|
||||
xls: resolveTestFixturePath('sample.xls'),
|
||||
png: resolveTestFixturePath('sample.png'),
|
||||
jpg: resolveTestFixturePath('sample.jpg'),
|
||||
jpeg: resolveTestFixturePath('sample.jpeg'),
|
||||
gif: resolveTestFixturePath('sample.gif'),
|
||||
bmp: resolveTestFixturePath('sample.bmp'),
|
||||
tiff: resolveTestFixturePath('sample.tiff'),
|
||||
webp: resolveTestFixturePath('sample.webp'),
|
||||
md: resolveTestFixturePath('sample.md'),
|
||||
eml: resolveTestFixturePath('sample.eml'),
|
||||
html: resolveTestFixturePath('sample.html'),
|
||||
txt: resolveTestFixturePath('sample.txt'),
|
||||
xml: resolveTestFixturePath('sample.xml'),
|
||||
csv: resolveTestFixturePath('sample.csv')
|
||||
};
|
||||
|
||||
// File format to test file mapping
|
||||
const getTestFileForFormat = (format: string): string => {
|
||||
const formatMap: Record<string, string> = {
|
||||
'pdf': TEST_FILES.pdf,
|
||||
'docx': TEST_FILES.docx,
|
||||
'doc': TEST_FILES.doc,
|
||||
'pptx': TEST_FILES.pptx,
|
||||
'ppt': TEST_FILES.ppt,
|
||||
'xlsx': TEST_FILES.xlsx,
|
||||
'xls': TEST_FILES.xls,
|
||||
'office': TEST_FILES.docx, // Default office file
|
||||
'image': TEST_FILES.png, // Default image file
|
||||
'png': TEST_FILES.png,
|
||||
'jpg': TEST_FILES.jpg,
|
||||
'jpeg': TEST_FILES.jpeg,
|
||||
'gif': TEST_FILES.gif,
|
||||
'bmp': TEST_FILES.bmp,
|
||||
'tiff': TEST_FILES.tiff,
|
||||
'webp': TEST_FILES.webp,
|
||||
'md': TEST_FILES.md,
|
||||
'eml': TEST_FILES.eml,
|
||||
'html': TEST_FILES.html,
|
||||
'txt': TEST_FILES.txt,
|
||||
'xml': TEST_FILES.xml,
|
||||
'csv': TEST_FILES.csv
|
||||
};
|
||||
|
||||
return formatMap[format] || TEST_FILES.pdf; // Fallback to PDF
|
||||
};
|
||||
|
||||
// Expected file extensions for target formats
|
||||
const getExpectedExtension = (toFormat: string): string => {
|
||||
const extensionMap: Record<string, string> = {
|
||||
'pdf': '.pdf',
|
||||
'docx': '.docx',
|
||||
'pptx': '.pptx',
|
||||
'txt': '.txt',
|
||||
'html': '.zip', // HTML is zipped
|
||||
'xml': '.xml',
|
||||
'csv': '.csv',
|
||||
'md': '.md',
|
||||
'image': '.png', // Default for image conversion
|
||||
'png': '.png',
|
||||
'jpg': '.jpg',
|
||||
'jpeg': '.jpeg',
|
||||
'gif': '.gif',
|
||||
'bmp': '.bmp',
|
||||
'tiff': '.tiff',
|
||||
'webp': '.webp',
|
||||
'pdfa': '.pdf'
|
||||
};
|
||||
|
||||
return extensionMap[toFormat] || '.pdf';
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic test function for any conversion
|
||||
*/
|
||||
async function testConversion(page: Page, conversion: ConversionEndpoint) {
|
||||
const expectedExtension = getExpectedExtension(conversion.toFormat);
|
||||
|
||||
console.log(`Testing ${conversion.endpoint}: ${conversion.fromFormat} → ${conversion.toFormat}`);
|
||||
|
||||
// File should already be uploaded, click the Convert tool button
|
||||
await page.click('[data-testid="tool-convert"]');
|
||||
|
||||
// Wait for the FileEditor to load in convert mode with file thumbnails
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 5000 });
|
||||
|
||||
// Click the file thumbnail checkbox to select it in the FileEditor
|
||||
await page.click('[data-testid="file-thumbnail-checkbox"]');
|
||||
|
||||
// Wait for the conversion settings to appear after file selection
|
||||
await page.waitForSelector('[data-testid="convert-from-dropdown"]', { timeout: 5000 });
|
||||
|
||||
// Select FROM format
|
||||
await page.click('[data-testid="convert-from-dropdown"]');
|
||||
const fromFormatOption = page.locator(`[data-testid="format-option-${conversion.fromFormat}"]`);
|
||||
await fromFormatOption.scrollIntoViewIfNeeded();
|
||||
await fromFormatOption.click();
|
||||
|
||||
// Select TO format
|
||||
await page.click('[data-testid="convert-to-dropdown"]');
|
||||
const toFormatOption = page.locator(`[data-testid="format-option-${conversion.toFormat}"]`);
|
||||
await toFormatOption.scrollIntoViewIfNeeded();
|
||||
await toFormatOption.click();
|
||||
|
||||
// Handle format-specific options
|
||||
if (conversion.toFormat === 'image' || ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp'].includes(conversion.toFormat)) {
|
||||
// Set image conversion options if they appear
|
||||
const imageOptionsVisible = await page.locator('[data-testid="image-options-section"]').isVisible().catch(() => false);
|
||||
if (imageOptionsVisible) {
|
||||
// Click the color type dropdown and select "Color"
|
||||
await page.click('[data-testid="color-type-select"]');
|
||||
await page.getByRole('option', { name: 'Color' }).click();
|
||||
|
||||
// Set DPI value
|
||||
await page.fill('[data-testid="dpi-input"]', '150');
|
||||
|
||||
// Click the output type dropdown and select "Multiple"
|
||||
await page.click('[data-testid="output-type-select"]');
|
||||
|
||||
await page.getByRole('option', { name: 'single' }).click();
|
||||
}
|
||||
}
|
||||
|
||||
if (conversion.fromFormat === 'image' && conversion.toFormat === 'pdf') {
|
||||
// Set PDF creation options if they appear
|
||||
const pdfOptionsVisible = await page.locator('[data-testid="pdf-options-section"]').isVisible().catch(() => false);
|
||||
if (pdfOptionsVisible) {
|
||||
// Click the color type dropdown and select "Color"
|
||||
await page.click('[data-testid="color-type-select"]');
|
||||
await page.locator('[data-value="color"]').click();
|
||||
}
|
||||
}
|
||||
|
||||
if (conversion.fromFormat === 'pdf' && conversion.toFormat === 'csv') {
|
||||
// Set CSV extraction options if they appear
|
||||
const csvOptionsVisible = await page.locator('[data-testid="csv-options-section"]').isVisible().catch(() => false);
|
||||
if (csvOptionsVisible) {
|
||||
// Set specific page numbers for testing (test pages 1-2)
|
||||
await page.fill('[data-testid="page-numbers-input"]', '1-2');
|
||||
}
|
||||
}
|
||||
|
||||
// Start conversion
|
||||
await page.click('[data-testid="convert-button"]');
|
||||
|
||||
// Wait for conversion to complete (with generous timeout)
|
||||
await page.waitForSelector('[data-testid="download-button"]', { timeout: 60000 });
|
||||
|
||||
// Verify download is available
|
||||
const downloadButton = page.locator('[data-testid="download-button"]');
|
||||
await expect(downloadButton).toBeVisible();
|
||||
|
||||
// Start download and verify file
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await downloadButton.click();
|
||||
const download = await downloadPromise;
|
||||
|
||||
// Verify file extension
|
||||
expect(download.suggestedFilename()).toMatch(new RegExp(`\\${expectedExtension}$`));
|
||||
|
||||
// Save and verify file is not empty
|
||||
const path = await download.path();
|
||||
if (path) {
|
||||
const fs = require('fs');
|
||||
const stats = fs.statSync(path);
|
||||
expect(stats.size).toBeGreaterThan(0);
|
||||
|
||||
// Format-specific validations
|
||||
if (conversion.toFormat === 'pdf' || conversion.toFormat === 'pdfa') {
|
||||
// Verify PDF header
|
||||
const buffer = fs.readFileSync(path);
|
||||
const header = buffer.toString('utf8', 0, 4);
|
||||
expect(header).toBe('%PDF');
|
||||
}
|
||||
|
||||
if (conversion.toFormat === 'txt') {
|
||||
// Verify text content exists
|
||||
const content = fs.readFileSync(path, 'utf8');
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
if (conversion.toFormat === 'csv') {
|
||||
// Verify CSV content contains separators
|
||||
const content = fs.readFileSync(path, 'utf8');
|
||||
expect(content).toContain(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discover conversions at module level before tests are defined
|
||||
let allConversions: ConversionEndpoint[] = [];
|
||||
let availableConversions: ConversionEndpoint[] = [];
|
||||
let unavailableConversions: ConversionEndpoint[] = [];
|
||||
|
||||
// Pre-populate conversions synchronously for test generation
|
||||
(async () => {
|
||||
try {
|
||||
availableConversions = await conversionDiscovery.getAvailableConversions();
|
||||
unavailableConversions = await conversionDiscovery.getUnavailableConversions();
|
||||
allConversions = [...availableConversions, ...unavailableConversions];
|
||||
} catch (error) {
|
||||
console.error('Failed to discover conversions during module load:', error);
|
||||
}
|
||||
})();
|
||||
|
||||
test.describe('Convert Tool E2E Tests', () => {
|
||||
|
||||
test.beforeAll(async () => {
|
||||
// Re-discover to ensure fresh data at test time
|
||||
console.log('Re-discovering available conversion endpoints...');
|
||||
availableConversions = await conversionDiscovery.getAvailableConversions();
|
||||
unavailableConversions = await conversionDiscovery.getUnavailableConversions();
|
||||
|
||||
console.log(`Found ${availableConversions.length} available conversions:`);
|
||||
availableConversions.forEach(conv => {
|
||||
console.log(` ✓ ${conv.endpoint}: ${conv.fromFormat} → ${conv.toFormat}`);
|
||||
});
|
||||
|
||||
if (unavailableConversions.length > 0) {
|
||||
console.log(`Found ${unavailableConversions.length} unavailable conversions:`);
|
||||
unavailableConversions.forEach(conv => {
|
||||
console.log(` ✗ ${conv.endpoint}: ${conv.fromFormat} → ${conv.toFormat}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Navigate to the homepage
|
||||
await page.goto(`${BASE_URL}`);
|
||||
|
||||
// Wait for the page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait for the file upload area to appear (shown when no active files)
|
||||
await page.waitForSelector('[data-testid="file-dropzone"]', { timeout: 10000 });
|
||||
});
|
||||
|
||||
test.describe('Dynamic Conversion Tests', () => {
|
||||
|
||||
// Generate a test for each potentially available conversion
|
||||
// We'll discover all possible conversions and then skip unavailable ones at runtime
|
||||
test('PDF to PNG conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/img', fromFormat: 'pdf', toFormat: 'png' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('PDF to DOCX conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/word', fromFormat: 'pdf', toFormat: 'docx' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('DOCX to PDF conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/file/pdf', fromFormat: 'docx', toFormat: 'pdf' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('Image to PDF conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/img/pdf', fromFormat: 'png', toFormat: 'pdf' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('PDF to TXT conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/text', fromFormat: 'pdf', toFormat: 'txt' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('PDF to HTML conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/html', fromFormat: 'pdf', toFormat: 'html' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('PDF to XML conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/xml', fromFormat: 'pdf', toFormat: 'xml' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('PDF to CSV conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/csv', fromFormat: 'pdf', toFormat: 'csv' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
|
||||
test('PDF to PDFA conversion', async ({ page }) => {
|
||||
const conversion = { endpoint: '/api/v1/convert/pdf/pdfa', fromFormat: 'pdf', toFormat: 'pdfa' };
|
||||
const isAvailable = availableConversions.some(c => c.apiPath === conversion.endpoint);
|
||||
test.skip(!isAvailable, `Endpoint ${conversion.endpoint} is not available`);
|
||||
|
||||
const testFile = getTestFileForFormat(conversion.fromFormat);
|
||||
await page.setInputFiles('input[type="file"]', testFile);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
await testConversion(page, conversion);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Static Tests', () => {
|
||||
|
||||
// Test that disabled conversions don't appear in dropdowns when they shouldn't
|
||||
test('should not show conversion button when no valid conversions available', async ({ page }) => {
|
||||
// This test ensures the convert button is disabled when no valid conversion is possible
|
||||
await page.setInputFiles('input[type="file"]', TEST_FILES.pdf);
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 10000 });
|
||||
|
||||
// Click the Convert tool button
|
||||
await page.click('[data-testid="tool-convert"]');
|
||||
|
||||
// Wait for convert mode and select file
|
||||
await page.waitForSelector('[data-testid="file-thumbnail"]', { timeout: 5000 });
|
||||
await page.click('[data-testid="file-thumbnail-checkbox"]');
|
||||
|
||||
// Don't select any formats - convert button should not exist
|
||||
const convertButton = page.locator('[data-testid="convert-button"]');
|
||||
await expect(convertButton).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
581
frontend/src/tests/convert/ConvertIntegration.test.tsx
Normal file
581
frontend/src/tests/convert/ConvertIntegration.test.tsx
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* Integration tests for Convert Tool - Tests actual conversion functionality
|
||||
*
|
||||
* These tests verify the integration between frontend components and backend:
|
||||
* 1. useConvertOperation hook makes correct API calls
|
||||
* 2. File upload/download flow functions properly
|
||||
* 3. Error handling works for various failure scenarios
|
||||
* 4. Parameter passing works between frontend and backend
|
||||
* 5. FileContext integration works correctly
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
|
||||
import { ConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
||||
import { FileContextProvider } from '../../contexts/FileContext';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from '../../i18n/config';
|
||||
import axios from 'axios';
|
||||
|
||||
// Mock axios
|
||||
vi.mock('axios');
|
||||
const mockedAxios = vi.mocked(axios);
|
||||
|
||||
// Mock utility modules
|
||||
vi.mock('../../utils/thumbnailUtils', () => ({
|
||||
generateThumbnailForFile: vi.fn().mockResolvedValue('data:image/png;base64,fake-thumbnail')
|
||||
}));
|
||||
|
||||
vi.mock('../../utils/api', () => ({
|
||||
makeApiUrl: vi.fn((path: string) => `/api/v1${path}`)
|
||||
}));
|
||||
|
||||
// Create realistic test files
|
||||
const createTestFile = (name: string, content: string, type: string): File => {
|
||||
return new File([content], name, { type });
|
||||
};
|
||||
|
||||
const createPDFFile = (): File => {
|
||||
const pdfContent = '%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\ntrailer\n<<\n/Size 2\n/Root 1 0 R\n>>\nstartxref\n0\n%%EOF';
|
||||
return createTestFile('test.pdf', pdfContent, 'application/pdf');
|
||||
};
|
||||
|
||||
// Test wrapper component
|
||||
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<FileContextProvider>
|
||||
{children}
|
||||
</FileContextProvider>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
describe('Convert Tool Integration Tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Setup default axios mock
|
||||
mockedAxios.post = vi.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('useConvertOperation Integration', () => {
|
||||
|
||||
test('should make correct API call for PDF to PNG conversion', async () => {
|
||||
const mockBlob = new Blob(['fake-image-data'], { type: 'image/png' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
statusText: 'OK'
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
// Verify axios was called with correct parameters
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
'/api/v1/convert/pdf/img',
|
||||
expect.any(FormData),
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
// Verify FormData contains correct parameters
|
||||
const formDataCall = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formDataCall.get('imageFormat')).toBe('png');
|
||||
expect(formDataCall.get('colorType')).toBe('color');
|
||||
expect(formDataCall.get('dpi')).toBe('300');
|
||||
expect(formDataCall.get('singleOrMultiple')).toBe('multiple');
|
||||
|
||||
// Verify hook state updates
|
||||
expect(result.current.downloadUrl).toBeTruthy();
|
||||
expect(result.current.downloadFilename).toBe('test_converted.png');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.errorMessage).toBe(null);
|
||||
});
|
||||
|
||||
test('should handle API error responses correctly', async () => {
|
||||
const errorMessage = 'Invalid file format';
|
||||
mockedAxios.post.mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 400,
|
||||
data: errorMessage
|
||||
},
|
||||
message: errorMessage
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createTestFile('invalid.txt', 'not a pdf', 'text/plain');
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
// Verify error handling
|
||||
expect(result.current.errorMessage).toBe(errorMessage);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.downloadUrl).toBe(null);
|
||||
});
|
||||
|
||||
test('should handle network errors gracefully', async () => {
|
||||
mockedAxios.post.mockRejectedValueOnce(new Error('Network error'));
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
expect(result.current.errorMessage).toBe('Network error');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('API and Hook Integration', () => {
|
||||
|
||||
test('should correctly map image conversion parameters to API call', async () => {
|
||||
const mockBlob = new Blob(['fake-data'], { type: 'image/jpeg' });
|
||||
mockedAxios.post.mockResolvedValueOnce({ data: mockBlob });
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'jpg',
|
||||
pageNumbers: 'all',
|
||||
imageOptions: {
|
||||
colorType: 'grayscale',
|
||||
dpi: 150,
|
||||
singleOrMultiple: 'single',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
// Verify integration: hook parameters → FormData → axios call → hook state
|
||||
const formDataCall = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formDataCall.get('imageFormat')).toBe('jpg');
|
||||
expect(formDataCall.get('colorType')).toBe('grayscale');
|
||||
expect(formDataCall.get('dpi')).toBe('150');
|
||||
expect(formDataCall.get('singleOrMultiple')).toBe('single');
|
||||
|
||||
// Verify complete workflow: API response → hook state → FileContext integration
|
||||
expect(result.current.downloadUrl).toBeTruthy();
|
||||
expect(result.current.files).toHaveLength(1);
|
||||
expect(result.current.files[0].name).toBe('test_converted.jpg');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
test('should make correct API call for PDF to CSV conversion with simplified workflow', async () => {
|
||||
const mockBlob = new Blob(['fake-csv-data'], { type: 'text/csv' });
|
||||
mockedAxios.post.mockResolvedValueOnce({
|
||||
data: mockBlob,
|
||||
status: 200,
|
||||
statusText: 'OK'
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'csv',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
// Verify correct endpoint is called
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||
'/api/v1/convert/pdf/csv',
|
||||
expect.any(FormData),
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
// Verify FormData contains correct parameters for simplified CSV conversion
|
||||
const formDataCall = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formDataCall.get('pageNumbers')).toBe('all'); // Always "all" for simplified workflow
|
||||
expect(formDataCall.get('fileInput')).toBe(testFile);
|
||||
|
||||
// Verify hook state updates correctly
|
||||
expect(result.current.downloadUrl).toBeTruthy();
|
||||
expect(result.current.downloadFilename).toBe('test_converted.csv');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.errorMessage).toBe(null);
|
||||
});
|
||||
|
||||
test('should handle complete unsupported conversion workflow', async () => {
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'unsupported',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
// Verify integration: utils validation prevents API call, hook shows error
|
||||
expect(mockedAxios.post).not.toHaveBeenCalled();
|
||||
expect(result.current.errorMessage).toContain('errorNotSupported');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.downloadUrl).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('File Upload Integration', () => {
|
||||
|
||||
test('should handle multiple file uploads correctly', async () => {
|
||||
const mockBlob = new Blob(['zip-content'], { type: 'application/zip' });
|
||||
mockedAxios.post.mockResolvedValueOnce({ data: mockBlob });
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
const files = [
|
||||
createPDFFile(),
|
||||
createTestFile('test2.pdf', '%PDF-1.4...', 'application/pdf')
|
||||
]
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, files);
|
||||
});
|
||||
|
||||
// Verify both files were uploaded
|
||||
const calls = mockedAxios.post.mock.calls;
|
||||
|
||||
for (let i = 0; i < calls.length; i++) {
|
||||
const formData = calls[i][1] as FormData;
|
||||
const fileInputs = formData.getAll('fileInput');
|
||||
expect(fileInputs).toHaveLength(1);
|
||||
expect(fileInputs[0]).toBeInstanceOf(File);
|
||||
expect(fileInputs[0].name).toBe(files[i].name);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
test('should handle no files selected', async () => {
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, []);
|
||||
});
|
||||
|
||||
expect(mockedAxios.post).not.toHaveBeenCalled();
|
||||
expect(result.current.status).toContain('noFileSelected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Boundary Integration', () => {
|
||||
|
||||
test('should handle corrupted file gracefully', async () => {
|
||||
mockedAxios.post.mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 422,
|
||||
data: 'Processing failed'
|
||||
}
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const corruptedFile = createTestFile('corrupted.pdf', 'not-a-pdf', 'application/pdf');
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [corruptedFile]);
|
||||
});
|
||||
|
||||
expect(result.current.errorMessage).toBe('Processing failed');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
test('should handle backend service unavailable', async () => {
|
||||
mockedAxios.post.mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 503,
|
||||
data: 'Service unavailable'
|
||||
}
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
expect(result.current.errorMessage).toBe('Service unavailable');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FileContext Integration', () => {
|
||||
|
||||
test('should record operation in FileContext', async () => {
|
||||
const mockBlob = new Blob(['fake-data'], { type: 'image/png' });
|
||||
mockedAxios.post.mockResolvedValueOnce({ data: mockBlob });
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
// Verify operation was successful and files were processed
|
||||
expect(result.current.files).toHaveLength(1);
|
||||
expect(result.current.files[0].name).toBe('test_converted.png');
|
||||
expect(result.current.downloadUrl).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should clean up blob URLs on reset', async () => {
|
||||
const mockBlob = new Blob(['fake-data'], { type: 'image/png' });
|
||||
mockedAxios.post.mockResolvedValueOnce({ data: mockBlob });
|
||||
|
||||
const { result } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testFile = createPDFFile();
|
||||
const parameters: ConvertParameters = {
|
||||
fromExtension: 'pdf',
|
||||
toExtension: 'png',
|
||||
imageOptions: {
|
||||
colorType: 'color',
|
||||
dpi: 300,
|
||||
singleOrMultiple: 'multiple',
|
||||
fitOption: 'maintainAspectRatio',
|
||||
autoRotate: true,
|
||||
combineImages: true
|
||||
},
|
||||
isSmartDetection: false,
|
||||
smartDetectionType: 'none'
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
await result.current.executeOperation(parameters, [testFile]);
|
||||
});
|
||||
|
||||
expect(result.current.downloadUrl).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
result.current.resetResults();
|
||||
});
|
||||
|
||||
expect(result.current.downloadUrl).toBe(null);
|
||||
expect(result.current.files).toHaveLength(0);
|
||||
expect(result.current.errorMessage).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Additional Integration Tests That Require Real Backend
|
||||
*
|
||||
* These tests would require a running backend server and are better suited
|
||||
* for E2E testing with tools like Playwright or Cypress:
|
||||
*
|
||||
* 1. **Real File Conversion Tests**
|
||||
* - Upload actual PDF files and verify conversion quality
|
||||
* - Test image format outputs are valid and viewable
|
||||
* - Test CSV/TXT outputs contain expected content
|
||||
* - Test file size limits and memory constraints
|
||||
*
|
||||
* 2. **Performance Integration Tests**
|
||||
* - Test conversion time for various file sizes
|
||||
* - Test memory usage during large file conversions
|
||||
* - Test concurrent conversion requests
|
||||
* - Test timeout handling for long-running conversions
|
||||
*
|
||||
* 3. **Authentication Integration**
|
||||
* - Test conversions with and without authentication
|
||||
* - Test rate limiting and user quotas
|
||||
* - Test permission-based endpoint access
|
||||
*
|
||||
* 4. **File Preview Integration**
|
||||
* - Test that converted files integrate correctly with viewer
|
||||
* - Test thumbnail generation for converted files
|
||||
* - Test file download functionality
|
||||
* - Test FileContext persistence across tool switches
|
||||
*
|
||||
* 5. **Endpoint Availability Tests**
|
||||
* - Test real endpoint availability checking
|
||||
* - Test graceful degradation when endpoints are disabled
|
||||
* - Test dynamic endpoint configuration updates
|
||||
*/
|
||||
@@ -0,0 +1,505 @@
|
||||
/**
|
||||
* Integration tests for Convert Tool Smart Detection with real file scenarios
|
||||
* Tests the complete flow from file upload through auto-detection to API calls
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useConvertOperation } from '../../hooks/tools/convert/useConvertOperation';
|
||||
import { useConvertParameters } from '../../hooks/tools/convert/useConvertParameters';
|
||||
import { FileContextProvider } from '../../contexts/FileContext';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from '../../i18n/config';
|
||||
import axios from 'axios';
|
||||
import { detectFileExtension } from '../../utils/fileUtils';
|
||||
|
||||
// Mock axios
|
||||
vi.mock('axios');
|
||||
const mockedAxios = vi.mocked(axios);
|
||||
|
||||
// Mock utility modules
|
||||
vi.mock('../../utils/thumbnailUtils', () => ({
|
||||
generateThumbnailForFile: vi.fn().mockResolvedValue('data:image/png;base64,fake-thumbnail')
|
||||
}));
|
||||
|
||||
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<FileContextProvider>
|
||||
{children}
|
||||
</FileContextProvider>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
describe('Convert Tool - Smart Detection Integration Tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock successful API response
|
||||
mockedAxios.post.mockResolvedValue({
|
||||
data: new Blob(['fake converted content'], { type: 'application/pdf' })
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up any blob URLs created during tests
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Single File Auto-Detection Flow', () => {
|
||||
test('should auto-detect PDF from DOCX and convert to PDF', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
// Create mock DOCX file
|
||||
const docxFile = new File(['docx content'], 'document.docx', { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
|
||||
|
||||
// Test auto-detection
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes([docxFile]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paramsResult.current.parameters.fromExtension).toBe('docx');
|
||||
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
||||
expect(paramsResult.current.parameters.isSmartDetection).toBe(false);
|
||||
});
|
||||
|
||||
// Test conversion operation
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
[docxFile]
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/file/pdf', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle unknown file type with file-to-pdf fallback', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
// Create mock unknown file
|
||||
const unknownFile = new File(['unknown content'], 'document.xyz', { type: 'application/octet-stream' });
|
||||
|
||||
// Test auto-detection
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes([unknownFile]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paramsResult.current.parameters.fromExtension).toBe('file-xyz');
|
||||
expect(paramsResult.current.parameters.toExtension).toBe('pdf'); // Fallback
|
||||
expect(paramsResult.current.parameters.isSmartDetection).toBe(false);
|
||||
});
|
||||
|
||||
// Test conversion operation
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
[unknownFile]
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/file/pdf', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multi-File Smart Detection Flow', () => {
|
||||
|
||||
test('should detect all images and use img-to-pdf endpoint', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
// Create mock image files
|
||||
const imageFiles = [
|
||||
new File(['jpg content'], 'photo1.jpg', { type: 'image/jpeg' }),
|
||||
new File(['png content'], 'photo2.png', { type: 'image/png' }),
|
||||
new File(['gif content'], 'photo3.gif', { type: 'image/gif' })
|
||||
];
|
||||
|
||||
// Test smart detection for all images
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes(imageFiles);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paramsResult.current.parameters.fromExtension).toBe('image');
|
||||
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
||||
expect(paramsResult.current.parameters.isSmartDetection).toBe(true);
|
||||
expect(paramsResult.current.parameters.smartDetectionType).toBe('images');
|
||||
});
|
||||
|
||||
// Test conversion operation
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
imageFiles
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/img/pdf', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
// Should send all files in single request
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
const files = formData.getAll('fileInput');
|
||||
expect(files).toHaveLength(3);
|
||||
});
|
||||
|
||||
test('should detect mixed file types and use file-to-pdf endpoint', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
// Create mixed file types
|
||||
const mixedFiles = [
|
||||
new File(['pdf content'], 'document.pdf', { type: 'application/pdf' }),
|
||||
new File(['docx content'], 'spreadsheet.xlsx', { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }),
|
||||
new File(['pptx content'], 'presentation.pptx', { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' })
|
||||
];
|
||||
|
||||
// Test smart detection for mixed types
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes(mixedFiles);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paramsResult.current.parameters.fromExtension).toBe('any');
|
||||
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
||||
expect(paramsResult.current.parameters.isSmartDetection).toBe(true);
|
||||
expect(paramsResult.current.parameters.smartDetectionType).toBe('mixed');
|
||||
});
|
||||
|
||||
// Test conversion operation
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
mixedFiles
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/file/pdf', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
});
|
||||
});
|
||||
|
||||
test('should detect all web files and use html-to-pdf endpoint', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
// Create mock web files
|
||||
const webFiles = [
|
||||
new File(['<html>content</html>'], 'page1.html', { type: 'text/html' }),
|
||||
new File(['zip content'], 'site.zip', { type: 'application/zip' })
|
||||
];
|
||||
|
||||
// Test smart detection for web files
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes(webFiles);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(paramsResult.current.parameters.fromExtension).toBe('html');
|
||||
expect(paramsResult.current.parameters.toExtension).toBe('pdf');
|
||||
expect(paramsResult.current.parameters.isSmartDetection).toBe(true);
|
||||
expect(paramsResult.current.parameters.smartDetectionType).toBe('web');
|
||||
});
|
||||
|
||||
// Test conversion operation
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
webFiles
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/html/pdf', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
// Should process files separately for web files
|
||||
expect(mockedAxios.post).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Web and Email Conversion Options Integration', () => {
|
||||
|
||||
test('should send correct HTML parameters for web-to-pdf conversion', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const htmlFile = new File(['<html>content</html>'], 'page.html', { type: 'text/html' });
|
||||
|
||||
// Set up HTML conversion parameters
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes([htmlFile]);
|
||||
paramsResult.current.updateParameter('htmlOptions', {
|
||||
zoomLevel: 1.5
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
[htmlFile]
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formData.get('zoom')).toBe('1.5');
|
||||
});
|
||||
|
||||
test('should send correct email parameters for eml-to-pdf conversion', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const emlFile = new File(['email content'], 'email.eml', { type: 'message/rfc822' });
|
||||
|
||||
// Set up email conversion parameters
|
||||
act(() => {
|
||||
paramsResult.current.updateParameter('fromExtension', 'eml');
|
||||
paramsResult.current.updateParameter('toExtension', 'pdf');
|
||||
paramsResult.current.updateParameter('emailOptions', {
|
||||
includeAttachments: false,
|
||||
maxAttachmentSizeMB: 20,
|
||||
downloadHtml: true,
|
||||
includeAllRecipients: true
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
[emlFile]
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formData.get('includeAttachments')).toBe('false');
|
||||
expect(formData.get('maxAttachmentSizeMB')).toBe('20');
|
||||
expect(formData.get('downloadHtml')).toBe('true');
|
||||
expect(formData.get('includeAllRecipients')).toBe('true');
|
||||
});
|
||||
|
||||
test('should send correct PDF/A parameters for pdf-to-pdfa conversion', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const pdfFile = new File(['pdf content'], 'document.pdf', { type: 'application/pdf' });
|
||||
|
||||
// Set up PDF/A conversion parameters
|
||||
act(() => {
|
||||
paramsResult.current.updateParameter('fromExtension', 'pdf');
|
||||
paramsResult.current.updateParameter('toExtension', 'pdfa');
|
||||
paramsResult.current.updateParameter('pdfaOptions', {
|
||||
outputFormat: 'pdfa'
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
[pdfFile]
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formData.get('outputFormat')).toBe('pdfa');
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith('/api/v1/convert/pdf/pdfa', expect.any(FormData), {
|
||||
responseType: 'blob'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Image Conversion Options Integration', () => {
|
||||
|
||||
test('should send correct parameters for image-to-pdf conversion', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const imageFiles = [
|
||||
new File(['jpg1'], 'photo1.jpg', { type: 'image/jpeg' }),
|
||||
new File(['jpg2'], 'photo2.jpg', { type: 'image/jpeg' })
|
||||
];
|
||||
|
||||
// Set up image conversion parameters
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes(imageFiles);
|
||||
paramsResult.current.updateParameter('imageOptions', {
|
||||
colorType: 'grayscale',
|
||||
dpi: 150,
|
||||
singleOrMultiple: 'single',
|
||||
fitOption: 'fitToPage',
|
||||
autoRotate: false,
|
||||
combineImages: true
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
imageFiles
|
||||
);
|
||||
});
|
||||
|
||||
const formData = mockedAxios.post.mock.calls[0][1] as FormData;
|
||||
expect(formData.get('fitOption')).toBe('fitToPage');
|
||||
expect(formData.get('colorType')).toBe('grayscale');
|
||||
expect(formData.get('autoRotate')).toBe('false');
|
||||
});
|
||||
|
||||
test('should process images separately when combineImages is false', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const imageFiles = [
|
||||
new File(['jpg1'], 'photo1.jpg', { type: 'image/jpeg' }),
|
||||
new File(['jpg2'], 'photo2.jpg', { type: 'image/jpeg' })
|
||||
];
|
||||
|
||||
// Set up for separate processing
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes(imageFiles);
|
||||
paramsResult.current.updateParameter('imageOptions', {
|
||||
...paramsResult.current.parameters.imageOptions,
|
||||
combineImages: false
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
imageFiles
|
||||
);
|
||||
});
|
||||
|
||||
// Should make separate API calls for each file
|
||||
expect(mockedAxios.post).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Scenarios in Smart Detection', () => {
|
||||
|
||||
|
||||
test('should handle partial failures in multi-file processing', async () => {
|
||||
const { result: paramsResult } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const { result: operationResult } = renderHook(() => useConvertOperation(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
// Mock one success, one failure
|
||||
mockedAxios.post
|
||||
.mockResolvedValueOnce({
|
||||
data: new Blob(['converted1'], { type: 'application/pdf' })
|
||||
})
|
||||
.mockRejectedValueOnce(new Error('File 2 failed'));
|
||||
|
||||
const mixedFiles = [
|
||||
new File(['file1'], 'doc1.txt', { type: 'text/plain' }),
|
||||
new File(['file2'], 'doc2.xyz', { type: 'application/octet-stream' })
|
||||
];
|
||||
|
||||
// Set up for separate processing (mixed smart detection)
|
||||
act(() => {
|
||||
paramsResult.current.analyzeFileTypes(mixedFiles);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await operationResult.current.executeOperation(
|
||||
paramsResult.current.parameters,
|
||||
mixedFiles
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
// Should have processed at least one file successfully
|
||||
expect(operationResult.current.files.length).toBeGreaterThan(0);
|
||||
expect(mockedAxios.post).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real File Extension Detection', () => {
|
||||
|
||||
test('should correctly detect various file extensions', async () => {
|
||||
const { result } = renderHook(() => useConvertParameters(), {
|
||||
wrapper: TestWrapper
|
||||
});
|
||||
|
||||
const testCases = [
|
||||
{ filename: 'document.PDF', expected: 'pdf' },
|
||||
{ filename: 'image.JPEG', expected: 'jpg' }, // JPEG should normalize to jpg
|
||||
{ filename: 'photo.jpeg', expected: 'jpg' }, // jpeg should normalize to jpg
|
||||
{ filename: 'archive.tar.gz', expected: 'gz' },
|
||||
{ filename: 'file.', expected: '' },
|
||||
{ filename: '.hidden', expected: 'hidden' },
|
||||
{ filename: 'noextension', expected: '' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ filename, expected }) => {
|
||||
const detected = detectFileExtension(filename);
|
||||
expect(detected).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
264
frontend/src/tests/convert/README.md
Normal file
264
frontend/src/tests/convert/README.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Convert Tool Test Suite
|
||||
|
||||
This directory contains comprehensive tests for the Convert Tool functionality.
|
||||
|
||||
## Test Files Overview
|
||||
|
||||
### 1. ConvertTool.test.tsx
|
||||
**Purpose**: Unit/Component testing for the Convert Tool UI components
|
||||
- Tests dropdown behavior and navigation
|
||||
- Tests format availability based on endpoint status
|
||||
- Tests UI state management and form validation
|
||||
- Mocks backend dependencies for isolated testing
|
||||
|
||||
**Key Test Areas**:
|
||||
- FROM dropdown enables/disables formats based on endpoint availability
|
||||
- TO dropdown shows correct conversions for selected source format
|
||||
- Format-specific options appear/disappear correctly
|
||||
- Parameter validation and state management
|
||||
|
||||
### 2. ConvertIntegration.test.ts
|
||||
**Purpose**: Integration testing for Convert Tool business logic
|
||||
- Tests parameter validation and conversion matrix logic
|
||||
- Tests endpoint resolution and availability checking
|
||||
- Tests file extension detection
|
||||
- Provides framework for testing actual conversions (requires backend)
|
||||
|
||||
**Key Test Areas**:
|
||||
- Endpoint availability checking matches real backend status
|
||||
- Conversion parameters are correctly validated
|
||||
- File extension detection works properly
|
||||
- Conversion matrix returns correct available formats
|
||||
|
||||
### 3. ConvertE2E.spec.ts
|
||||
**Purpose**: End-to-End testing using Playwright with Dynamic Endpoint Discovery
|
||||
- **Automatically discovers available conversion endpoints** from the backend
|
||||
- Tests complete user workflows from file upload to download
|
||||
- Tests actual file conversions with real backend
|
||||
- **Skips tests for unavailable endpoints** automatically
|
||||
- Tests error handling and edge cases
|
||||
- Tests UI/UX flow and user interactions
|
||||
|
||||
**Key Test Areas**:
|
||||
- **Dynamic endpoint discovery** using `/api/v1/config/endpoints-enabled` API
|
||||
- Complete conversion workflows for **all available endpoints**
|
||||
- **Unavailable endpoint testing** - verifies disabled conversions are properly blocked
|
||||
- File upload, conversion, and download process
|
||||
- Error handling for corrupted files and network issues
|
||||
- Performance testing with large files
|
||||
- UI responsiveness and progress indicators
|
||||
|
||||
**Supported Conversions** (tested if available):
|
||||
- PDF ↔ Images (PNG, JPG, GIF, BMP, TIFF, WebP)
|
||||
- PDF ↔ Office (DOCX, PPTX)
|
||||
- PDF ↔ Text (TXT, HTML, XML, CSV, Markdown)
|
||||
- Office → PDF (DOCX, PPTX, XLSX, etc.)
|
||||
- Email (EML) → PDF
|
||||
- HTML → PDF, URL → PDF
|
||||
- Markdown → PDF
|
||||
|
||||
## Running the Tests
|
||||
|
||||
**Important**: All commands should be run from the `frontend/` directory:
|
||||
```bash
|
||||
cd frontend
|
||||
```
|
||||
|
||||
### Setup (First Time Only)
|
||||
```bash
|
||||
# Install dependencies (includes test frameworks)
|
||||
npm install
|
||||
|
||||
# Install Playwright browsers for E2E tests
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
### Unit Tests (ConvertTool.test.tsx)
|
||||
```bash
|
||||
# Run all unit tests
|
||||
npm test
|
||||
|
||||
# Run specific test file
|
||||
npm test ConvertTool.test.tsx
|
||||
|
||||
# Run with coverage
|
||||
npm run test:coverage
|
||||
|
||||
# Run in watch mode (re-runs on file changes)
|
||||
npm run test:watch
|
||||
|
||||
# Run specific test pattern
|
||||
npm test -- --grep "dropdown"
|
||||
```
|
||||
|
||||
### Integration Tests (ConvertIntegration.test.ts)
|
||||
```bash
|
||||
# Run integration tests
|
||||
npm test ConvertIntegration.test.ts
|
||||
|
||||
# Run with verbose output
|
||||
npm test ConvertIntegration.test.ts -- --reporter=verbose
|
||||
```
|
||||
|
||||
### E2E Tests (ConvertE2E.spec.ts)
|
||||
```bash
|
||||
# Prerequisites: Backend must be running on localhost:8080
|
||||
# Start backend first, then:
|
||||
|
||||
# Run all E2E tests (automatically discovers available endpoints)
|
||||
npm run test:e2e
|
||||
|
||||
# Run specific E2E test file
|
||||
npx playwright test ConvertE2E.spec.ts
|
||||
|
||||
# Run with UI mode for debugging
|
||||
npx playwright test --ui
|
||||
|
||||
# Run specific test by endpoint name (dynamic)
|
||||
npx playwright test -g "pdf-to-img:"
|
||||
|
||||
# Run only available conversion tests
|
||||
npx playwright test -g "Dynamic Conversion Tests"
|
||||
|
||||
# Run only unavailable conversion tests
|
||||
npx playwright test -g "Unavailable Conversions"
|
||||
|
||||
# Run in headed mode (see browser)
|
||||
npx playwright test --headed
|
||||
|
||||
# Generate HTML report
|
||||
npx playwright test ConvertE2E.spec.ts --reporter=html
|
||||
```
|
||||
|
||||
**Test Discovery Process:**
|
||||
1. Tests automatically query `/api/v1/config/endpoints-enabled` to discover available conversions
|
||||
2. Tests are generated dynamically for each available endpoint
|
||||
3. Tests for unavailable endpoints verify they're properly disabled in the UI
|
||||
4. Console output shows which endpoints were discovered
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### For Unit Tests
|
||||
- No special requirements
|
||||
- All dependencies are mocked
|
||||
- Can run in any environment
|
||||
|
||||
### For Integration Tests
|
||||
- May require backend API for full functionality
|
||||
- Uses mock data for endpoint availability
|
||||
- Tests business logic in isolation
|
||||
|
||||
### For E2E Tests
|
||||
- **Requires running backend server** (localhost:8080)
|
||||
- **Requires test fixture files** (see ../test-fixtures/README.md)
|
||||
- Requires frontend dev server (localhost:5173)
|
||||
- Tests real conversion functionality
|
||||
|
||||
## Test Data
|
||||
|
||||
The tests use realistic endpoint availability data based on your current server configuration:
|
||||
|
||||
**Available Endpoints** (should pass):
|
||||
- `file-to-pdf`: true (DOCX, XLSX, PPTX → PDF)
|
||||
- `img-to-pdf`: true (PNG, JPG, etc. → PDF)
|
||||
- `markdown-to-pdf`: true (MD → PDF)
|
||||
- `pdf-to-csv`: true (PDF → CSV)
|
||||
- `pdf-to-img`: true (PDF → PNG, JPG, etc.)
|
||||
- `pdf-to-text`: true (PDF → TXT)
|
||||
|
||||
**Disabled Endpoints** (should be blocked):
|
||||
- `eml-to-pdf`: false
|
||||
- `html-to-pdf`: false
|
||||
- `pdf-to-html`: false
|
||||
- `pdf-to-markdown`: false
|
||||
- `pdf-to-pdfa`: false
|
||||
- `pdf-to-presentation`: false
|
||||
- `pdf-to-word`: false
|
||||
- `pdf-to-xml`: false
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Success Scenarios (Available Endpoints)
|
||||
1. **PDF → Image**: PDF to PNG/JPG with various DPI and color settings
|
||||
2. **PDF → Data**: PDF to CSV (table extraction), PDF to TXT (text extraction)
|
||||
3. **Office → PDF**: DOCX/XLSX/PPTX to PDF conversion
|
||||
4. **Image → PDF**: PNG/JPG to PDF with image options
|
||||
5. **Markdown → PDF**: MD to PDF with formatting preservation
|
||||
|
||||
### Blocked Scenarios (Disabled Endpoints)
|
||||
1. **EML conversions**: Should be disabled in FROM dropdown
|
||||
2. **PDF → Office**: PDF to Word/PowerPoint should be disabled
|
||||
3. **PDF → Web**: PDF to HTML/XML should be disabled
|
||||
4. **PDF → PDF/A**: Should be disabled
|
||||
|
||||
### Error Scenarios
|
||||
1. **Corrupted files**: Should show helpful error messages
|
||||
2. **Network failures**: Should handle backend unavailability
|
||||
3. **Large files**: Should handle memory constraints gracefully
|
||||
4. **Invalid parameters**: Should validate before submission
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
When adding new conversion formats:
|
||||
|
||||
1. **Update ConvertTool.test.tsx**:
|
||||
- Add the new format to test data
|
||||
- Test dropdown behavior for the new format
|
||||
- Test format-specific options if any
|
||||
|
||||
2. **Update ConvertIntegration.test.ts**:
|
||||
- Add endpoint availability test cases
|
||||
- Add conversion matrix test cases
|
||||
- Add parameter validation tests
|
||||
|
||||
3. **Update ConvertE2E.spec.ts**:
|
||||
- Add end-to-end workflow tests
|
||||
- Add test fixture files
|
||||
- Test actual conversion functionality
|
||||
|
||||
4. **Update test fixtures**:
|
||||
- Add sample files for the new format
|
||||
- Update ../test-fixtures/README.md
|
||||
|
||||
## Debugging Failed Tests
|
||||
|
||||
### Unit Test Failures
|
||||
- Check mock data matches real endpoint status
|
||||
- Verify component props and state management
|
||||
- Check for React hook dependency issues
|
||||
|
||||
### Integration Test Failures
|
||||
- Verify conversion matrix includes new formats
|
||||
- Check endpoint name mappings
|
||||
- Ensure parameter validation logic is correct
|
||||
|
||||
### E2E Test Failures
|
||||
- Ensure backend server is running
|
||||
- Check test fixture files exist and are valid
|
||||
- Verify element selectors match current UI
|
||||
- Check for timing issues (increase timeouts if needed)
|
||||
|
||||
## Test Maintenance
|
||||
|
||||
### Regular Updates Needed
|
||||
1. **Endpoint Status**: Update mock data when backend endpoints change
|
||||
2. **UI Selectors**: Update test selectors when UI changes
|
||||
3. **Test Fixtures**: Replace old test files with new ones periodically
|
||||
4. **Performance Benchmarks**: Update expected performance metrics
|
||||
|
||||
### CI/CD Integration
|
||||
- Unit tests: Run on every commit
|
||||
- Integration tests: Run on pull requests
|
||||
- E2E tests: Run on staging deployment
|
||||
- Performance tests: Run weekly or on major releases
|
||||
|
||||
## Performance Expectations
|
||||
|
||||
These tests focus on frontend functionality, not backend performance:
|
||||
|
||||
- **File upload/UI**: < 1 second for small test files
|
||||
- **Dropdown interactions**: < 200ms
|
||||
- **Form validation**: < 100ms
|
||||
- **Conversion UI flow**: < 5 seconds for small test files
|
||||
|
||||
Tests will fail if UI interactions are slow, indicating frontend performance issues.
|
||||
304
frontend/src/tests/helpers/conversionEndpointDiscovery.ts
Normal file
304
frontend/src/tests/helpers/conversionEndpointDiscovery.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* Conversion Endpoint Discovery for E2E Testing
|
||||
*
|
||||
* Uses the backend's endpoint configuration API to discover available conversions
|
||||
*/
|
||||
|
||||
import { useMultipleEndpointsEnabled } from '../../hooks/useEndpointConfig';
|
||||
|
||||
export interface ConversionEndpoint {
|
||||
endpoint: string;
|
||||
fromFormat: string;
|
||||
toFormat: string;
|
||||
description: string;
|
||||
apiPath: string;
|
||||
}
|
||||
|
||||
// Complete list of conversion endpoints based on EndpointConfiguration.java
|
||||
const ALL_CONVERSION_ENDPOINTS: ConversionEndpoint[] = [
|
||||
{
|
||||
endpoint: 'pdf-to-img',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'image',
|
||||
description: 'Convert PDF to images (PNG, JPG, GIF, etc.)',
|
||||
apiPath: '/api/v1/convert/pdf/img'
|
||||
},
|
||||
{
|
||||
endpoint: 'img-to-pdf',
|
||||
fromFormat: 'image',
|
||||
toFormat: 'pdf',
|
||||
description: 'Convert images to PDF',
|
||||
apiPath: '/api/v1/convert/img/pdf'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-pdfa',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'pdfa',
|
||||
description: 'Convert PDF to PDF/A',
|
||||
apiPath: '/api/v1/convert/pdf/pdfa'
|
||||
},
|
||||
{
|
||||
endpoint: 'file-to-pdf',
|
||||
fromFormat: 'office',
|
||||
toFormat: 'pdf',
|
||||
description: 'Convert office files to PDF',
|
||||
apiPath: '/api/v1/convert/file/pdf'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-word',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'docx',
|
||||
description: 'Convert PDF to Word document',
|
||||
apiPath: '/api/v1/convert/pdf/word'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-presentation',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'pptx',
|
||||
description: 'Convert PDF to PowerPoint presentation',
|
||||
apiPath: '/api/v1/convert/pdf/presentation'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-text',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'txt',
|
||||
description: 'Convert PDF to plain text',
|
||||
apiPath: '/api/v1/convert/pdf/text'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-html',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'html',
|
||||
description: 'Convert PDF to HTML',
|
||||
apiPath: '/api/v1/convert/pdf/html'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-xml',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'xml',
|
||||
description: 'Convert PDF to XML',
|
||||
apiPath: '/api/v1/convert/pdf/xml'
|
||||
},
|
||||
{
|
||||
endpoint: 'html-to-pdf',
|
||||
fromFormat: 'html',
|
||||
toFormat: 'pdf',
|
||||
description: 'Convert HTML to PDF',
|
||||
apiPath: '/api/v1/convert/html/pdf'
|
||||
},
|
||||
{
|
||||
endpoint: 'url-to-pdf',
|
||||
fromFormat: 'url',
|
||||
toFormat: 'pdf',
|
||||
description: 'Convert web page to PDF',
|
||||
apiPath: '/api/v1/convert/url/pdf'
|
||||
},
|
||||
{
|
||||
endpoint: 'markdown-to-pdf',
|
||||
fromFormat: 'md',
|
||||
toFormat: 'pdf',
|
||||
description: 'Convert Markdown to PDF',
|
||||
apiPath: '/api/v1/convert/markdown/pdf'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-csv',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'csv',
|
||||
description: 'Extract CSV data from PDF',
|
||||
apiPath: '/api/v1/convert/pdf/csv'
|
||||
},
|
||||
{
|
||||
endpoint: 'pdf-to-markdown',
|
||||
fromFormat: 'pdf',
|
||||
toFormat: 'md',
|
||||
description: 'Convert PDF to Markdown',
|
||||
apiPath: '/api/v1/convert/pdf/markdown'
|
||||
},
|
||||
{
|
||||
endpoint: 'eml-to-pdf',
|
||||
fromFormat: 'eml',
|
||||
toFormat: 'pdf',
|
||||
description: 'Convert email (EML) to PDF',
|
||||
apiPath: '/api/v1/convert/eml/pdf'
|
||||
}
|
||||
];
|
||||
|
||||
export class ConversionEndpointDiscovery {
|
||||
private baseUrl: string;
|
||||
private cache: Map<string, boolean> | null = null;
|
||||
private cacheExpiry: number = 0;
|
||||
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
constructor(baseUrl: string = process.env.BACKEND_URL || 'http://localhost:8080') {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available conversion endpoints by checking with backend
|
||||
*/
|
||||
async getAvailableConversions(): Promise<ConversionEndpoint[]> {
|
||||
const endpointStatuses = await this.getEndpointStatuses();
|
||||
|
||||
return ALL_CONVERSION_ENDPOINTS.filter(conversion =>
|
||||
endpointStatuses.get(conversion.endpoint) === true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all unavailable conversion endpoints
|
||||
*/
|
||||
async getUnavailableConversions(): Promise<ConversionEndpoint[]> {
|
||||
const endpointStatuses = await this.getEndpointStatuses();
|
||||
|
||||
return ALL_CONVERSION_ENDPOINTS.filter(conversion =>
|
||||
endpointStatuses.get(conversion.endpoint) === false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific conversion is available
|
||||
*/
|
||||
async isConversionAvailable(endpoint: string): Promise<boolean> {
|
||||
const endpointStatuses = await this.getEndpointStatuses();
|
||||
return endpointStatuses.get(endpoint) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available conversions grouped by source format
|
||||
*/
|
||||
async getConversionsByFormat(): Promise<Record<string, ConversionEndpoint[]>> {
|
||||
const availableConversions = await this.getAvailableConversions();
|
||||
|
||||
const grouped: Record<string, ConversionEndpoint[]> = {};
|
||||
|
||||
availableConversions.forEach(conversion => {
|
||||
if (!grouped[conversion.fromFormat]) {
|
||||
grouped[conversion.fromFormat] = [];
|
||||
}
|
||||
grouped[conversion.fromFormat].push(conversion);
|
||||
});
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported target formats for a given source format
|
||||
*/
|
||||
async getSupportedTargetFormats(fromFormat: string): Promise<string[]> {
|
||||
const availableConversions = await this.getAvailableConversions();
|
||||
|
||||
return availableConversions
|
||||
.filter(conversion => conversion.fromFormat === fromFormat)
|
||||
.map(conversion => conversion.toFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all supported source formats
|
||||
*/
|
||||
async getSupportedSourceFormats(): Promise<string[]> {
|
||||
const availableConversions = await this.getAvailableConversions();
|
||||
|
||||
const sourceFormats = new Set(
|
||||
availableConversions.map(conversion => conversion.fromFormat)
|
||||
);
|
||||
|
||||
return Array.from(sourceFormats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get endpoint statuses from backend using batch API
|
||||
*/
|
||||
private async getEndpointStatuses(): Promise<Map<string, boolean>> {
|
||||
// Return cached result if still valid
|
||||
if (this.cache && Date.now() < this.cacheExpiry) {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
try {
|
||||
const endpointNames = ALL_CONVERSION_ENDPOINTS.map(conv => conv.endpoint);
|
||||
const endpointsParam = endpointNames.join(',');
|
||||
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch endpoint statuses: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const statusMap: Record<string, boolean> = await response.json();
|
||||
|
||||
// Convert to Map and cache
|
||||
this.cache = new Map(Object.entries(statusMap));
|
||||
this.cacheExpiry = Date.now() + this.CACHE_DURATION;
|
||||
|
||||
console.log(`Retrieved status for ${Object.keys(statusMap).length} conversion endpoints`);
|
||||
return this.cache;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to get endpoint statuses:', error);
|
||||
|
||||
// Fallback: assume all endpoints are disabled
|
||||
const fallbackMap = new Map<string, boolean>();
|
||||
ALL_CONVERSION_ENDPOINTS.forEach(conv => {
|
||||
fallbackMap.set(conv.endpoint, false);
|
||||
});
|
||||
|
||||
return fallbackMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to create a skipping condition for tests
|
||||
*/
|
||||
static createSkipCondition(endpoint: string, discovery: ConversionEndpointDiscovery) {
|
||||
return async () => {
|
||||
const available = await discovery.isConversionAvailable(endpoint);
|
||||
return !available;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed conversion info by endpoint name
|
||||
*/
|
||||
getConversionInfo(endpoint: string): ConversionEndpoint | undefined {
|
||||
return ALL_CONVERSION_ENDPOINTS.find(conv => conv.endpoint === endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversion endpoints (regardless of availability)
|
||||
*/
|
||||
getAllConversions(): ConversionEndpoint[] {
|
||||
return [...ALL_CONVERSION_ENDPOINTS];
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance for reuse across tests
|
||||
export const conversionDiscovery = new ConversionEndpointDiscovery();
|
||||
|
||||
/**
|
||||
* React hook version for use in components (wraps the class)
|
||||
*/
|
||||
export function useConversionEndpoints() {
|
||||
const endpointNames = ALL_CONVERSION_ENDPOINTS.map(conv => conv.endpoint);
|
||||
const { endpointStatus, loading, error, refetch } = useMultipleEndpointsEnabled(endpointNames);
|
||||
|
||||
const availableConversions = ALL_CONVERSION_ENDPOINTS.filter(
|
||||
conv => endpointStatus[conv.endpoint] === true
|
||||
);
|
||||
|
||||
const unavailableConversions = ALL_CONVERSION_ENDPOINTS.filter(
|
||||
conv => endpointStatus[conv.endpoint] === false
|
||||
);
|
||||
|
||||
return {
|
||||
availableConversions,
|
||||
unavailableConversions,
|
||||
allConversions: ALL_CONVERSION_ENDPOINTS,
|
||||
endpointStatus,
|
||||
loading,
|
||||
error,
|
||||
refetch,
|
||||
isConversionAvailable: (endpoint: string) => endpointStatus[endpoint] === true
|
||||
};
|
||||
}
|
||||
132
frontend/src/tests/test-fixtures/README.md
Normal file
132
frontend/src/tests/test-fixtures/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Test Fixtures for Convert Tool Testing
|
||||
|
||||
This directory contains sample files for testing the convert tool functionality.
|
||||
|
||||
## Required Test Files
|
||||
|
||||
To run the full test suite, please add the following test files to this directory:
|
||||
|
||||
### 1. sample.pdf
|
||||
- A small PDF document (1-2 pages)
|
||||
- Should contain text and ideally a simple table for CSV conversion testing
|
||||
- Should be under 1MB for fast testing
|
||||
|
||||
### 2. sample.docx
|
||||
- A Microsoft Word document with basic formatting
|
||||
- Should contain headers, paragraphs, and possibly a table
|
||||
- Should be under 500KB
|
||||
|
||||
### 3. sample.png
|
||||
- A small PNG image (e.g., 500x500 pixels)
|
||||
- Should be a real image, not just a test pattern
|
||||
- Should be under 100KB
|
||||
|
||||
### 3b. sample.jpg
|
||||
- A small JPG image (same image as PNG, different format)
|
||||
- Should be under 100KB
|
||||
- Can be created by converting sample.png to JPG
|
||||
|
||||
### 4. sample.md
|
||||
- A Markdown file with various formatting elements:
|
||||
```markdown
|
||||
# Test Document
|
||||
|
||||
This is a **test** markdown file.
|
||||
|
||||
## Features
|
||||
|
||||
- Lists
|
||||
- **Bold text**
|
||||
- *Italic text*
|
||||
- [Links](https://example.com)
|
||||
|
||||
### Code Block
|
||||
|
||||
```javascript
|
||||
console.log('Hello, world!');
|
||||
```
|
||||
|
||||
| Column 1 | Column 2 |
|
||||
|----------|----------|
|
||||
| Data 1 | Data 2 |
|
||||
```
|
||||
|
||||
### 5. sample.eml (Optional)
|
||||
- An email file with headers and body
|
||||
- Can be exported from any email client
|
||||
- Should contain some attachments for testing
|
||||
|
||||
### 6. sample.html (Optional)
|
||||
- A simple HTML file with various elements
|
||||
- Should include text, headings, and basic styling
|
||||
|
||||
|
||||
## File Creation Tips
|
||||
|
||||
### Creating a test PDF:
|
||||
1. Create a document in LibreOffice Writer or Google Docs
|
||||
2. Add some text, headers, and a simple table
|
||||
3. Export/Save as PDF
|
||||
|
||||
### Creating a test DOCX:
|
||||
1. Create a document in Microsoft Word or LibreOffice Writer
|
||||
2. Add formatted content (headers, bold, italic, lists)
|
||||
3. Save as DOCX format
|
||||
|
||||
### Creating a test PNG:
|
||||
1. Use any image editor or screenshot tool
|
||||
2. Create a simple image with text or shapes
|
||||
3. Save as PNG format
|
||||
|
||||
### Creating a test EML:
|
||||
1. In your email client, save an email as .eml format
|
||||
2. Or create manually with proper headers:
|
||||
```
|
||||
From: test@example.com
|
||||
To: recipient@example.com
|
||||
Subject: Test Email
|
||||
Date: Mon, 1 Jan 2024 12:00:00 +0000
|
||||
|
||||
This is a test email for conversion testing.
|
||||
```
|
||||
|
||||
## Test File Structure
|
||||
|
||||
```
|
||||
frontend/src/tests/test-fixtures/
|
||||
├── README.md (this file)
|
||||
├── sample.pdf
|
||||
├── sample.docx
|
||||
├── sample.png
|
||||
├── sample.jpg
|
||||
├── sample.md
|
||||
├── sample.eml (optional)
|
||||
└── sample.html (optional)
|
||||
```
|
||||
|
||||
## Usage in Tests
|
||||
|
||||
These files are referenced in the test files:
|
||||
|
||||
- `ConvertE2E.spec.ts` - Uses all files for E2E testing
|
||||
- `ConvertIntegration.test.ts` - Uses files for integration testing
|
||||
- Manual testing scenarios
|
||||
|
||||
## Security Note
|
||||
|
||||
These are test files only and should not contain any sensitive information. They will be committed to the repository and used in automated testing.
|
||||
|
||||
## File Size Guidelines
|
||||
|
||||
- Keep test files small for fast CI/CD pipelines and frontend testing
|
||||
- PDF files: < 1MB (preferably 100-500KB)
|
||||
- Image files: < 100KB
|
||||
- Text files: < 50KB
|
||||
- Focus on frontend functionality, not backend performance
|
||||
|
||||
## Maintenance
|
||||
|
||||
When updating the convert tool with new formats:
|
||||
1. Add corresponding test files to this directory
|
||||
2. Update the test files list above
|
||||
3. Update the test cases to include the new formats
|
||||
1
frontend/src/tests/test-fixtures/corrupted.pdf
Normal file
1
frontend/src/tests/test-fixtures/corrupted.pdf
Normal file
@@ -0,0 +1 @@
|
||||
This is not a valid PDF file
|
||||
6
frontend/src/tests/test-fixtures/sample.csv
Normal file
6
frontend/src/tests/test-fixtures/sample.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
Name,Age,City,Country
|
||||
John Doe,30,New York,USA
|
||||
Jane Smith,25,London,UK
|
||||
Bob Johnson,35,Toronto,Canada
|
||||
Alice Brown,28,Sydney,Australia
|
||||
Charlie Wilson,42,Berlin,Germany
|
||||
|
10
frontend/src/tests/test-fixtures/sample.doc
Normal file
10
frontend/src/tests/test-fixtures/sample.doc
Normal file
@@ -0,0 +1,10 @@
|
||||
# Test DOC File
|
||||
|
||||
This is a test DOC file for conversion testing.
|
||||
|
||||
Content:
|
||||
- Test bullet point 1
|
||||
- Test bullet point 2
|
||||
- Test bullet point 3
|
||||
|
||||
This file should be sufficient for testing office document conversions.
|
||||
BIN
frontend/src/tests/test-fixtures/sample.docx
Normal file
BIN
frontend/src/tests/test-fixtures/sample.docx
Normal file
Binary file not shown.
105
frontend/src/tests/test-fixtures/sample.eml
Normal file
105
frontend/src/tests/test-fixtures/sample.eml
Normal file
@@ -0,0 +1,105 @@
|
||||
Return-Path: <test@example.com>
|
||||
Delivered-To: recipient@example.com
|
||||
Received: from mail.example.com (mail.example.com [192.168.1.1])
|
||||
by mx.example.com (Postfix) with ESMTP id 1234567890
|
||||
for <recipient@example.com>; Mon, 1 Jan 2024 12:00:00 +0000 (UTC)
|
||||
Message-ID: <test123@example.com>
|
||||
Date: Mon, 1 Jan 2024 12:00:00 +0000
|
||||
From: Test Sender <test@example.com>
|
||||
User-Agent: Mozilla/5.0 (compatible; Test Email Client)
|
||||
MIME-Version: 1.0
|
||||
To: Test Recipient <recipient@example.com>
|
||||
Subject: Test Email for Convert Tool
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="------------boundary123456789"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------boundary123456789
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Test Email for Convert Tool
|
||||
===========================
|
||||
|
||||
This is a test email for testing the EML to PDF conversion functionality.
|
||||
|
||||
Email Details:
|
||||
- From: test@example.com
|
||||
- To: recipient@example.com
|
||||
- Subject: Test Email for Convert Tool
|
||||
- Date: January 1, 2024
|
||||
|
||||
Content Features:
|
||||
- Plain text content
|
||||
- HTML content (in alternative part)
|
||||
- Headers and metadata
|
||||
- MIME structure
|
||||
|
||||
This email should convert to a PDF that includes:
|
||||
1. Email headers (From, To, Subject, Date)
|
||||
2. Email body content
|
||||
3. Proper formatting
|
||||
|
||||
Important Notes:
|
||||
- This is a test email only
|
||||
- Generated for Stirling PDF testing
|
||||
- Contains no sensitive information
|
||||
- Should preserve email formatting in PDF
|
||||
|
||||
Best regards,
|
||||
Test Email System
|
||||
|
||||
--------------boundary123456789
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test Email</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<h1 style="color: #2c3e50;">Test Email for Convert Tool</h1>
|
||||
|
||||
<p>This is a <strong>test email</strong> for testing the EML to PDF conversion functionality.</p>
|
||||
|
||||
<h2 style="color: #34495e;">Email Details:</h2>
|
||||
<ul>
|
||||
<li><strong>From:</strong> test@example.com</li>
|
||||
<li><strong>To:</strong> recipient@example.com</li>
|
||||
<li><strong>Subject:</strong> Test Email for Convert Tool</li>
|
||||
<li><strong>Date:</strong> January 1, 2024</li>
|
||||
</ul>
|
||||
|
||||
<h2 style="color: #34495e;">Content Features:</h2>
|
||||
<ul>
|
||||
<li>Plain text content</li>
|
||||
<li><em>HTML content</em> (this part)</li>
|
||||
<li>Headers and metadata</li>
|
||||
<li>MIME structure</li>
|
||||
</ul>
|
||||
|
||||
<div style="background-color: #f8f9fa; padding: 15px; border-left: 4px solid #007bff; margin: 20px 0;">
|
||||
<p><strong>This email should convert to a PDF that includes:</strong></p>
|
||||
<ol>
|
||||
<li>Email headers (From, To, Subject, Date)</li>
|
||||
<li>Email body content</li>
|
||||
<li>Proper formatting</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h3 style="color: #6c757d;">Important Notes:</h3>
|
||||
<ul>
|
||||
<li>This is a test email only</li>
|
||||
<li>Generated for Stirling PDF testing</li>
|
||||
<li>Contains no sensitive information</li>
|
||||
<li>Should preserve email formatting in PDF</li>
|
||||
</ul>
|
||||
|
||||
<p>Best regards,<br>
|
||||
<strong>Test Email System</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
--------------boundary123456789--
|
||||
125
frontend/src/tests/test-fixtures/sample.htm
Normal file
125
frontend/src/tests/test-fixtures/sample.htm
Normal file
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test HTML Document</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 40px;
|
||||
color: #333;
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
h2 {
|
||||
color: #34495e;
|
||||
margin-top: 30px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #fff3cd;
|
||||
padding: 10px;
|
||||
border-left: 4px solid #ffc107;
|
||||
margin: 20px 0;
|
||||
}
|
||||
code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test HTML Document for Convert Tool</h1>
|
||||
|
||||
<p>This is a <strong>test HTML file</strong> for testing the HTML to PDF conversion functionality. It contains various HTML elements to ensure proper conversion.</p>
|
||||
|
||||
<h2>Text Formatting</h2>
|
||||
<p>This paragraph contains <strong>bold text</strong>, <em>italic text</em>, and <code>inline code</code>.</p>
|
||||
|
||||
<div class="highlight">
|
||||
<p><strong>Important:</strong> This is a highlighted section that should be preserved in the PDF output.</p>
|
||||
</div>
|
||||
|
||||
<h2>Lists</h2>
|
||||
<h3>Unordered List</h3>
|
||||
<ul>
|
||||
<li>First item</li>
|
||||
<li>Second item with <a href="https://example.com">a link</a></li>
|
||||
<li>Third item</li>
|
||||
</ul>
|
||||
|
||||
<h3>Ordered List</h3>
|
||||
<ol>
|
||||
<li>Primary point</li>
|
||||
<li>Secondary point</li>
|
||||
<li>Tertiary point</li>
|
||||
</ol>
|
||||
|
||||
<h2>Table</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Column 1</th>
|
||||
<th>Column 2</th>
|
||||
<th>Column 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Data A</td>
|
||||
<td>Data B</td>
|
||||
<td>Data C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Test 1</td>
|
||||
<td>Test 2</td>
|
||||
<td>Test 3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sample X</td>
|
||||
<td>Sample Y</td>
|
||||
<td>Sample Z</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Code Block</h2>
|
||||
<pre><code>function testFunction() {
|
||||
console.log("This is a test function");
|
||||
return "Hello from HTML to PDF conversion";
|
||||
}</code></pre>
|
||||
|
||||
<h2>Final Notes</h2>
|
||||
<p>This HTML document should convert to a well-formatted PDF that preserves:</p>
|
||||
<ul>
|
||||
<li>Text formatting (bold, italic)</li>
|
||||
<li>Headings and hierarchy</li>
|
||||
<li>Tables with proper borders</li>
|
||||
<li>Lists (ordered and unordered)</li>
|
||||
<li>Code formatting</li>
|
||||
<li>Basic CSS styling</li>
|
||||
</ul>
|
||||
|
||||
<p><small>Generated for Stirling PDF Convert Tool testing purposes.</small></p>
|
||||
</body>
|
||||
</html>
|
||||
125
frontend/src/tests/test-fixtures/sample.html
Normal file
125
frontend/src/tests/test-fixtures/sample.html
Normal file
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test HTML Document</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 40px;
|
||||
color: #333;
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
h2 {
|
||||
color: #34495e;
|
||||
margin-top: 30px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #fff3cd;
|
||||
padding: 10px;
|
||||
border-left: 4px solid #ffc107;
|
||||
margin: 20px 0;
|
||||
}
|
||||
code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test HTML Document for Convert Tool</h1>
|
||||
|
||||
<p>This is a <strong>test HTML file</strong> for testing the HTML to PDF conversion functionality. It contains various HTML elements to ensure proper conversion.</p>
|
||||
|
||||
<h2>Text Formatting</h2>
|
||||
<p>This paragraph contains <strong>bold text</strong>, <em>italic text</em>, and <code>inline code</code>.</p>
|
||||
|
||||
<div class="highlight">
|
||||
<p><strong>Important:</strong> This is a highlighted section that should be preserved in the PDF output.</p>
|
||||
</div>
|
||||
|
||||
<h2>Lists</h2>
|
||||
<h3>Unordered List</h3>
|
||||
<ul>
|
||||
<li>First item</li>
|
||||
<li>Second item with <a href="https://example.com">a link</a></li>
|
||||
<li>Third item</li>
|
||||
</ul>
|
||||
|
||||
<h3>Ordered List</h3>
|
||||
<ol>
|
||||
<li>Primary point</li>
|
||||
<li>Secondary point</li>
|
||||
<li>Tertiary point</li>
|
||||
</ol>
|
||||
|
||||
<h2>Table</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Column 1</th>
|
||||
<th>Column 2</th>
|
||||
<th>Column 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Data A</td>
|
||||
<td>Data B</td>
|
||||
<td>Data C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Test 1</td>
|
||||
<td>Test 2</td>
|
||||
<td>Test 3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sample X</td>
|
||||
<td>Sample Y</td>
|
||||
<td>Sample Z</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Code Block</h2>
|
||||
<pre><code>function testFunction() {
|
||||
console.log("This is a test function");
|
||||
return "Hello from HTML to PDF conversion";
|
||||
}</code></pre>
|
||||
|
||||
<h2>Final Notes</h2>
|
||||
<p>This HTML document should convert to a well-formatted PDF that preserves:</p>
|
||||
<ul>
|
||||
<li>Text formatting (bold, italic)</li>
|
||||
<li>Headings and hierarchy</li>
|
||||
<li>Tables with proper borders</li>
|
||||
<li>Lists (ordered and unordered)</li>
|
||||
<li>Code formatting</li>
|
||||
<li>Basic CSS styling</li>
|
||||
</ul>
|
||||
|
||||
<p><small>Generated for Stirling PDF Convert Tool testing purposes.</small></p>
|
||||
</body>
|
||||
</html>
|
||||
BIN
frontend/src/tests/test-fixtures/sample.jpg
Normal file
BIN
frontend/src/tests/test-fixtures/sample.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
49
frontend/src/tests/test-fixtures/sample.md
Normal file
49
frontend/src/tests/test-fixtures/sample.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Test Document for Convert Tool
|
||||
|
||||
This is a **test** markdown file for testing the markdown to PDF conversion functionality.
|
||||
|
||||
## Features Being Tested
|
||||
|
||||
- **Bold text**
|
||||
- *Italic text*
|
||||
- [Links](https://example.com)
|
||||
- Lists and formatting
|
||||
|
||||
### Code Block
|
||||
|
||||
```javascript
|
||||
console.log('Hello, world!');
|
||||
function testFunction() {
|
||||
return "This is a test";
|
||||
}
|
||||
```
|
||||
|
||||
### Table
|
||||
|
||||
| Column 1 | Column 2 | Column 3 |
|
||||
|----------|----------|----------|
|
||||
| Data 1 | Data 2 | Data 3 |
|
||||
| Test A | Test B | Test C |
|
||||
|
||||
## Lists
|
||||
|
||||
### Unordered List
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Nested item
|
||||
- Another nested item
|
||||
- Item 3
|
||||
|
||||
### Ordered List
|
||||
1. First item
|
||||
2. Second item
|
||||
3. Third item
|
||||
|
||||
## Blockquote
|
||||
|
||||
> This is a blockquote for testing purposes.
|
||||
> It should be properly formatted in the PDF output.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This markdown file contains various elements to test the conversion functionality. The PDF output should preserve formatting, tables, code blocks, and other markdown elements.
|
||||
BIN
frontend/src/tests/test-fixtures/sample.pdf
Normal file
BIN
frontend/src/tests/test-fixtures/sample.pdf
Normal file
Binary file not shown.
BIN
frontend/src/tests/test-fixtures/sample.png
Normal file
BIN
frontend/src/tests/test-fixtures/sample.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
12
frontend/src/tests/test-fixtures/sample.pptx
Normal file
12
frontend/src/tests/test-fixtures/sample.pptx
Normal file
@@ -0,0 +1,12 @@
|
||||
# Test PPTX Presentation
|
||||
|
||||
## Slide 1: Title
|
||||
This is a test PowerPoint presentation for conversion testing.
|
||||
|
||||
## Slide 2: Content
|
||||
- Test bullet point 1
|
||||
- Test bullet point 2
|
||||
- Test bullet point 3
|
||||
|
||||
## Slide 3: Conclusion
|
||||
This file should be sufficient for testing presentation conversions.
|
||||
32
frontend/src/tests/test-fixtures/sample.svg
Normal file
32
frontend/src/tests/test-fixtures/sample.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background -->
|
||||
<rect width="400" height="300" fill="#f8f9fa" stroke="#dee2e6" stroke-width="2"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="200" y="40" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#2c3e50">
|
||||
Test Image for Convert Tool
|
||||
</text>
|
||||
|
||||
<!-- Shapes for visual content -->
|
||||
<circle cx="100" cy="120" r="30" fill="#3498db" stroke="#2980b9" stroke-width="2"/>
|
||||
<rect x="180" y="90" width="60" height="60" fill="#e74c3c" stroke="#c0392b" stroke-width="2"/>
|
||||
<polygon points="320,90 350,150 290,150" fill="#f39c12" stroke="#e67e22" stroke-width="2"/>
|
||||
|
||||
<!-- Labels -->
|
||||
<text x="100" y="170" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#7f8c8d">Circle</text>
|
||||
<text x="210" y="170" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#7f8c8d">Square</text>
|
||||
<text x="320" y="170" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#7f8c8d">Triangle</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="200" y="210" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#34495e">
|
||||
This image tests conversion functionality
|
||||
</text>
|
||||
<text x="200" y="230" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#95a5a6">
|
||||
PNG/JPG ↔ PDF conversions
|
||||
</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="200" y="270" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" fill="#bdc3c7">
|
||||
Generated for Stirling PDF testing - 400x300px
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
8
frontend/src/tests/test-fixtures/sample.txt
Normal file
8
frontend/src/tests/test-fixtures/sample.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
This is a test text file for conversion testing.
|
||||
|
||||
It contains multiple lines of text to test various conversion scenarios.
|
||||
Special characters: àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
|
||||
Numbers: 1234567890
|
||||
Symbols: !@#$%^&*()_+-=[]{}|;':\",./<>?
|
||||
|
||||
This file should be sufficient for testing text-based conversions.
|
||||
6
frontend/src/tests/test-fixtures/sample.xlsx
Normal file
6
frontend/src/tests/test-fixtures/sample.xlsx
Normal file
@@ -0,0 +1,6 @@
|
||||
Name,Age,City,Country,Department,Salary
|
||||
John Doe,30,New York,USA,Engineering,75000
|
||||
Jane Smith,25,London,UK,Marketing,65000
|
||||
Bob Johnson,35,Toronto,Canada,Sales,70000
|
||||
Alice Brown,28,Sydney,Australia,Design,68000
|
||||
Charlie Wilson,42,Berlin,Germany,Operations,72000
|
||||
18
frontend/src/tests/test-fixtures/sample.xml
Normal file
18
frontend/src/tests/test-fixtures/sample.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document>
|
||||
<title>Test Document</title>
|
||||
<content>
|
||||
<section id="1">
|
||||
<heading>Introduction</heading>
|
||||
<paragraph>This is a test XML document for conversion testing.</paragraph>
|
||||
</section>
|
||||
<section id="2">
|
||||
<heading>Data</heading>
|
||||
<data>
|
||||
<item name="test1" value="value1"/>
|
||||
<item name="test2" value="value2"/>
|
||||
<item name="test3" value="value3"/>
|
||||
</data>
|
||||
</section>
|
||||
</content>
|
||||
</document>
|
||||
Reference in New Issue
Block a user