// @vitest-environment jsdom import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' vi.mock('@/actions/products', () => ({ createProductAction: vi.fn(), updateProductAction: vi.fn(), })) vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })) vi.mock('@/stores/products-store', () => ({ useProductsStore: vi.fn((selector: (s: { addProduct: () => void; updateProduct: () => void }) => unknown) => selector({ addProduct: vi.fn(), updateProduct: vi.fn() }) ), })) import { ProductDialog } from '@/components/dialogs/product-dialog' import { createProductAction, updateProductAction } from '@/actions/products' import { toast } from 'sonner' const mockCreate = createProductAction as ReturnType const mockUpdate = updateProductAction as ReturnType const mockToast = toast as unknown as { success: ReturnType error: ReturnType } const PRODUCT = { id: 'prod-1', name: 'Mijn Product', code: 'MP', description: 'Een product', repo_url: 'https://github.com/org/repo', definition_of_done: 'Alles groen', auto_pr: false, } beforeEach(() => { vi.clearAllMocks() }) describe('ProductDialog — create mode', () => { it('rendert met lege velden en "Nieuw product" titel', () => { render( ) expect(screen.getByText('Nieuw product')).toBeTruthy() expect(screen.getByLabelText(/Naam/)).toBeTruthy() expect((screen.getByLabelText(/Naam/) as HTMLInputElement).value).toBe('') }) it('toont validatiefout als naam leeg is bij submit', async () => { render( ) fireEvent.click(screen.getByRole('button', { name: 'Aanmaken' })) await waitFor(() => { expect(screen.getByText('Naam is verplicht')).toBeTruthy() }) expect(mockCreate).not.toHaveBeenCalled() }) it('roept createProductAction aan bij geldig formulier', async () => { mockCreate.mockResolvedValue({ success: true, productId: 'new-prod' }) render( ) fireEvent.change(screen.getByLabelText(/Naam/), { target: { value: 'Nieuw Product' } }) fireEvent.submit(document.getElementById('product-form')!) await waitFor(() => { expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ name: 'Nieuw Product' }) ) }) expect(mockToast.success).toHaveBeenCalledWith('Product aangemaakt') }) it('toont error-toast als createProductAction een error retourneert', async () => { mockCreate.mockResolvedValue({ error: 'Code is al in gebruik' }) render( ) fireEvent.change(screen.getByLabelText(/Naam/), { target: { value: 'Test' } }) fireEvent.submit(document.getElementById('product-form')!) await waitFor(() => { expect(mockToast.error).toHaveBeenCalledWith('Code is al in gebruik') }) }) }) describe('ProductDialog — edit mode', () => { it('rendert met bestaande waarden vooringevuld', () => { render( ) expect(screen.getByText('Product bewerken')).toBeTruthy() expect((screen.getByLabelText(/Naam/) as HTMLInputElement).value).toBe('Mijn Product') }) it('roept updateProductAction aan bij opslaan', async () => { mockUpdate.mockResolvedValue({ success: true }) render( ) fireEvent.change(screen.getByLabelText(/Naam/), { target: { value: 'Gewijzigd Product' } }) fireEvent.submit(document.getElementById('product-form')!) await waitFor(() => { expect(mockUpdate).toHaveBeenCalledWith( PRODUCT.id, expect.objectContaining({ name: 'Gewijzigd Product' }) ) }) expect(mockToast.success).toHaveBeenCalledWith('Product opgeslagen') }) }) describe('ProductDialog — demo mode', () => { it('submit-knop is disabled in demo-modus', () => { render( ) const submitBtn = screen.getByRole('button', { name: 'Aanmaken' }) expect(submitBtn).toHaveProperty('disabled', true) }) })