From 2638f9711e1e189b814c3472f8b488edf7f6da04 Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Mon, 24 Nov 2025 09:37:30 +1000 Subject: [PATCH 1/4] Add responsive pick item controls and tests --- e2e/picklist.spec.ts | 75 ++++++++++-- src/components/PickItemRow.tsx | 215 +++++++++++++++++---------------- 2 files changed, 176 insertions(+), 114 deletions(-) diff --git a/e2e/picklist.spec.ts b/e2e/picklist.spec.ts index 046f7fd..75a4748 100644 --- a/e2e/picklist.spec.ts +++ b/e2e/picklist.spec.ts @@ -34,6 +34,7 @@ const addProductToPickList = async (page: Page, productName: string) => { }; test.describe('Active pick list', () => { + test.use({ viewport: { width: 1280, height: 900 } }); test('adds a product via search and renders it with quantity and packaging', async ({ page }) => { await navigateToNewPickList(page); @@ -45,7 +46,7 @@ test.describe('Active pick list', () => { .click(); await expect(page.getByText(secondaryProduct, { exact: true })).toBeVisible(); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); }); test('creates a pick list and adds products from the search bar', async ({ page }) => { @@ -67,7 +68,7 @@ test.describe('Active pick list', () => { await addProductToPickList(page, additionalProduct); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); }); test('allows adding and editing products from the manage products screen', async ({ page }) => { @@ -190,7 +191,7 @@ test.describe('Active pick list', () => { await expect(quantityLabel).toBeVisible(); await page.getByRole('button', { name: 'Increase quantity' }).click(); - await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 }); await page.reload(); await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible(); @@ -200,7 +201,7 @@ test.describe('Active pick list', () => { await decrementButton.click(); await decrementButton.click(); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); await page.reload(); await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); @@ -251,21 +252,21 @@ test.describe('Active pick list', () => { const increaseButton = page.getByLabel('Increase quantity'); await increaseButton.click(); - await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 }); await page.getByLabel('Switch to carton packaging').click(); - await expect(page.getByText(/Qty:\s*2\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*2\s+carton/i)).toBeVisible({ timeout: 10000 }); await page.getByLabel('Decrease quantity').click(); - await expect(page.getByText(/Qty:\s*1\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+carton/i)).toBeVisible({ timeout: 10000 }); await page.getByLabel('Switch to unit packaging').click(); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); await increaseButton.click(); await increaseButton.click(); await page.getByLabel('Switch to carton packaging').click(); - await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible({ timeout: 10000 }); await page.getByRole('button', { name: 'Save and Return' }).click(); await expect(page.getByRole('heading', { name: 'Pick Lists' })).toBeVisible(); @@ -273,10 +274,26 @@ test.describe('Active pick list', () => { await page.reload(); await page.getByRole('link', { name: areaName }).first().click(); await expect(page.getByRole('heading', { name: `${areaName} List` })).toBeVisible(); - await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible({ timeout: 10000 }); await expect(page.getByLabel('Switch to unit packaging')).toBeVisible(); }); + test('shows inline controls on wide view without the popup trigger', async ({ page }) => { + await navigateToNewPickList(page); + await addProductToPickList(page, additionalProduct); + + const productRow = page + .getByText(additionalProduct) + .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]') + .first(); + + await expect(productRow.getByLabel('Switch to carton packaging')).toBeVisible(); + await expect(productRow.getByLabel('Decrease quantity')).toBeVisible(); + await expect(productRow.getByLabel('Increase quantity')).toBeVisible(); + await expect(productRow.getByLabel('Delete item')).toBeVisible(); + await expect(productRow.getByLabel('Open item controls')).toHaveCount(0); + }); + test('lets a user mark an item as picked then revert it back to pending', async ({ page }) => { await navigateToNewPickList(page); await addProductToPickList(page, additionalProduct); @@ -316,3 +333,41 @@ test.describe('Active pick list', () => { await expect(page.getByText(additionalProduct).first()).not.toBeVisible(); }); }); + +test.describe('Active pick list responsive controls on mobile', () => { + test.use({ viewport: { width: 430, height: 900 } }); + + test('shows popup controls on narrow screens and hides inline buttons', async ({ page }) => { + await navigateToNewPickList(page); + await addProductToPickList(page, additionalProduct); + + const productRow = page + .getByText(additionalProduct) + .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]') + .first(); + + await expect(productRow.getByLabel('Switch to carton packaging')).toHaveCount(0); + await expect(productRow.getByLabel('Decrease quantity')).toHaveCount(0); + await expect(productRow.getByLabel('Increase quantity')).toHaveCount(0); + await expect(productRow.getByLabel('Delete item')).toHaveCount(0); + + const controlsButton = productRow.getByLabel('Open item controls'); + await expect(controlsButton).toBeVisible(); + await controlsButton.click(); + + const itemDialog = page.getByRole('dialog', { name: additionalProduct }); + await expect(itemDialog).toBeVisible(); + await expect(itemDialog.getByText(/Quantity:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); + await expect(itemDialog.getByLabel('Increase quantity')).toBeVisible(); + + await itemDialog.getByLabel('Increase quantity').click(); + await expect(itemDialog.getByText(/Quantity:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 }); + await itemDialog.getByLabel('Switch to carton packaging').click(); + await expect(itemDialog.getByText(/Quantity:\s*2\s+carton/i)).toBeVisible({ timeout: 10000 }); + + await itemDialog.getByLabel('Delete item').click(); + await expect(page.getByRole('dialog', { name: 'Delete item' })).toBeVisible(); + await page.getByRole('button', { name: 'Cancel', exact: true }).click(); + await expect(page.getByRole('dialog', { name: additionalProduct })).toBeVisible(); + }); +}); diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx index 1eaf3b4..2080f1e 100644 --- a/src/components/PickItemRow.tsx +++ b/src/components/PickItemRow.tsx @@ -1,4 +1,4 @@ -import { Add, Close, Delete, Inventory2, Remove } from '@mui/icons-material'; +import { Add, Close, Delete, Inventory2, MoreHoriz, Remove } from '@mui/icons-material'; import { Button, Checkbox, @@ -11,10 +11,11 @@ import { Stack, Typography, } from '@mui/material'; -import { alpha } from '@mui/material/styles'; +import { alpha, useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; import { useState } from 'react'; import { PickItem } from '../models/PickItem'; -import { Product } from '../models/Product'; +import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE, Product } from '../models/Product'; interface PickItemRowProps { item: PickItem; @@ -37,9 +38,11 @@ export const PickItemRow = ({ }: PickItemRowProps) => { const [isConfirmOpen, setIsConfirmOpen] = useState(false); const [isControlsOpen, setIsControlsOpen] = useState(false); + const theme = useTheme(); + const isDesktop = useMediaQuery(theme.breakpoints.up('sm')); const packagingLabel = item.is_carton - ? product?.bulk_name ?? 'Carton' - : product?.unit_type ?? 'Unit'; + ? product?.bulk_name ?? DEFAULT_BULK_NAME + : product?.unit_type ?? DEFAULT_UNIT_TYPE; const toggleStatus = (checked: boolean) => { onStatusChange(checked ? 'picked' : 'pending'); @@ -50,6 +53,8 @@ export const PickItemRow = ({ setIsConfirmOpen(false); }; + const openControls = () => setIsControlsOpen(true); + return ( setIsControlsOpen(true)} - role="button" - tabIndex={0} - onKeyDown={(event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - setIsControlsOpen(true); - } + cursor: isDesktop ? 'default' : 'pointer', }} + {...(!isDesktop + ? { + role: 'button', + tabIndex: 0, + onClick: openControls, + onKeyDown: (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + openControls(); + } + }, + } + : undefined)} > toggleStatus(event.target.checked)} inputProps={{ 'aria-label': 'Toggle picked status' }} - onClick={(event) => event.stopPropagation()} /> - - - - {product?.name ?? 'Unknown product'} - - - {item.quantity} {packagingLabel} - + + + {product?.name ?? 'Unknown product'} - - Tap to adjust quantity and packaging + + {`Qty: ${item.quantity} ${packagingLabel}`} - - setIsControlsOpen(false)} - fullWidth - maxWidth="xs" - aria-labelledby="item-controls-title" - > - - - {product?.name ?? 'Unknown product'} - - setIsControlsOpen(false)}> - + {isDesktop ? ( + + `0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}` + : 'none', + }} + > + - - - - - Quantity: {item.quantity} {packagingLabel} + + + + + + + setIsConfirmOpen(true)} + aria-label="Delete item" + sx={{ ml: { xs: 0, sm: 1 } }} + > + + + + ) : ( + + + + )} + + {!isDesktop && ( + setIsControlsOpen(false)} + fullWidth + maxWidth="xs" + aria-labelledby="item-controls-title" + > + + + {product?.name ?? 'Unknown product'} - - `0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}` - : 'none', - }} - > - - - - - - - - - setIsConfirmOpen(true)} - aria-label="Delete item" - > - - + setIsControlsOpen(false)}> + + + + + + + {`Quantity: ${item.quantity} ${packagingLabel}`} + + + `0 0 0 8px ${alpha(dialogTheme.palette.primary.main, 0.15)}` + : 'none', + }} + > + + + + + + + + + setIsConfirmOpen(true)} aria-label="Delete item"> + + + - - - + + + )} setIsConfirmOpen(false)} aria-label="Cancel delete" autoFocus> Cancel - From cf994b876a99e99e0a5150ec7663f92a3e4a1952 Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Mon, 24 Nov 2025 09:45:58 +1000 Subject: [PATCH 2/4] Harden pick list e2e product selection waits --- e2e/picklist.spec.ts | 85 ++++++++++--- src/components/PickItemRow.tsx | 215 +++++++++++++++++---------------- 2 files changed, 182 insertions(+), 118 deletions(-) diff --git a/e2e/picklist.spec.ts b/e2e/picklist.spec.ts index 046f7fd..1388b64 100644 --- a/e2e/picklist.spec.ts +++ b/e2e/picklist.spec.ts @@ -25,15 +25,18 @@ const addProductToPickList = async (page: Page, productName: string) => { const searchInput = page.getByPlaceholder('Search products'); await searchInput.click(); await searchInput.fill(productName); - await page + const productOption = page .getByRole('option', { name: new RegExp(`${productName} \\(${areaName}\\)`, 'i') }) - .first() - .click(); + .first(); - await expect(page.getByText(productName).first()).toBeVisible(); + await expect(productOption).toBeVisible({ timeout: 15000 }); + await productOption.click(); + + await expect(page.getByText(productName).first()).toBeVisible({ timeout: 15000 }); }; test.describe('Active pick list', () => { + test.use({ viewport: { width: 1280, height: 900 } }); test('adds a product via search and renders it with quantity and packaging', async ({ page }) => { await navigateToNewPickList(page); @@ -45,7 +48,7 @@ test.describe('Active pick list', () => { .click(); await expect(page.getByText(secondaryProduct, { exact: true })).toBeVisible(); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); }); test('creates a pick list and adds products from the search bar', async ({ page }) => { @@ -67,7 +70,7 @@ test.describe('Active pick list', () => { await addProductToPickList(page, additionalProduct); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); }); test('allows adding and editing products from the manage products screen', async ({ page }) => { @@ -190,7 +193,7 @@ test.describe('Active pick list', () => { await expect(quantityLabel).toBeVisible(); await page.getByRole('button', { name: 'Increase quantity' }).click(); - await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 }); await page.reload(); await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible(); @@ -200,7 +203,7 @@ test.describe('Active pick list', () => { await decrementButton.click(); await decrementButton.click(); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); await page.reload(); await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); @@ -251,21 +254,21 @@ test.describe('Active pick list', () => { const increaseButton = page.getByLabel('Increase quantity'); await increaseButton.click(); - await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 }); await page.getByLabel('Switch to carton packaging').click(); - await expect(page.getByText(/Qty:\s*2\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*2\s+carton/i)).toBeVisible({ timeout: 10000 }); await page.getByLabel('Decrease quantity').click(); - await expect(page.getByText(/Qty:\s*1\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+carton/i)).toBeVisible({ timeout: 10000 }); await page.getByLabel('Switch to unit packaging').click(); - await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); await increaseButton.click(); await increaseButton.click(); await page.getByLabel('Switch to carton packaging').click(); - await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible({ timeout: 10000 }); await page.getByRole('button', { name: 'Save and Return' }).click(); await expect(page.getByRole('heading', { name: 'Pick Lists' })).toBeVisible(); @@ -273,10 +276,26 @@ test.describe('Active pick list', () => { await page.reload(); await page.getByRole('link', { name: areaName }).first().click(); await expect(page.getByRole('heading', { name: `${areaName} List` })).toBeVisible(); - await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible(); + await expect(page.getByText(/Qty:\s*3\s+carton/i)).toBeVisible({ timeout: 10000 }); await expect(page.getByLabel('Switch to unit packaging')).toBeVisible(); }); + test('shows inline controls on wide view without the popup trigger', async ({ page }) => { + await navigateToNewPickList(page); + await addProductToPickList(page, additionalProduct); + + const productRow = page + .getByText(additionalProduct) + .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]') + .first(); + + await expect(productRow.getByLabel('Switch to carton packaging')).toBeVisible(); + await expect(productRow.getByLabel('Decrease quantity')).toBeVisible(); + await expect(productRow.getByLabel('Increase quantity')).toBeVisible(); + await expect(productRow.getByLabel('Delete item')).toBeVisible(); + await expect(productRow.getByLabel('Open item controls')).toHaveCount(0); + }); + test('lets a user mark an item as picked then revert it back to pending', async ({ page }) => { await navigateToNewPickList(page); await addProductToPickList(page, additionalProduct); @@ -316,3 +335,41 @@ test.describe('Active pick list', () => { await expect(page.getByText(additionalProduct).first()).not.toBeVisible(); }); }); + +test.describe('Active pick list responsive controls on mobile', () => { + test.use({ viewport: { width: 430, height: 900 } }); + + test('shows popup controls on narrow screens and hides inline buttons', async ({ page }) => { + await navigateToNewPickList(page); + await addProductToPickList(page, additionalProduct); + + const productRow = page + .getByText(additionalProduct) + .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]') + .first(); + + await expect(productRow.getByLabel('Switch to carton packaging')).toHaveCount(0); + await expect(productRow.getByLabel('Decrease quantity')).toHaveCount(0); + await expect(productRow.getByLabel('Increase quantity')).toHaveCount(0); + await expect(productRow.getByLabel('Delete item')).toHaveCount(0); + + const controlsButton = productRow.getByLabel('Open item controls'); + await expect(controlsButton).toBeVisible(); + await controlsButton.click(); + + const itemDialog = page.getByRole('dialog', { name: additionalProduct }); + await expect(itemDialog).toBeVisible(); + await expect(itemDialog.getByText(/Quantity:\s*1\s+unit/i)).toBeVisible({ timeout: 10000 }); + await expect(itemDialog.getByLabel('Increase quantity')).toBeVisible(); + + await itemDialog.getByLabel('Increase quantity').click(); + await expect(itemDialog.getByText(/Quantity:\s*2\s+unit/i)).toBeVisible({ timeout: 10000 }); + await itemDialog.getByLabel('Switch to carton packaging').click(); + await expect(itemDialog.getByText(/Quantity:\s*2\s+carton/i)).toBeVisible({ timeout: 10000 }); + + await itemDialog.getByLabel('Delete item').click(); + await expect(page.getByRole('dialog', { name: 'Delete item' })).toBeVisible(); + await page.getByRole('button', { name: 'Cancel', exact: true }).click(); + await expect(page.getByRole('dialog', { name: additionalProduct })).toBeVisible(); + }); +}); diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx index 1eaf3b4..2080f1e 100644 --- a/src/components/PickItemRow.tsx +++ b/src/components/PickItemRow.tsx @@ -1,4 +1,4 @@ -import { Add, Close, Delete, Inventory2, Remove } from '@mui/icons-material'; +import { Add, Close, Delete, Inventory2, MoreHoriz, Remove } from '@mui/icons-material'; import { Button, Checkbox, @@ -11,10 +11,11 @@ import { Stack, Typography, } from '@mui/material'; -import { alpha } from '@mui/material/styles'; +import { alpha, useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; import { useState } from 'react'; import { PickItem } from '../models/PickItem'; -import { Product } from '../models/Product'; +import { DEFAULT_BULK_NAME, DEFAULT_UNIT_TYPE, Product } from '../models/Product'; interface PickItemRowProps { item: PickItem; @@ -37,9 +38,11 @@ export const PickItemRow = ({ }: PickItemRowProps) => { const [isConfirmOpen, setIsConfirmOpen] = useState(false); const [isControlsOpen, setIsControlsOpen] = useState(false); + const theme = useTheme(); + const isDesktop = useMediaQuery(theme.breakpoints.up('sm')); const packagingLabel = item.is_carton - ? product?.bulk_name ?? 'Carton' - : product?.unit_type ?? 'Unit'; + ? product?.bulk_name ?? DEFAULT_BULK_NAME + : product?.unit_type ?? DEFAULT_UNIT_TYPE; const toggleStatus = (checked: boolean) => { onStatusChange(checked ? 'picked' : 'pending'); @@ -50,6 +53,8 @@ export const PickItemRow = ({ setIsConfirmOpen(false); }; + const openControls = () => setIsControlsOpen(true); + return ( setIsControlsOpen(true)} - role="button" - tabIndex={0} - onKeyDown={(event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - setIsControlsOpen(true); - } + cursor: isDesktop ? 'default' : 'pointer', }} + {...(!isDesktop + ? { + role: 'button', + tabIndex: 0, + onClick: openControls, + onKeyDown: (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + openControls(); + } + }, + } + : undefined)} > toggleStatus(event.target.checked)} inputProps={{ 'aria-label': 'Toggle picked status' }} - onClick={(event) => event.stopPropagation()} /> - - - - {product?.name ?? 'Unknown product'} - - - {item.quantity} {packagingLabel} - + + + {product?.name ?? 'Unknown product'} - - Tap to adjust quantity and packaging + + {`Qty: ${item.quantity} ${packagingLabel}`} - - setIsControlsOpen(false)} - fullWidth - maxWidth="xs" - aria-labelledby="item-controls-title" - > - - - {product?.name ?? 'Unknown product'} - - setIsControlsOpen(false)}> - + {isDesktop ? ( + + `0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}` + : 'none', + }} + > + - - - - - Quantity: {item.quantity} {packagingLabel} + + + + + + + setIsConfirmOpen(true)} + aria-label="Delete item" + sx={{ ml: { xs: 0, sm: 1 } }} + > + + + + ) : ( + + + + )} + + {!isDesktop && ( + setIsControlsOpen(false)} + fullWidth + maxWidth="xs" + aria-labelledby="item-controls-title" + > + + + {product?.name ?? 'Unknown product'} - - `0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}` - : 'none', - }} - > - - - - - - - - - setIsConfirmOpen(true)} - aria-label="Delete item" - > - - + setIsControlsOpen(false)}> + + + + + + + {`Quantity: ${item.quantity} ${packagingLabel}`} + + + `0 0 0 8px ${alpha(dialogTheme.palette.primary.main, 0.15)}` + : 'none', + }} + > + + + + + + + + + setIsConfirmOpen(true)} aria-label="Delete item"> + + + - - - + + + )} setIsConfirmOpen(false)} aria-label="Cancel delete" autoFocus> Cancel - From d36ef675d3a2be16847354884f1abd91da81d95c Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Mon, 24 Nov 2025 10:00:13 +1000 Subject: [PATCH 3/4] Align pick item header typography --- src/components/PickItemRow.test.tsx | 28 ++++++++++++++++++++++++++++ src/components/PickItemRow.tsx | 17 ++++++++++------- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/components/PickItemRow.test.tsx b/src/components/PickItemRow.test.tsx index 59e2020..3b72490 100644 --- a/src/components/PickItemRow.test.tsx +++ b/src/components/PickItemRow.test.tsx @@ -29,6 +29,34 @@ const baseProduct: Product = { }; describe('PickItemRow', () => { + it('keeps quantity inline with matching typography to the product name', () => { + render( + , + ); + + const productName = screen.getByText(baseProduct.name); + const quantityLabel = screen.getByText('1 unit'); + const titleRow = screen.getByTestId('pick-item-title-row'); + + const rowStyle = getComputedStyle(titleRow); + expect(rowStyle.display).toBe('flex'); + expect(rowStyle.flexDirection).toBe('row'); + + const productStyle = getComputedStyle(productName); + const quantityStyle = getComputedStyle(quantityLabel); + + expect(productStyle.fontSize).toBe(quantityStyle.fontSize); + expect(productStyle.fontWeight).toBe(quantityStyle.fontWeight); + }); + it('asks for confirmation before deleting a product from the pick list', async () => { const onDelete = vi.fn(); const user = userEvent.setup(); diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx index cf409ff..ff22dd7 100644 --- a/src/components/PickItemRow.tsx +++ b/src/components/PickItemRow.tsx @@ -100,27 +100,30 @@ export const PickItemRow = ({ onClick={(event) => event.stopPropagation()} /> - {product?.name ?? 'Unknown product'} {item.quantity} {packagingLabel} - + {isNarrowScreen && ( Tap to adjust quantity and packaging From 073e3131b23d4e5bc565754bea054e9720f27503 Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Mon, 24 Nov 2025 10:01:35 +1000 Subject: [PATCH 4/4] Ensure mobile controls dialog closes reliably --- e2e/picklist.spec.ts | 25 +++++++++++++++++++++++++ src/components/PickItemRow.tsx | 11 ++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/e2e/picklist.spec.ts b/e2e/picklist.spec.ts index 046f7fd..3d4c485 100644 --- a/e2e/picklist.spec.ts +++ b/e2e/picklist.spec.ts @@ -206,6 +206,31 @@ test.describe('Active pick list', () => { await expect(page.getByText(/Qty:\s*1\s+unit/i)).toBeVisible(); }); + test('closes mobile controls with the close button and backdrop', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + + await navigateToNewPickList(page); + await addProductToPickList(page, additionalProduct); + + const productRow = page + .getByText(additionalProduct, { exact: true }) + .locator('xpath=ancestor::div[contains(@class, "MuiStack-root")]') + .first(); + const controlsDialog = page.getByRole('dialog', { name: additionalProduct }); + + await productRow.click(); + await expect(controlsDialog).toBeVisible(); + + await page.getByRole('button', { name: 'Close controls' }).click(); + await expect(controlsDialog).toHaveCount(0); + + await productRow.click(); + await expect(controlsDialog).toBeVisible(); + + await page.locator('.MuiBackdrop-root').click({ position: { x: 10, y: 10 } }); + await expect(controlsDialog).toHaveCount(0); + }); + test('toggles picked visibility and disables the filter when all items are picked', async ({ page }) => { await navigateToNewPickList(page); await addProductToPickList(page, secondaryProduct); diff --git a/src/components/PickItemRow.tsx b/src/components/PickItemRow.tsx index cf409ff..3944147 100644 --- a/src/components/PickItemRow.tsx +++ b/src/components/PickItemRow.tsx @@ -60,6 +60,10 @@ export const PickItemRow = ({ } }; + const handleCloseControls = () => { + setIsControlsOpen(false); + }; + const handleRowKeyDown = (event: React.KeyboardEvent) => { if (!isNarrowScreen) return; @@ -182,7 +186,7 @@ export const PickItemRow = ({ {isNarrowScreen && ( setIsControlsOpen(false)} + onClose={handleCloseControls} fullWidth maxWidth="xs" aria-labelledby="item-controls-title" @@ -196,10 +200,7 @@ export const PickItemRow = ({ { - stopPropagation(event); - setIsControlsOpen(false); - }} + onClick={handleCloseControls} >