My Sea picker phase: three-card cross (sig + cover/leave/loom) — Sprint 5 iter 2 of My Sea roadmap — TDD
After the FREE DRAW click on iter 1's landing swaps `data-phase` to `picker`, the picker now renders a stripped Celtic Cross: user's saved significator pinned in `.sea-pos-core`, three drawn-card drop zones around it — cover (overlaid on sig), leave (left of core), loom (right of core). Crown / lay / cross from the gameroom's 6-position spread are deliberately forsaken (user-locked spec).
DRY w. the gameroom sea-overlay: reuses `.sea-cards-col` + `.sea-cross` + `.sea-crucifix-cell` + `.sea-pos-*` + `.sea-card-slot--empty` + `.sea-sig-card` classes & their _card-deck.scss styling (1181-1331). Only divergence from the room: a `.my-sea-cross` modifier in `_gameboard.scss` overrides `grid-template-areas` from the room's `". crown . / leave core loom / . lay ."` 3×3 to a single-row `"leave core loom"` — drops the crown + lay rows since those positions are forsaken. Cover stays nested inside `.sea-pos-core` so the absolute-overlay rules from _card-deck.scss line 1310-1331 carry over for free.
Picker bg = `rgba(var(--duoUser), 1)` on `.my-sea-page[data-phase="picker"]` — parallels `.my-sign-page[data-phase="picker"]` from _card-deck.scss line 704, so the landing→picker swap reads as a continuous surface (hex face → felt) like on /billboard/my-sign/.
The sig card renders w. `data-card-id="{{ significator.id }}"` + `.fan-corner-rank` + `.fa-solid {suit-icon}` (mirrors the gameroom's `.sea-sig-card` minimal markup at `_sea_overlay.html` line 33-39). Full card-face / FYI / SPIN wiring deferred — iter 3 lands the form col + interactive draw flow.
View context: `my_sea` now passes `significator` (FK pass-through) + `significator_reversed` so the template can render the corner rank + suit icon at render time without re-fetching.
- 3 FTs in new `MySeaPickerPhaseTest`: sig card w. `data-card-id` matching `user.significator.id` in `.sea-pos-core`; cover/leave/loom empty drop zones render; crown/lay/cross absent. Shared `_enter_picker_phase()` helper polls for `data-phase='picker'` after the ~800ms seat-1C animation delay.
- 4 ITs in new `MySeaPickerPhaseTemplateTest`: server-render contract for sig in core + cover/leave/loom classes + forsaken-positions-absent + picker entirely absent when user has no sig (4b gate precedence).
Tests: 28/28 FT green across test_bill_my_sign + test_game_my_sea (~219s); 1041/1041 IT/UT green (53s).
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:
@@ -586,3 +586,49 @@ class MySeaDrawSeaLandingViewTest(TestCase):
|
||||
self.user.save(update_fields=["significator"])
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertNotContains(response, 'id="id_draw_sea_btn"')
|
||||
|
||||
|
||||
class MySeaPickerPhaseTemplateTest(TestCase):
|
||||
"""Sprint 5 iter 2 — picker-phase template render contract: the
|
||||
three-card cross (sig in core + cover/leave/loom drop zones) is
|
||||
server-rendered (hidden until JS swaps data-phase after FREE DRAW).
|
||||
Crown / lay / cross from the gameroom's 6-position Celtic Cross are
|
||||
deliberately forsaken in the solo flow."""
|
||||
|
||||
def setUp(self):
|
||||
from apps.epic.models import personal_sig_cards
|
||||
self.user = User.objects.create(email="picker@test.io")
|
||||
self.client.force_login(self.user)
|
||||
self.target = personal_sig_cards(self.user)[0]
|
||||
self.user.significator = self.target
|
||||
self.user.save(update_fields=["significator"])
|
||||
|
||||
def test_picker_renders_significator_in_core_cell(self):
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
html = response.content.decode()
|
||||
# Sig card carries the user's significator id so iter 3's draw
|
||||
# flow can target it for SPIN / FLIP / FYI without re-fetching.
|
||||
self.assertIn('sea-pos-core', html)
|
||||
self.assertIn('sea-sig-card', html)
|
||||
self.assertIn(f'data-card-id="{self.target.id}"', html)
|
||||
|
||||
def test_picker_renders_cover_leave_loom_positions(self):
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertContains(response, "sea-pos-cover")
|
||||
self.assertContains(response, "sea-pos-leave")
|
||||
self.assertContains(response, "sea-pos-loom")
|
||||
|
||||
def test_picker_does_not_render_forsaken_positions(self):
|
||||
# Crown / lay / cross are gameroom-only — user-locked spec drops
|
||||
# them from the solo three-card spread.
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertNotContains(response, "sea-pos-crown")
|
||||
self.assertNotContains(response, "sea-pos-lay")
|
||||
self.assertNotContains(response, "sea-pos-cross")
|
||||
|
||||
def test_picker_not_rendered_when_user_has_no_sig(self):
|
||||
# 4b gate wins; picker has no business rendering without a sig.
|
||||
self.user.significator = None
|
||||
self.user.save(update_fields=["significator"])
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertNotContains(response, "my-sea-picker")
|
||||
|
||||
@@ -187,6 +187,12 @@ def my_sea(request):
|
||||
"user_has_sig": user_has_sig,
|
||||
"no_equipped_deck": no_equipped_deck,
|
||||
"show_backup_intro_banner": user_has_sig and no_equipped_deck,
|
||||
# Sprint 5 iter 2 — significator pinned in `.sea-pos-core` on the
|
||||
# picker phase. Template guards on `user_has_sig` so a None pass-
|
||||
# through is safe; we pass the FK directly so `.corner_rank` +
|
||||
# `.suit_icon` resolve at render time.
|
||||
"significator": request.user.significator,
|
||||
"significator_reversed": request.user.significator_reversed,
|
||||
"page_class": "page-gameboard page-my-sea",
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user