IcostPro/__tests__/components/IngredientTable.test.tsx
2026-02-12 21:40:57 +01:00

121 lines
4.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import IngredientTable, { type Ingredient } from '@/app/dashboard/ingredients/IngredientTable';
const makeIngredient = (overrides: Partial<Ingredient> = {}): Ingredient => ({
_id: 'ing1',
code: 'ING-001',
name: 'Butter',
category: 'Dairy',
subcategory: 'Fresh',
quantity: 10,
unit: 'kg',
unitPrice: 5.00,
vat: 9,
supplier: 'Acme',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
...overrides,
});
describe('IngredientTable', () => {
const defaultProps = {
ingredients: [makeIngredient()],
onView: vi.fn(),
onEdit: vi.fn(),
onDelete: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
cleanup();
});
it('renders ingredient rows with correct data', () => {
render(<IngredientTable {...defaultProps} />);
expect(screen.getAllByText('Butter').length).toBeGreaterThan(0);
expect(screen.getAllByText(/ING-001/).length).toBeGreaterThan(0);
});
it('calculates net price correctly (unitPrice * (1 + vat/100))', () => {
// unitPrice=5.00, vat=9 → net = 5.00 * 1.09 = 5.45
render(<IngredientTable {...defaultProps} />);
const netPriceElements = screen.getAllByText(/5\.45/);
expect(netPriceElements.length).toBeGreaterThan(0);
});
it('toggles individual ingredient selection', () => {
render(<IngredientTable {...defaultProps} />);
const checkboxes = screen.getAllByRole('checkbox') as HTMLInputElement[];
// Find an unchecked ingredient checkbox (not the select-all header)
const ingredientCheckbox = checkboxes.find(cb => !cb.checked)!;
expect(ingredientCheckbox.checked).toBe(false);
fireEvent.click(ingredientCheckbox);
expect(ingredientCheckbox.checked).toBe(true);
fireEvent.click(ingredientCheckbox);
expect(ingredientCheckbox.checked).toBe(false);
});
it('toggles all ingredients', () => {
const ingredients = [
makeIngredient({ _id: 'ing1', name: 'Butter' }),
makeIngredient({ _id: 'ing2', name: 'Milk' }),
];
const { container } = render(
<IngredientTable {...defaultProps} ingredients={ingredients} />
);
// Get the select-all checkbox from the desktop table header (thead)
const thead = container.querySelector('thead');
const selectAllCheckbox = thead!.querySelector('input[type="checkbox"]') as HTMLInputElement;
// Select all
fireEvent.click(selectAllCheckbox);
expect(selectAllCheckbox.checked).toBe(true);
// Verify ingredient checkboxes in tbody are also checked
const tbody = container.querySelector('tbody');
const tbodyCheckboxes = tbody!.querySelectorAll('input[type="checkbox"]') as NodeListOf<HTMLInputElement>;
tbodyCheckboxes.forEach(cb => expect(cb.checked).toBe(true));
// Deselect all
fireEvent.click(selectAllCheckbox);
expect(selectAllCheckbox.checked).toBe(false);
tbodyCheckboxes.forEach(cb => expect(cb.checked).toBe(false));
});
it('renders empty when ingredients list is empty', () => {
const { container } = render(
<IngredientTable {...defaultProps} ingredients={[]} />
);
const tbody = container.querySelector('tbody');
expect(tbody?.children.length ?? 0).toBe(0);
});
it('calculates net price with 0% VAT', () => {
const ingredients = [makeIngredient({ unitPrice: 10.00, vat: 0 })];
render(<IngredientTable {...defaultProps} ingredients={ingredients} />);
// net = 10.00 * 1.0 = 10.00
const netPriceElements = screen.getAllByText(/10\.00/);
expect(netPriceElements.length).toBeGreaterThan(0);
});
it('calculates net price with high VAT', () => {
const ingredients = [makeIngredient({ unitPrice: 10.00, vat: 25 })];
render(<IngredientTable {...defaultProps} ingredients={ingredients} />);
// net = 10.00 * 1.25 = 12.50
const netPriceElements = screen.getAllByText(/12\.50/);
expect(netPriceElements.length).toBeGreaterThan(0);
});
});