fix: return FT components directly for proper toast injection
POST routes were returning HTMLResponse(content=to_xml(...)) which bypassed FastHTML's toast middleware. The middleware only injects toasts for tuple, FT, or FtResponse responses. Changed 12 routes to return render_page() directly: - actions.py: 7 routes (cohort, hatch, tag-add, tag-end, attrs, outcome, status-correct) - eggs.py: 2 routes (product-collected, product-sold) - feed.py: 2 routes (feed-given, feed-purchased) - move.py: 1 route (animal-move) Updated tests to check for toast content in response body instead of session cookie, since middleware now renders toasts inline. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -206,15 +206,11 @@ async def animal_cohort(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form
|
# Success: re-render fresh form
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
cohort_form(locations, species_list),
|
||||||
request,
|
title="Create Cohort - AnimalTrack",
|
||||||
cohort_form(locations, species_list),
|
active_nav=None,
|
||||||
title="Create Cohort - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -349,15 +345,11 @@ async def hatch_recorded(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form
|
# Success: re-render fresh form
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
hatch_form(locations, species_list),
|
||||||
request,
|
title="Record Hatch - AnimalTrack",
|
||||||
hatch_form(locations, species_list),
|
active_nav=None,
|
||||||
title="Record Hatch - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -694,15 +686,11 @@ async def animal_tag_add(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form
|
# Success: re-render fresh form
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
tag_add_form(),
|
||||||
request,
|
title="Add Tag - AnimalTrack",
|
||||||
tag_add_form(),
|
active_nav=None,
|
||||||
title="Add Tag - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -947,15 +935,11 @@ async def animal_tag_end(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form
|
# Success: re-render fresh form
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
tag_end_form(),
|
||||||
request,
|
title="End Tag - AnimalTrack",
|
||||||
tag_end_form(),
|
active_nav=None,
|
||||||
title="End Tag - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1183,15 +1167,11 @@ async def animal_attrs(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form
|
# Success: re-render fresh form
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
attrs_form(),
|
||||||
request,
|
title="Update Attributes - AnimalTrack",
|
||||||
attrs_form(),
|
active_nav=None,
|
||||||
title="Update Attributes - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1468,22 +1448,18 @@ async def animal_outcome(request: Request, session):
|
|||||||
product_repo = ProductRepository(db)
|
product_repo = ProductRepository(db)
|
||||||
products = [(p.code, p.name) for p in product_repo.list_all() if p.active]
|
products = [(p.code, p.name) for p in product_repo.list_all() if p.active]
|
||||||
|
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
outcome_form(
|
||||||
request,
|
filter_str="",
|
||||||
outcome_form(
|
resolved_ids=[],
|
||||||
filter_str="",
|
roster_hash="",
|
||||||
resolved_ids=[],
|
ts_utc=int(time.time() * 1000),
|
||||||
roster_hash="",
|
resolved_count=0,
|
||||||
ts_utc=int(time.time() * 1000),
|
products=products,
|
||||||
resolved_count=0,
|
|
||||||
products=products,
|
|
||||||
),
|
|
||||||
title="Record Outcome - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Record Outcome - AnimalTrack",
|
||||||
|
active_nav=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1692,21 +1668,17 @@ async def animal_status_correct(req: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form
|
# Success: re-render fresh form
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
req,
|
||||||
render_page(
|
status_correct_form(
|
||||||
req,
|
filter_str="",
|
||||||
status_correct_form(
|
resolved_ids=[],
|
||||||
filter_str="",
|
roster_hash="",
|
||||||
resolved_ids=[],
|
ts_utc=int(time.time() * 1000),
|
||||||
roster_hash="",
|
resolved_count=0,
|
||||||
ts_utc=int(time.time() * 1000),
|
|
||||||
resolved_count=0,
|
|
||||||
),
|
|
||||||
title="Correct Status - AnimalTrack",
|
|
||||||
active_nav=None,
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Correct Status - AnimalTrack",
|
||||||
|
active_nav=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -209,22 +209,18 @@ async def product_collected(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render form with location sticking, qty cleared
|
# Success: re-render form with location sticking, qty cleared
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
eggs_page(
|
||||||
request,
|
locations,
|
||||||
eggs_page(
|
products,
|
||||||
locations,
|
active_tab="harvest",
|
||||||
products,
|
selected_location_id=location_id,
|
||||||
active_tab="harvest",
|
harvest_action=product_collected,
|
||||||
selected_location_id=location_id,
|
sell_action=product_sold,
|
||||||
harvest_action=product_collected,
|
|
||||||
sell_action=product_sold,
|
|
||||||
),
|
|
||||||
title="Eggs - AnimalTrack",
|
|
||||||
active_nav="eggs",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Eggs - AnimalTrack",
|
||||||
|
active_nav="eggs",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -320,22 +316,18 @@ async def product_sold(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render form with product sticking
|
# Success: re-render form with product sticking
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
eggs_page(
|
||||||
request,
|
locations,
|
||||||
eggs_page(
|
products,
|
||||||
locations,
|
active_tab="sell",
|
||||||
products,
|
selected_product_code=product_code,
|
||||||
active_tab="sell",
|
harvest_action=product_collected,
|
||||||
selected_product_code=product_code,
|
sell_action=product_sold,
|
||||||
harvest_action=product_collected,
|
|
||||||
sell_action=product_sold,
|
|
||||||
),
|
|
||||||
title="Eggs - AnimalTrack",
|
|
||||||
active_nav="eggs",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Eggs - AnimalTrack",
|
||||||
|
active_nav="eggs",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -245,25 +245,21 @@ async def feed_given(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render form with location/type sticking, amount reset
|
# Success: re-render form with location/type sticking, amount reset
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=str(
|
request,
|
||||||
render_page(
|
feed_page(
|
||||||
request,
|
locations,
|
||||||
feed_page(
|
feed_types,
|
||||||
locations,
|
active_tab="give",
|
||||||
feed_types,
|
selected_location_id=location_id,
|
||||||
active_tab="give",
|
selected_feed_type_code=feed_type_code,
|
||||||
selected_location_id=location_id,
|
default_amount_kg=default_amount_kg,
|
||||||
selected_feed_type_code=feed_type_code,
|
balance_warning=balance_warning,
|
||||||
default_amount_kg=default_amount_kg,
|
give_action=feed_given,
|
||||||
balance_warning=balance_warning,
|
purchase_action=feed_purchased,
|
||||||
give_action=feed_given,
|
|
||||||
purchase_action=feed_purchased,
|
|
||||||
),
|
|
||||||
title="Feed - AnimalTrack",
|
|
||||||
active_nav="feed",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Feed - AnimalTrack",
|
||||||
|
active_nav="feed",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -404,21 +400,17 @@ async def feed_purchased(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render form with fields cleared
|
# Success: re-render form with fields cleared
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=str(
|
request,
|
||||||
render_page(
|
feed_page(
|
||||||
request,
|
locations,
|
||||||
feed_page(
|
feed_types,
|
||||||
locations,
|
active_tab="purchase",
|
||||||
feed_types,
|
give_action=feed_given,
|
||||||
active_tab="purchase",
|
purchase_action=feed_purchased,
|
||||||
give_action=feed_given,
|
|
||||||
purchase_action=feed_purchased,
|
|
||||||
),
|
|
||||||
title="Feed - AnimalTrack",
|
|
||||||
active_nav="feed",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Feed - AnimalTrack",
|
||||||
|
active_nav="feed",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -299,18 +299,14 @@ async def animal_move(request: Request, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Success: re-render fresh form (nothing sticks per spec)
|
# Success: re-render fresh form (nothing sticks per spec)
|
||||||
return HTMLResponse(
|
return render_page(
|
||||||
content=to_xml(
|
request,
|
||||||
render_page(
|
move_form(
|
||||||
request,
|
locations,
|
||||||
move_form(
|
action=animal_move,
|
||||||
locations,
|
|
||||||
action=animal_move,
|
|
||||||
),
|
|
||||||
title="Move - AnimalTrack",
|
|
||||||
active_nav="move",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
title="Move - AnimalTrack",
|
||||||
|
active_nav="move",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class TestCohortCreationSuccess:
|
|||||||
assert count_after == count_before + 3
|
assert count_after == count_before + 3
|
||||||
|
|
||||||
def test_cohort_success_returns_toast(self, client, seeded_db, location_strip1_id):
|
def test_cohort_success_returns_toast(self, client, seeded_db, location_strip1_id):
|
||||||
"""Successful cohort creation stores toast in session."""
|
"""Successful cohort creation renders toast in response body."""
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/actions/animal-cohort",
|
"/actions/animal-cohort",
|
||||||
data={
|
data={
|
||||||
@@ -164,20 +164,8 @@ class TestCohortCreationSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
# Toast is stored in session cookie (FastHTML's add_toast mechanism)
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
# The session cookie contains base64-encoded toast data with "toasts" key
|
assert "Created 2 duck" in resp.text
|
||||||
assert "set-cookie" in resp.headers
|
|
||||||
session_cookie = resp.headers["set-cookie"]
|
|
||||||
assert "session_=" in session_cookie
|
|
||||||
# Base64 decode contains toast message (eyJ0b2FzdHMi... = {"toasts"...)
|
|
||||||
import base64
|
|
||||||
|
|
||||||
# Extract base64 portion from cookie value
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
# FastHTML uses itsdangerous, so format is base64.timestamp.signature
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Created 2 duck" in decoded
|
|
||||||
|
|
||||||
|
|
||||||
class TestCohortCreationValidation:
|
class TestCohortCreationValidation:
|
||||||
@@ -375,7 +363,7 @@ class TestHatchRecordingSuccess:
|
|||||||
assert count_at_nursery >= 3
|
assert count_at_nursery >= 3
|
||||||
|
|
||||||
def test_hatch_success_returns_toast(self, client, seeded_db, location_strip1_id):
|
def test_hatch_success_returns_toast(self, client, seeded_db, location_strip1_id):
|
||||||
"""Successful hatch recording stores toast in session."""
|
"""Successful hatch recording renders toast in response body."""
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/actions/hatch-recorded",
|
"/actions/hatch-recorded",
|
||||||
data={
|
data={
|
||||||
@@ -387,16 +375,8 @@ class TestHatchRecordingSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
# Toast is stored in session cookie (FastHTML's add_toast mechanism)
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
assert "set-cookie" in resp.headers
|
assert "Recorded 2 hatchling" in resp.text
|
||||||
session_cookie = resp.headers["set-cookie"]
|
|
||||||
assert "session_=" in session_cookie
|
|
||||||
import base64
|
|
||||||
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Recorded 2 hatchling" in decoded
|
|
||||||
|
|
||||||
|
|
||||||
class TestHatchRecordingValidation:
|
class TestHatchRecordingValidation:
|
||||||
@@ -729,8 +709,7 @@ class TestTagAddSuccess:
|
|||||||
assert tag_count >= len(animals_for_tagging)
|
assert tag_count >= len(animals_for_tagging)
|
||||||
|
|
||||||
def test_tag_add_success_returns_toast(self, client, seeded_db, animals_for_tagging):
|
def test_tag_add_success_returns_toast(self, client, seeded_db, animals_for_tagging):
|
||||||
"""Successful tag add stores toast in session."""
|
"""Successful tag add renders toast in response body."""
|
||||||
import base64
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from animaltrack.selection import compute_roster_hash
|
from animaltrack.selection import compute_roster_hash
|
||||||
@@ -751,14 +730,8 @@ class TestTagAddSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
# Toast is stored in session cookie
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
assert "set-cookie" in resp.headers
|
assert "Tagged" in resp.text and "test-tag-toast" in resp.text
|
||||||
session_cookie = resp.headers["set-cookie"]
|
|
||||||
assert "session_=" in session_cookie
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Tagged" in decoded and "test-tag-toast" in decoded
|
|
||||||
|
|
||||||
|
|
||||||
class TestTagAddValidation:
|
class TestTagAddValidation:
|
||||||
@@ -925,8 +898,7 @@ class TestTagEndSuccess:
|
|||||||
assert open_after == 0
|
assert open_after == 0
|
||||||
|
|
||||||
def test_tag_end_success_returns_toast(self, client, seeded_db, tagged_animals):
|
def test_tag_end_success_returns_toast(self, client, seeded_db, tagged_animals):
|
||||||
"""Successful tag end stores toast in session."""
|
"""Successful tag end renders toast in response body."""
|
||||||
import base64
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from animaltrack.selection import compute_roster_hash
|
from animaltrack.selection import compute_roster_hash
|
||||||
@@ -947,14 +919,8 @@ class TestTagEndSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
# Toast is stored in session cookie
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
assert "set-cookie" in resp.headers
|
assert "Ended tag" in resp.text and "test-end-tag" in resp.text
|
||||||
session_cookie = resp.headers["set-cookie"]
|
|
||||||
assert "session_=" in session_cookie
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Ended tag" in decoded and "test-end-tag" in decoded
|
|
||||||
|
|
||||||
|
|
||||||
class TestTagEndValidation:
|
class TestTagEndValidation:
|
||||||
@@ -1103,8 +1069,7 @@ class TestAttrsSuccess:
|
|||||||
assert adult_count == len(animals_for_tagging)
|
assert adult_count == len(animals_for_tagging)
|
||||||
|
|
||||||
def test_attrs_success_returns_toast(self, client, seeded_db, animals_for_tagging):
|
def test_attrs_success_returns_toast(self, client, seeded_db, animals_for_tagging):
|
||||||
"""Successful attrs update stores toast in session."""
|
"""Successful attrs update renders toast in response body."""
|
||||||
import base64
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from animaltrack.selection import compute_roster_hash
|
from animaltrack.selection import compute_roster_hash
|
||||||
@@ -1125,14 +1090,8 @@ class TestAttrsSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
# Toast is stored in session cookie
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
assert "set-cookie" in resp.headers
|
assert "Updated attributes" in resp.text
|
||||||
session_cookie = resp.headers["set-cookie"]
|
|
||||||
assert "session_=" in session_cookie
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Updated attributes" in decoded
|
|
||||||
|
|
||||||
|
|
||||||
class TestAttrsValidation:
|
class TestAttrsValidation:
|
||||||
@@ -1280,8 +1239,7 @@ class TestOutcomeSuccess:
|
|||||||
assert harvested_count == len(animals_for_tagging)
|
assert harvested_count == len(animals_for_tagging)
|
||||||
|
|
||||||
def test_outcome_success_returns_toast(self, client, seeded_db, animals_for_tagging):
|
def test_outcome_success_returns_toast(self, client, seeded_db, animals_for_tagging):
|
||||||
"""Successful outcome recording stores toast in session."""
|
"""Successful outcome recording renders toast in response body."""
|
||||||
import base64
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from animaltrack.selection import compute_roster_hash
|
from animaltrack.selection import compute_roster_hash
|
||||||
@@ -1302,14 +1260,8 @@ class TestOutcomeSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
# Toast is stored in session cookie
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
assert "set-cookie" in resp.headers
|
assert "Recorded sold" in resp.text
|
||||||
session_cookie = resp.headers["set-cookie"]
|
|
||||||
assert "session_=" in session_cookie
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Recorded sold" in decoded
|
|
||||||
|
|
||||||
|
|
||||||
class TestOutcomeValidation:
|
class TestOutcomeValidation:
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class TestMoveAnimalSuccess:
|
|||||||
location_strip2_id,
|
location_strip2_id,
|
||||||
ducks_at_strip1,
|
ducks_at_strip1,
|
||||||
):
|
):
|
||||||
"""Successful move returns session cookie with toast."""
|
"""Successful move renders toast in response body."""
|
||||||
ts_utc = int(time.time() * 1000)
|
ts_utc = int(time.time() * 1000)
|
||||||
filter_str = 'location:"Strip 1"'
|
filter_str = 'location:"Strip 1"'
|
||||||
filter_ast = parse_filter(filter_str)
|
filter_ast = parse_filter(filter_str)
|
||||||
@@ -219,16 +219,8 @@ class TestMoveAnimalSuccess:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert "set-cookie" in resp.headers
|
# Toast is injected into response body by FastHTML's toast middleware
|
||||||
session_cookie = resp.headers["set-cookie"]
|
assert "Moved 5 animals to Strip 2" in resp.text
|
||||||
assert "session_=" in session_cookie
|
|
||||||
# Base64 decode contains toast message
|
|
||||||
import base64
|
|
||||||
|
|
||||||
cookie_value = session_cookie.split("session_=")[1].split(";")[0]
|
|
||||||
base64_data = cookie_value.split(".")[0]
|
|
||||||
decoded = base64.b64decode(base64_data).decode()
|
|
||||||
assert "Moved 5 animals to Strip 2" in decoded
|
|
||||||
|
|
||||||
def test_move_success_resets_form(
|
def test_move_success_resets_form(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Reference in New Issue
Block a user