import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import DashboardCloseCycleControls from '../DashboardCloseCycleControls.tsx'; const { requestCloseMock, cancelCloseRequestMock, reloadMock, farmState, authState } = vi.hoisted( () => ({ requestCloseMock: vi.fn().mockResolvedValue({}), cancelCloseRequestMock: vi.fn().mockResolvedValue({}), reloadMock: vi.fn().mockResolvedValue(undefined), farmState: { selectedCycle: { id: 5, start_date: '2026-09-01', end_date: null, status: 'active', proposed_end_date: null, visible_through_date: '2026-09-10', } as Record | null, }, authState: { user: { id: 1, user_name: 'staff', display_name: 'Staff', status: 'active', }, }, }) ); vi.mock('../../../services/apiClient.ts', () => ({ api: { cycles: { requestClose: requestCloseMock, cancelCloseRequest: cancelCloseRequestMock, }, }, errorMessage: (err: unknown) => (err instanceof Error ? err.message : 'Terjadi kesalahan'), })); vi.mock('../../../context/AuthContext.tsx', () => ({ useAuth: () => authState, })); vi.mock('../../../context/FarmContext.tsx', () => ({ useFarm: () => ({ selectedCycle: farmState.selectedCycle, reload: reloadMock, }), })); describe('DashboardCloseCycleControls', () => { beforeEach(() => { requestCloseMock.mockClear(); cancelCloseRequestMock.mockClear(); reloadMock.mockClear(); authState.user.status = 'active'; farmState.selectedCycle = { id: 5, start_date: '2026-09-01', end_date: null, status: 'active', proposed_end_date: null, visible_through_date: '2026-09-10', }; }); it('shows date + Ajukan tutup for active cycle and requires confirm before API', async () => { render(); expect(screen.getByRole('button', { name: /Ajukan tutup/i })).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /Ajukan tutup/i })); expect(requestCloseMock).not.toHaveBeenCalled(); expect(screen.getByRole('dialog')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /Ya, ajukan tutup/i })); await waitFor(() => { expect(requestCloseMock).toHaveBeenCalledWith(5, { proposed_end_date: expect.any(String), }); }); expect(reloadMock).toHaveBeenCalled(); }); it('shows cancel control when pending_close', () => { farmState.selectedCycle = { id: 5, start_date: '2026-09-01', end_date: null, status: 'pending_close', proposed_end_date: '2026-09-15', visible_through_date: '2026-09-10', }; render(); expect(screen.queryByRole('button', { name: /Ajukan tutup/i })).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: /Batalkan permintaan/i })).toBeInTheDocument(); }); it('hides controls for non-active users', () => { authState.user.status = 'superadmin'; render(); expect(screen.queryByRole('button', { name: /Ajukan tutup/i })).not.toBeInTheDocument(); }); it('shows API failure in an alert popup', async () => { requestCloseMock.mockRejectedValueOnce( new Error('gagal kirim ke pusat — coba lagi (400: {"detail":"Cycle is already closed."})') ); render(); fireEvent.click(screen.getByRole('button', { name: /Ajukan tutup/i })); fireEvent.click(screen.getByRole('button', { name: /Ya, ajukan tutup/i })); expect(await screen.findByRole('alertdialog')).toBeInTheDocument(); expect(screen.getByText(/Cycle is already closed/i)).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /^Tutup$/i })); expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); }); });