feat: move store item management into modal

This commit is contained in:
Nico 2026-03-29 01:01:38 -07:00
parent 7c8c655cba
commit f6a66a37ea
6 changed files with 326 additions and 181 deletions

View File

@ -155,6 +155,7 @@ For `app/api/**/[param]/route.ts`:
- Tap targets remain >= 40px on mobile. - Tap targets remain >= 40px on mobile.
- Modal overlays must close on outside click/tap. - Modal overlays must close on outside click/tap.
- For every frontend action that manipulates database state, show a toast/bubble notification with basic outcome details (action + target + success/failure). - For every frontend action that manipulates database state, show a toast/bubble notification with basic outcome details (action + target + success/failure).
- Frontend destructive actions should use the shared `ConfirmSlideModal` pattern instead of browser `confirm()` unless there is a documented exception.
- Progress-type notifications must reuse the existing upload toaster pattern (`UploadQueueContext` + `UploadToaster`) for consistency. - Progress-type notifications must reuse the existing upload toaster pattern (`UploadQueueContext` + `UploadToaster`) for consistency.
- Add Playwright UI tests for new UI features and critical flows. - Add Playwright UI tests for new UI features and critical flows.

View File

@ -92,7 +92,7 @@ export default function ManageStores() {
<section className="manage-section"> <section className="manage-section">
<h2>Your Stores ({householdStores.length})</h2> <h2>Your Stores ({householdStores.length})</h2>
<p className="manage-stores-help"> <p className="manage-stores-help">
Item management lives inside each store card below for items already used in that household/store. Use each store card's Manage Items button to edit or delete the household/store item list.
</p> </p>
{!isAdmin && ( {!isAdmin && (
<p className="manage-stores-note"> <p className="manage-stores-note">

View File

@ -7,6 +7,7 @@ import {
import useActionToast from "../../hooks/useActionToast"; import useActionToast from "../../hooks/useActionToast";
import getApiErrorMessage from "../../lib/getApiErrorMessage"; import getApiErrorMessage from "../../lib/getApiErrorMessage";
import AvailableItemEditorModal from "../modals/AvailableItemEditorModal"; import AvailableItemEditorModal from "../modals/AvailableItemEditorModal";
import ConfirmSlideModal from "../modals/ConfirmSlideModal";
function itemImageSource(item) { function itemImageSource(item) {
if (!item?.item_image) { if (!item?.item_image) {
@ -19,7 +20,7 @@ function itemImageSource(item) {
export default function StoreAvailableItemsManager({ householdId, store, isAdmin }) { export default function StoreAvailableItemsManager({ householdId, store, isAdmin }) {
const toast = useActionToast(); const toast = useActionToast();
const [expanded, setExpanded] = useState(true); const [isOpen, setIsOpen] = useState(false);
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
const [catalogReady, setCatalogReady] = useState(true); const [catalogReady, setCatalogReady] = useState(true);
const [catalogMessage, setCatalogMessage] = useState(""); const [catalogMessage, setCatalogMessage] = useState("");
@ -27,6 +28,7 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [editorItem, setEditorItem] = useState(null); const [editorItem, setEditorItem] = useState(null);
const [showEditor, setShowEditor] = useState(false); const [showEditor, setShowEditor] = useState(false);
const [pendingDeleteItem, setPendingDeleteItem] = useState(null);
const loadItems = useCallback(async (search = query) => { const loadItems = useCallback(async (search = query) => {
if (!householdId || !store?.id) { if (!householdId || !store?.id) {
@ -41,10 +43,10 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
setCatalogReady(response.data.catalog_ready !== false); setCatalogReady(response.data.catalog_ready !== false);
setCatalogMessage(response.data.message || ""); setCatalogMessage(response.data.message || "");
} catch (error) { } catch (error) {
console.error("Failed to load available items:", error); console.error("Failed to load store items:", error);
setCatalogReady(false); setCatalogReady(false);
setCatalogMessage("Store item catalog is unavailable right now."); setCatalogMessage("Store item management is unavailable right now.");
const message = getApiErrorMessage(error, "Failed to load available items"); const message = getApiErrorMessage(error, "Failed to load store items");
toast.error("Load store items failed", `Load store items failed: ${message}`); toast.error("Load store items failed", `Load store items failed: ${message}`);
} finally { } finally {
setLoading(false); setLoading(false);
@ -52,18 +54,23 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
}, [householdId, query, store?.id, toast]); }, [householdId, query, store?.id, toast]);
useEffect(() => { useEffect(() => {
if (!expanded) { if (!isOpen) {
return; return;
} }
loadItems(query); loadItems(query);
}, [expanded, query, loadItems]); }, [isOpen, query, loadItems]);
const closeManager = () => {
setIsOpen(false);
setPendingDeleteItem(null);
};
const handleUpdate = async (payload) => { const handleUpdate = async (payload) => {
if (!catalogReady) { if (!catalogReady) {
toast.info( toast.info(
"Store item catalog unavailable", "Store item management unavailable",
catalogMessage || "Store item catalog is unavailable until the latest database migration is applied." catalogMessage || "Store item management is unavailable until the latest database migration is applied."
); );
return; return;
} }
@ -75,32 +82,25 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
setEditorItem(null); setEditorItem(null);
await loadItems(query); await loadItems(query);
} catch (error) { } catch (error) {
const message = getApiErrorMessage(error, "Failed to update available item"); const message = getApiErrorMessage(error, "Failed to update store item");
toast.error("Update store item failed", `Update store item failed: ${message}`); toast.error("Update store item failed", `Update store item failed: ${message}`);
throw error; throw error;
} }
}; };
const handleDelete = async (item) => { const handleDeleteConfirm = async () => {
if (!catalogReady) { if (!pendingDeleteItem) {
toast.info(
"Store item catalog unavailable",
catalogMessage || "Store item catalog is unavailable until the latest database migration is applied."
);
return;
}
if (!confirm(`Remove ${item.item_name} from ${store.name}'s available items?`)) {
return; return;
} }
try { try {
await deleteAvailableItem(householdId, store.id, item.item_id); await deleteAvailableItem(householdId, store.id, pendingDeleteItem.item_id);
toast.success("Cleared store item settings", `Cleared settings for ${item.item_name} in ${store.name}`); toast.success("Deleted store item", `Deleted ${pendingDeleteItem.item_name} from ${store.name}`);
setPendingDeleteItem(null);
await loadItems(query); await loadItems(query);
} catch (error) { } catch (error) {
const message = getApiErrorMessage(error, "Failed to clear store item settings"); const message = getApiErrorMessage(error, "Failed to delete store item");
toast.error("Clear store item settings failed", `Clear store item settings failed: ${message}`); toast.error("Delete store item failed", `Delete store item failed: ${message}`);
} }
}; };
@ -109,29 +109,40 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
} }
return ( return (
<div className="store-available-items"> <>
<div className="store-available-items-header"> <button
type="button"
className="btn-secondary btn-small store-available-items-trigger"
onClick={() => setIsOpen(true)}
>
Manage Items
</button>
{isOpen ? (
<div className="store-items-modal-overlay" onClick={closeManager}>
<div className="store-items-modal" onClick={(event) => event.stopPropagation()}>
<div className="store-items-modal-header">
<div> <div>
<h4>Store Item Catalog</h4> <h3>{store.name} Items</h3>
<p>Manage settings for items already used in {store.name} for this household.</p> <p>Manage the household/store items used for suggestions and store defaults.</p>
</div> </div>
<button <button
type="button" type="button"
className="btn-secondary btn-small" className="store-items-modal-close"
onClick={() => setExpanded((value) => !value)} onClick={closeManager}
aria-label="Close manage items modal"
> >
{expanded ? "Hide Items" : "Manage Items"} x
</button> </button>
</div> </div>
{expanded ? (
<div className="store-available-items-panel">
{!catalogReady ? ( {!catalogReady ? (
<p className="store-available-items-notice"> <p className="store-available-items-notice">
{catalogMessage || "Store item catalog is unavailable until the latest database migration is applied."} {catalogMessage || "Store item management is unavailable until the latest database migration is applied."}
</p> </p>
) : null} ) : null}
<div className="store-available-items-toolbar">
<div className="store-items-modal-toolbar">
<input <input
className="store-available-items-search" className="store-available-items-search"
value={query} value={query}
@ -141,19 +152,29 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
/> />
</div> </div>
<div className="store-items-modal-body">
{!catalogReady ? ( {!catalogReady ? (
<p className="empty-message">Run the latest database migrations to enable this catalog.</p> <p className="empty-message">Run the latest database migrations to enable store item management.</p>
) : loading ? ( ) : loading ? (
<p className="empty-message">Loading store items...</p> <p className="empty-message">Loading store items...</p>
) : items.length === 0 ? ( ) : items.length === 0 ? (
<p className="empty-message">No household items found for this store yet.</p> <p className="empty-message">No household items found for this store yet.</p>
) : ( ) : (
<div className="store-available-items-list"> <div className="store-items-table">
<div className="store-items-table-head" aria-hidden="true">
<span>Item</span>
<span>Store Defaults</span>
<span>Actions</span>
</div>
<div className="store-items-table-body">
{items.map((item) => { {items.map((item) => {
const imageSrc = itemImageSource(item); const imageSrc = itemImageSource(item);
const details = [item.item_type, item.item_group, item.zone].filter(Boolean); const details = [item.item_type, item.item_group, item.zone].filter(Boolean);
return ( return (
<div key={item.item_id} className="store-available-items-card"> <div key={item.item_id} className="store-items-table-row">
<div className="store-items-table-cell store-items-table-item">
<span className="store-items-mobile-label">Item</span>
<div className="store-available-items-summary"> <div className="store-available-items-summary">
{imageSrc ? ( {imageSrc ? (
<img src={imageSrc} alt="" className="store-available-items-thumb" /> <img src={imageSrc} alt="" className="store-available-items-thumb" />
@ -164,9 +185,19 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
)} )}
<div className="store-available-items-copy"> <div className="store-available-items-copy">
<strong>{item.item_name}</strong> <strong>{item.item_name}</strong>
<span>{details.join(" | ") || "No store defaults set"}</span>
</div> </div>
</div> </div>
</div>
<div className="store-items-table-cell">
<span className="store-items-mobile-label">Store Defaults</span>
<span className="store-items-defaults-text">
{details.join(" | ") || "No store defaults set"}
</span>
</div>
<div className="store-items-table-cell store-items-table-actions">
<span className="store-items-mobile-label">Actions</span>
<div className="store-available-items-actions"> <div className="store-available-items-actions">
<button <button
type="button" type="button"
@ -176,24 +207,26 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
setShowEditor(true); setShowEditor(true);
}} }}
> >
Edit Edit Settings
</button> </button>
{item.has_managed_settings ? (
<button <button
type="button" type="button"
className="btn-danger btn-small" className="btn-danger btn-small"
onClick={() => handleDelete(item)} onClick={() => setPendingDeleteItem(item)}
> >
Clear Settings Delete Item
</button> </button>
) : null} </div>
</div> </div>
</div> </div>
); );
})} })}
</div> </div>
</div>
)} )}
</div> </div>
</div>
</div>
) : null} ) : null}
<AvailableItemEditorModal <AvailableItemEditorModal
@ -205,6 +238,19 @@ export default function StoreAvailableItemsManager({ householdId, store, isAdmin
}} }}
onSave={handleUpdate} onSave={handleUpdate}
/> />
</div>
<ConfirmSlideModal
isOpen={Boolean(pendingDeleteItem)}
title={pendingDeleteItem ? `Delete ${pendingDeleteItem.item_name}?` : "Delete item?"}
description={
pendingDeleteItem
? `Slide to confirm. This permanently deletes ${pendingDeleteItem.item_name} from ${store.name} for this household, including current list entries and history.`
: ""
}
confirmLabel="Delete Item"
onClose={() => setPendingDeleteItem(null)}
onConfirm={handleDeleteConfirm}
/>
</>
); );
} }

View File

@ -101,10 +101,10 @@ export default function AvailableItemEditorModal({ isOpen, item = null, onCancel
<div className="available-item-editor-overlay" onClick={onCancel}> <div className="available-item-editor-overlay" onClick={onCancel}>
<div className="available-item-editor-modal" onClick={(event) => event.stopPropagation()}> <div className="available-item-editor-modal" onClick={(event) => event.stopPropagation()}>
<h2 className="available-item-editor-title"> <h2 className="available-item-editor-title">
{item ? `Edit ${item.item_name}` : "Add Available Item"} {item ? `Edit ${item.item_name}` : "Edit Store Item"}
</h2> </h2>
<p className="available-item-editor-subtitle"> <p className="available-item-editor-subtitle">
Save store-specific item defaults for this household. Save store-specific defaults for this household/store item.
</p> </p>
<div className="available-item-editor-field"> <div className="available-item-editor-field">

View File

@ -1,32 +1,75 @@
.store-available-items { .store-available-items-trigger {
border-top: var(--border-width-thin) solid var(--color-border-light); width: 100%;
padding-top: var(--spacing-md);
} }
.store-available-items-header { .store-items-modal-overlay {
position: fixed;
inset: 0;
z-index: var(--z-modal);
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-md);
background: var(--modal-backdrop-bg);
}
.store-items-modal {
width: min(960px, 100%);
max-height: min(80vh, 760px);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
padding: var(--spacing-lg);
border: var(--border-width-thin) solid var(--color-border-light);
border-radius: var(--border-radius-xl);
background: var(--modal-bg);
box-shadow: var(--shadow-xl);
}
.store-items-modal-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: var(--spacing-md); gap: var(--spacing-md);
align-items: flex-start; align-items: flex-start;
} }
.store-available-items-header h4 { .store-items-modal-header h3 {
margin: 0; margin: 0;
color: var(--color-text-primary); color: var(--color-text-primary);
font-size: var(--font-size-base); font-size: var(--font-size-xl);
} }
.store-available-items-header p { .store-items-modal-header p {
margin: var(--spacing-xs) 0 0; margin: var(--spacing-xs) 0 0;
color: var(--color-text-secondary); color: var(--color-text-secondary);
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
} }
.store-available-items-panel { .store-items-modal-close {
margin-top: var(--spacing-md); width: 40px;
display: flex; height: 40px;
flex-direction: column; border: var(--border-width-thin) solid var(--color-border-light);
gap: var(--spacing-md); border-radius: 50%;
background: var(--color-bg-surface);
color: var(--color-text-primary);
font-size: var(--font-size-lg);
line-height: 1;
}
.store-items-modal-toolbar {
position: sticky;
top: 0;
z-index: 1;
background: var(--modal-bg);
}
.store-available-items-search {
width: 100%;
padding: var(--input-padding-y) var(--input-padding-x);
border: var(--border-width-thin) solid var(--input-border-color);
border-radius: var(--input-border-radius);
background: var(--color-bg-surface);
color: var(--color-text-primary);
} }
.store-available-items-notice { .store-available-items-notice {
@ -38,45 +81,58 @@
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
.store-available-items-toolbar { .store-items-modal-body {
display: flex; min-height: 0;
gap: var(--spacing-sm); overflow-y: auto;
align-items: center;
flex-wrap: wrap;
} }
.store-available-items-search { .store-items-table {
flex: 1 1 240px;
padding: var(--input-padding-y) var(--input-padding-x);
border: var(--border-width-thin) solid var(--input-border-color);
border-radius: var(--input-border-radius);
background: var(--color-bg-surface);
color: var(--color-text-primary);
}
.store-available-items-toolbar-actions {
display: flex;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
.store-available-items-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-sm); gap: var(--spacing-sm);
} }
.store-available-items-card { .store-items-table-head,
display: flex; .store-items-table-row {
justify-content: space-between; display: grid;
gap: var(--spacing-sm); grid-template-columns: minmax(220px, 2fr) minmax(180px, 2fr) minmax(170px, 1fr);
gap: var(--spacing-md);
align-items: center; align-items: center;
}
.store-items-table-head {
position: sticky;
top: 0;
padding: 0 var(--spacing-sm) var(--spacing-xs);
background: var(--modal-bg);
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.store-items-table-body {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.store-items-table-row {
padding: var(--spacing-sm); padding: var(--spacing-sm);
border: var(--border-width-thin) solid var(--color-border-light); border: var(--border-width-thin) solid var(--color-border-light);
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
background: var(--color-bg-surface); background: var(--color-bg-surface);
} }
.store-items-table-cell {
min-width: 0;
}
.store-items-table-item {
min-width: 0;
}
.store-available-items-summary { .store-available-items-summary {
display: flex; display: flex;
align-items: center; align-items: center;
@ -110,34 +166,68 @@
.store-available-items-copy strong { .store-available-items-copy strong {
color: var(--color-text-primary); color: var(--color-text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.store-available-items-copy span { .store-items-defaults-text {
color: var(--color-text-secondary); color: var(--color-text-secondary);
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
} }
.store-items-table-actions {
justify-self: end;
}
.store-available-items-actions { .store-available-items-actions {
display: flex; display: flex;
gap: var(--spacing-xs); gap: var(--spacing-xs);
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end;
} }
@media (max-width: 640px) { .store-items-mobile-label {
.store-available-items-header, display: none;
.store-available-items-card, }
.store-available-items-toolbar {
@media (max-width: 720px) {
.store-items-modal {
max-height: min(88vh, 900px);
padding: var(--spacing-md);
}
.store-items-table-head {
display: none;
}
.store-items-table-row {
display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
gap: var(--spacing-sm);
} }
.store-available-items-actions, .store-items-mobile-label {
.store-available-items-toolbar-actions { display: block;
margin-bottom: 4px;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.store-items-table-actions {
justify-self: stretch;
}
.store-available-items-actions {
width: 100%; width: 100%;
justify-content: stretch;
} }
.store-available-items-actions button, .store-available-items-actions button {
.store-available-items-toolbar-actions button { flex: 1 1 0;
flex: 1;
} }
} }

View File

@ -45,7 +45,7 @@ async function mockHouseholdAndStoreShell(page: import("@playwright/test").Page)
}); });
} }
test("manage stores lets admins edit settings for existing household/store items", async ({ page }) => { test("manage stores opens a modal to edit and delete household store items", async ({ page }) => {
await seedAuthStorage(page); await seedAuthStorage(page);
await mockConfig(page); await mockConfig(page);
await mockHouseholdAndStoreShell(page); await mockHouseholdAndStoreShell(page);
@ -128,21 +128,11 @@ test("manage stores lets admins edit settings for existing household/store items
await page.route("**/households/1/stores/10/available-items/501", async (route) => { await page.route("**/households/1/stores/10/available-items/501", async (route) => {
if (route.request().method() === "DELETE") { if (route.request().method() === "DELETE") {
availableItems = availableItems.map((item) => availableItems = availableItems.filter((item) => item.item_id !== 501);
item.item_id === 501
? {
...item,
item_type: null,
item_group: null,
zone: null,
has_managed_settings: false,
}
: item
);
await route.fulfill({ await route.fulfill({
status: 200, status: 200,
contentType: "application/json", contentType: "application/json",
body: JSON.stringify({ message: "Store item settings cleared" }), body: JSON.stringify({ message: "Store item deleted" }),
}); });
return; return;
} }
@ -154,14 +144,16 @@ test("manage stores lets admins edit settings for existing household/store items
const storeCard = page.locator(".store-card").filter({ hasText: "Costco" }); const storeCard = page.locator(".store-card").filter({ hasText: "Costco" });
await expect(storeCard).toBeVisible(); await expect(storeCard).toBeVisible();
await expect(storeCard.getByText("Store Item Catalog")).toBeVisible(); await expect(storeCard.getByRole("button", { name: "Manage Items" })).toBeVisible();
await expect(storeCard.getByText("milk")).toBeVisible(); await storeCard.getByRole("button", { name: "Manage Items" }).click();
await expect(storeCard.getByText("apples")).toBeVisible();
await expect(storeCard.getByRole("button", { name: "Add Item" })).toHaveCount(0);
await expect(storeCard.getByRole("button", { name: "Import Current List" })).toHaveCount(0);
await storeCard.locator(".store-available-items-card").filter({ hasText: "apples" }).getByRole("button", { name: "Edit" }).click(); const managerModal = page.locator(".store-items-modal");
await expect(managerModal).toBeVisible();
await expect(managerModal.getByText("milk")).toBeVisible();
await expect(managerModal.getByText("apples")).toBeVisible();
await managerModal.locator(".store-items-table-row").filter({ hasText: "apples" }).getByRole("button", { name: "Edit Settings" }).click();
const editorModal = page.locator(".available-item-editor-modal"); const editorModal = page.locator(".available-item-editor-modal");
await expect(editorModal).toBeVisible(); await expect(editorModal).toBeVisible();
await expect(editorModal.getByLabel("Item Name")).toBeDisabled(); await expect(editorModal.getByLabel("Item Name")).toBeDisabled();
@ -171,13 +163,29 @@ test("manage stores lets admins edit settings for existing household/store items
await editorModal.getByRole("button", { name: "Save Changes" }).click(); await editorModal.getByRole("button", { name: "Save Changes" }).click();
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Updated store item"); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Updated store item");
await expect(storeCard.getByText("produce | Fruits | Produce & Fresh Vegetables")).toBeVisible(); await expect(managerModal.getByText("produce | Fruits | Produce & Fresh Vegetables")).toBeVisible();
page.once("dialog", (dialog) => dialog.accept()); await managerModal.locator(".store-items-table-row").filter({ hasText: "milk" }).getByRole("button", { name: "Delete Item" }).click();
await storeCard.locator(".store-available-items-card").filter({ hasText: "milk" }).getByRole("button", { name: "Clear Settings" }).click(); const confirmModal = page.locator(".confirm-slide-modal");
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Cleared store item settings"); await expect(confirmModal).toBeVisible();
await expect(storeCard.locator(".store-available-items-card").filter({ hasText: "milk" }).getByText("No store defaults set")).toBeVisible(); await expect(confirmModal.getByText("Delete milk?")).toBeVisible();
await expect(storeCard.locator(".store-available-items-card").filter({ hasText: "milk" }).getByRole("button", { name: "Clear Settings" })).toHaveCount(0);
const slider = confirmModal.locator(".confirm-slide-handle");
const track = confirmModal.locator(".confirm-slide-track");
const sliderBox = await slider.boundingBox();
const trackBox = await track.boundingBox();
if (!sliderBox || !trackBox) {
throw new Error("Confirm slide control was not measurable");
}
await page.mouse.move(sliderBox.x + sliderBox.width / 2, sliderBox.y + sliderBox.height / 2);
await page.mouse.down();
await page.mouse.move(trackBox.x + trackBox.width - 4, sliderBox.y + sliderBox.height / 2, { steps: 8 });
await page.mouse.up();
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Deleted store item");
await expect(managerModal.getByText("milk")).toHaveCount(0);
}); });
test("grocery page remains unchanged and does not show a store items picker", async ({ page }) => { test("grocery page remains unchanged and does not show a store items picker", async ({ page }) => {