116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
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<string, unknown> | 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(<DashboardCloseCycleControls />);
|
|
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(<DashboardCloseCycleControls />);
|
|
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(<DashboardCloseCycleControls />);
|
|
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(<DashboardCloseCycleControls />);
|
|
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();
|
|
});
|
|
});
|