btn-primary label renames + stage-card polarity color refinements — two interleaved threads from one session, committing together since both touch sig + sea stage cards ; LABEL RENAMES: PICK SIGS → SCAN SIGS (room.html #id_pick_sigs_btn), PICK SKY → CAST SKY (room.html #id_pick_sky_btn × 2), PICK SEA → DRAW SEA (room.html #id_pick_sea_btn), TAKE SIG → SAVE SIG (sig-select.js _takeSigBtn.textContent × 2 callsites + section comment) — Element IDs (id_pick_sky_btn etc.), URL names (epic:pick_sigs, epic:pick_sky), and Python state enums (TableStatus.PICK_SKY, PICK_SEA, SIG_SELECT) intentionally retained as stable identifiers; the renamed text is purely the .btn-primary user-facing label ; FT + IT mentions of the old labels swept in test_game_room_select_{sig,sky,sea,role}.py, test_billboard.py, setup_sea_session.py mgmt cmd, apps/epic/{views,utils,models,tasks,tests/integrated/test_views}.py, SigSelectSpec.js, sky_overlay/sea_overlay/dashboard/sky.html, _card-deck.scss, _sky.scss — all docstring/comment references updated for cascade-grep cleanliness ; STAGE-CARD COLOR + CLASS REFINEMENTS (earlier in session): sig-stage card text colour split per polarity — gravity gets --terUser on .fan-card-name + .fan-card-reversal-{name,qualifier} + .sig-qualifier-{above,below}, levity gets --quiUser on the same five slots; all selectors prefixed w. .sig-stage-card to match the 0,4,0 specificity of the default .sig-stage .sig-stage-card .fan-card-face .sig-qualifier-* rule (without the prefix the polarity overrides lose the cascade — .sig-qualifier-below was visibly stuck on the default --quiUser) ; .stat-face-label gets polarity-inverse colours — gravity stat-block bg is --secUser (opposite of card's --priUser) so the label takes --quiUser to stay legible; levity is the symmetric flip (label = --terUser on --priUser stat-block bg) ; levity card title/qualifier drop-shadow swapped from rgba(0,0,0,…) → rgba(255,255,255,…) — dark drop reads as harsh smudge against the inverted-frame levity --secUser bg; applied to both sig-overlay[data-polarity="levity"] stage card AND sea-stage--levity via $_sea-title-shadow-levity (former shared $_sea-title-shadow split into per-polarity {levity,gravity} variants) ; reversal-face class/content alignment so each .fan-card-reversal-* class always carries its semantic content — DOM order per arcana type controls visual layout after the 180° SPIN (DOM-second appears visually on top): Major → title in .fan-card-reversal-name @ DOM-second (visually top after spin), qualifier in .fan-card-reversal-qualifier @ DOM-first; Non-major → title in .fan-card-reversal-name @ DOM-first (visually bottom after spin), qualifier in .fan-card-reversal-qualifier @ DOM-second (preserves the original "qualifier word reads first after spin" layout for Middle/Minor arcana — e.g. "Relieving / Eight of Crowns" not "Eight of Crowns / Relieving") ; _tarot_fan.html renders per-arcana DOM order directly (Django template branches handle both layouts); sig + sea overlays render a fixed two-<p> skeleton (one DOM order) so stage-card.js's populator dynamically rewrites the two <p>s' className per arcana — Major/override branch flips DOM-second to .fan-card-reversal-name + content, DOM-first to .fan-card-reversal-qualifier; non-major branch keeps DOM-first as .fan-card-reversal-name + title, DOM-second as .fan-card-reversal-qualifier + reversalQualifier-or-polarity-fallback ; SigSelectSpec.js + SeaDealSpec.js fixtures + Major reversed-face assertion updated for the new semantic — TDD
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:
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
Management command for manual single-gamer PICK SEA testing.
|
||||
Management command for manual single-gamer DRAW SEA testing.
|
||||
|
||||
Creates a room at SKY_SELECT with one seated gamer whose sky is already
|
||||
confirmed, so the PICK SEA overlay is immediately visible on page load.
|
||||
confirmed, so the DRAW SEA overlay is immediately visible on page load.
|
||||
|
||||
Usage:
|
||||
python src/manage.py setup_sea_session
|
||||
@@ -32,7 +32,7 @@ def _make_session(user):
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Set up a SKY_SELECT room with sky confirmed and print a PICK SEA URL"
|
||||
help = "Set up a SKY_SELECT room with sky confirmed and print a DRAW SEA URL"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--base-url", default="http://localhost:8000")
|
||||
|
||||
@@ -453,7 +453,7 @@ class BillscrollGearMenuTest(FunctionalTest):
|
||||
FT: the billscroll page has a gear menu that filters events by label.
|
||||
|
||||
Frame = all regular (non-struck) drama entries.
|
||||
Redact = struck-through (retracted) entries, e.g. a WAIT NVM after TAKE SIG.
|
||||
Redact = struck-through (retracted) entries, e.g. a WAIT NVM after SAVE SIG.
|
||||
|
||||
Scenario (one gamer, Role + Sig events):
|
||||
1. Both labels checked by default — all events visible.
|
||||
|
||||
@@ -843,12 +843,12 @@ class RoleSelectChannelsTest(ChannelsFunctionalTest):
|
||||
))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Test 7 — PICK SIGS appears + card stack removed on last role #
|
||||
# Test 7 — SCAN SIGS appears + card stack removed on last role #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_pick_sigs_appears_and_card_stack_removed_on_last_role(self):
|
||||
"""When the sixth and final role is confirmed, the all_roles_filled
|
||||
WS event makes the PICK SIGS button visible and removes the card
|
||||
WS event makes the SCAN SIGS button visible and removes the card
|
||||
stack from the DOM entirely."""
|
||||
emails = [
|
||||
"founder@test.io", "amigo@test.io", "bud@test.io",
|
||||
@@ -883,7 +883,7 @@ class RoleSelectChannelsTest(ChannelsFunctionalTest):
|
||||
self.browser.find_element(By.CSS_SELECTOR, "#id_role_select .card").click()
|
||||
self.confirm_guard()
|
||||
|
||||
# PICK SIGS wrap must become visible via the all_roles_filled WS event.
|
||||
# SCAN SIGS wrap must become visible via the all_roles_filled WS event.
|
||||
self.wait_for(lambda: self.assertFalse(
|
||||
self.browser.find_element(By.ID, "id_pick_sigs_wrap").get_attribute("style"),
|
||||
))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Functional tests for the PICK SEA overlay — Celtic Cross draw."""
|
||||
"""Functional tests for the DRAW SEA overlay — Celtic Cross draw."""
|
||||
|
||||
from django.test import tag
|
||||
from django.urls import reverse
|
||||
@@ -39,7 +39,7 @@ def _make_sky_confirmed_room(live_server_url, user, earthman):
|
||||
@tag("channels")
|
||||
class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
|
||||
"""After sky confirm, the sky overlay closes and the room reloads to the
|
||||
table hex w. the PICK SEA btn visible — the gamer must opt into the sea
|
||||
table hex w. the DRAW SEA btn visible — the gamer must opt into the sea
|
||||
overlay rather than be auto-launched into it."""
|
||||
|
||||
def setUp(self):
|
||||
@@ -92,22 +92,22 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
|
||||
""")
|
||||
|
||||
def test_pick_sea_btn_visible_after_sky_confirm(self):
|
||||
"""Confirming sky reloads the room to the hex w. PICK SEA replacing
|
||||
PICK SKY; the sea overlay is NOT auto-opened."""
|
||||
"""Confirming sky reloads the room to the hex w. DRAW SEA replacing
|
||||
CAST SKY; the sea overlay is NOT auto-opened."""
|
||||
self.create_pre_authenticated_session("founder@test.io")
|
||||
self.browser.get(self.room_url)
|
||||
|
||||
# Sky not yet confirmed — PICK SKY btn present.
|
||||
# Sky not yet confirmed — CAST SKY btn present.
|
||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))
|
||||
|
||||
self._confirm_sky()
|
||||
|
||||
# Page reloads → hex shows PICK SEA in place of PICK SKY.
|
||||
# Page reloads → hex shows DRAW SEA in place of CAST SKY.
|
||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sea_btn"))
|
||||
self.assertEqual(self.browser.find_elements(By.ID, "id_pick_sky_btn"), [])
|
||||
|
||||
# Sea overlay is NOT auto-opened — it only appears once the gamer
|
||||
# clicks PICK SEA.
|
||||
# clicks DRAW SEA.
|
||||
has_sea_open = self.browser.execute_script(
|
||||
"return document.documentElement.classList.contains('sea-open');"
|
||||
)
|
||||
@@ -126,7 +126,7 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
|
||||
self.assertTrue(not sky or not sky[0].is_displayed())
|
||||
|
||||
def test_clicking_pick_sea_btn_opens_sea_overlay(self):
|
||||
"""The gamer's explicit click on PICK SEA is what opens the sea overlay."""
|
||||
"""The gamer's explicit click on DRAW SEA is what opens the sea overlay."""
|
||||
self.create_pre_authenticated_session("founder@test.io")
|
||||
self.browser.get(self.room_url)
|
||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))
|
||||
@@ -134,7 +134,7 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
|
||||
self._confirm_sky()
|
||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sea_btn"))
|
||||
|
||||
# On slow CI, the PICK SEA btn parses into the DOM before the inline
|
||||
# On slow CI, the DRAW SEA btn parses into the DOM before the inline
|
||||
# `<script>` at the bottom of _sea_overlay.html has bound `openSea` to
|
||||
# it; a one-shot click can land before the handler exists. Retry click
|
||||
# + assert together via wait_for so the race resolves naturally.
|
||||
@@ -150,7 +150,7 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
|
||||
self.assertTrue(sea_overlay.is_displayed())
|
||||
|
||||
|
||||
# ── Helpers for PICK SEA deal tests ──────────────────────────────────────────
|
||||
# ── Helpers for DRAW SEA deal tests ──────────────────────────────────────────
|
||||
|
||||
def _seed_earthman_cards(earthman, count=20):
|
||||
"""Seed enough Middle Arcana cards for the deck piles."""
|
||||
@@ -167,7 +167,7 @@ def _seed_earthman_cards(earthman, count=20):
|
||||
|
||||
|
||||
def _make_sea_ready_room(earthman):
|
||||
"""Create a SKY_SELECT room with a confirmed Character ready for PICK SEA.
|
||||
"""Create a SKY_SELECT room with a confirmed Character ready for DRAW SEA.
|
||||
|
||||
Returns (room, gamer, seat, char, room_url).
|
||||
"""
|
||||
@@ -199,7 +199,7 @@ def _make_sea_ready_room(earthman):
|
||||
|
||||
@tag("channels")
|
||||
class PickSeaDealTest(ChannelsFunctionalTest):
|
||||
"""PICK SEA deck stacks, OK btn interaction, card draw, and LOCK HAND."""
|
||||
"""DRAW SEA deck stacks, OK btn interaction, card draw, and LOCK HAND."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
@@ -325,25 +325,25 @@ class SigSelectThemeTest(FunctionalTest):
|
||||
self.assertEqual(corr.text, "")
|
||||
|
||||
|
||||
# ── TAKE SIG / WAIT NVM — ready gate ──────────────────────────────────────────
|
||||
# ── SAVE SIG / WAIT NVM — ready gate ──────────────────────────────────────────
|
||||
#
|
||||
# TAKE SIG (.btn.btn-primary) appears at the bottom-left corner of the card
|
||||
# SAVE SIG (.btn.btn-primary) appears at the bottom-left corner of the card
|
||||
# stage preview once a gamer has clicked OK on a card (SigReservation exists).
|
||||
# Clicking it sets the gamer's status to ready and changes the btn to WAIT NVM.
|
||||
# WAIT NVM cancels the ready status and reverts back to TAKE SIG.
|
||||
# WAIT NVM cancels the ready status and reverts back to SAVE SIG.
|
||||
#
|
||||
# When all three gamers in a polarity WS room are ready, a 12-second countdown
|
||||
# starts. Any WAIT NVM during the countdown cancels it; the saved remaining time
|
||||
# is resumed when all three are ready again. When the countdown completes
|
||||
# (client POSTs sig_confirm) the polarity group returns to the table hex.
|
||||
# When both polarity groups have confirmed, PICK SKY btn appears in the hex
|
||||
# When both polarity groups have confirmed, CAST SKY btn appears in the hex
|
||||
# center for all six gamers.
|
||||
#
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class SigReadyGateTest(FunctionalTest):
|
||||
"""Single-browser tests for TAKE SIG / WAIT NVM btn."""
|
||||
"""Single-browser tests for SAVE SIG / WAIT NVM btn."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -377,10 +377,10 @@ class SigReadyGateTest(FunctionalTest):
|
||||
)
|
||||
ok_btn.click()
|
||||
|
||||
# ── SRG1: TAKE SIG btn not visible before OK ──────────────────────── #
|
||||
# ── SRG1: SAVE SIG btn not visible before OK ──────────────────────── #
|
||||
|
||||
def test_take_sig_btn_not_visible_before_ok_click(self):
|
||||
"""TAKE SIG must be absent until the gamer has OK'd a card."""
|
||||
"""SAVE SIG must be absent until the gamer has OK'd a card."""
|
||||
room = self._setup_sig_room()
|
||||
self.create_pre_authenticated_session("founder@test.io")
|
||||
self.browser.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
||||
@@ -389,7 +389,7 @@ class SigReadyGateTest(FunctionalTest):
|
||||
take_sig_btns = self.browser.find_elements(By.ID, "id_take_sig_btn")
|
||||
self.assertEqual(len(take_sig_btns), 0)
|
||||
|
||||
# ── SRG2: TAKE SIG btn appears after OK ──────────────────────────── #
|
||||
# ── SRG2: SAVE SIG btn appears after OK ──────────────────────────── #
|
||||
|
||||
def test_take_sig_btn_appears_after_ok_click(self):
|
||||
room = self._setup_sig_room()
|
||||
@@ -401,9 +401,9 @@ class SigReadyGateTest(FunctionalTest):
|
||||
take_sig_btn = self.wait_for(
|
||||
lambda: self.browser.find_element(By.ID, "id_take_sig_btn")
|
||||
)
|
||||
self.assertIn("TAKE SIG", take_sig_btn.text.upper())
|
||||
self.assertIn("SAVE SIG", take_sig_btn.text.upper())
|
||||
|
||||
# ── SRG3: TAKE SIG → WAIT NVM ─────────────────────────────────────── #
|
||||
# ── SRG3: SAVE SIG → WAIT NVM ─────────────────────────────────────── #
|
||||
|
||||
def test_take_sig_btn_becomes_wait_no_after_click(self):
|
||||
room = self._setup_sig_room()
|
||||
@@ -429,7 +429,7 @@ class SigReadyGateTest(FunctionalTest):
|
||||
).get_attribute("class")
|
||||
)
|
||||
|
||||
# ── SRG4: WAIT NVM → TAKE SIG ─────────────────────────────────────── #
|
||||
# ── SRG4: WAIT NVM → SAVE SIG ─────────────────────────────────────── #
|
||||
|
||||
def test_wait_no_reverts_to_take_sig(self):
|
||||
room = self._setup_sig_room()
|
||||
@@ -446,11 +446,11 @@ class SigReadyGateTest(FunctionalTest):
|
||||
By.ID, "id_take_sig_btn").text.upper()
|
||||
)
|
||||
btn = self.browser.find_element(By.ID, "id_take_sig_btn")
|
||||
btn.click() # → TAKE SIG again
|
||||
btn.click() # → SAVE SIG again
|
||||
|
||||
self.wait_for(
|
||||
lambda: self.assertIn(
|
||||
"TAKE SIG",
|
||||
"SAVE SIG",
|
||||
self.browser.find_element(By.ID, "id_take_sig_btn").text.upper(),
|
||||
)
|
||||
)
|
||||
@@ -458,7 +458,7 @@ class SigReadyGateTest(FunctionalTest):
|
||||
|
||||
@tag("channels")
|
||||
class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
"""Multi-browser WebSocket tests for the polarity-room countdown and PICK SKY."""
|
||||
"""Multi-browser WebSocket tests for the polarity-room countdown and CAST SKY."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -546,7 +546,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
|
||||
@tag("channels")
|
||||
def test_countdown_element_appears_when_all_three_levity_gamers_ready(self):
|
||||
"""When PC, NC, and SC each click TAKE SIG the countdown becomes visible."""
|
||||
"""When PC, NC, and SC each click SAVE SIG the countdown becomes visible."""
|
||||
room, emails = self._setup_sig_select_room()
|
||||
levity_emails = [emails[0], emails[1], emails[3]] # PC, NC, SC
|
||||
browsers = []
|
||||
@@ -556,7 +556,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
b.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
||||
browsers.append(b)
|
||||
|
||||
# Each levity gamer OK's a card then clicks TAKE SIG
|
||||
# Each levity gamer OK's a card then clicks SAVE SIG
|
||||
for b in browsers:
|
||||
self._ok_card_in_browser(b)
|
||||
self.wait_for(
|
||||
@@ -616,12 +616,12 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
for b in browsers:
|
||||
b.quit()
|
||||
|
||||
# ── SRG7: PICK SKY btn appears after both polarity groups confirm ─── #
|
||||
# ── SRG7: CAST SKY btn appears after both polarity groups confirm ─── #
|
||||
|
||||
@tag("channels")
|
||||
def test_pick_sky_btn_appears_in_hex_after_both_groups_confirm(self):
|
||||
"""Once both levity and gravity countdowns complete, all six browsers
|
||||
see the PICK SKY btn in the table hex center."""
|
||||
see the CAST SKY btn in the table hex center."""
|
||||
# This test drives the full flow end-to-end but uses ORM shortcuts
|
||||
# to set all-ready state for one polarity, letting the other complete
|
||||
# via the UI, to keep execution time manageable.
|
||||
@@ -647,7 +647,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
b.get(self.live_server_url + f"/gameboard/room/{room.pk}/")
|
||||
browsers.append(b)
|
||||
|
||||
# All levity gamers OK and TAKE SIG
|
||||
# All levity gamers OK and SAVE SIG
|
||||
for b in browsers:
|
||||
self._ok_card_in_browser(b)
|
||||
self.wait_for(
|
||||
@@ -655,7 +655,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
)
|
||||
b.find_element(By.ID, "id_take_sig_btn").click()
|
||||
|
||||
# Wait for countdown to expire or be confirmed; PICK SKY appears in hex
|
||||
# Wait for countdown to expire or be confirmed; CAST SKY appears in hex
|
||||
# countdown is 12 s so use wait_for_slow (MAX_WAIT=10 is not enough)
|
||||
for b in browsers:
|
||||
self.wait_for_slow(
|
||||
@@ -711,7 +711,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest):
|
||||
# class PickSkyTrayFlowTest(FunctionalTest):
|
||||
#
|
||||
# def test_pick_sky_btn_opens_tray_with_sig_card_in_slot_2(self):
|
||||
# """Clicking PICK SKY opens #id_tray; tray cell 2 shows the gamer's
|
||||
# """Clicking CAST SKY opens #id_tray; tray cell 2 shows the gamer's
|
||||
# sig card icon (Blank.svg placeholder until card-specific icons land)."""
|
||||
# ...
|
||||
#
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Functional tests for the PICK SKY overlay — natal chart entry."""
|
||||
"""Functional tests for the CAST SKY overlay — natal chart entry."""
|
||||
|
||||
import json as _json
|
||||
|
||||
@@ -42,7 +42,7 @@ def _make_sky_select_room():
|
||||
|
||||
|
||||
class PickSkyLocalStorageTest(FunctionalTest):
|
||||
"""PICK SKY form fields persist to localStorage."""
|
||||
"""CAST SKY form fields persist to localStorage."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -145,7 +145,7 @@ class PickSkyLocalStorageTest(FunctionalTest):
|
||||
|
||||
|
||||
class PickSkyDelTest(FunctionalTest):
|
||||
"""PICK SKY overlay gets a DEL btn at the wheel center: clicking opens the
|
||||
"""CAST SKY overlay gets a DEL btn at the wheel center: clicking opens the
|
||||
global guard portal; OK clears the wheel SVG, resets the form fields, &
|
||||
purges the localStorage entry that would otherwise rehydrate the form on
|
||||
the next overlay open / page refresh. No server hit (the wheel here is
|
||||
@@ -173,7 +173,7 @@ class PickSkyDelTest(FunctionalTest):
|
||||
self.create_pre_authenticated_session(self.founder_email)
|
||||
self.browser.get(self.room_url)
|
||||
|
||||
# Open PICK SKY modal
|
||||
# Open CAST SKY modal
|
||||
btn = self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))
|
||||
self.browser.execute_script("arguments[0].click()", btn)
|
||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_sky_overlay"))
|
||||
|
||||
Reference in New Issue
Block a user