164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { render, screen, waitFor, fireEvent, cleanup, within } from '@testing-library/react';
|
|
import ViewIngredientModal from '@/app/dashboard/ingredients/ViewIngredientModal';
|
|
|
|
const baseIngredient = {
|
|
_id: 'ing1',
|
|
code: 'ING-001',
|
|
name: 'Organic Butter',
|
|
category: 'Dairy',
|
|
subcategory: 'Fresh',
|
|
quantity: 10,
|
|
unit: 'kg',
|
|
unitPrice: 5.00,
|
|
vat: 9,
|
|
supplier: 'Acme Foods',
|
|
createdAt: '2024-01-01T00:00:00Z',
|
|
updatedAt: '2024-01-02T00:00:00Z',
|
|
};
|
|
|
|
function mockFetch(data: unknown) {
|
|
return vi.spyOn(global, 'fetch').mockResolvedValue({
|
|
json: () => Promise.resolve(data),
|
|
} as Response);
|
|
}
|
|
|
|
describe('ViewIngredientModal', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
it('renders nothing when open=false', () => {
|
|
const { container } = render(
|
|
<ViewIngredientModal open={false} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
expect(container.innerHTML).toBe('');
|
|
});
|
|
|
|
it('shows loading spinner then ingredient data', async () => {
|
|
mockFetch(baseIngredient);
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
|
|
expect(within(container).getByText('Loading...')).toBeInTheDocument();
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Organic Butter')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('displays pricing section (gross, VAT, net price)', async () => {
|
|
mockFetch(baseIngredient);
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Pricing')).toBeInTheDocument();
|
|
});
|
|
|
|
// Gross: €5.00
|
|
expect(within(container).getByText(/€5\.00/)).toBeInTheDocument();
|
|
// VAT: rendered as {vat}% — separate text nodes so use regex
|
|
expect(within(container).getByText(/9%/)).toBeInTheDocument();
|
|
// Net: 5.00 * 1.09 = 5.45
|
|
expect(within(container).getByText(/€5\.45/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows discount section only when discountValue > 0', async () => {
|
|
const withDiscount = {
|
|
...baseIngredient,
|
|
discountType: 'percent' as const,
|
|
discountValue: 10,
|
|
applyDiscountToNet: false,
|
|
};
|
|
mockFetch(withDiscount);
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Discount')).toBeInTheDocument();
|
|
});
|
|
|
|
expect(within(container).getByText('Percentage')).toBeInTheDocument();
|
|
expect(within(container).getByText('10%')).toBeInTheDocument();
|
|
});
|
|
|
|
it('hides discount section when not present', async () => {
|
|
mockFetch(baseIngredient);
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Organic Butter')).toBeInTheDocument();
|
|
});
|
|
|
|
expect(within(container).queryByText('Discount')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('shows advanced section only when advanced fields present', async () => {
|
|
const withAdvanced = {
|
|
...baseIngredient,
|
|
minStockLevel: 5,
|
|
storageInstructions: 'Keep refrigerated',
|
|
shelfLifeDays: 30,
|
|
notes: 'Premium quality',
|
|
};
|
|
mockFetch(withAdvanced);
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Additional Details')).toBeInTheDocument();
|
|
});
|
|
|
|
expect(within(container).getByText('Keep refrigerated')).toBeInTheDocument();
|
|
expect(within(container).getByText('30 days')).toBeInTheDocument();
|
|
expect(within(container).getByText('Premium quality')).toBeInTheDocument();
|
|
});
|
|
|
|
it('hides advanced section when not present', async () => {
|
|
mockFetch(baseIngredient);
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={vi.fn()} />
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Organic Butter')).toBeInTheDocument();
|
|
});
|
|
|
|
expect(within(container).queryByText('Additional Details')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('calls onClose on close button', async () => {
|
|
mockFetch(baseIngredient);
|
|
const onClose = vi.fn();
|
|
|
|
const { container } = render(
|
|
<ViewIngredientModal open={true} ingredientId="ing1" onClose={onClose} />
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(within(container).getByText('Organic Butter')).toBeInTheDocument();
|
|
});
|
|
|
|
fireEvent.click(within(container).getByRole('button', { name: 'Close' }));
|
|
expect(onClose).toHaveBeenCalledOnce();
|
|
});
|
|
});
|