mirror of
https://github.com/sstent/foodplanner.git
synced 2026-04-06 04:54:10 +00:00
feat(phase): Complete Phase 3: UI & Cookbook Refinement
This commit is contained in:
@@ -15,7 +15,10 @@ router = APIRouter()
|
|||||||
@router.get("/meals", response_class=HTMLResponse)
|
@router.get("/meals", response_class=HTMLResponse)
|
||||||
async def meals_page(request: Request, db: Session = Depends(get_db)):
|
async def meals_page(request: Request, db: Session = Depends(get_db)):
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
meals = db.query(Meal).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).all()
|
# Filter out single food entries and snapshots
|
||||||
|
meals = db.query(Meal).filter(
|
||||||
|
Meal.meal_type.notin_(["single_food", "tracked_snapshot"])
|
||||||
|
).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).all()
|
||||||
foods = db.query(Food).all()
|
foods = db.query(Food).all()
|
||||||
return templates.TemplateResponse("meals.html",
|
return templates.TemplateResponse("meals.html",
|
||||||
{"request": request, "meals": meals, "foods": foods})
|
{"request": request, "meals": meals, "foods": foods})
|
||||||
|
|||||||
@@ -70,15 +70,15 @@
|
|||||||
{% for tracked_meal in meals_for_time %}
|
{% for tracked_meal in meals_for_time %}
|
||||||
{# 1. Create stable slugs #}
|
{# 1. Create stable slugs #}
|
||||||
{% set meal_time_slug = meal_time|slugify %}
|
{% set meal_time_slug = meal_time|slugify %}
|
||||||
{% set meal_name_safe = tracked_meal.meal.name|slugify %}
|
{% set display_meal_name = (tracked_meal.name or tracked_meal.meal.name) if (tracked_meal.name or tracked_meal.meal) else "Unnamed Meal" %}
|
||||||
|
{% set meal_name_safe = display_meal_name|slugify %}
|
||||||
|
|
||||||
{# 2. Construct the core Unique Meal ID for non-ambiguous locating #}
|
{# 2. Construct the core Unique Meal ID for non-ambiguous locating #}
|
||||||
{% set unique_meal_id = meal_time_slug + '-' + meal_name_safe + '-' + loop.index|string %}
|
{% set unique_meal_id = meal_time_slug + '-' + meal_name_safe + '-' + loop.index|string %}
|
||||||
<div class="mb-3 p-3 bg-light rounded" data-testid="meal-card-{{ unique_meal_id }}">
|
<div class="mb-3 p-3 bg-light rounded" data-testid="meal-card-{{ unique_meal_id }}">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<div>
|
<div>
|
||||||
<strong data-testid="meal-name-{{ unique_meal_id }}">{{ tracked_meal.meal.name
|
<strong data-testid="meal-name-{{ unique_meal_id }}">{{ display_meal_name }}</strong>
|
||||||
}}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-sm btn-outline-secondary me-1"
|
<button class="btn btn-sm btn-outline-secondary me-1"
|
||||||
@@ -126,6 +126,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{# Display base meal foods, applying overrides #}
|
{# Display base meal foods, applying overrides #}
|
||||||
|
{% if tracked_meal.meal %}
|
||||||
{% for meal_food in tracked_meal.meal.meal_foods %}
|
{% for meal_food in tracked_meal.meal.meal_foods %}
|
||||||
{% if meal_food.food_id not in deleted_food_ids and meal_food.food_id not in
|
{% if meal_food.food_id not in deleted_food_ids and meal_food.food_id not in
|
||||||
overrides.keys() %}
|
overrides.keys() %}
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Display overridden/new foods #}
|
{# Display overridden/new foods #}
|
||||||
{% for food_id, tmf in overrides.items() %}
|
{% for food_id, tmf in overrides.items() %}
|
||||||
|
|||||||
32
tests/tracked_meal_refactor.spec.js
Normal file
32
tests/tracked_meal_refactor.spec.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test('add single food to tracker and verify it is not in meals page', async ({ page }) => {
|
||||||
|
await page.goto('/tracker');
|
||||||
|
|
||||||
|
// Add single food to breakfast
|
||||||
|
await page.locator('[data-testid="add-food-breakfast"]').click();
|
||||||
|
// Select a food (Verification Beans)
|
||||||
|
await page.locator('#addSingleFoodModal select[name="food_id"]').selectOption({ label: 'Verification Beans' });
|
||||||
|
await page.locator('#addSingleFoodModal input[name="quantity"]').fill('200');
|
||||||
|
await page.getByRole('button', { name: 'Add Food', exact: true }).click();
|
||||||
|
|
||||||
|
// Verify it appears in the tracker
|
||||||
|
// The name should be just the food name
|
||||||
|
const mealNameLocator = page.locator('[data-testid^="meal-name-breakfast-verification-beans"]');
|
||||||
|
await expect(mealNameLocator).toBeVisible();
|
||||||
|
await expect(mealNameLocator).toHaveText('Verification Beans');
|
||||||
|
|
||||||
|
// Verify it contains the food with correct quantity
|
||||||
|
const foodRowLocator = page.locator('[data-testid^="food-row-breakfast-verification-beans"][data-testid$="verification-beans"]');
|
||||||
|
await expect(foodRowLocator).toBeVisible();
|
||||||
|
await expect(foodRowLocator).toContainText('Verification Beans');
|
||||||
|
await expect(foodRowLocator).toContainText('200.0 g');
|
||||||
|
|
||||||
|
// Navigate to Meals page
|
||||||
|
await page.goto('/meals');
|
||||||
|
|
||||||
|
// Verify 'Verification Beans' is NOT in the meals list as a meal name
|
||||||
|
// It might be in the ingredients dropdown, but shouldn't be a <strong> heading in a card
|
||||||
|
const mealCardHeading = page.locator('.card-title:has-text("Verification Beans")');
|
||||||
|
await expect(mealCardHeading).not.toBeVisible();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user