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:
ConnorYoh
2025-08-01 16:08:04 +01:00
committed by GitHub
parent 8881f19b03
commit 9c9acbfb5b
68 changed files with 9173 additions and 87 deletions

View File

@@ -13,6 +13,7 @@ export interface OperationButtonProps {
fullWidth?: boolean;
mt?: string;
type?: 'button' | 'submit' | 'reset';
'data-testid'?: string;
}
const OperationButton = ({
@@ -25,7 +26,8 @@ const OperationButton = ({
color = 'blue',
fullWidth = true,
mt = 'md',
type = 'button'
type = 'button',
'data-testid': dataTestId
}: OperationButtonProps) => {
const { t } = useTranslation();
@@ -39,6 +41,7 @@ const OperationButton = ({
disabled={disabled}
variant={variant}
color={color}
data-testid={dataTestId}
>
{isLoading
? (loadingText || t("loading", "Loading..."))

View File

@@ -37,28 +37,29 @@ const ResultsPreview = ({
}
return (
<Box mt="lg" p="md" style={{ backgroundColor: 'var(--mantine-color-gray-0)', borderRadius: 8 }}>
<Box mt="lg" p="md" style={{ backgroundColor: 'var(--mantine-color-gray-0)', borderRadius: 8 }} data-testid="results-preview-container">
{title && (
<Text fw={500} size="md" mb="sm">
<Text fw={500} size="md" mb="sm" data-testid="results-preview-title">
{title} ({files.length} files)
</Text>
)}
{isGeneratingThumbnails ? (
<Center p="lg">
<Center p="lg" data-testid="results-preview-loading">
<Stack align="center" gap="sm">
<Loader size="sm" />
<Text size="sm" c="dimmed">{loadingMessage}</Text>
</Stack>
</Center>
) : (
<Grid>
<Grid data-testid="results-preview-grid">
{files.map((result, index) => (
<Grid.Col span={{ base: 6, sm: 4, md: 3 }} key={index}>
<Paper
p="xs"
withBorder
onClick={() => onFileClick?.(result.file)}
data-testid={`results-preview-thumbnail-${index}`}
style={{
textAlign: 'center',
height: '10rem',

View File

@@ -43,7 +43,7 @@ const ToolStep = ({
return parent ? parent.visibleStepCount >= 3 : false;
}, [showNumber, parent]);
const stepNumber = useContext(ToolStepContext)?.getStepNumber?.() || 1;
const stepNumber = parent?.getStepNumber?.() || 1;
return (
<Paper