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: 'gm', 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: {} }), }, insights: { list: vi.fn().mockResolvedValue([]), }, manualInputs: { list: vi.fn().mockResolvedValue(manualRows), }, }, errorMessage: (err: unknown) => (err instanceof Error ? err.message : 'Terjadi kesalahan'), })); 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: '', }, 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); vi.mocked(api.insights.list).mockResolvedValue([]); }); it('renders the recreated hitung karung view with stats, saldo, and daily table', async () => { vi.stubGlobal('ResizeObserver', ResizeObserverStub); try { render( 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 hari ini/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( 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(); } }); });