import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
// Mock tesseract.js module
vi.mock('tesseract.js', () => {
const mockWorker = {
recognize: vi.fn(() => Promise.resolve({
data: {
text: 'Mock OCR text',
words: [
{
text: 'Mock',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 95,
},
{
text: 'OCR',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 92,
},
{
text: 'text',
bbox: { x0: 100, y0: 10, x1: 140, y1: 30 },
confidence: 88,
},
],
}
})),
load: vi.fn(() => Promise.resolve()),
loadLanguage: vi.fn(() => Promise.resolve()),
initialize: vi.fn(() => Promise.resolve()),
setParameters: vi.fn(() => Promise.resolve()),
terminate: vi.fn(() => Promise.resolve())
};
return {
createWorker: vi.fn(() => Promise.resolve(mockWorker)),
mockWorker // Export for test access
};
});
// Mock image-utils module
vi.mock('../../src/image-utils', () => ({
imageToBase64: vi.fn().mockResolvedValue('data:image/png;base64,mockBase64String'),
}));
// Mock OCR worker pool
vi.mock('../../src/core/ocr-worker-pool', () => {
const mockWorkerPool = {
recognize: vi.fn().mockResolvedValue({
data: {
text: 'Mock OCR text',
words: [
{
text: 'Mock',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 95,
},
{
text: 'OCR',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 92,
},
{
text: 'text',
bbox: { x0: 100, y0: 10, x1: 140, y1: 30 },
confidence: 88,
},
],
},
}),
getMetrics: vi.fn().mockReturnValue({ totalWorkers: 1, activeWorkers: 0 }),
getWorkerStates: vi.fn().mockReturnValue([]),
};
return {
initializeOCRWorkerPool: vi.fn().mockResolvedValue(undefined),
shutdownOCRWorkerPool: vi.fn().mockResolvedValue(undefined),
getOCRWorkerPool: vi.fn().mockReturnValue(mockWorkerPool),
OCRTaskPriority: {
LOW: 'low',
NORMAL: 'normal',
HIGH: 'high',
URGENT: 'urgent'
},
mockWorkerPool // Export for test access
};
});
// Import the modules after mocking
import {
initializeOCR,
terminateOCR,
extractTextFromImage,
findTextInImage,
getTextLocations,
getOCRMetrics,
getOCRWorkerStates,
isUsingWorkerPool,
extractTextFromImages,
getTextLocationsFromImages,
OCRTaskPriority,
configureOCR,
getOCRConfig,
resetOCRConfig,
findTextWithOptions,
getOCRCacheStats,
clearOCRCache,
type OCRConfig,
} from '../../src/ocr-utils';
// Get the mocked functions for assertions
const mockTesseract = await vi.importMock<any>('tesseract.js');
const mockCreateWorker = mockTesseract.createWorker;
const mockWorker = mockTesseract.mockWorker;
// Import mocked image utils
const mockImageUtils = await vi.importMock('../../src/image-utils');
const mockImageToBase64 = mockImageUtils.imageToBase64;
// Import mocked worker pool
const mockOCRWorkerPool = await vi.importMock('../../src/core/ocr-worker-pool');
const mockWorkerPool = mockOCRWorkerPool.mockWorkerPool;
describe('ocr-utils', () => {
beforeEach(() => {
vi.clearAllMocks();
// Reset the imageToBase64 mock
mockImageToBase64.mockResolvedValue('data:image/png;base64,mockBase64String');
// Reset the worker mock
mockWorker.recognize.mockResolvedValue({
data: {
text: 'Mock OCR text',
words: [
{
text: 'Mock',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 95,
},
{
text: 'OCR',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 92,
},
{
text: 'text',
bbox: { x0: 100, y0: 10, x1: 140, y1: 30 },
confidence: 88,
},
],
},
});
// Reset the worker pool mock
mockWorkerPool.recognize.mockResolvedValue({
data: {
text: 'Mock OCR text',
words: [
{
text: 'Mock',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 95,
},
{
text: 'OCR',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 92,
},
{
text: 'text',
bbox: { x0: 100, y0: 10, x1: 140, y1: 30 },
confidence: 88,
},
],
},
});
// Reset worker pool mocks with proper metrics
mockWorkerPool.getMetrics.mockReturnValue({ totalWorkers: 1, idleWorkers: 0, busyWorkers: 1, queuedTasks: 0, completedTasks: 0, failedTasks: 0, averageTaskTime: 0, totalTaskTime: 0, uptime: 0 });
mockWorkerPool.getWorkerStates.mockReturnValue([{ id: 'test-worker', isIdle: true, isHealthy: true, tasksCompleted: 0, averageTaskTime: 0 }]);
mockOCRWorkerPool.getOCRWorkerPool.mockReturnValue(mockWorkerPool);
});
afterEach(async () => {
// Terminate OCR to reset state between tests
await terminateOCR();
});
describe('initializeOCR', () => {
it('should initialize worker pool by default', async () => {
await initializeOCR();
expect(mockOCRWorkerPool.initializeOCRWorkerPool).toHaveBeenCalled();
expect(isUsingWorkerPool()).toBe(true);
});
it('should not create multiple workers with legacy mode', async () => {
await initializeOCR(true); // Use legacy mode
await initializeOCR(true); // Use legacy mode
expect(mockCreateWorker).toHaveBeenCalledTimes(1);
});
});
describe('terminateOCR', () => {
it('should terminate the worker if it exists', async () => {
await initializeOCR();
await terminateOCR();
expect(mockOCRWorkerPool.shutdownOCRWorkerPool).toHaveBeenCalledOnce();
});
it('should handle termination when no worker exists', async () => {
await terminateOCR();
expect(mockWorker.terminate).not.toHaveBeenCalled();
});
});
describe('extractTextFromImage', () => {
it('should extract text from image', async () => {
// Force legacy mode
await initializeOCR(true);
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await extractTextFromImage(mockImage as any);
expect(result).toBe('Mock OCR text');
expect(mockCreateWorker).toHaveBeenCalledWith('eng');
});
it('should trim whitespace from extracted text', async () => {
await initializeOCR(true); // Force legacy mode
mockWorker.recognize.mockResolvedValueOnce({
data: {
text: ' Trimmed text \n',
},
});
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await extractTextFromImage(mockImage as any);
expect(result).toBe('Trimmed text');
});
it('should handle OCR errors', async () => {
// Force legacy mode to avoid worker pool retry logic
await initializeOCR(true);
mockWorker.recognize.mockRejectedValueOnce(new Error('OCR failed'));
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
await expect(extractTextFromImage(mockImage as any)).rejects.toThrow('Text extraction failed: OCR failed');
});
});
describe('findTextInImage', () => {
it('should find text in image (case-insensitive)', async () => {
await initializeOCR(true); // Force legacy mode
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await findTextInImage(mockImage as any, 'mock');
expect(result).toBe(true);
});
it('should return false when text is not found', async () => {
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await findTextInImage(mockImage as any, 'notfound');
expect(result).toBe(false);
});
it('should handle errors gracefully', async () => {
mockWorker.recognize.mockRejectedValueOnce(new Error('OCR failed'));
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await findTextInImage(mockImage as any, 'test');
expect(result).toBe(false);
});
});
describe('getTextLocations', () => {
it('should return text locations with high confidence', async () => {
await initializeOCR(true); // Force legacy mode
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await getTextLocations(mockImage as any);
expect(result).toHaveLength(3);
expect(result[0]).toEqual({
text: 'Mock',
x: 10,
y: 10,
width: 40,
height: 20,
confidence: 95,
});
});
it('should filter out low confidence words', async () => {
await initializeOCR(true); // Force legacy mode
mockWorker.recognize.mockResolvedValueOnce({
data: {
text: 'Test text',
words: [
{
text: 'Good',
bbox: { x0: 0, y0: 0, x1: 40, y1: 20 },
confidence: 80,
},
{
text: 'Bad',
bbox: { x0: 50, y0: 0, x1: 80, y1: 20 },
confidence: 30,
},
],
},
});
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await getTextLocations(mockImage as any);
expect(result).toHaveLength(1);
expect(result[0].text).toBe('Good');
});
it('should handle missing words data', async () => {
await initializeOCR(true); // Force legacy mode
mockWorker.recognize.mockResolvedValueOnce({
data: {
text: 'No words data',
},
});
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const result = await getTextLocations(mockImage as any);
expect(result).toHaveLength(0);
});
it('should handle errors', async () => {
// Force legacy mode to avoid worker pool retry logic
await initializeOCR(true);
mockWorker.recognize.mockRejectedValueOnce(new Error('OCR failed'));
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
await expect(getTextLocations(mockImage as any)).rejects.toThrow(
'Failed to get text locations: OCR failed'
);
});
});
describe('worker pool functionality', () => {
afterEach(async () => {
await terminateOCR();
});
it('should initialize with worker pool by default', async () => {
await initializeOCR(); // Should use worker pool by default
expect(isUsingWorkerPool()).toBe(true);
const metrics = getOCRMetrics();
expect(metrics).toBeDefined();
expect(metrics!.totalWorkers).toBeGreaterThanOrEqual(1);
});
it('should fallback to legacy worker when specified', async () => {
await initializeOCR(true); // Use legacy worker
expect(isUsingWorkerPool()).toBe(false);
const metrics = getOCRMetrics();
expect(metrics).toBeNull();
});
it('should provide worker state information when using pool', async () => {
await initializeOCR(); // Worker pool
const workerStates = getOCRWorkerStates();
expect(workerStates).toBeDefined();
expect(Array.isArray(workerStates)).toBe(true);
});
it('should return null for worker states when using legacy', async () => {
await initializeOCR(true); // Legacy worker
const workerStates = getOCRWorkerStates();
expect(workerStates).toBeNull();
});
it('should extract text from multiple images concurrently', async () => {
await initializeOCR(); // Worker pool for concurrency
const mockImages = [
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) },
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) },
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) }
];
const results = await extractTextFromImages(mockImages as any[]);
expect(results).toHaveLength(3);
results.forEach(result => {
expect(result).toBe('Mock OCR text');
});
});
it('should extract text from multiple images sequentially with legacy worker', async () => {
await initializeOCR(true); // Legacy worker
const mockImages = [
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) },
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) }
];
const results = await extractTextFromImages(mockImages as any[]);
expect(results).toHaveLength(2);
results.forEach(result => {
expect(result).toBe('Mock OCR text');
});
});
it('should get text locations from multiple images', async () => {
await initializeOCR(); // Worker pool
const mockImages = [
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) },
{ width: 100, height: 100, channels: 3, data: new Uint8Array(100 * 100 * 3) }
];
const results = await getTextLocationsFromImages(mockImages as any[]);
expect(results).toHaveLength(2);
results.forEach(locations => {
expect(locations).toHaveLength(3); // Mock returns 3 words
expect(locations[0].text).toBe('Mock');
expect(locations[1].text).toBe('OCR');
expect(locations[2].text).toBe('text');
});
});
it('should handle priority in OCR operations', async () => {
await initializeOCR(); // Worker pool
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
// Test with different priorities
const normalResult = await extractTextFromImage(mockImage as any, undefined, OCRTaskPriority.NORMAL);
const highResult = await extractTextFromImage(mockImage as any, undefined, OCRTaskPriority.HIGH);
const lowResult = await extractTextFromImage(mockImage as any, undefined, OCRTaskPriority.LOW);
expect(normalResult).toBe('Mock OCR text');
expect(highResult).toBe('Mock OCR text');
expect(lowResult).toBe('Mock OCR text');
});
it('should handle priority in text search operations', async () => {
await initializeOCR(); // Worker pool
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const found = await findTextInImage(mockImage as any, 'Mock', OCRTaskPriority.HIGH);
expect(found).toBe(true);
});
it('should handle priority in location detection operations', async () => {
await initializeOCR(); // Worker pool
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
const locations = await getTextLocations(mockImage as any, undefined, undefined, OCRTaskPriority.URGENT);
expect(locations).toHaveLength(3);
});
});
describe('OCR Configuration', () => {
beforeEach(() => {
resetOCRConfig();
});
afterEach(() => {
resetOCRConfig();
});
it('should have default configuration', () => {
const config = getOCRConfig();
expect(config.minConfidence).toBe(50);
expect(config.fuzzyMatchThreshold).toBe(0.7);
expect(config.relaxedFuzzyThreshold).toBe(0.5);
expect(config.cacheEnabled).toBe(true);
expect(config.cacheTTL).toBe(30000);
expect(config.maxCacheSize).toBe(100);
expect(config.timeoutMs).toBe(30000);
});
it('should allow partial configuration updates', () => {
configureOCR({ minConfidence: 75, fuzzyMatchThreshold: 0.8 });
const config = getOCRConfig();
expect(config.minConfidence).toBe(75);
expect(config.fuzzyMatchThreshold).toBe(0.8);
expect(config.relaxedFuzzyThreshold).toBe(0.5); // Should remain default
expect(config.cacheEnabled).toBe(true); // Should remain default
});
it('should reset configuration to defaults', () => {
configureOCR({ minConfidence: 80, cacheEnabled: false });
resetOCRConfig();
const config = getOCRConfig();
expect(config.minConfidence).toBe(50);
expect(config.cacheEnabled).toBe(true);
});
});
describe('Fuzzy Text Matching', () => {
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
beforeEach(async () => {
await initializeOCR();
resetOCRConfig();
});
it('should find exact text matches', async () => {
const locations = await getTextLocations(mockImage as any, 'Mock');
expect(locations).toHaveLength(1);
expect(locations[0].text).toBe('Mock');
});
it('should find case-insensitive matches', async () => {
const locations = await getTextLocations(mockImage as any, 'mock');
expect(locations).toHaveLength(1);
expect(locations[0].text).toBe('Mock');
});
it('should find partial matches', async () => {
const locations = await getTextLocations(mockImage as any, 'oc');
expect(locations).toHaveLength(1);
expect(locations[0].text).toBe('Mock');
});
it('should handle fuzzy matching with typos', async () => {
// Test with a typo that should still match within threshold
const locations = await getTextLocations(mockImage as any, 'Mok'); // Missing 'c'
expect(locations.length).toBeGreaterThan(0);
});
it('should respect fuzzy match threshold configuration', async () => {
configureOCR({ fuzzyMatchThreshold: 0.9 }); // Very strict matching
const locations = await getTextLocations(mockImage as any, 'Mok');
// Should find fewer matches with stricter threshold
expect(locations.length).toBeLessThanOrEqual(1);
});
it('should fall back to relaxed matching when no strict matches found', async () => {
configureOCR({ fuzzyMatchThreshold: 0.99 }); // Very strict
const locations = await getTextLocations(mockImage as any, 'xyz'); // No match
expect(locations).toHaveLength(0); // Should not find relaxed matches for completely different text
});
it('should sort results by similarity and confidence', async () => {
mockWorkerPool.recognize.mockResolvedValueOnce({
data: {
text: 'Mock Test Mock2',
words: [
{
text: 'Mock',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 95,
},
{
text: 'Test',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 85,
},
{
text: 'Mock2',
bbox: { x0: 100, y0: 10, x1: 140, y1: 30 },
confidence: 90,
},
],
},
});
const locations = await getTextLocations(mockImage as any, 'Mock');
expect(locations).toHaveLength(2);
// Should prioritize exact match over partial match
expect(locations[0].text).toBe('Mock');
expect(locations[1].text).toBe('Mock2');
});
});
describe('OCR Caching', () => {
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
beforeEach(async () => {
await initializeOCR();
resetOCRConfig();
clearOCRCache();
});
afterEach(() => {
clearOCRCache();
});
it('should cache OCR results', async () => {
await extractTextFromImage(mockImage as any);
await extractTextFromImage(mockImage as any); // Second call should use cache
const stats = getOCRCacheStats();
expect(stats.totalEntries).toBeGreaterThan(0);
expect(stats.cacheEnabled).toBe(true);
});
it('should respect cache configuration', async () => {
configureOCR({ cacheEnabled: false });
await extractTextFromImage(mockImage as any);
const stats = getOCRCacheStats();
expect(stats.totalEntries).toBe(0);
expect(stats.cacheEnabled).toBe(false);
});
it('should expire cache entries after TTL', async () => {
configureOCR({ cacheTTL: 100 }); // Very short TTL
await extractTextFromImage(mockImage as any);
// Wait for cache to expire
await new Promise(resolve => setTimeout(resolve, 150));
const stats = getOCRCacheStats();
expect(stats.expiredEntries).toBeGreaterThan(0);
});
it('should provide cache statistics', () => {
const stats = getOCRCacheStats();
expect(stats).toHaveProperty('totalEntries');
expect(stats).toHaveProperty('validEntries');
expect(stats).toHaveProperty('expiredEntries');
expect(stats).toHaveProperty('cacheHitRate');
expect(stats).toHaveProperty('cacheEnabled');
expect(stats).toHaveProperty('cacheTTL');
expect(stats).toHaveProperty('maxCacheSize');
});
it('should clear cache manually', async () => {
await extractTextFromImage(mockImage as any);
let stats = getOCRCacheStats();
expect(stats.totalEntries).toBeGreaterThan(0);
clearOCRCache();
stats = getOCRCacheStats();
expect(stats.totalEntries).toBe(0);
});
});
describe('OCR Consistency between extract_text and find_text', () => {
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
beforeEach(async () => {
await initializeOCR();
resetOCRConfig();
clearOCRCache();
});
it('should use same OCR engine for both extract_text and find_text', async () => {
const extractedText = await extractTextFromImage(mockImage as any);
const found = await findTextInImage(mockImage as any, 'Mock');
expect(extractedText).toContain('Mock');
expect(found).toBe(true);
});
it('should find text that exists in extracted text', async () => {
const extractedText = await extractTextFromImage(mockImage as any);
const words = extractedText.split(/\s+/).filter(word => word.length > 0);
for (const word of words) {
const found = await findTextInImage(mockImage as any, word);
expect(found).toBe(true);
}
});
it('should use same confidence filtering in both functions', async () => {
configureOCR({ minConfidence: 80 });
// Mock low confidence words
mockWorkerPool.recognize.mockResolvedValueOnce({
data: {
text: 'High Low',
words: [
{
text: 'High',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 90, // Above threshold
},
{
text: 'Low',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 70, // Below threshold
},
],
},
});
const extractedText = await extractTextFromImage(mockImage as any);
const foundHigh = await findTextInImage(mockImage as any, 'High');
const foundLow = await findTextInImage(mockImage as any, 'Low');
expect(extractedText).toContain('High');
expect(extractedText).toContain('Low'); // extractTextFromImage includes all text
expect(foundHigh).toBe(true); // Should be found (high confidence)
expect(foundLow).toBe(false); // Should not be found (low confidence)
});
it('should use same caching for both functions', async () => {
// First call populates cache
await extractTextFromImage(mockImage as any);
// Second call should use cache
const found = await findTextInImage(mockImage as any, 'Mock');
expect(found).toBe(true);
const stats = getOCRCacheStats();
expect(stats.totalEntries).toBeGreaterThan(0);
});
});
describe('Enhanced findTextWithOptions', () => {
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
beforeEach(async () => {
await initializeOCR();
resetOCRConfig();
});
it('should use default options when none provided', async () => {
const locations = await findTextWithOptions(mockImage as any, 'Mock');
expect(locations).toHaveLength(1);
expect(locations[0].text).toBe('Mock');
});
it('should respect custom fuzzy threshold', async () => {
const strictLocations = await findTextWithOptions(mockImage as any, 'Mok', {
fuzzyThreshold: 0.9
});
const relaxedLocations = await findTextWithOptions(mockImage as any, 'Mok', {
fuzzyThreshold: 0.5
});
expect(relaxedLocations.length).toBeGreaterThanOrEqual(strictLocations.length);
});
it('should respect custom confidence threshold', async () => {
mockWorkerPool.recognize.mockResolvedValueOnce({
data: {
text: 'High Medium Low',
words: [
{
text: 'High',
bbox: { x0: 10, y0: 10, x1: 50, y1: 30 },
confidence: 95,
},
{
text: 'Medium',
bbox: { x0: 60, y0: 10, x1: 90, y1: 30 },
confidence: 75,
},
{
text: 'Low',
bbox: { x0: 100, y0: 10, x1: 140, y1: 30 },
confidence: 55,
},
],
},
});
const highConfidenceLocations = await findTextWithOptions(mockImage as any, '', {
minConfidence: 80
});
const lowConfidenceLocations = await findTextWithOptions(mockImage as any, '', {
minConfidence: 50
});
expect(highConfidenceLocations.length).toBeLessThan(lowConfidenceLocations.length);
expect(highConfidenceLocations.every(loc => loc.confidence >= 80)).toBe(true);
});
it('should handle priority parameter', async () => {
const locations = await findTextWithOptions(mockImage as any, 'Mock', {
priority: OCRTaskPriority.HIGH
});
expect(locations).toHaveLength(1);
expect(locations[0].text).toBe('Mock');
});
it('should handle errors gracefully', async () => {
mockWorkerPool.recognize.mockRejectedValueOnce(new Error('OCR failed'));
const locations = await findTextWithOptions(mockImage as any, 'Mock');
expect(locations).toHaveLength(0);
});
});
describe('Error Handling and Robustness', () => {
const mockImage = {
width: 100,
height: 100,
channels: 3,
data: new Uint8Array(100 * 100 * 3),
};
beforeEach(async () => {
await initializeOCR();
resetOCRConfig();
});
it('should handle timeout errors', async () => {
configureOCR({ timeoutMs: 1 }); // Very short timeout
mockWorkerPool.recognize.mockImplementationOnce(() =>
new Promise(resolve => setTimeout(resolve, 100)) // Longer than timeout
);
await expect(extractTextFromImage(mockImage as any)).rejects.toThrow('OCR recognition timed out');
});
it('should handle invalid OCR results', async () => {
mockWorkerPool.recognize.mockResolvedValueOnce(null);
await expect(extractTextFromImage(mockImage as any)).rejects.toThrow('Invalid OCR result structure');
});
it('should handle missing image data', async () => {
await expect(extractTextFromImage(null as any)).rejects.toThrow();
});
it('should handle empty search text', async () => {
const locations = await getTextLocations(mockImage as any, '');
expect(locations).toHaveLength(3); // Should return all locations
});
it('should handle whitespace-only search text', async () => {
const locations = await getTextLocations(mockImage as any, ' ');
expect(locations).toHaveLength(3); // Should return all locations
});
});
});