diff options
| author | Tom Flux <tom@tomflux.xyz> | 2026-06-23 19:55:16 +0100 |
|---|---|---|
| committer | Tom Flux <tom@tomflux.xyz> | 2026-06-23 19:55:16 +0100 |
| commit | 0aabf50834a31ff4ae9f8fd58639e444a449110b (patch) | |
| tree | 3c0033c8326268e098ebf4bac020d5a427ee3f9c /kitchen/tests.py | |
| parent | e34081ce36d1993912dad514127924440437a2e9 (diff) | |
Phase 1: mobile-first pantry redesign with In/Low/Out state
Track presence (In/Low/Out) as the primary signal instead of exact
quantities; quantity becomes an optional integer, unit optional too
(migration 0003 backfills state from quantity: 0 -> out, else in).
- Pantry rebuilt as a phone-first card list: colored state rail,
segmented In/Low/Out switch (server-driven HTMX), greyed out-items,
live summary counts.
- Fast add: bottom add bar with type-ahead autocomplete, location
picker, and quick-add chips for things you've run out of.
- Per-item menu (move / set expiry / delete); expiry is now an
optional quiet pill, never required.
- New endpoints: pantry search + set-state; dropped the old
edit-expiry/cancel flow and its partial.
- Recipes matcher made presence-based (have-it beats have-enough);
state added to admin. Tests cover state, add/restock, search,
presence matching, and page rendering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'kitchen/tests.py')
| -rw-r--r-- | kitchen/tests.py | 106 |
1 files changed, 104 insertions, 2 deletions
diff --git a/kitchen/tests.py b/kitchen/tests.py index 7ce503c..31e0665 100644 --- a/kitchen/tests.py +++ b/kitchen/tests.py @@ -1,3 +1,105 @@ -from django.test import TestCase +from django.test import TestCase, Client +from django.urls import reverse -# Create your tests here. +from kitchen.models import Ingredient, PantryItem, MetaRecipe, Slot, SlotOption + + +class PantryStateTests(TestCase): + def setUp(self): + self.client = Client() + self.eggs = Ingredient.objects.create(name="eggs", default_unit="items") + self.item = PantryItem.objects.create( + ingredient=self.eggs, location="fridge", state="in" + ) + + def test_state_defaults_to_in(self): + self.assertEqual(self.item.state, "in") + + def test_quantity_is_optional(self): + self.assertIsNone(self.item.quantity) + + def test_set_state_endpoint(self): + resp = self.client.post( + reverse("app-pantry-set-state", args=[self.item.id]), {"to": "low"} + ) + self.assertEqual(resp.status_code, 200) + self.item.refresh_from_db() + self.assertEqual(self.item.state, "low") + + def test_set_state_rejects_unknown_value(self): + self.client.post( + reverse("app-pantry-set-state", args=[self.item.id]), {"to": "bogus"} + ) + self.item.refresh_from_db() + self.assertEqual(self.item.state, "in") # unchanged + + def test_out_items_still_listed(self): + self.item.state = "out" + self.item.save() + resp = self.client.get(reverse("app-pantry")) + self.assertContains(resp, "eggs") + + def test_add_creates_in_stock_item(self): + resp = self.client.post( + reverse("app-pantry-add"), + {"ingredient_name": "milk", "location": "fridge"}, + ) + self.assertEqual(resp.status_code, 200) + self.assertTrue( + PantryItem.objects.filter(ingredient__name="milk", state="in").exists() + ) + + def test_add_restocks_existing_to_in(self): + self.item.state = "out" + self.item.save() + self.client.post( + reverse("app-pantry-add"), + {"ingredient_name": "eggs", "location": "fridge"}, + ) + # No duplicate row, and the existing one is back in stock. + items = PantryItem.objects.filter(ingredient=self.eggs, location="fridge") + self.assertEqual(items.count(), 1) + self.assertEqual(items.first().state, "in") + + def test_search_matches_by_name(self): + resp = self.client.get( + reverse("app-pantry-search"), {"ingredient_name": "egg"} + ) + self.assertContains(resp, "eggs") + + +class RecipesPresenceTests(TestCase): + """The recipes matcher is presence-based now: having an ingredient (not + 'out') satisfies a slot regardless of quantity.""" + + def setUp(self): + self.client = Client() + self.noodles = Ingredient.objects.create(name="noodles", default_unit="nests") + self.mr = MetaRecipe.objects.create(name="Noodles", method="boil") + slot = Slot.objects.create(meta_recipe=self.mr, name="carb", required=True) + SlotOption.objects.create( + slot=slot, ingredient=self.noodles, quantity_per_serving=2, unit="nests" + ) + + def test_present_ingredient_no_quantity_is_available(self): + # 'in' with no quantity should still count as available. + PantryItem.objects.create( + ingredient=self.noodles, location="cupboard", state="in" + ) + resp = self.client.get(reverse("app-recipes")) + self.assertEqual(resp.status_code, 200) + self.assertContains(resp, "Noodles") + + def test_out_ingredient_renders_without_error(self): + PantryItem.objects.create( + ingredient=self.noodles, location="cupboard", state="out" + ) + resp = self.client.get(reverse("app-recipes")) + self.assertEqual(resp.status_code, 200) + + +class PageSmokeTests(TestCase): + def test_pages_render(self): + for name in ("app-pantry", "app-recipes", "app-shopping", "app-log"): + with self.subTest(page=name): + self.assertEqual(self.client.get(reverse(name)).status_code, 200) |
