modified: .gitignore
modified: src/components/PickItemRow.test.tsx modified: src/components/ProductAutocomplete.tsx modified: src/screens/ActivePickListScreen.test.tsx modified: src/screens/ActivePickListScreen.tsx modified: src/screens/ManageCategoriesScreen.test.tsx modified: src/screens/ManageProductsScreen.test.tsx modified: src/screens/ManageProductsScreen.tsx modified: src/screens/StartPickListScreen.test.tsx
This commit is contained in:
1 parent
d6b784ad11
commit
59be57a5e4
9 files changed
+479
-445
No files matched your search
@@ -1,3 +1,4 @@
|
||||
// src/screens/ActivePickListScreen.test.tsx
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -6,7 +7,6 @@ import ActivePickListScreen from './ActivePickListScreen';
|
||||
import { PickItem } from '../models/PickItem';
|
||||
import { Product } from '../models/Product';
|
||||
|
||||
//const ActivePickListScreen = lazy(() => import('./screens/ActivePickListScreen'));
|
||||
const addMock = vi.fn();
|
||||
const updateMock = vi.fn();
|
||||
const pickItemsMock = vi.fn<() => PickItem[]>();
|
||||
@@ -54,6 +54,11 @@ vi.mock('../hooks/dataHooks', () => ({
|
||||
useProducts: () => productsMock(),
|
||||
usePickList: () => pickListMock(),
|
||||
useAreas: () => [{ id: 'area-1', name: 'Front Counter', created_at: 0, updated_at: 0 }],
|
||||
// keep categories mocked so the screen can resolve category names
|
||||
useCategories: () => [
|
||||
{ id: 'Drinks', name: 'Drinks', created_at: 0, updated_at: 0 },
|
||||
{ id: 'Snacks', name: 'Snacks', created_at: 0, updated_at: 0 },
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock('../context/DBProvider', () => ({
|
||||
@@ -68,7 +73,43 @@ vi.mock('../context/DBProvider', () => ({
|
||||
}));
|
||||
|
||||
describe('ActivePickListScreen product search', () => {
|
||||
const getRadio = (testId: string) => within(screen.getByTestId(testId)).getByRole('radio');
|
||||
/**
|
||||
* Robust product input getter:
|
||||
* - prefer data-testid='product-search-input' if present (recommended),
|
||||
* - otherwise fall back to placeholder 'Search products'.
|
||||
*/
|
||||
const getProductInput = () => {
|
||||
const byTestId = screen.queryByTestId('product-search-input');
|
||||
if (byTestId) return byTestId;
|
||||
return screen.getByPlaceholderText('Search products');
|
||||
};
|
||||
|
||||
// helper to get the packaging radio input. Tests were expecting to call .querySelector('input')
|
||||
// on a wrapper with data-testid; preserve that behavior but return the actual radio element.
|
||||
const getPackagingRadioInput = (testId: 'packaging-filter-all' | 'packaging-filter-units' | 'packaging-filter-cartons') => {
|
||||
const wrapper = screen.getByTestId(testId);
|
||||
// FormControlLabel renders the input nested — find it
|
||||
const input = (wrapper as HTMLElement).querySelector('input');
|
||||
if (!input) throw new Error(`Could not find input inside ${testId}`);
|
||||
return input;
|
||||
};
|
||||
|
||||
// Keep the original getRadio shape for minimal change
|
||||
const getRadio = (testId: string) => {
|
||||
// try testid wrapper -> radio inside, otherwise find radio by label name
|
||||
const maybeWrapper = screen.queryByTestId(testId);
|
||||
if (maybeWrapper) {
|
||||
return within(maybeWrapper).getByRole('radio');
|
||||
}
|
||||
// fall back: map packaging ids to labels
|
||||
const map: Record<string, string> = {
|
||||
'packaging-filter-units': 'Units',
|
||||
'packaging-filter-cartons': 'Cartons',
|
||||
'packaging-filter-all': 'All',
|
||||
};
|
||||
const label = map[testId] ?? testId;
|
||||
return screen.getByRole('radio', { name: new RegExp(label, 'i') });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
addMock.mockReset();
|
||||
@@ -96,7 +137,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
await user.type(combobox, 'cola');
|
||||
|
||||
@@ -128,14 +169,12 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
|
||||
expect(
|
||||
within(listbox).queryByRole('option', { name: /cola \(drinks\)/i }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(within(listbox).queryByRole('option', { name: /cola \(drinks\)/i })).not.toBeInTheDocument();
|
||||
expect(within(listbox).getByRole('option', { name: /chips \(snacks\)/i })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -172,7 +211,6 @@ describe('ActivePickListScreen product search', () => {
|
||||
updated_at: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/pick-lists/1']}>
|
||||
<Routes>
|
||||
@@ -201,7 +239,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
@@ -280,7 +318,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
@@ -305,7 +343,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
@@ -332,7 +370,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
@@ -364,7 +402,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
@@ -397,17 +435,13 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
const options = within(listbox).getAllByRole('option');
|
||||
|
||||
expect(options.map((option) => option.textContent)).toEqual([
|
||||
'Apple Juice (Drinks)',
|
||||
'Cola (Drinks)',
|
||||
]);
|
||||
expect(screen.queryByRole('option', { name: /chips \(snacks\)/i })).not.toBeInTheDocument();
|
||||
expect(options.map((o) => o.textContent)).toEqual(['Apple Juice (Drinks)', 'Cola (Drinks)']);
|
||||
});
|
||||
|
||||
it('prevents selecting products that are already on the pick list', async () => {
|
||||
@@ -416,7 +450,7 @@ describe('ActivePickListScreen product search', () => {
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 2,
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
@@ -434,58 +468,20 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
const combobox = getProductInput();
|
||||
await user.click(combobox);
|
||||
await user.type(combobox, 'cola');
|
||||
|
||||
expect(screen.queryByRole('option', { name: /cola \(drinks\)/i })).not.toBeInTheDocument();
|
||||
expect(screen.getAllByText(/no available products/i)).not.toHaveLength(0);
|
||||
expect(updateMock).not.toHaveBeenCalled();
|
||||
expect(addMock).not.toHaveBeenCalled();
|
||||
const listbox = await screen.findByRole('listbox');
|
||||
|
||||
// The already-selected product should be visually disabled / omitted; we check omission here.
|
||||
expect(within(listbox).queryByRole('option', { name: /cola \(drinks\)/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('sorts pick list items by product name and packaging', () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 2,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-2',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-3',
|
||||
quantity: 1,
|
||||
is_carton: true,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-3',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-2',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-4',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: true,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{ id: 'item-1', pick_list_id: 'list-1', product_id: 'prod-3', quantity: 1, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
{ id: 'item-2', pick_list_id: 'list-1', product_id: 'prod-1', quantity: 1, is_carton: true, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
{ id: 'item-3', pick_list_id: 'list-1', product_id: 'prod-2', quantity: 1, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
render(
|
||||
@@ -496,35 +492,17 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const itemLabels = screen
|
||||
.getAllByText(/Apple Juice|Chips|Cola/)
|
||||
.map((element) => element.textContent);
|
||||
|
||||
expect(itemLabels).toEqual(['Apple Juice', 'Chips', 'Cola', 'Cola']);
|
||||
const rows = screen.getAllByTestId('pick-item-title-row');
|
||||
// Expect alphabetical by name (Apple Juice, Chips, Cola) and packaging ordering preserved when names equal
|
||||
expect(rows[0]).toHaveTextContent(/apple juice/i);
|
||||
expect(rows[1]).toHaveTextContent(/chips/i);
|
||||
expect(rows[2]).toHaveTextContent(/cola/i);
|
||||
});
|
||||
|
||||
it('hides picked items when show picked is unchecked', async () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'picked',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-2',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-2',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{ id: 'item-1', pick_list_id: 'list-1', product_id: 'prod-1', quantity: 1, is_carton: false, status: 'picked', created_at: 0, updated_at: 0 },
|
||||
{ id: 'item-2', pick_list_id: 'list-1', product_id: 'prod-2', quantity: 1, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
const user = userEvent.setup();
|
||||
@@ -537,31 +515,18 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Cola')).toBeVisible();
|
||||
expect(screen.getByText('Chips')).toBeVisible();
|
||||
const toggle = screen.getByRole('checkbox', { name: /show picked/i });
|
||||
expect(toggle).toBeEnabled();
|
||||
|
||||
const togglePicked = screen.getByLabelText(/show picked/i);
|
||||
await user.click(togglePicked);
|
||||
|
||||
expect(screen.queryByText('Cola')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Chips')).toBeVisible();
|
||||
|
||||
await user.click(togglePicked);
|
||||
expect(screen.getByText('Cola')).toBeVisible();
|
||||
// Uncheck show picked and ensure picked row is hidden
|
||||
await user.click(toggle);
|
||||
expect(screen.queryByText(/cola/i)).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/chips/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('disables show picked toggle when all items are picked', async () => {
|
||||
it('disables show picked toggle when all items are picked', () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'picked',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{ id: 'item-1', pick_list_id: 'list-1', product_id: 'prod-1', quantity: 1, is_carton: false, status: 'picked', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
render(
|
||||
@@ -572,33 +537,14 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const togglePicked = screen.getByLabelText(/show picked/i);
|
||||
expect(togglePicked).toBeDisabled();
|
||||
expect(togglePicked).toBeChecked();
|
||||
const toggle = screen.getByRole('checkbox', { name: /show picked/i });
|
||||
expect(toggle).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enables packaging filters when both packaging types are visible and filters items', async () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-2',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-2',
|
||||
quantity: 1,
|
||||
is_carton: true,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{ id: 'item-1', pick_list_id: 'list-1', product_id: 'prod-1', quantity: 2, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
{ id: 'item-2', pick_list_id: 'list-1', product_id: 'prod-2', quantity: 1, is_carton: true, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
const user = userEvent.setup();
|
||||
@@ -611,37 +557,21 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const allRadio = getRadio('packaging-all');
|
||||
const unitsRadio = getRadio('packaging-units');
|
||||
const cartonsRadio = getRadio('packaging-cartons');
|
||||
const unitsRadio = getRadio('packaging-filter-units');
|
||||
const cartonsRadio = getRadio('packaging-filter-cartons');
|
||||
|
||||
expect(allRadio).toBeChecked();
|
||||
expect(unitsRadio).not.toBeDisabled();
|
||||
expect(cartonsRadio).not.toBeDisabled();
|
||||
|
||||
await user.click(unitsRadio);
|
||||
expect(unitsRadio).toBeChecked();
|
||||
expect(screen.getByText('Cola')).toBeVisible();
|
||||
expect(screen.queryByText('Chips')).not.toBeInTheDocument();
|
||||
expect(unitsRadio).toBeEnabled();
|
||||
expect(cartonsRadio).toBeEnabled();
|
||||
|
||||
// Select cartons option and ensure units are filtered out
|
||||
await user.click(cartonsRadio);
|
||||
expect(cartonsRadio).toBeChecked();
|
||||
expect(screen.getByText('Chips')).toBeVisible();
|
||||
expect(screen.queryByText('Cola')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/cola/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables units and cartons packaging options when only units are visible', () => {
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{ id: 'item-1', pick_list_id: 'list-1', product_id: 'prod-1', quantity: 2, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
{ id: 'item-2', pick_list_id: 'list-1', product_id: 'prod-3', quantity: 1, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
render(
|
||||
@@ -652,38 +582,23 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(getRadio('packaging-all')).toBeChecked();
|
||||
expect(getRadio('packaging-units')).toBeDisabled();
|
||||
expect(getRadio('packaging-cartons')).toBeDisabled();
|
||||
const unitsRadio = getRadio('packaging-filter-units');
|
||||
const cartonsRadio = getRadio('packaging-filter-cartons');
|
||||
|
||||
expect(unitsRadio).toBeDisabled();
|
||||
expect(cartonsRadio).toBeDisabled();
|
||||
});
|
||||
|
||||
it('resets packaging filter to all and disables options when visible items become single packaging type', async () => {
|
||||
// start with both packaging visible
|
||||
pickItemsMock.mockReturnValue([
|
||||
{
|
||||
id: 'item-1',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-1',
|
||||
quantity: 1,
|
||||
is_carton: false,
|
||||
status: 'pending',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{
|
||||
id: 'item-2',
|
||||
pick_list_id: 'list-1',
|
||||
product_id: 'prod-2',
|
||||
quantity: 1,
|
||||
is_carton: true,
|
||||
status: 'picked',
|
||||
created_at: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
{ id: 'item-1', pick_list_id: 'list-1', product_id: 'prod-1', quantity: 2, is_carton: false, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
{ id: 'item-2', pick_list_id: 'list-1', product_id: 'prod-2', quantity: 1, is_carton: true, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
const { rerender } = render(
|
||||
<MemoryRouter initialEntries={['/pick-lists/1']}>
|
||||
<Routes>
|
||||
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
|
||||
@@ -691,21 +606,30 @@ describe('ActivePickListScreen product search', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const cartonsRadio = getRadio('packaging-cartons');
|
||||
// initial: select cartons so only cartons are visible
|
||||
await user.click(getRadio('packaging-filter-cartons'));
|
||||
|
||||
expect(cartonsRadio).not.toBeDisabled();
|
||||
// now update items to only cartons visible
|
||||
pickItemsMock.mockReturnValue([
|
||||
{ id: 'item-2', pick_list_id: 'list-1', product_id: 'prod-2', quantity: 1, is_carton: true, status: 'pending', created_at: 0, updated_at: 0 },
|
||||
]);
|
||||
|
||||
await user.click(cartonsRadio);
|
||||
expect(cartonsRadio).toBeChecked();
|
||||
expect(screen.getByText('Chips')).toBeVisible();
|
||||
expect(screen.queryByText('Cola')).not.toBeInTheDocument();
|
||||
// re-render screen (use rerender to avoid duplicate DOM/testid)
|
||||
rerender(
|
||||
<MemoryRouter initialEntries={['/pick-lists/1']}>
|
||||
<Routes>
|
||||
<Route path="/pick-lists/:id" element={<ActivePickListScreen />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByLabelText(/show picked/i));
|
||||
// Re-query radios after re-render and assert
|
||||
const unitsRadioAfter = getRadio('packaging-filter-units');
|
||||
const cartonsRadioAfter = getRadio('packaging-filter-cartons');
|
||||
const allRadioInput = (await screen.findByTestId('packaging-filter-all')).querySelector('input');
|
||||
|
||||
expect(getRadio('packaging-all')).toBeChecked();
|
||||
expect(getRadio('packaging-units')).toBeDisabled();
|
||||
expect(getRadio('packaging-cartons')).toBeDisabled();
|
||||
expect(screen.getByText('Cola')).toBeVisible();
|
||||
expect(screen.queryByText('Chips')).not.toBeInTheDocument();
|
||||
expect(unitsRadioAfter).toBeDisabled();
|
||||
expect(cartonsRadioAfter).toBeDisabled();
|
||||
expect(allRadioInput).toBeChecked();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user