navbar: GATE VIEW swaps for CONT GAME on room pages (page-room) → room gate-view — TDD
Phase 0 of the room GATE VIEW + seat-renewal sprint. Mirrors the my-sea treatment: on any room page the self-referential CONT GAME is replaced by a GATE VIEW button that opens the room's renewal gate-view. - `room_view` page_class → "page-gameboard page-room"; the bare gameboard listing stays "page-gameboard" (no page-room) so CONT GAME persists there for returning to a recent room. - `_navbar.html` GATE VIEW branch fires on `page-my-sea` OR `page-room`; onclick routes, in precedence: page-room → epic:room_gate (room in context); my-sea-visit → visitor gate; else owner's sea gate. One consolidated branch (DRY) instead of two near-identical button blocks. Tests: RoomNavbarGateViewTest (4) — room page shows GATE VIEW not CONT GAME, links to room_gate, gate-view page also shows it, page-room marker present. 826 epic+gameboard ITs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2730,3 +2730,35 @@ class RoomRenewTokenTest(TestCase):
|
|||||||
def test_renew_get_redirects(self):
|
def test_renew_get_redirects(self):
|
||||||
response = self.client.get(self.url)
|
response = self.client.get(self.url)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomNavbarGateViewTest(TestCase):
|
||||||
|
"""Navbar swaps CONT GAME → GATE VIEW on room pages (mirror my-sea),
|
||||||
|
routing to the room gate-view. The gameboard listing keeps CONT GAME
|
||||||
|
(no `page-room` marker)."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.owner = User.objects.create(email="owner@test.io", username="owner")
|
||||||
|
self.room = Room.objects.create(
|
||||||
|
name="Nav Room", owner=self.owner,
|
||||||
|
gate_status=Room.OPEN, table_status=Room.ROLE_SELECT,
|
||||||
|
)
|
||||||
|
self.client.force_login(self.owner)
|
||||||
|
|
||||||
|
def test_room_page_shows_gate_view_not_cont_game(self):
|
||||||
|
response = self.client.get(reverse("epic:room", args=[self.room.id]))
|
||||||
|
self.assertContains(response, "id_navbar_gate_view_btn")
|
||||||
|
self.assertNotContains(response, 'id="id_cont_game"')
|
||||||
|
|
||||||
|
def test_gate_view_btn_links_to_room_gate(self):
|
||||||
|
response = self.client.get(reverse("epic:room", args=[self.room.id]))
|
||||||
|
self.assertContains(
|
||||||
|
response, reverse("epic:room_gate", args=[self.room.id]))
|
||||||
|
|
||||||
|
def test_room_gate_page_also_shows_gate_view(self):
|
||||||
|
response = self.client.get(reverse("epic:room_gate", args=[self.room.id]))
|
||||||
|
self.assertContains(response, "id_navbar_gate_view_btn")
|
||||||
|
|
||||||
|
def test_room_page_carries_page_room_marker(self):
|
||||||
|
response = self.client.get(reverse("epic:room", args=[self.room.id]))
|
||||||
|
self.assertIn("page-room", response.context["page_class"])
|
||||||
|
|||||||
@@ -449,7 +449,11 @@ def room_view(request, room_id):
|
|||||||
room = Room.objects.get(id=room_id)
|
room = Room.objects.get(id=room_id)
|
||||||
ctx = _role_select_context(room, request.user)
|
ctx = _role_select_context(room, request.user)
|
||||||
ctx["room"] = room
|
ctx["room"] = room
|
||||||
ctx["page_class"] = "page-gameboard"
|
# `page-room` drives the navbar GATE VIEW swap (mirrors my-sea's
|
||||||
|
# `page-my-sea`) so the table page reaches the renewal gate-view instead
|
||||||
|
# of a self-referential CONT GAME. The bare gameboard listing stays
|
||||||
|
# `page-gameboard` (no page-room) → keeps CONT GAME.
|
||||||
|
ctx["page_class"] = "page-gameboard page-room"
|
||||||
# Reversal-rate hint label under DRAW SEA's SPREAD select — same helper as
|
# Reversal-rate hint label under DRAW SEA's SPREAD select — same helper as
|
||||||
# sea_partial so the value tracks any future per-user override automatically.
|
# sea_partial so the value tracks any future per-user override automatically.
|
||||||
ctx["stack_reversal_pct"] = int(round(stack_reversal_probability(request.user, room) * 100))
|
ctx["stack_reversal_pct"] = int(round(stack_reversal_probability(request.user, room) * 100))
|
||||||
|
|||||||
@@ -21,27 +21,31 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% if 'page-my-sea' in page_class %}
|
{% if 'page-my-sea' in page_class or 'page-room' in page_class %}
|
||||||
{# Sprint 6 iter 6b — on any my-sea page (landing/picker or #}
|
{# Sprint 6 iter 6b — on any my-sea page (landing/picker or #}
|
||||||
{# the gatekeeper itself), CONT GAME swaps for GATE VIEW so #}
|
{# the gatekeeper itself), CONT GAME swaps for GATE VIEW so #}
|
||||||
{# the user can always reach the token-deposit gatekeeper #}
|
{# the user can always reach the token-deposit gatekeeper #}
|
||||||
{# (regardless of quota state). `<button>` (not `<a>`) #}
|
{# (regardless of quota state). 2026-05-31: extended to room #}
|
||||||
{# because UA-default fonts differ + `.btn` doesn't reset #}
|
{# pages (`page-room`, set by room_view + room_gate) — the #}
|
||||||
{# font-family — anchors render serif, buttons stay sans- #}
|
{# 3rd-person table reaches the renewal gate-view the same #}
|
||||||
{# serif (locked via the in-hex GATE VIEW fix in iter 4c). #}
|
{# way, instead of a self-referential CONT GAME. `<button>` #}
|
||||||
{# Direct child of `.container-fluid` (no form wrapper) so #}
|
{# (not `<a>`) because UA-default fonts differ + `.btn` #}
|
||||||
{# the `> #id_navbar_gate_view_btn` SCSS pin (top-center in #}
|
{# doesn't reset font-family — anchors render serif, buttons #}
|
||||||
{# landscape) matches; inline onclick handles navigation — #}
|
{# stay sans-serif (locked via the in-hex GATE VIEW fix in #}
|
||||||
{# no confirm guard since GATE VIEW is non-destructive nav. #}
|
{# iter 4c). Direct child of `.container-fluid` (no form #}
|
||||||
{# On a my_sea_VISIT page (incl. its gate, whose page_class also #}
|
{# wrapper) so the `> #id_navbar_gate_view_btn` SCSS pin #}
|
||||||
{# carries `page-my-sea-visit`), GATE VIEW must open THIS owner's #}
|
{# (top-center in landscape) matches; inline onclick handles #}
|
||||||
{# visitor gatekeeper — not the viewer's own sea gate. `owner` is #}
|
{# navigation — no confirm guard since GATE VIEW is #}
|
||||||
{# in context on those pages. #}
|
{# non-destructive nav. #}
|
||||||
|
{# Targets, in precedence order: a room page → that room's #}
|
||||||
|
{# gate-view (`room` is in context); a my_sea_VISIT page #}
|
||||||
|
{# (incl. its gate) → THIS owner's visitor gatekeeper #}
|
||||||
|
{# (`owner` in context); else the owner's own sea gate. #}
|
||||||
<button
|
<button
|
||||||
id="id_navbar_gate_view_btn"
|
id="id_navbar_gate_view_btn"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
type="button"
|
type="button"
|
||||||
onclick="window.location.href='{% if 'page-my-sea-visit' in page_class %}{% url 'my_sea_visit_gate' owner.id %}{% else %}{% url 'my_sea_gate' %}{% endif %}'"
|
onclick="window.location.href='{% if 'page-room' in page_class %}{% url 'epic:room_gate' room.id %}{% elif 'page-my-sea-visit' in page_class %}{% url 'my_sea_visit_gate' owner.id %}{% else %}{% url 'my_sea_gate' %}{% endif %}'"
|
||||||
>
|
>
|
||||||
GATE<br>VIEW
|
GATE<br>VIEW
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user