my-sea gear NVM: gatekeeper + picker nav-back to table hex instead of ejecting to gameboard — TDD

User-spec'd 2026-05-26: the gear menu's NVM was always nav-backing to /gameboard/ from every my-sea state, which ejects the user one step further than they want when bailing on the gatekeeper or the spread/crucifix picker. Both should nav-back to the my-sea TABLE HEX (the landing) — one step back, not all the way out.

## templates/apps/gameboard/_partials/_my_sea_gear.html

Now takes an optional `nvm_url` context variable; falls back to `{% url 'gameboard' %}` when unset. The onclick handler reads `{{ nvm_url }}` so callers can override per-page.

## templates/apps/gameboard/my_sea.html

```
{% if show_picker %}{% url 'my_sea' as nvm_url %}{% endif %}
{% include "apps/gameboard/_partials/_my_sea_gear.html" %}
```

Picker phase → nvm_url = my_sea landing (table hex). Sign-gate + landing phases fall through to the default (gameboard) — landing's NVM = "back out of my-sea entirely", which is the existing behaviour.

## templates/apps/gameboard/my_sea_gate.html

```
{% url 'my_sea' as nvm_url %}
{% include "apps/gameboard/_partials/_my_sea_gear.html" %}
```

Gatekeeper unconditionally nav-backs to the my-sea landing.

## Tests

`apps/gameboard/tests/integrated/test_views.py`:
- `MySeaViewTest.test_gear_nvm_navs_to_gameboard_on_landing_phase` — landing NVM still goes to /gameboard/ (regression guard).
- `MySeaViewTest.test_gear_nvm_navs_to_my_sea_landing_on_picker_phase` — picker NVM goes to /gameboard/my-sea/ (seeds a non-empty hand to trigger show_picker per views.py:277's `hand_non_empty or (phase_param and show_paid_draw)` logic).
- `MySeaGateViewTest.test_gear_nvm_navs_to_my_sea_landing_not_gameboard` — gatekeeper NVM goes to /gameboard/my-sea/ (and explicitly NOT /gameboard/).

## Verification

All 1364 IT+UT green. No FTs assert the gear-menu NVM target URL (per pre-change grep) so no test fixes required there.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-26 22:39:26 -04:00
parent ca960d1d43
commit 5cade51d03
4 changed files with 53 additions and 10 deletions

View File

@@ -1037,6 +1037,31 @@ class MySeaViewTest(TestCase):
self.assertIn('id="id_bud_btn"', body)
self.assertIn('id="id_burger_btn"', body)
def test_gear_nvm_navs_to_gameboard_on_landing_phase(self):
"""Landing-phase NVM = "back out of my-sea entirely" → /gameboard/."""
response = self.client.get(reverse("my_sea"))
self.assertIn("location.href='/gameboard/'", response.content.decode())
def test_gear_nvm_navs_to_my_sea_landing_on_picker_phase(self):
"""Picker-phase NVM = "back out of the spread to the table hex"
/gameboard/my-sea/ (the landing). User-spec'd 2026-05-26 so the
spread/crucifix exit doesn't eject all the way to /gameboard/.
Picker phase triggers on a non-empty hand (see views.py:277)."""
from apps.epic.models import personal_sig_cards, TarotCard
from apps.gameboard.models import MySeaDraw
sig = personal_sig_cards(self.user)[0]
self.user.significator = sig
self.user.save(update_fields=["significator"])
card = TarotCard.objects.exclude(pk=sig.pk).first()
MySeaDraw.objects.create(
user=self.user, spread="situation-action-outcome",
significator_id=sig.id,
hand=[{"position": "lay", "card_id": card.id,
"reversed": False, "polarity": "gravity"}],
)
response = self.client.get(reverse("my_sea"))
self.assertIn("location.href='/gameboard/my-sea/'", response.content.decode())
def test_sea_stage_stat_block_renders_rank_suit_chip_per_face(self):
"""Sprint A.7.5 — `_sea_stage.html` modal scaffold (included from
my_sea-picker-phase + the gameroom sea overlay) carries the new
@@ -2189,6 +2214,15 @@ class MySeaGateViewTest(TestCase):
self.assertContains(response, "id_my_sea_paid_draw_btn")
self.assertContains(response, reverse("my_sea_refund_token"))
def test_gear_nvm_navs_to_my_sea_landing_not_gameboard(self):
"""Gatekeeper NVM = "back out of the gate to the table hex"
/gameboard/my-sea/ (the landing), NOT /gameboard/. User-spec'd
2026-05-26 so the gatekeeper exit lands one step back."""
response = self.client.get(reverse("my_sea_gate"))
body = response.content.decode()
self.assertIn("location.href='/gameboard/my-sea/'", body)
self.assertNotIn("location.href='/gameboard/'", body)
def test_gate_view_renders_burger_btn_and_fan(self):
response = self.client.get(reverse("my_sea_gate"))
self.assertContains(response, 'id="id_burger_btn"')