room ?seat=N seat-switch: CARTE gamer previews an owned seat's card-stack — data-active-slot + ineligible .fa-ban when it's not the table's turn — TDD
Workstream B of the position-circle tooltips sprint. _role_select_context consumes ?seat=N (threaded from room_view): a multi-seat (CARTE) gamer previews a specific owned seat. The card-stack's active slot becomes that seat; it stays 'eligible' only when the previewed seat is also the table's current turn (lowest unassigned seat), else it renders the ineligible .fa-ban. Strictly additive — a one-seat gamer never passes ?seat, and an unowned/garbage param is ignored, so the normal role-select flow (RoleSelectRenderingTest) is untouched. Tests: CarteSeatSwitchTest.test_switching_seat_loads_that_seats_role_view FT green; 2 new render ITs (seat-param-previews + no-seat-keeps-canonical); RoleSelectRenderingTest still green. [[project-position-circle-tooltips]] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -745,6 +745,23 @@ class PositionTooltipCarteRenderTest(TestCase):
|
|||||||
chunk = content[idx:idx + 800]
|
chunk = content[idx:idx + 800]
|
||||||
self.assertIn("seat=4", chunk)
|
self.assertIn("seat=4", chunk)
|
||||||
|
|
||||||
|
def test_seat_param_previews_that_seat_in_card_stack(self):
|
||||||
|
# Un-assigned owned seats → ?seat=4 previews seat 4, which is not the
|
||||||
|
# table's current turn (slot 1) → card-stack ineligible (.fa-ban).
|
||||||
|
for n in range(1, 7):
|
||||||
|
TableSeat.objects.create(room=self.room, gamer=self.viewer, slot_number=n)
|
||||||
|
content = self.client.get(self.room_url + "?seat=4").content.decode()
|
||||||
|
self.assertIn('data-active-slot="4"', content)
|
||||||
|
self.assertIn('data-state="ineligible"', content)
|
||||||
|
|
||||||
|
def test_no_seat_param_keeps_canonical_active_slot(self):
|
||||||
|
for n in range(1, 7):
|
||||||
|
TableSeat.objects.create(room=self.room, gamer=self.viewer, slot_number=n)
|
||||||
|
content = self.client.get(self.room_url).content.decode()
|
||||||
|
# Default: the table's turn is slot 1, and the viewer owns it → eligible.
|
||||||
|
self.assertIn('data-active-slot="1"', content)
|
||||||
|
self.assertIn('data-state="eligible"', content)
|
||||||
|
|
||||||
|
|
||||||
class PickRolesViewTest(TestCase):
|
class PickRolesViewTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -428,6 +428,27 @@ def _role_select_context(room, user, seat_param=None):
|
|||||||
if user.is_authenticated else []
|
if user.is_authenticated else []
|
||||||
)
|
)
|
||||||
active_slot = active_seat.slot_number if active_seat else None
|
active_slot = active_seat.slot_number if active_seat else None
|
||||||
|
# CARTE seat-switch (?seat=N): a multi-seat gamer previews a specific owned
|
||||||
|
# seat. The card-stack's active slot becomes that seat; it stays "eligible"
|
||||||
|
# (role-pickable now) only when the previewed seat is also the table's
|
||||||
|
# current turn (the lowest unassigned seat), else it shows the ineligible
|
||||||
|
# .fa-ban. No-op for the normal flow — a one-seat gamer never passes ?seat,
|
||||||
|
# and an unowned/garbage param is ignored.
|
||||||
|
if seat_param and user.is_authenticated and card_stack_state is not None:
|
||||||
|
try:
|
||||||
|
_seat_n = int(seat_param)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
_seat_n = None
|
||||||
|
if _seat_n is not None and room.table_seats.filter(
|
||||||
|
gamer=user, slot_number=_seat_n
|
||||||
|
).exists():
|
||||||
|
active_slot = _seat_n
|
||||||
|
_turn = unassigned.first() if unassigned.exists() else None
|
||||||
|
card_stack_state = (
|
||||||
|
"eligible"
|
||||||
|
if _turn is not None and _turn.slot_number == _seat_n
|
||||||
|
else "ineligible"
|
||||||
|
)
|
||||||
_my_role = assigned_seats[0].role if assigned_seats else None
|
_my_role = assigned_seats[0].role if assigned_seats else None
|
||||||
# `equipped_deck_id` gates the JS role-select guard (role-select.js L165).
|
# `equipped_deck_id` gates the JS role-select guard (role-select.js L165).
|
||||||
# Falls back to ANY of the user's seats in this room w. deck_variant set
|
# Falls back to ANY of the user's seats in this room w. deck_variant set
|
||||||
|
|||||||
@@ -226,7 +226,6 @@ class CarteSeatSwitchTest(FunctionalTest):
|
|||||||
)
|
)
|
||||||
self.assertEqual(circle.get_attribute("data-tt-tokens"), "6")
|
self.assertEqual(circle.get_attribute("data-tt-tokens"), "6")
|
||||||
|
|
||||||
@skip(_RED + " [workstream B — ?seat card-stack consumption]")
|
|
||||||
def test_switching_seat_loads_that_seats_role_view(self):
|
def test_switching_seat_loads_that_seats_role_view(self):
|
||||||
# Clicking the me-also seat-4 circle loads ?seat=4 and the card-stack
|
# Clicking the me-also seat-4 circle loads ?seat=4 and the card-stack
|
||||||
# reflects seat 4 (a non-active seat → ineligible / .fa-ban atop deck).
|
# reflects seat 4 (a non-active seat → ineligible / .fa-ban atop deck).
|
||||||
|
|||||||
Reference in New Issue
Block a user