import { describe, it, expect, beforeEach, vi } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import OnboardingForm from '../OnboardingForm' import { onboardingService } from '../../services/onboarding.service' import type { OnboardingResponse } from '../../types/onboarding' // Mock the onboarding service vi.mock('../../services/onboarding.service', () => ({ onboardingService: { onboardEmployee: vi.fn(), }, })) // Mock Next.js navigation vi.mock('next/navigation', () => ({ useRouter: () => ({ push: vi.fn(), refresh: vi.fn(), }), })) describe('OnboardingForm', () => { beforeEach(() => { vi.clearAllMocks() }) describe('Rendering', () => { it('should render all required fields', () => { render() // 必填欄位 expect(screen.getByLabelText(/Resume ID/i)).toBeInTheDocument() expect(screen.getByLabelText(/Keycloak User ID/i)).toBeInTheDocument() expect(screen.getByLabelText(/Keycloak Username/i)).toBeInTheDocument() expect(screen.getByLabelText(/Hire Date/i)).toBeInTheDocument() // 選填欄位 expect(screen.getByLabelText(/Storage Quota \(GB\)/i)).toBeInTheDocument() expect(screen.getByLabelText(/Email Quota \(MB\)/i)).toBeInTheDocument() // 提交按鈕 expect(screen.getByRole('button', { name: /Submit/i })).toBeInTheDocument() }) it('should render department assignment section', () => { render() expect(screen.getByText(/Department Assignments/i)).toBeInTheDocument() expect(screen.getByRole('button', { name: /Add Department/i })).toBeInTheDocument() }) it('should render role assignment section', () => { render() expect(screen.getByText(/Role Assignments/i)).toBeInTheDocument() }) }) describe('Form Validation', () => { it('should show validation errors for required fields', async () => { const user = userEvent.setup() render() const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(screen.getByText(/Resume ID is required/i)).toBeInTheDocument() expect(screen.getByText(/Keycloak User ID is required/i)).toBeInTheDocument() expect(screen.getByText(/Keycloak Username is required/i)).toBeInTheDocument() expect(screen.getByText(/Hire Date is required/i)).toBeInTheDocument() }) }) it('should validate Keycloak User ID format (UUID)', async () => { const user = userEvent.setup() render() const keycloakUserIdInput = screen.getByLabelText(/Keycloak User ID/i) await user.type(keycloakUserIdInput, 'invalid-uuid') const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(screen.getByText(/Invalid UUID format/i)).toBeInTheDocument() }) }) it('should validate storage quota is a positive number', async () => { const user = userEvent.setup() render() const storageQuotaInput = screen.getByLabelText(/Storage Quota \(GB\)/i) await user.clear(storageQuotaInput) await user.type(storageQuotaInput, '-10') const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(screen.getByText(/Storage quota must be positive/i)).toBeInTheDocument() }) }) }) describe('Department Assignment', () => { it('should add a new department assignment', async () => { const user = userEvent.setup() render() const addButton = screen.getByRole('button', { name: /Add Department/i }) await user.click(addButton) await waitFor(() => { expect(screen.getByLabelText(/Department ID/i)).toBeInTheDocument() expect(screen.getByLabelText(/Position/i)).toBeInTheDocument() expect(screen.getByLabelText(/Membership Type/i)).toBeInTheDocument() }) }) it('should remove a department assignment', async () => { const user = userEvent.setup() render() // 添加部門 const addButton = screen.getByRole('button', { name: /Add Department/i }) await user.click(addButton) // 刪除部門 const removeButton = screen.getByRole('button', { name: /Remove/i }) await user.click(removeButton) await waitFor(() => { expect(screen.queryByLabelText(/Department ID/i)).not.toBeInTheDocument() }) }) it('should validate department assignment fields', async () => { const user = userEvent.setup() render() const addButton = screen.getByRole('button', { name: /Add Department/i }) await user.click(addButton) const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(screen.getByText(/Department ID is required/i)).toBeInTheDocument() expect(screen.getByText(/Position is required/i)).toBeInTheDocument() }) }) }) describe('Form Submission', () => { it('should submit form successfully with valid data', async () => { const user = userEvent.setup() const mockResponse: OnboardingResponse = { message: 'Employee onboarded successfully', employee: { tenant_id: 1, seq_no: 1, tenant_emp_code: 'PWD0001', keycloak_user_id: '550e8400-e29b-41d4-a716-446655440000', keycloak_username: 'wang.ming', name: '王明', hire_date: '2026-02-21', }, summary: { departments_assigned: 1, roles_assigned: 2, services_enabled: 5, }, } vi.mocked(onboardingService.onboardEmployee).mockResolvedValueOnce(mockResponse) render() // 填寫表單 await user.type(screen.getByLabelText(/Resume ID/i), '1') await user.type( screen.getByLabelText(/Keycloak User ID/i), '550e8400-e29b-41d4-a716-446655440000' ) await user.type(screen.getByLabelText(/Keycloak Username/i), 'wang.ming') await user.type(screen.getByLabelText(/Hire Date/i), '2026-02-21') // 提交表單 const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(onboardingService.onboardEmployee).toHaveBeenCalledWith( expect.objectContaining({ resume_id: 1, keycloak_user_id: '550e8400-e29b-41d4-a716-446655440000', keycloak_username: 'wang.ming', hire_date: '2026-02-21', }) ) }) // 驗證成功訊息 await waitFor(() => { expect(screen.getByText(/Employee onboarded successfully/i)).toBeInTheDocument() }) }) it('should handle API errors gracefully', async () => { const user = userEvent.setup() const errorMessage = 'Resume ID not found' vi.mocked(onboardingService.onboardEmployee).mockRejectedValueOnce({ response: { data: { detail: errorMessage }, status: 404, }, }) render() // 填寫表單 await user.type(screen.getByLabelText(/Resume ID/i), '999') await user.type( screen.getByLabelText(/Keycloak User ID/i), '550e8400-e29b-41d4-a716-446655440000' ) await user.type(screen.getByLabelText(/Keycloak Username/i), 'test.user') await user.type(screen.getByLabelText(/Hire Date/i), '2026-02-21') // 提交表單 const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(screen.getByText(errorMessage)).toBeInTheDocument() }) }) it('should show loading state during submission', async () => { const user = userEvent.setup() // 模擬延遲的 API 回應 vi.mocked(onboardingService.onboardEmployee).mockImplementation( () => new Promise((resolve) => setTimeout(resolve, 100)) ) render() // 填寫表單 await user.type(screen.getByLabelText(/Resume ID/i), '1') await user.type( screen.getByLabelText(/Keycloak User ID/i), '550e8400-e29b-41d4-a716-446655440000' ) await user.type(screen.getByLabelText(/Keycloak Username/i), 'wang.ming') await user.type(screen.getByLabelText(/Hire Date/i), '2026-02-21') // 提交表單 const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) // 驗證載入狀態 expect(screen.getByText(/Submitting.../i)).toBeInTheDocument() expect(submitButton).toBeDisabled() }) it('should reset form after successful submission', async () => { const user = userEvent.setup() const mockResponse: OnboardingResponse = { message: 'Employee onboarded successfully', employee: { tenant_id: 1, seq_no: 1, tenant_emp_code: 'PWD0001', keycloak_user_id: '550e8400-e29b-41d4-a716-446655440000', keycloak_username: 'wang.ming', name: '王明', hire_date: '2026-02-21', }, summary: { departments_assigned: 0, roles_assigned: 0, services_enabled: 5, }, } vi.mocked(onboardingService.onboardEmployee).mockResolvedValueOnce(mockResponse) render() // 填寫表單 const resumeIdInput = screen.getByLabelText(/Resume ID/i) as HTMLInputElement await user.type(resumeIdInput, '1') await user.type( screen.getByLabelText(/Keycloak User ID/i), '550e8400-e29b-41d4-a716-446655440000' ) await user.type(screen.getByLabelText(/Keycloak Username/i), 'wang.ming') await user.type(screen.getByLabelText(/Hire Date/i), '2026-02-21') // 提交表單 const submitButton = screen.getByRole('button', { name: /Submit/i }) await user.click(submitButton) await waitFor(() => { expect(screen.getByText(/Employee onboarded successfully/i)).toBeInTheDocument() }) // 驗證表單已重置 await waitFor(() => { expect(resumeIdInput.value).toBe('') }) }) }) })