My Sea iter 6c: bud-btn invite stub + #id_my_sea_menu gear (NVM-only, %applet-menu-styled, on both /gameboard/my-sea/ and the gatekeeper) + PAID DRAW now deletes the row and redirects to ?phase=picker so the user drops straight into picking cards instead of looping back to GATE VIEW — Sprint 5 iter 6c of My Sea roadmap — TDD

Bundled fix for the PAID-DRAW-loops-to-GATE-VIEW bug surfaced 2026-05-20 in
live testing: previously the view reset `created_at = now()` + cleared the
hand, but the row's continued existence meant `quota_spent=True` on the
next render → landing rendered GATE VIEW → user clicked it → back to
gatekeeper → loop.

Now PAID DRAW does `active_draw.delete()` after debiting the token + then
redirects to `/gameboard/my-sea/?phase=picker`. The my_sea view honors
`?phase=picker` (only when no active_draw exists — can't bypass
post-DEL GATE VIEW) by forcing `show_picker=True` so the user lands in
the picker ready to draw. First card draw creates a fresh row w. fresh
`created_at`, starting the new 24h quota cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-20 09:47:47 -04:00
parent 1e37fe1475
commit 4417b8c972
9 changed files with 267 additions and 41 deletions

View File

@@ -1561,3 +1561,98 @@ class MySeaBudBtnStubTest(FunctionalTest):
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-banner")
)
self.assertIn("coming soon", brief.text.lower())
class MySeaGearBtnTest(FunctionalTest):
"""Sprint 6 iter 6c — `.gear-btn` on every my-sea page state
(landing / picker / gatekeeper). Opens a NVM-only menu (DEL/BYE
deliberately omitted — gear is for "back out without committing");
NVM nav-backs to /gameboard/ mirroring the room's gear-menu
convention. Same partial included on both /gameboard/my-sea/ and
/gameboard/my-sea/gate/ so visual + behavior is identical."""
def setUp(self):
super().setUp()
_seed_earthman_sig_pile()
_seed_gameboard_applets()
self.email = "gear@test.io"
self.gamer = User.objects.create(email=self.email)
_assign_sig(self.gamer)
# Seed a quota row so the gatekeeper has an active_draw context.
from apps.gameboard.models import MySeaDraw
MySeaDraw.objects.create(
user=self.gamer, spread="situation-action-outcome",
significator_id=self.gamer.significator_id, hand=[],
)
def test_gear_btn_renders_on_gatekeeper(self):
self.create_pre_authenticated_session(self.email)
self.browser.get(self.live_server_url + "/gameboard/my-sea/gate/")
gear = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".my-sea-page .gear-btn"
)
)
# Menu wired via data-menu-target.
self.assertEqual(
gear.get_attribute("data-menu-target"),
"id_my_sea_menu",
)
def test_gear_btn_renders_on_landing_without_active_draw(self):
"""User direction 2026-05-20 — gear stays on /gameboard/my-sea/
even when no gatekeeper / no active_draw row exists. Fresh user
with a sig + no draw lands here + still sees the gear."""
from apps.gameboard.models import MySeaDraw
MySeaDraw.objects.filter(user=self.gamer).delete()
self.create_pre_authenticated_session(self.email)
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".my-sea-page .gear-btn"
)
)
def test_gear_btn_opens_menu_with_nvm_only(self):
self.create_pre_authenticated_session(self.email)
self.browser.get(self.live_server_url + "/gameboard/my-sea/gate/")
gear = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".my-sea-page .gear-btn"
)
)
gear.click()
menu = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_my_sea_menu")
)
self.assertEqual(menu.value_of_css_property("display"), "block")
# NVM link present, DEL + BYE deliberately absent.
nvm = menu.find_element(By.CSS_SELECTOR, ".btn-cancel")
self.assertIn("/gameboard/", nvm.get_attribute("href"))
self.assertEqual(
len(menu.find_elements(By.CSS_SELECTOR, ".btn-danger")), 0,
)
self.assertEqual(
len(menu.find_elements(By.CSS_SELECTOR, ".btn-abandon")), 0,
)
def test_nvm_navigates_back_to_gameboard(self):
self.create_pre_authenticated_session(self.email)
self.browser.get(self.live_server_url + "/gameboard/my-sea/gate/")
gear = self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, ".my-sea-page .gear-btn"
)
)
gear.click()
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_my_sea_menu")
)
self.browser.find_element(
By.CSS_SELECTOR, "#id_my_sea_menu .btn-cancel"
).click()
self.wait_for(
lambda: self.assertRegex(
self.browser.current_url, r"/gameboard/$"
)
)