diff --git a/e2e/tests/websocket.spec.ts b/e2e/tests/websocket.spec.ts index 845d7a3..a493bb8 100644 --- a/e2e/tests/websocket.spec.ts +++ b/e2e/tests/websocket.spec.ts @@ -20,12 +20,25 @@ test("a list updates live for another user via websocket", async ({ page, browse // Give the websocket connections a moment to establish. await page.waitForTimeout(500); + // Both start with an empty list. + await expect(pageB.locator("#list-meta")).toHaveText("0 items left out of 0"); + // User A adds an item. await addItem(page, "Apple", "2"); // It should appear on User B's page without any reload. await expect(pageB.locator(".item-row").filter({ hasText: "Apple" })).toBeVisible(); await expect(pageB.locator(".item-row").filter({ hasText: "Apple" }).locator(".item-qty")).toHaveText("(2)"); + // The left/total count should also update live for User B. + await expect(pageB.locator("#list-meta")).toHaveText("1 items left out of 1"); + + // User A removes the item. + await page.locator(".item-actions-button").first().click(); + await page.locator("[id^='item-edit-delete']").click(); + + // The item and the count should update live on User B's page. + await expect(pageB.locator(".item-row").filter({ hasText: "Apple" })).toHaveCount(0); + await expect(pageB.locator("#list-meta")).toHaveText("0 items left out of 0"); await contextB.close(); }); diff --git a/src/views.rs b/src/views.rs index d1b342b..227bbd1 100644 --- a/src/views.rs +++ b/src/views.rs @@ -795,7 +795,7 @@ pub fn list_page( div { p class="eyebrow" { "SHARED LIST" } h1 { (list.name) } - p class="list-meta" { (items.iter().filter(|item| !item.checked).count()) " items to get" } + (list_meta_fragment(items, false)) } div class="list-topbar-actions" { @if list.archived_at.is_some() { @@ -1095,6 +1095,21 @@ pub fn categories_panel(categories: &[Category], csrf_token: &str, out_of_band: } } +fn list_meta_fragment(items: &[Item], out_of_band: bool) -> Markup { + let left = items.iter().filter(|item| !item.checked).count(); + let total = items.len(); + let meta = html! { + p id="list-meta" class="list-meta" { (left) " items left out of " (total) } + }; + if out_of_band { + html! { + p id="list-meta" class="list-meta" hx-swap-oob="outerHTML" { (left) " items left out of " (total) } + } + } else { + meta + } +} + pub fn live_list_fragments( list: &GroceryList, items: &[Item], @@ -1104,6 +1119,7 @@ pub fn live_list_fragments( ) -> Markup { let editable = list.archived_at.is_none(); html! { + (list_meta_fragment(items, true)) (list_items_fragment(list, items, categories, csrf_token, true, editable)) (list_meals_panel(list_meals, list.id, csrf_token, true, editable)) }