183 lines
5.6 KiB
TypeScript
183 lines
5.6 KiB
TypeScript
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import DataManagementPage from '../DataManagementPage.tsx';
|
|
import { api } from '../../../services/apiClient.ts';
|
|
|
|
vi.mock('../../../services/apiClient.ts', () => ({
|
|
api: {
|
|
kpis: {
|
|
list: vi.fn().mockResolvedValue([]),
|
|
},
|
|
countings: {
|
|
list: vi.fn().mockResolvedValue([]),
|
|
create: vi.fn(),
|
|
update: vi.fn(),
|
|
sync: vi.fn().mockResolvedValue({ upserted: 2, dates: ['2026-09-02', '2026-09-03'] }),
|
|
},
|
|
manualInputs: {
|
|
list: vi.fn().mockResolvedValue([
|
|
{
|
|
id: 1,
|
|
cycle: 1,
|
|
date: '2026-08-10',
|
|
mortality_manual: 10,
|
|
mortality_manual_total: 10,
|
|
harvest_manual: 0,
|
|
harvest_weight_manual: 0,
|
|
},
|
|
]),
|
|
create: vi.fn(),
|
|
},
|
|
},
|
|
errorMessage: (err: unknown) => (err instanceof Error ? err.message : 'Terjadi kesalahan'),
|
|
}));
|
|
|
|
vi.mock('../../shared/PageAiInsight.tsx', () => ({
|
|
PageAiInsight: () => <div data-testid="page-ai-insight" />,
|
|
}));
|
|
|
|
vi.mock('../../../context/FarmContext.tsx', () => ({
|
|
useFarm: () => ({
|
|
selectedSite: { id: 1, site_name: 'Sukawarna', user: 1, created_at: '', updated_at: '' },
|
|
selectedKandang: {
|
|
id: 1,
|
|
kandang_name: 'Kandang 1',
|
|
site: 1,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
selectedKandangId: 1,
|
|
selectedCycle: {
|
|
id: 1,
|
|
kandang: 1,
|
|
flock: 1,
|
|
total_days: 35,
|
|
current_day: 1,
|
|
visible_through_date: '2026-08-10',
|
|
start_date: '2026-08-10',
|
|
end_date: '2026-09-13',
|
|
chick_in_weight: 45,
|
|
doc_in_count: 20000,
|
|
doc_in_weight: 45,
|
|
status: 'active',
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../utils/format.ts', async () => {
|
|
const actual = await vi.importActual<typeof import('../../../utils/format.ts')>(
|
|
'../../../utils/format.ts'
|
|
);
|
|
return {
|
|
...actual,
|
|
todayIso: () => '2026-08-12',
|
|
};
|
|
});
|
|
|
|
describe('DataManagementPage', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(api.kpis.list).mockResolvedValue([]);
|
|
vi.mocked(api.countings.list).mockResolvedValue([]);
|
|
vi.mocked(api.countings.sync).mockResolvedValue({
|
|
upserted: 2,
|
|
dates: ['2026-09-02', '2026-09-03'],
|
|
});
|
|
});
|
|
|
|
it('renders the Hitung Ayam dashboard view with stats and daily rows', async () => {
|
|
render(<DataManagementPage onSettings={() => undefined} />);
|
|
|
|
expect(await screen.findByRole('heading', { name: 'Hitung Ayam' })).toBeInTheDocument();
|
|
expect(screen.getByText(/Populasi Awal/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/Total Mortalitas/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/Tingkat Mortalitas/i)).toBeInTheDocument();
|
|
expect(screen.getAllByText(/Hari 0/i).length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText(/Hari 2/i).length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('does not offer manual or counting input forms', async () => {
|
|
render(<DataManagementPage onSettings={() => undefined} />);
|
|
|
|
expect(await screen.findByRole('heading', { name: 'Hitung Ayam' })).toBeInTheDocument();
|
|
expect(screen.queryByRole('heading', { name: /Input manual/i })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole('button', { name: /Simpan hitungan/i })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole('button', { name: /Edit/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('shows Mati IoT as 0 and Akurasi Mati when counting row is missing', async () => {
|
|
render(<DataManagementPage onSettings={() => undefined} />);
|
|
|
|
expect(await screen.findByRole('heading', { name: 'Hitung Ayam' })).toBeInTheDocument();
|
|
expect(screen.getAllByText('Mati IoT').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('Akurasi Mati').length).toBeGreaterThan(0);
|
|
// manual 10 vs iot 0 → 0.0%
|
|
expect(screen.getAllByText('0.0%').length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('calls chicken counting sync when Sync is clicked', async () => {
|
|
render(<DataManagementPage onSettings={() => undefined} />);
|
|
|
|
await screen.findByRole('heading', { name: 'Hitung Ayam' });
|
|
fireEvent.click(screen.getByRole('button', { name: /Sync/i }));
|
|
|
|
await waitFor(() => {
|
|
expect(api.countings.sync).toHaveBeenCalledWith({ cycle_id: 1 });
|
|
});
|
|
});
|
|
|
|
it('shows under-count IoT accuracy warning for the latest IoT day', async () => {
|
|
vi.mocked(api.kpis.list).mockResolvedValue([
|
|
{
|
|
id: 1,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-08-12',
|
|
age: 2,
|
|
mortality: 0,
|
|
mortality_total: 10,
|
|
feed: 0,
|
|
feed_total: 0,
|
|
harvest: 0,
|
|
harvest_total: 0,
|
|
harvest_weight: 0,
|
|
harvest_weight_total: 0,
|
|
chicken_life: 19990,
|
|
chicken_life_percentage: 99.95,
|
|
iot_weight: 0,
|
|
average_harvest_day: 0,
|
|
average_harvest_weight: 0,
|
|
fcr: 0,
|
|
eef: 0,
|
|
cc: 0,
|
|
cw: 0,
|
|
fs: 0,
|
|
manual: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
]);
|
|
vi.mocked(api.countings.list).mockResolvedValue([
|
|
{
|
|
id: 1,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-08-12',
|
|
total_count: 18000,
|
|
mortality_count: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
]);
|
|
|
|
render(<DataManagementPage onSettings={() => undefined} />);
|
|
|
|
const alert = await screen.findByRole('alert');
|
|
expect(within(alert).getByText(/IoT lebih rendah dari stock/i)).toBeInTheDocument();
|
|
expect(within(alert).getByText(/Hari 2/i)).toBeInTheDocument();
|
|
});
|
|
});
|