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:
Disco DeDisco
2026-05-31 23:11:05 -04:00
parent 516b917420
commit 65689295a7
3 changed files with 55 additions and 15 deletions

View File

@@ -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"])

View File

@@ -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))

View File

@@ -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>