3 Commits
Author SHA1 Message Date
sbstp 279cdb869d cargo fmt
ci/woodpecker/push/e2e Pipeline was successful
ci/woodpecker/push/fmt Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
2026-08-01 23:05:37 -04:00
sbstp fbb49bc560 fix flaky e2e test 2026-08-01 23:05:20 -04:00
sbstp 7309971c74 websocket sync e2e test 2026-08-01 23:01:42 -04:00
5 changed files with 44 additions and 25 deletions
+1
View File
@@ -6,3 +6,4 @@
/e2e/node_modules/
/e2e/test-results/
/e2e/playwright-report/
/test-results/
+2 -2
View File
@@ -48,7 +48,7 @@ test("the add-meal picker closes when clicking outside", async ({ page }) => {
const picker = page.locator(".meal-picker-backdrop");
await expect(picker).toBeVisible();
// Click the backdrop itself (outside the modal card).
await picker.click({ position: { x: 5, y: 5 } });
// Click the backdrop itself (outside the modal card), at the viewport corner.
await page.mouse.click(10, 10);
await expect(picker).toHaveCount(0);
});
+31
View File
@@ -0,0 +1,31 @@
import { expect } from "@playwright/test";
import { test } from "../fixtures";
import { registerAndLogin, createList, addItem } from "../helpers";
test("a list updates live for another user via websocket", async ({ page, browser, server }) => {
// User A registers and creates a list.
await registerAndLogin(page, "alice@example.com");
await createList(page, "Weekly shop");
const listUrl = page.url();
// User B registers in a separate context (separate session).
const contextB = await browser.newContext({ baseURL: server.baseURL });
const pageB = await contextB.newPage();
await registerAndLogin(pageB, "bob@example.com");
// Both users open the same list.
await page.goto(listUrl);
await pageB.goto(listUrl);
// Give the websocket connections a moment to establish.
await page.waitForTimeout(500);
// 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)");
await contextB.close();
});
+7 -7
View File
@@ -1,12 +1,10 @@
use std::sync::Arc;
use crate::domain::{
DomainError, DomainResult, GroceryList, Item, Meal, SessionUser, User,
};
use crate::domain::{DomainError, DomainResult, GroceryList, Item, Meal, SessionUser, User};
use crate::ports::{
CategoryRepository, InvitationRepository, ItemRepository, ListRepository, MealIngredientRepository,
MealRepository, NewItem, PasswordHasher, RealtimeNotifier, SessionRepository, TokenGenerator,
UserRepository,
CategoryRepository, InvitationRepository, ItemRepository, ListRepository,
MealIngredientRepository, MealRepository, NewItem, PasswordHasher, RealtimeNotifier,
SessionRepository, TokenGenerator, UserRepository,
};
use crate::sqlite::SqliteDatabase;
@@ -312,7 +310,9 @@ impl MealService {
pub async fn create_meal(&self, name: String, description: String) -> DomainResult<Meal> {
let meals = Arc::clone(&self.meals);
self.db
.run(move |txn| Box::pin(async move { meals.create_meal(txn, name, description).await }))
.run(move |txn| {
Box::pin(async move { meals.create_meal(txn, name, description).await })
})
.await
}
+3 -16
View File
@@ -237,11 +237,7 @@ pub fn meal_picker(meals: &[Meal], list_id: i64, csrf_token: &str) -> Markup {
}
}
pub fn meal_form_page(
user: &User,
meal: Option<&Meal>,
csrf_token: &str,
) -> Markup {
pub fn meal_form_page(user: &User, meal: Option<&Meal>, csrf_token: &str) -> Markup {
let (title, action, name, description) = match meal {
Some(meal) => (
"Edit meal",
@@ -276,12 +272,7 @@ pub fn meal_form_page(
)
}
pub fn meal_page(
user: &User,
meal: &Meal,
categories: &[Category],
csrf_token: &str,
) -> Markup {
pub fn meal_page(user: &User, meal: &Meal, categories: &[Category], csrf_token: &str) -> Markup {
page(
&meal.name,
Some(user),
@@ -743,11 +734,7 @@ fn category_options(categories: &[Category], selected: Option<i64>) -> Markup {
}
}
pub fn categories_panel(
categories: &[Category],
csrf_token: &str,
out_of_band: bool,
) -> Markup {
pub fn categories_panel(categories: &[Category], csrf_token: &str, out_of_band: bool) -> Markup {
let panel = html! {
div class="panel-heading" {
h2 { "Categories" }