290 lines
8.6 KiB
TypeScript
290 lines
8.6 KiB
TypeScript
import { render, screen } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { api } from '../../services/apiClient.ts';
|
|
import FeedSackCountingPage from '../FeedSackCountingPage.tsx';
|
|
|
|
const { karungRows, manualRows, cycle, mismatchedKarungRows, mismatchedManualRows } = vi.hoisted(
|
|
() => {
|
|
const cycle = {
|
|
id: 1,
|
|
kandang: 1,
|
|
flock: 1,
|
|
total_days: 35,
|
|
current_day: 2,
|
|
start_date: '2026-08-10',
|
|
end_date: '2026-09-13',
|
|
chick_in_weight: 45,
|
|
doc_in_count: 20000,
|
|
status: 'active',
|
|
feed_initial_balance: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
};
|
|
const karungRows = [
|
|
{
|
|
id: 1,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-08-10',
|
|
in_today: 10,
|
|
out_today: 4,
|
|
in_total: 10,
|
|
out_total: 4,
|
|
feed_use_today: 4,
|
|
feed_use_total: 4,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
{
|
|
id: 2,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-08-11',
|
|
in_today: 12,
|
|
out_today: 6,
|
|
in_total: 22,
|
|
out_total: 10,
|
|
feed_use_today: 6,
|
|
feed_use_total: 10,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
];
|
|
const manualRows = [
|
|
{
|
|
id: 1,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-08-10',
|
|
age_manual: 0,
|
|
mortality_manual: 0,
|
|
mortality_manual_total: 0,
|
|
feed_in_manual: 10,
|
|
feed_use_manual: 4,
|
|
feed_in_manual_total: 10,
|
|
feed_use_manual_total: 4,
|
|
feed_out_manual: 0,
|
|
feed_out_manual_total: 0,
|
|
harvest_manual: 0,
|
|
harvest_manual_total: 0,
|
|
harvest_weight_manual: 0,
|
|
harvest_weight_manual_total: 0,
|
|
manual_weight: 0,
|
|
average_harvest_day_manual: 0,
|
|
average_harvest_weight_manual: 0,
|
|
fcr_manual: 0,
|
|
eef_manual: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
{
|
|
id: 2,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-08-11',
|
|
age_manual: 1,
|
|
mortality_manual: 0,
|
|
mortality_manual_total: 0,
|
|
feed_in_manual: 10,
|
|
feed_use_manual: 7,
|
|
feed_in_manual_total: 20,
|
|
feed_use_manual_total: 11,
|
|
feed_out_manual: 0,
|
|
feed_out_manual_total: 0,
|
|
harvest_manual: 0,
|
|
harvest_manual_total: 0,
|
|
harvest_weight_manual: 0,
|
|
harvest_weight_manual_total: 0,
|
|
manual_weight: 0,
|
|
average_harvest_day_manual: 0,
|
|
average_harvest_weight_manual: 0,
|
|
fcr_manual: 0,
|
|
eef_manual: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
];
|
|
// IoT already on the newer day; manual only exists for the previous day.
|
|
const mismatchedKarungRows = [
|
|
{
|
|
id: 10,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-09-02',
|
|
in_today: 192,
|
|
out_today: 1,
|
|
in_total: 192,
|
|
out_total: 1,
|
|
feed_use_today: 7,
|
|
feed_use_total: 7,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
{
|
|
id: 11,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-09-03',
|
|
in_today: 50,
|
|
out_today: 0,
|
|
in_total: 242,
|
|
out_total: 1,
|
|
feed_use_today: 4,
|
|
feed_use_total: 11,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
];
|
|
const mismatchedManualRows = [
|
|
{
|
|
id: 10,
|
|
cycle: 1,
|
|
kandang: 1,
|
|
flock_id: 1,
|
|
date: '2026-09-02',
|
|
age_manual: 0,
|
|
mortality_manual: 0,
|
|
mortality_manual_total: 0,
|
|
feed_in_manual: 192,
|
|
feed_use_manual: 7,
|
|
feed_in_manual_total: 192,
|
|
feed_use_manual_total: 7,
|
|
feed_out_manual: 1,
|
|
feed_out_manual_total: 1,
|
|
harvest_manual: 0,
|
|
harvest_manual_total: 0,
|
|
harvest_weight_manual: 0,
|
|
harvest_weight_manual_total: 0,
|
|
manual_weight: 0,
|
|
average_harvest_day_manual: 0,
|
|
average_harvest_weight_manual: 0,
|
|
fcr_manual: 0,
|
|
eef_manual: 0,
|
|
created_at: '',
|
|
updated_at: '',
|
|
},
|
|
];
|
|
return { karungRows, manualRows, cycle, mismatchedKarungRows, mismatchedManualRows };
|
|
});
|
|
|
|
vi.mock('../../services/apiClient.ts', () => ({
|
|
api: {
|
|
karungs: {
|
|
list: vi.fn().mockResolvedValue(karungRows),
|
|
request: vi.fn().mockResolvedValue({ karung: {}, upstream: {} }),
|
|
},
|
|
manualInputs: {
|
|
list: vi.fn().mockResolvedValue(manualRows),
|
|
},
|
|
},
|
|
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: cycle,
|
|
cycles: [cycle],
|
|
setSelectedCycleId: vi.fn(),
|
|
updateCycle: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
class ResizeObserverStub {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
}
|
|
|
|
describe('FeedSackCountingPage', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(api.karungs.list).mockResolvedValue(karungRows);
|
|
vi.mocked(api.manualInputs.list).mockResolvedValue(manualRows);
|
|
});
|
|
|
|
it('renders the recreated hitung karung view with stats, saldo, and daily table', async () => {
|
|
vi.stubGlobal('ResizeObserver', ResizeObserverStub);
|
|
try {
|
|
render(<FeedSackCountingPage onSettings={() => undefined} />);
|
|
|
|
expect(await screen.findByRole('heading', { name: 'Hitung Karung' })).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText('Monitoring karung masuk dan karung dituang per hari')
|
|
).toBeInTheDocument();
|
|
expect(screen.getByText(/10 Agu 2026 - 13 Sep 2026/i)).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: /Sync/i })).toBeInTheDocument();
|
|
|
|
// Ringkasan stats
|
|
expect(screen.getAllByText('Karung Masuk').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('Karung Dituang').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('Karung Keluar').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('12').length).toBeGreaterThan(0); // in_today dari snapshot terbaru
|
|
|
|
// Saldo card
|
|
expect(screen.getByRole('heading', { name: 'Saldo Karung IOT' })).toBeInTheDocument();
|
|
|
|
// Trend chart
|
|
expect(screen.getByRole('heading', { name: 'Tren Harian Karung IOT' })).toBeInTheDocument();
|
|
|
|
// Daily table (separate masuk & tuang boxes, each with Manual/IOT/Akurasi)
|
|
expect(
|
|
screen.getByRole('heading', { name: 'Rincian Harian Karung IOT' })
|
|
).toBeInTheDocument();
|
|
expect(screen.getAllByText('KARUNG MASUK').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('KARUNG TUANG').length).toBeGreaterThan(0);
|
|
// Headers appear in both boxes
|
|
expect(screen.getAllByText('Manual').length).toBeGreaterThanOrEqual(2);
|
|
expect(screen.getAllByText('IOT').length).toBeGreaterThanOrEqual(2);
|
|
expect(screen.getAllByText('Akurasi').length).toBeGreaterThanOrEqual(2);
|
|
// Total columns still show snapshot totals
|
|
expect(screen.getAllByText('22').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('10').length).toBeGreaterThan(0);
|
|
// Accuracy: hari ke-2 masuk manual 10 vs IOT 12 -> 83.3%
|
|
expect(screen.getAllByText('83.3%').length).toBeGreaterThan(0);
|
|
// Hari ke-1 matching values -> 100.0%
|
|
expect(screen.getAllByText('100.0%').length).toBeGreaterThan(0);
|
|
} finally {
|
|
vi.unstubAllGlobals();
|
|
}
|
|
});
|
|
|
|
it('does not reuse yesterday manual values on the latest IoT day stats cards', async () => {
|
|
vi.stubGlobal('ResizeObserver', ResizeObserverStub);
|
|
vi.mocked(api.karungs.list).mockResolvedValue(mismatchedKarungRows);
|
|
vi.mocked(api.manualInputs.list).mockResolvedValue(mismatchedManualRows);
|
|
try {
|
|
render(<FeedSackCountingPage onSettings={() => undefined} />);
|
|
|
|
expect(await screen.findByRole('heading', { name: 'Hitung Karung' })).toBeInTheDocument();
|
|
// Latest IoT day still shows.
|
|
expect(screen.getAllByText('50').length).toBeGreaterThan(0);
|
|
// Summary Manual cards show 0 when the latest IoT day has no matching manual row.
|
|
expect(screen.getAllByText('0').length).toBeGreaterThanOrEqual(6);
|
|
// Daily table still keeps the real 2 Sep manual row across masuk/tuang/keluar boxes.
|
|
expect(screen.getAllByText('192').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('3 Sep 2026').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('2 Sep 2026').length).toBeGreaterThan(0);
|
|
} finally {
|
|
vi.unstubAllGlobals();
|
|
}
|
|
});
|
|
});
|