diff --git a/src/apps/epic/static/apps/epic/sea.js b/src/apps/epic/static/apps/epic/sea.js index 4921375..732d47a 100644 --- a/src/apps/epic/static/apps/epic/sea.js +++ b/src/apps/epic/static/apps/epic/sea.js @@ -112,6 +112,19 @@ var SeaDeal = (function () { _showStage(isLevity); } + // Like `openStage` but DOESN'T show the stage modal — used by AUTO + // DRAW (my_sea.html) to place cards quietly while keeping them + // clickable later. The overlay click handler reads `_seaHand[pos]`; + // without an entry here, click silently no-ops (the user-reported + // bug fixed 2026-05-21). Routing slot fill thru SeaDeal's internal + // `_fillSlot` (instead of the inline shim) also ensures + // `dataset.posKey` stays consistent w. the manual-FLIP path + // (selector form like ".sea-pos-cover", not raw "cover"). + function register(card, posSelector, isLevity) { + _seaHand[posSelector] = { card: card, isLevity: isLevity }; + _fillSlot(posSelector, card, isLevity); + } + // ── Init ────────────────────────────────────────────────────────────────── function init() { @@ -239,6 +252,7 @@ var SeaDeal = (function () { return { openStage: openStage, + register: register, resetHand: resetHand, reinit: init, // call after overlay is injected into the DOM _testInit: function () { diff --git a/src/functional_tests/test_game_my_sea.py b/src/functional_tests/test_game_my_sea.py index fbca23b..beebc3f 100644 --- a/src/functional_tests/test_game_my_sea.py +++ b/src/functional_tests/test_game_my_sea.py @@ -15,6 +15,14 @@ from apps.epic.models import personal_sig_cards from apps.lyric.models import User +def _count_filled_slots(picker): + """Count `.sea-card-slot--filled` elements inside a picker container. + Module-level so multiple test classes can use it without inheriting.""" + return len( + picker.find_elements(By.CSS_SELECTOR, ".sea-card-slot--filled") + ) + + def _seed_gameboard_applets(): """My Sea + the rest of the gameboard applets so /gameboard/ renders without missing-applet errors during the applet-side assertions. @@ -840,6 +848,60 @@ class MySeaCardDrawTest(FunctionalTest): ) self.assertIn("GATE", action_btn.text.upper()) + # ── Test 5b — AUTO DRAW slot click reopens preview modal ──────────────── + + def test_auto_drawn_slots_can_reopen_stage_modal_on_click(self): + """Iter-6c bug-fix (user-reported 2026-05-21): cards placed by + AUTO DRAW were silently un-clickable — the inline `_fillSlot` + bypassed `SeaDeal.openStage` so SeaDeal's `_seaHand` dict was + never populated for those slots, and the overlay click handler + short-circuits on `if (!_seaHand[pos]) return;` → no modal. + + Reproduce: draw one card manually (sets `_seaHand[lay]`), AUTO + DRAW the rest (sets `_seaHand[lay]` still, but NOT cover/crown + if the bug exists), then click an auto-drawn slot — should + re-open the stage modal w. that card's preview. + + Fix landed: `SeaDeal.register(card, posSelector, isLevity)` + public method populates `_seaHand` + delegates to SeaDeal's + internal `_fillSlot` (so `dataset.posKey` stays consistent w. + the manual-FLIP path). AUTO DRAW now calls register, not the + inline shim.""" + picker = self._enter_picker_phase() + # Manual draw: lay (first position in SAO order). + self._draw_one(picker, "levity") + self.assertEqual(_count_filled_slots(picker), 1) + + # AUTO DRAW the remaining 2 (cover + crown). + action_btn = picker.find_element(By.CSS_SELECTOR, "#id_sea_action_btn") + action_btn.click() + confirm = self.wait_for( + lambda: self.browser.find_element( + By.CSS_SELECTOR, "#id_guard_portal.active .guard-yes" + ) + ) + confirm.click() + # Wait for hand-complete transition (action btn → GATE VIEW). + self.wait_for( + lambda: self.assertEqual(action_btn.get_attribute("data-state"), "gate-view") + ) + # All 3 slots filled. + self.wait_for(lambda: self.assertEqual(_count_filled_slots(picker), 3)) + + # Click an AUTO-drawn slot (cover — second in SAO draw order, so + # placed by AUTO DRAW). Two-tap pattern: first click focuses, + # second click opens the modal (per SeaDeal's overlay handler). + cover_slot = picker.find_element( + By.CSS_SELECTOR, ".sea-pos-cover .sea-card-slot--filled" + ) + cover_slot.click() + cover_slot.click() + # Stage modal must open w. the card preview. + stage = self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_sea_stage") + ) + self.wait_for(lambda: self.assertTrue(stage.is_displayed())) + # ── Test 6 ─────────────────────────────────────────────────────────────── def test_del_btn_is_disabled_until_hand_complete(self): diff --git a/src/templates/apps/gameboard/my_sea.html b/src/templates/apps/gameboard/my_sea.html index 8cf4876..340651a 100644 --- a/src/templates/apps/gameboard/my_sea.html +++ b/src/templates/apps/gameboard/my_sea.html @@ -591,7 +591,20 @@ ); if (stack) _showOk(stack); setTimeout(function () { - _fillSlot(e.posName, e.card, e.isLevity); + // Route thru SeaDeal.register (not the inline + // `_fillSlot`) so SeaDeal's `_seaHand` dict + // gets the entry — the overlay click handler + // reads from there, so without registration + // the auto-drawn slots are silently un- + // clickable (user-reported bug, 2026-05-21). + // Fallback to the inline shim only if sea.js + // hasn't loaded yet (defensive — script load + // order makes this unlikely). + if (window.SeaDeal && window.SeaDeal.register) { + SeaDeal.register(e.card, '.sea-pos-' + e.posName, e.isLevity); + } else { + _fillSlot(e.posName, e.card, e.isLevity); + } cross.querySelector('.sea-pos-' + e.posName + ' .sea-card-slot') .classList.add('sea-card-slot--visible'); _filled++;