my_sea_visit nav: phase-aware NVM (hex→bud page, draw→hex) + navbar GATE VIEW → visit gate + guard reposition on resize — TDD
Spectator guard-portal + NVM/GATE VIEW routing (user-spec 2026-05-30): - The leave-the-sea guard now confirms with OK (was NVM); `_my_sea_gear.html` gains an `nvm_handler` param so a caller can swap the default leave-nav. - my_sea_visit NVM is phase-aware (`mySeaVisitNvm`): on the DRAW/spread phase it flips back to the table hex (client toggle, stays in-ecosphere → no voice guard, mirroring the owner's picker→landing); on the table hex it LEAVES to the bud's page (`billboard:bud_page`) behind the shared voice-disconnect guard. - The navbar GATE VIEW opens THIS owner's visitor gatekeeper on my_sea_visit (+ its gate page, whose page_class also carries `page-my-sea-visit`), not the viewer's own sea gate; owner pages are unchanged. - showGuard now RE-POSITIONS the open guard on resize/orientationchange (rAF-throttled) so it follows its anchor instead of stranding at its show-time coords — a cross-cutting fix (every gear-menu guard) for the portal landing off-screen after an orientation flip relocated the gear menu. Coverage: MySeaVisitNavTest (navbar→visit gate, gear NVM→mySeaVisitNvm, bud URL, OK label) + MySeaOwnerNavbarGateUnaffectedTest (owner gate untouched). Verified live in Firefox: picker NVM returns to the hex; the guard followed its anchor from 882px→54px on a simulated orientation flip, staying in-viewport. Code architected by Disco DeDisco <discodedisco@outlook.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -257,6 +257,61 @@ class MySeaVisitEmptyHandLabelsTest(TestCase):
|
||||
f'data-position="{caption.lower()}">{caption}</span>', html)
|
||||
|
||||
|
||||
class MySeaVisitNavTest(TestCase):
|
||||
"""NVM + GATE VIEW routing on the spectator surface (user-spec 2026-05-30):
|
||||
the navbar GATE VIEW opens THIS owner's visitor gate (not the viewer's own);
|
||||
the gear NVM is phase-aware (mySeaVisitNvm) — table hex → the bud's page,
|
||||
draw phase → back to the hex; the leave-the-sea guard confirms with OK."""
|
||||
|
||||
def setUp(self):
|
||||
self.owner = _owner_with_sig()
|
||||
self.bud = User.objects.create(email="bud@test.io", username="budster")
|
||||
SeaInvite.objects.create(
|
||||
owner=self.owner, invitee=self.bud, invitee_email=self.bud.email,
|
||||
status=SeaInvite.ACCEPTED, accepted_at=timezone.now(),
|
||||
token_deposited_at=timezone.now(),
|
||||
voice_until=timezone.now() + timedelta(hours=24),
|
||||
)
|
||||
self.client.force_login(self.bud)
|
||||
self.html = self.client.get(
|
||||
reverse("my_sea_visit", args=[self.owner.id])).content.decode()
|
||||
|
||||
def test_navbar_gate_view_targets_the_visit_gate_not_own(self):
|
||||
visit_gate = reverse("my_sea_visit_gate", args=[self.owner.id])
|
||||
self.assertIn(f"window.location.href='{visit_gate}'", self.html)
|
||||
# The viewer's OWN sea gate must NOT be the navbar target here.
|
||||
self.assertNotIn(
|
||||
f"window.location.href='{reverse('my_sea_gate')}'", self.html)
|
||||
|
||||
def test_gear_nvm_uses_the_phase_aware_visit_handler(self):
|
||||
self.assertIn('onclick="mySeaVisitNvm(event)"', self.html)
|
||||
|
||||
def test_visit_nvm_leaves_to_the_bud_page_from_the_hex(self):
|
||||
bud_url = reverse("billboard:bud_page", args=[self.owner.id])
|
||||
self.assertIn(f"BUD_URL = '{bud_url}'", self.html)
|
||||
|
||||
def test_leave_guard_confirms_with_OK(self):
|
||||
self.assertIn("yesLabel: 'OK'", self.html)
|
||||
self.assertNotIn("yesLabel: 'NVM'", self.html)
|
||||
|
||||
|
||||
class MySeaOwnerNavbarGateUnaffectedTest(TestCase):
|
||||
"""Regression — the OWNER's own my_sea navbar GATE VIEW still routes to the
|
||||
user's own gate (the visit-gate branch must not leak onto owner pages)."""
|
||||
|
||||
def setUp(self):
|
||||
from apps.epic.models import personal_sig_cards
|
||||
self.user = User.objects.create(email="owner@test.io", username="discoself")
|
||||
self.user.significator = personal_sig_cards(self.user)[0]
|
||||
self.user.save(update_fields=["significator"])
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_owner_navbar_gate_targets_own_gate(self):
|
||||
html = self.client.get(reverse("my_sea")).content.decode()
|
||||
self.assertIn(
|
||||
f"window.location.href='{reverse('my_sea_gate')}'", html)
|
||||
|
||||
|
||||
class MySeaVisitOwnerSeatedTest(TestCase):
|
||||
"""Phase 2 — the owner shows seated in 1C on the spectator hex whenever
|
||||
she's committed to a draw cycle (drawn OR paid), not only once a card
|
||||
|
||||
Reference in New Issue
Block a user