diff --git a/src/apps/gameboard/views.py b/src/apps/gameboard/views.py index 987c7ca..33b50fe 100644 --- a/src/apps/gameboard/views.py +++ b/src/apps/gameboard/views.py @@ -249,6 +249,21 @@ def _my_sea_deck_data(user): "levity_qualifier": c.levity_qualifier, "gravity_qualifier": c.gravity_qualifier, "reversal_qualifier": c.reversal_qualifier, + # Polarity-split full-title overrides — required for Major + # Arcana (Earthman trumps 19-21 + cards 48-49) to render + # their per-polarity emanation/reversal names on the stage + # card. Without these StageCard.populateCard falls back to + # the plain `name_title` w. no qualifier. Mirrors the + # gameroom `epic.views.sea_deck` JSON shape exactly. + "levity_emanation": c.levity_emanation, + "gravity_emanation": c.gravity_emanation, + "levity_reversal": c.levity_reversal, + "gravity_reversal": c.gravity_reversal, + "italic_word": c.italic_word, + "keywords_upright": c.keywords_upright, + "keywords_reversed": c.keywords_reversed, + "energies": c.energies, + "operations": c.operations, "reversed": random.random() < reversal_prob, } diff --git a/src/functional_tests/test_game_my_sea.py b/src/functional_tests/test_game_my_sea.py index 8d9ed1c..6aa9105 100644 --- a/src/functional_tests/test_game_my_sea.py +++ b/src/functional_tests/test_game_my_sea.py @@ -533,7 +533,7 @@ class MySeaSpreadFormTest(FunctionalTest): "past-present-future": {"leave", "cover", "loom"}, "situation-action-outcome": {"lay", "cover", "crown"}, "mind-body-spirit": {"crown", "lay", "loom"}, - "desire-obstacle-solution": {"loom", "cross", "cover"}, + "desire-obstacle-solution": {"loom", "cross", "crown"}, "waite-smith": ALL_POSITIONS, "escape-velocity": ALL_POSITIONS, } @@ -583,7 +583,7 @@ class MySeaSpreadFormTest(FunctionalTest): "situation-action-outcome": {"lay": "Situation", "cover": "Action", "crown": "Outcome"}, "past-present-future": {"leave": "Past", "cover": "Present", "loom": "Future"}, "mind-body-spirit": {"crown": "Mind", "lay": "Body", "loom": "Spirit"}, - "desire-obstacle-solution": {"loom": "Desire", "cross": "Obstacle","cover":"Solution"}, + "desire-obstacle-solution": {"loom": "Desire", "cross": "Obstacle","crown":"Solution"}, "waite-smith": {"crown": "Crown", "leave": "Beneath", "cover": "Cover", "cross": "Cross", "loom": "Before", "lay": "Behind"}, } @@ -670,9 +670,10 @@ class MySeaCardDrawTest(FunctionalTest): ) ) - def _draw_one(self, picker, polarity): - """Click a polarity swatch + the FLIP btn that appears → - deposits a card. `polarity` is `'levity'` or `'gravity'`.""" + def _draw_open_modal(self, picker, polarity): + """Click a polarity swatch + the FLIP btn that appears → opens + the SeaDeal stage modal. Returns the stage element so callers + can assert on it before dismissing.""" stack = picker.find_element( By.CSS_SELECTOR, f".sea-deck-stack--{polarity}" ) @@ -680,9 +681,45 @@ class MySeaCardDrawTest(FunctionalTest): flip = self.wait_for( lambda: stack.find_element(By.CSS_SELECTOR, ".sea-stack-ok") ) - # FLIP btn becomes visible after the stack click; wait for it. self.wait_for(lambda: self.assertTrue(flip.is_displayed())) flip.click() + # SeaDeal.openStage shows #id_sea_stage. Wait for the modal. + return self.wait_for( + lambda: self._stage_visible() + ) + + def _stage_visible(self): + stage = self.browser.find_element(By.CSS_SELECTOR, "#id_sea_stage") + if not stage.is_displayed(): + raise AssertionError("sea-stage not visible after FLIP click") + return stage + + def _dismiss_modal(self): + """Click the stage backdrop → SeaDeal._hideStage → modal hides + + slot gains `.--visible` (thumbnail fades in). + + Uses `execute_script` to dispatch the click rather than a native + Selenium `.click()` — `.sea-stage-content` overlays the backdrop + visually (centered card + stat block), so Selenium reports + ElementClickInterceptedException for a direct click. This is + the documented Selenium-limitation exception per the TDD skill; + the actual backdrop-click → close behaviour is Jasmine-tested + in [[SeaDealSpec.js]] / "Backdrop click closes the stage".""" + self.browser.execute_script( + "document.querySelector('#id_sea_stage .sea-stage-backdrop').click();" + ) + self.wait_for( + lambda: self.assertFalse( + self.browser.find_element(By.CSS_SELECTOR, "#id_sea_stage").is_displayed() + ) + ) + + def _draw_one(self, picker, polarity): + """Full single-draw cycle: open modal + dismiss it. Used by FTs + that need to deposit multiple cards in sequence (the stage + backdrop blocks subsequent deck-stack clicks).""" + self._draw_open_modal(picker, polarity) + self._dismiss_modal() # ── Test 1 ─────────────────────────────────────────────────────────────── @@ -890,3 +927,71 @@ class MySeaCardDrawTest(FunctionalTest): hint = picker.find_element(By.CSS_SELECTOR, ".sea-reversal-hint") self.assertIn("25", hint.text) self.assertIn("reversal", hint.text.lower()) + + # ── Test (modal bug fix) ──────────────────────────────────────────────── + + def test_flip_click_opens_portaled_stage_modal(self): + """Bug fix (2026-05-19): the user-reported missing modal. After + clicking the deck stack + the FLIP btn that appears, SeaDeal. + openStage should fire — showing `#id_sea_stage` (position-fixed + full-viewport portal) above everything else. Before the fix the + slot got filled directly at opacity 0 → 'thumbnail summarily + disappears'. Now: modal opens; slot stays at `--filled` but + `--visible` is NOT added yet (waits for backdrop dismiss).""" + picker = self._enter_picker_phase() + stage = self._draw_open_modal(picker, "levity") + # Stage card carries the drawn card's data — non-empty corner rank. + rank = stage.find_element( + By.CSS_SELECTOR, ".sea-stage-card .fan-card-corner--tl .fan-corner-rank" + ) + self.assertTrue(rank.text.strip(), "stage card should display the drawn card's corner rank") + # Slot in the cross is in `.--filled` state but the thumbnail is + # invisible until the modal dismisses (the bug we're guarding). + slot = picker.find_element( + By.CSS_SELECTOR, ".sea-pos-lay .sea-card-slot.sea-card-slot--filled" + ) + self.assertNotIn( + "sea-card-slot--visible", slot.get_attribute("class"), + "slot should still be in pre-reveal opacity-0 state while modal is open", + ) + + # ── Test (modal bug fix, dismiss reveal) ─────────────────────────────── + + def test_backdrop_click_dismisses_modal_and_reveals_thumbnail(self): + """Bug fix part 2: clicking the `.sea-stage-backdrop` closes the + modal AND adds `.sea-card-slot--visible` to the deposited slot, + making the thumbnail fade in. Confirms the user-reported 'card + appears where the slot was' behavior post-dismiss.""" + picker = self._enter_picker_phase() + self._draw_open_modal(picker, "levity") + self._dismiss_modal() + slot = picker.find_element( + By.CSS_SELECTOR, ".sea-pos-lay .sea-card-slot.sea-card-slot--filled" + ) + self.assertIn( + "sea-card-slot--visible", slot.get_attribute("class"), + "post-dismiss, the slot should fade in via `.--visible`", + ) + + # ── Test (modal bug fix, stat block populates) ───────────────────────── + + def test_modal_stage_renders_stat_block_dom_contract(self): + """SeaDeal._populate populates the stat-block keyword `