first commit
This commit is contained in:
commit
b54624be96
226 files changed
+108840
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import CountingAccuracyWarningBanner from '../CountingAccuracyWarningBanner.tsx';
|
||||
import type { CountingAccuracyWarning } from '../../../utils/countingAccuracyWarning.ts';
|
||||
|
||||
const base: CountingAccuracyWarning = {
|
||||
day: 2,
|
||||
date: '2026-08-12',
|
||||
iotCount: 900,
|
||||
stockAkhir: 1000,
|
||||
accuracy: 90,
|
||||
ratioPercent: 90,
|
||||
direction: 'under',
|
||||
};
|
||||
|
||||
describe('CountingAccuracyWarningBanner', () => {
|
||||
it('renders under-count manager checklist', () => {
|
||||
render(<CountingAccuracyWarningBanner warning={base} />);
|
||||
expect(screen.getByText(/IoT lebih rendah dari stock/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/video kamera lengkap/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Hari 2/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/90%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders over-count fraud / AI checklist', () => {
|
||||
render(
|
||||
<CountingAccuracyWarningBanner
|
||||
warning={{ ...base, iotCount: 1060, stockAkhir: 1000, accuracy: 94.3, ratioPercent: 106, direction: 'over' }}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/lebih tinggi dari stock manual/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/fraud/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/akurasi 94,3%/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/106%/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
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(),
|
||||
},
|
||||
insights: {
|
||||
list: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
},
|
||||
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: {
|
||||
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: 'gm',
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user