// File: frontend/components/admin/ai/__tests__/SandboxTabs.test.tsx import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import SandboxTabs from '../SandboxTabs'; const mockSubmitSandboxOcr = vi.fn(); const mockSubmitSandboxAiExtract = vi.fn(); const mockSubmitSandboxRagPrep = vi.fn(); const mockGetSandboxJobStatus = vi.fn(); vi.mock('@/lib/services/admin-ai.service', () => ({ adminAiService: { submitSandboxOcr: (...args: any) => mockSubmitSandboxOcr(...args), submitSandboxAiExtract: (...args: any) => mockSubmitSandboxAiExtract(...args), submitSandboxRagPrep: (...args: any) => mockSubmitSandboxRagPrep(...args), getSandboxJobStatus: (...args: any) => mockGetSandboxJobStatus(...args), }, })); vi.mock('@/hooks/use-master-data', () => ({ useProjects: vi.fn(() => ({ data: [{ publicId: 'proj-1', projectCode: 'P1', projectName: 'Project 1' }] })), useContracts: vi.fn(() => ({ data: [] })), })); vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), }, })); // ResizeObserver mock is needed for Radix UI select class ResizeObserver { observe() {} unobserve() {} disconnect() {} } window.ResizeObserver = ResizeObserver; describe('SandboxTabs', () => { beforeEach(() => { vi.clearAllMocks(); window.PointerEvent = MouseEvent as any; }); it('renders correctly', async () => { render(); await waitFor(() => { expect(screen.getByText(/รันบอร์ดทดลองการทำงาน/i)).toBeInTheDocument(); }); expect(screen.getByText('Step 1: Run OCR')).toBeInTheDocument(); expect(screen.getByText('Step 2: AI Extract')).toBeInTheDocument(); expect(screen.getByText('Step 3: RAG Prep')).toBeInTheDocument(); }); it('requires project and file for OCR', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByRole('button', { name: /เริ่มรัน OCR/i })).toBeDisabled(); }); // Upload file const file = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; await userEvent.upload(fileInput, file); // After uploading file, button is enabled, but submitting will fail without project in AI Extract step. // Wait, handleRunOcr checks if file exists, it doesn't check project! expect(screen.getByRole('button', { name: /เริ่มรัน OCR/i })).not.toBeDisabled(); }); it('runs OCR and polls status', async () => { const user = userEvent.setup(); mockSubmitSandboxOcr.mockResolvedValue({ requestPublicId: 'req-1' }); mockGetSandboxJobStatus.mockResolvedValue({ status: 'completed', ocrText: 'Extracted text from PDF' }); render(); // Upload file const file = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; await userEvent.upload(fileInput, file); const runBtn = screen.getByRole('button', { name: /เริ่มรัน OCR/i }); await user.click(runBtn); expect(mockSubmitSandboxOcr).toHaveBeenCalled(); await waitFor(() => { expect(mockGetSandboxJobStatus).toHaveBeenCalledWith('req-1'); }, { timeout: 3000 }); // Polling interval is 2s }); });