feat(lang): implement tests for this updated language selection

This commit is contained in:
perf3ct 2025-07-14 20:46:51 +00:00
parent ea09531911
commit 6e4579fd02
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
5 changed files with 506 additions and 28 deletions

View File

@ -45,21 +45,25 @@ test.describe('OCR Multiple Languages', () => {
await page.goto('/settings');
await helpers.waitForLoadingToComplete();
// Look for OCR language selector component
const languageSelector = page.locator('[data-testid="ocr-language-selector"], #ocr-language-label').first();
// Look for the new LanguageSelector component
const languageSelector = page.locator('label:has-text("OCR Languages")').first();
await expect(languageSelector).toBeVisible({ timeout: TIMEOUTS.medium });
// Check if the selector shows available languages
const selectInput = page.locator('div[role="combobox"], select[id*="ocr"], input[id*="language"]').first();
if (await selectInput.isVisible()) {
await selectInput.click();
// Check for the language selector button
const selectButton = page.locator('button:has-text("Select OCR languages")').first();
if (await selectButton.isVisible()) {
await selectButton.click();
// Wait for language options to appear
// Wait for dropdown panel to appear
await page.waitForTimeout(1000);
// Check for dropdown panel with languages
const dropdownPanel = page.locator('text="Available Languages"').first();
await expect(dropdownPanel).toBeVisible({ timeout: 3000 });
// Check for Spanish and English options
const spanishOption = page.locator('[data-value="spa"], option[value="spa"], :has-text("Spanish")').first();
const englishOption = page.locator('[data-value="eng"], option[value="eng"], :has-text("English")').first();
const spanishOption = page.locator('text="Spanish"').first();
const englishOption = page.locator('text="English"').first();
if (await spanishOption.isVisible({ timeout: 3000 })) {
console.log('✅ Spanish language option found');
@ -70,35 +74,53 @@ test.describe('OCR Multiple Languages', () => {
}
});
test('should change OCR language preference to Spanish', async ({ adminPage: page }) => {
test('should select multiple OCR languages', async ({ adminPage: page }) => {
await page.goto('/settings');
await helpers.waitForLoadingToComplete();
// Find and interact with language selector
const languageSelector = page.locator('[data-testid="ocr-language-selector"], div:has(label:text("OCR Language"))').first();
// Find the multi-language selector button
const selectButton = page.locator('button:has-text("Select OCR languages")').first();
if (await languageSelector.isVisible()) {
// Click on the selector to open dropdown
await languageSelector.click();
if (await selectButton.isVisible()) {
await selectButton.click();
await page.waitForTimeout(500);
// Select Spanish option
const spanishOption = page.locator('[data-value="spa"], option[value="spa"], li:has-text("Spanish")').first();
const spanishOption = page.locator('button:has-text("Spanish")').first();
if (await spanishOption.isVisible({ timeout: 5000 })) {
await spanishOption.click();
await page.waitForTimeout(500);
// Look for save button or auto-save indication
const saveButton = page.locator('button:has-text("Save"), button[type="submit"]').first();
if (await saveButton.isVisible({ timeout: 3000 })) {
// Wait for settings update API call
const updatePromise = helpers.waitForApiCall('/api/settings', TIMEOUTS.medium);
await saveButton.click();
await updatePromise;
// Select English option
const englishOption = page.locator('button:has-text("English")').first();
if (await englishOption.isVisible({ timeout: 5000 })) {
await englishOption.click();
await page.waitForTimeout(500);
// Close the dropdown
const closeButton = page.locator('button:has-text("Close")').first();
if (await closeButton.isVisible()) {
await closeButton.click();
}
// Verify both languages are selected and displayed as tags
await expect(page.locator('text="Spanish"')).toBeVisible({ timeout: 3000 });
await expect(page.locator('text="English"')).toBeVisible({ timeout: 3000 });
await expect(page.locator('text="(Primary)"')).toBeVisible({ timeout: 3000 });
// Look for save button
const saveButton = page.locator('button:has-text("Save"), button[type="submit"]').first();
if (await saveButton.isVisible({ timeout: 3000 })) {
// Wait for settings update API call
const updatePromise = helpers.waitForApiCall('/api/settings', TIMEOUTS.medium);
await saveButton.click();
await updatePromise;
// Check for success indication
await helpers.waitForToast();
console.log('✅ Multiple OCR languages selected and saved');
}
}
// Check for success indication
await helpers.waitForToast();
console.log('✅ OCR language changed to Spanish');
}
}
});
@ -475,7 +497,7 @@ test.describe('OCR Multiple Languages', () => {
await helpers.waitForLoadingToComplete();
// Look for language selector component
const languageSelector = page.locator('[data-testid="ocr-language-selector"]').first();
const languageSelector = page.locator('label:has-text("OCR Languages")').first();
// Check for error handling in language selector
const errorAlert = page.locator('[role="alert"], .error, .alert-warning').first();
@ -498,4 +520,160 @@ test.describe('OCR Multiple Languages', () => {
console.log('✅ Fallback language option available');
}
});
test('should upload document with multiple languages selected', async ({ adminPage: page }) => {
// First set multiple languages in settings
await page.goto('/settings');
await helpers.waitForLoadingToComplete();
const selectButton = page.locator('button:has-text("Select OCR languages")').first();
if (await selectButton.isVisible()) {
await selectButton.click();
// Select English and Spanish
const englishOption = page.locator('button:has-text("English")').first();
if (await englishOption.isVisible()) {
await englishOption.click();
await page.waitForTimeout(500);
}
const spanishOption = page.locator('button:has-text("Spanish")').first();
if (await spanishOption.isVisible()) {
await spanishOption.click();
await page.waitForTimeout(500);
}
// Close dropdown and save
const closeButton = page.locator('button:has-text("Close")').first();
if (await closeButton.isVisible()) {
await closeButton.click();
}
const saveButton = page.locator('button:has-text("Save")').first();
if (await saveButton.isVisible()) {
await saveButton.click();
await helpers.waitForToast();
}
}
// Navigate to upload page
await page.goto('/upload');
await helpers.waitForLoadingToComplete();
// Check if the upload form includes multi-language selector
const uploadLanguageSelector = page.locator('label:has-text("OCR Languages")').first();
if (await uploadLanguageSelector.isVisible()) {
console.log('✅ Multi-language selector available in upload form');
// Click to view language options
const uploadSelectButton = page.locator('button:has-text("Select OCR languages"), button:has-text("Add more languages")').first();
if (await uploadSelectButton.isVisible()) {
await uploadSelectButton.click();
await page.waitForTimeout(500);
// Verify languages are selectable for upload
const uploadDropdown = page.locator('text="Available Languages"').first();
if (await uploadDropdown.isVisible()) {
console.log('✅ Language options available for upload');
}
// Close the dropdown
const uploadCloseButton = page.locator('button:has-text("Close")').first();
if (await uploadCloseButton.isVisible()) {
await uploadCloseButton.click();
}
}
}
// Upload a test file
const fileInput = page.locator('input[type="file"]').first();
if (await fileInput.isAttached({ timeout: 10000 })) {
try {
await fileInput.setInputFiles(MULTILINGUAL_TEST_FILES.mixed);
// Verify file appears in upload list
await expect(page.getByText('mixed_language_test.pdf')).toBeVisible({ timeout: 5000 });
// Click upload button
const uploadButton = page.locator('button:has-text("Upload")').first();
if (await uploadButton.isVisible()) {
const uploadPromise = helpers.waitForApiCall('/api/documents', TIMEOUTS.upload);
await uploadButton.click();
await uploadPromise;
console.log('✅ Multi-language document uploaded successfully');
}
} catch (error) {
console.log(' Mixed language test file not found, skipping upload test');
}
}
});
test('should retry failed OCR with multiple languages', async ({ adminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();
// Look for retry button on any document
const retryButton = page.locator('button:has-text("Retry"), [data-testid="retry-ocr"]').first();
if (await retryButton.isVisible()) {
await retryButton.click();
// Check if retry dialog opens with multi-language options
const retryDialog = page.locator('[role="dialog"], .modal').first();
if (await retryDialog.isVisible({ timeout: 5000 })) {
// Look for multi-language toggle buttons
const multiLanguageButton = page.locator('button:has-text("Multiple Languages")').first();
if (await multiLanguageButton.isVisible()) {
await multiLanguageButton.click();
console.log('✅ Multi-language mode activated in retry dialog');
// Look for language selector in retry dialog
const retryLanguageSelector = page.locator('label:has-text("OCR Languages")').first();
if (await retryLanguageSelector.isVisible()) {
const retrySelectButton = page.locator('button:has-text("Select OCR languages")').first();
if (await retrySelectButton.isVisible()) {
await retrySelectButton.click();
// Select multiple languages for retry
const retryEnglishOption = page.locator('button:has-text("English")').first();
if (await retryEnglishOption.isVisible()) {
await retryEnglishOption.click();
await page.waitForTimeout(500);
}
const retrySpanishOption = page.locator('button:has-text("Spanish")').first();
if (await retrySpanishOption.isVisible()) {
await retrySpanishOption.click();
await page.waitForTimeout(500);
}
// Close language selector
const retryCloseButton = page.locator('button:has-text("Close")').first();
if (await retryCloseButton.isVisible()) {
await retryCloseButton.click();
}
}
}
// Confirm retry with multiple languages
const confirmRetryButton = page.locator('button:has-text("Retry OCR")').first();
if (await confirmRetryButton.isVisible()) {
const retryPromise = helpers.waitForApiCall('/retry', TIMEOUTS.ocr);
await confirmRetryButton.click();
try {
await retryPromise;
console.log('✅ OCR retry with multiple languages initiated');
} catch (error) {
console.log(' Multi-language retry may have failed or timed out');
}
}
}
}
} else {
console.log(' No retry buttons found for multi-language retry testing');
}
});
});

View File

@ -0,0 +1,293 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LanguageSelector from '../LanguageSelector';
import { renderWithProviders } from '../../../test/test-utils';
const renderLanguageSelector = (props: Partial<React.ComponentProps<typeof LanguageSelector>> = {}) => {
const defaultProps = {
selectedLanguages: [],
primaryLanguage: '',
onLanguagesChange: vi.fn(),
...props,
};
return renderWithProviders(<LanguageSelector {...defaultProps} />);
};
describe('LanguageSelector Component', () => {
let user: ReturnType<typeof userEvent.setup>;
beforeEach(() => {
user = userEvent.setup();
});
describe('Basic Rendering', () => {
test('should render the language selector container', () => {
renderLanguageSelector();
expect(screen.getByText('OCR Languages')).toBeInTheDocument();
});
test('should show default state text when no languages selected', () => {
renderLanguageSelector();
expect(screen.getByText('No languages selected. Documents will use default OCR language.')).toBeInTheDocument();
});
test('should show selection button', () => {
renderLanguageSelector();
expect(screen.getByText('Select OCR languages...')).toBeInTheDocument();
});
test('should show language count when languages are selected', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng'
});
expect(screen.getByText('OCR Languages (2/4)')).toBeInTheDocument();
});
test('should open dropdown when button is clicked', async () => {
renderLanguageSelector();
await user.click(screen.getByText('Select OCR languages...'));
expect(screen.getByText('Available Languages')).toBeInTheDocument();
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('Spanish')).toBeInTheDocument();
});
test('should apply custom className', () => {
const { container } = renderLanguageSelector({ className: 'custom-class' });
expect(container.firstChild).toHaveClass('custom-class');
});
});
describe('Language Selection', () => {
test('should show selected languages as tags', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng'
});
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('Spanish')).toBeInTheDocument();
expect(screen.getByText('(Primary)')).toBeInTheDocument();
});
test('should call onLanguagesChange when language is selected from dropdown', async () => {
const mockOnChange = vi.fn();
renderLanguageSelector({ onLanguagesChange: mockOnChange });
// Open dropdown
await user.click(screen.getByText('Select OCR languages...'));
// Select English from the dropdown
const englishButton = screen.getByText('English').closest('button');
if (englishButton) {
await user.click(englishButton);
}
expect(mockOnChange).toHaveBeenCalledWith(['eng'], 'eng');
});
test('should show "Add more languages" when languages are selected', () => {
renderLanguageSelector({
selectedLanguages: ['eng'],
primaryLanguage: 'eng'
});
expect(screen.getByText('Add more languages (3 remaining)')).toBeInTheDocument();
});
test('should handle maximum language limit', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa', 'fra', 'deu'],
primaryLanguage: 'eng',
maxLanguages: 4
});
expect(screen.getByText('Add more languages (0 remaining)')).toBeInTheDocument();
});
});
describe('Primary Language', () => {
test('should show primary language indicator', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng'
});
expect(screen.getByText('(Primary)')).toBeInTheDocument();
});
test('should handle primary language changes', async () => {
const mockOnChange = vi.fn();
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng',
onLanguagesChange: mockOnChange
});
// Open dropdown and click on a primary language option
await user.click(screen.getByText('Add more languages (2 remaining)'));
// The implementation should show primary selection when languages are selected
// This is more of an integration test
});
});
describe('Disabled State', () => {
test('should not show button when disabled', () => {
renderLanguageSelector({ disabled: true });
expect(screen.queryByText('Select OCR languages...')).not.toBeInTheDocument();
});
test('should not show remove buttons when disabled', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng',
disabled: true
});
// Should show languages but no interactive elements
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('Spanish')).toBeInTheDocument();
});
});
describe('Custom Configuration', () => {
test('should respect custom maxLanguages prop', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng',
maxLanguages: 3
});
expect(screen.getByText('OCR Languages (2/3)')).toBeInTheDocument();
expect(screen.getByText('Add more languages (1 remaining)')).toBeInTheDocument();
});
test('should handle edge case of maxLanguages = 1', () => {
renderLanguageSelector({
selectedLanguages: ['eng'],
primaryLanguage: 'eng',
maxLanguages: 1
});
expect(screen.getByText('OCR Languages (1/1)')).toBeInTheDocument();
expect(screen.getByText('Add more languages (0 remaining)')).toBeInTheDocument();
});
});
describe('Language Display', () => {
test('should show available languages in dropdown', async () => {
renderLanguageSelector();
await user.click(screen.getByText('Select OCR languages...'));
// Check for common languages
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('Spanish')).toBeInTheDocument();
expect(screen.getByText('French')).toBeInTheDocument();
expect(screen.getByText('German')).toBeInTheDocument();
expect(screen.getByText('Chinese (Simplified)')).toBeInTheDocument();
});
test('should handle less common languages', async () => {
renderLanguageSelector();
await user.click(screen.getByText('Select OCR languages...'));
// Check for some less common languages
expect(screen.getByText('Japanese')).toBeInTheDocument();
expect(screen.getByText('Arabic')).toBeInTheDocument();
expect(screen.getByText('Thai')).toBeInTheDocument();
});
});
describe('Integration Scenarios', () => {
test('should handle typical workflow: select language', async () => {
const mockOnChange = vi.fn();
renderLanguageSelector({ onLanguagesChange: mockOnChange });
// Start with no languages
expect(screen.getByText('No languages selected. Documents will use default OCR language.')).toBeInTheDocument();
// Open dropdown and select English
await user.click(screen.getByText('Select OCR languages...'));
const englishButton = screen.getByText('English').closest('button');
if (englishButton) {
await user.click(englishButton);
}
expect(mockOnChange).toHaveBeenCalledWith(['eng'], 'eng');
});
test('should handle selecting multiple languages', async () => {
const mockOnChange = vi.fn();
// Start with one language selected
renderLanguageSelector({
selectedLanguages: ['eng'],
primaryLanguage: 'eng',
onLanguagesChange: mockOnChange
});
// Should show the selected language
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('(Primary)')).toBeInTheDocument();
// Should show "Add more languages" button
expect(screen.getByText('Add more languages (3 remaining)')).toBeInTheDocument();
});
test('should handle deselecting all languages', () => {
const mockOnChange = vi.fn();
renderLanguageSelector({
selectedLanguages: [],
primaryLanguage: '',
onLanguagesChange: mockOnChange
});
expect(screen.getByText('No languages selected. Documents will use default OCR language.')).toBeInTheDocument();
});
});
describe('Accessibility', () => {
test('should be keyboard navigable', async () => {
renderLanguageSelector();
const button = screen.getByText('Select OCR languages...').closest('button');
// Tab to button and press Enter to open
button?.focus();
expect(button).toHaveFocus();
await user.keyboard('{Enter}');
expect(screen.getByText('Available Languages')).toBeInTheDocument();
});
test('should have proper button roles', () => {
renderLanguageSelector();
const button = screen.getByText('Select OCR languages...').closest('button');
expect(button).toHaveAttribute('type', 'button');
});
test('should have proper structure when languages are selected', () => {
renderLanguageSelector({
selectedLanguages: ['eng', 'spa'],
primaryLanguage: 'eng'
});
// Should have language tags
expect(screen.getByText('English')).toBeInTheDocument();
expect(screen.getByText('Spanish')).toBeInTheDocument();
// Should have proper button for adding more
const addButton = screen.getByText('Add more languages (2 remaining)');
expect(addButton.closest('button')).toHaveAttribute('type', 'button');
});
});
});

View File

@ -0,0 +1 @@
export { default } from './LanguageSelector';

View File

@ -21,6 +21,9 @@ use readur::{
fn create_empty_update_settings() -> UpdateSettings {
UpdateSettings {
ocr_language: None,
preferred_languages: None,
primary_language: None,
auto_detect_language_combination: None,
concurrent_ocr_jobs: None,
ocr_timeout_seconds: None,
max_file_size_mb: None,
@ -154,6 +157,9 @@ async fn setup_webdav_settings(state: &AppState, user_id: Uuid) {
webdav_auto_sync: Some(true),
webdav_sync_interval_minutes: Some(60),
ocr_language: None,
preferred_languages: None,
primary_language: None,
auto_detect_language_combination: None,
concurrent_ocr_jobs: None,
ocr_timeout_seconds: None,
max_file_size_mb: None,