burger z-index drop 318→314 to match .gear-btn; FT base dismiss_brief_if_present helper — TDD
Follow-up to 3ca986f. Lands the FT fixes the burger sprint surfaced + tightens the burger's z-stack so the existing kit_btn / bud_btn / bud_panel / dialog naturally cover it on overlap (vs. the explicit opacity-fade rules the first iteration tried).
## Burger z-index drop
`static_src/scss/_burger.scss`:
- `#id_burger_btn` z 318 → 314 (matches the page-level `.gear-btn` z). Below kit_btn + bud_btn (318), bud_panel (317), kit_bag_dialog (316) — so every overlapping surface visually covers the burger when it appears. The earlier `html.bud-open #id_burger_btn { opacity: 0 }` + `html:has(#id_kit_bag_dialog[open]) #id_burger_btn { opacity: 0 }` rules are now redundant + deleted.
- `#id_burger_fan` z 317 → 313 (stays just below burger so burger remains clickable when fan is open).
`static_src/scss/_game-kit.scss`:
- `#id_kit_bag_dialog` z 319 → 316 (reverts an earlier iteration that bumped it above burger). 316 keeps the dialog BELOW kit_btn + bud_btn so those stay visible + clickable when dialog opens — user re-clicks kit_btn to close. Resolves "kit btn disappears when dialog open" reported on iPad portrait.
`static_src/scss/_bud.scss`:
- `html.bud-open #id_kit_btn { opacity: 0 }` now wrapped in `@media (orientation: portrait)`. In landscape kit_btn lives at the TOP of the right sidebar + bud_panel sits at the BOTTOM — no visual conflict, kit stays visible.
## FT base — dismiss_brief_if_present()
`functional_tests/base.py`:
- New helper on both `FunctionalTest` + `ChannelsFunctionalTest`:
```python
def dismiss_brief_if_present(self, banner_selector=".note-banner", browser=None):
```
- Removes any matching Brief banner from the DOM via `execute_script`. No-op if absent. Default selector matches every Brief shape; pass a specific selector to target one kind. DOM-removal rather than NVM-click bypasses the dismiss_url POST flow that some Briefs (FREE/PAID DRAW) wire up — use this when the test cares about the page state AFTER a Brief, not the dismissal mechanics.
## FT — test_trinket_coin_on_a_string.py
Two methods (`test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot`, `test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled`) updated:
1. `self.dismiss_brief_if_present()` right after `wait_for(id_game_kit)`. `coin@test.io` is created fresh w/o a significator, so the `_my_sea_sign_gate_brief.html` auto-spawns on `/gameboard/` + intercepts the create-game btn click. Dismissing the banner clears the runway.
2. `self.wait_for(... dialog.rect["width"] > 50 ...)` after `kit_btn.click()` + before interacting w. tokens inside the dialog. The landscape kit_bag_dialog animates `max-width 0 → 5rem` over 0.25s; the existing wait_for(find_token).click() found the COIN .token in the DOM immediately + raced the animation — Selenium's scrollIntoView fails on a 0-width container. Waiting for the rect to widen past 50px (≈ 3rem) confirms layout has rendered.
## Verification
- All 4 previously-failing FTs from the burger sprint re-run green:
- CarteBlanche.test_carte_blanche_equip_and_multi_slot_gatekeeper (was flaking; passed on retry — pre-existing tooltip-population race, not in scope)
- CoinOnAString.test_coin_deposit_unequips_from_kit_bag_and_fills_one_slot
- CoinOnAString.test_coin_in_use_game_kit_shows_room_attribution_and_btn_disabled
- GatekeeperTest.test_second_gamer_drops_token_into_open_slot
- IT+UT suite still 1356 green (no touches to model/view code).
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:
@@ -138,6 +138,20 @@ class FunctionalTest(StaticLiveServerTestCase):
|
|||||||
b.execute_script("arguments[0].click()", btn)
|
b.execute_script("arguments[0].click()", btn)
|
||||||
self.wait_for(_click)
|
self.wait_for(_click)
|
||||||
|
|
||||||
|
def dismiss_brief_if_present(self, banner_selector=".note-banner", browser=None):
|
||||||
|
"""Remove any Brief banners matching `banner_selector` from the DOM so
|
||||||
|
subsequent clicks aren't intercepted by the slide-down overlay. No-op
|
||||||
|
if no matching banner exists. Default matches every Brief shape; pass
|
||||||
|
a specific selector (e.g. '.my-sea-sign-gate-brief') to target one.
|
||||||
|
|
||||||
|
DOM-removal rather than NVM-click — bypasses the dismiss_url POST
|
||||||
|
flow that some Briefs (FREE/PAID DRAW) wire up; use this when the
|
||||||
|
test cares about the page state AFTER a Brief, not the dismissal."""
|
||||||
|
b = browser or self.browser
|
||||||
|
b.execute_script(
|
||||||
|
f"document.querySelectorAll({banner_selector!r}).forEach(el => el.remove());"
|
||||||
|
)
|
||||||
|
|
||||||
@wait
|
@wait
|
||||||
def wait_to_be_logged_in(self, email):
|
def wait_to_be_logged_in(self, email):
|
||||||
self.browser.find_element(By.CSS_SELECTOR, "#id_logout"),
|
self.browser.find_element(By.CSS_SELECTOR, "#id_logout"),
|
||||||
@@ -225,6 +239,16 @@ class ChannelsFunctionalTest(ChannelsLiveServerTestCase):
|
|||||||
b.execute_script("arguments[0].click()", btn)
|
b.execute_script("arguments[0].click()", btn)
|
||||||
self.wait_for(_click)
|
self.wait_for(_click)
|
||||||
|
|
||||||
|
def dismiss_brief_if_present(self, banner_selector=".note-banner", browser=None):
|
||||||
|
"""Remove any Brief banners matching `banner_selector` from the DOM so
|
||||||
|
subsequent clicks aren't intercepted by the slide-down overlay. No-op
|
||||||
|
if no matching banner exists. See FunctionalTest.dismiss_brief_if_present
|
||||||
|
for the full docstring — mirrored here for ChannelsLiveServerTestCase."""
|
||||||
|
b = browser or self.browser
|
||||||
|
b.execute_script(
|
||||||
|
f"document.querySelectorAll({banner_selector!r}).forEach(el => el.remove());"
|
||||||
|
)
|
||||||
|
|
||||||
def create_pre_authenticated_session(self, email):
|
def create_pre_authenticated_session(self, email):
|
||||||
if self.test_server:
|
if self.test_server:
|
||||||
session_key = create_session_on_server(self.test_server, email)
|
session_key = create_session_on_server(self.test_server, email)
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ class CoinOnAStringTest(FunctionalTest):
|
|||||||
self.create_pre_authenticated_session("coin@test.io")
|
self.create_pre_authenticated_session("coin@test.io")
|
||||||
self.browser.get(self.live_server_url + "/gameboard/")
|
self.browser.get(self.live_server_url + "/gameboard/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
||||||
|
# The my-sea-sign-gate-brief auto-spawns on /gameboard/ for users w/o
|
||||||
|
# a significator (coin@test.io is fresh) + intercepts the create-game
|
||||||
|
# btn click. Dismiss it so subsequent clicks land.
|
||||||
|
self.dismiss_brief_if_present()
|
||||||
|
|
||||||
# Create a new room → land on gatekeeper
|
# Create a new room → land on gatekeeper
|
||||||
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Coin Room")
|
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Coin Room")
|
||||||
@@ -82,6 +86,13 @@ class CoinOnAStringTest(FunctionalTest):
|
|||||||
# Open kit bag, pick COIN, click rails btn — drop_token POSTs w. token_id
|
# Open kit bag, pick COIN, click rails btn — drop_token POSTs w. token_id
|
||||||
# via the kit-bag JS that injects a hidden token_id input before submit.
|
# via the kit-bag JS that injects a hidden token_id input before submit.
|
||||||
self.browser.find_element(By.ID, "id_kit_btn").click()
|
self.browser.find_element(By.ID, "id_kit_btn").click()
|
||||||
|
# Wait for the dialog's max-width animation (landscape: 0→5rem,
|
||||||
|
# portrait: max-height 0→5rem) to finish — clicking the COIN .token
|
||||||
|
# mid-animation fails scrollIntoView on the 0-dimension container.
|
||||||
|
self.wait_for(lambda: self.assertGreater(
|
||||||
|
self.browser.find_element(By.ID, "id_kit_bag_dialog").rect["width"],
|
||||||
|
50,
|
||||||
|
))
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(
|
lambda: self.browser.find_element(
|
||||||
By.CSS_SELECTOR,
|
By.CSS_SELECTOR,
|
||||||
@@ -136,6 +147,7 @@ class CoinOnAStringTest(FunctionalTest):
|
|||||||
self.create_pre_authenticated_session("coin@test.io")
|
self.create_pre_authenticated_session("coin@test.io")
|
||||||
self.browser.get(self.live_server_url + "/gameboard/")
|
self.browser.get(self.live_server_url + "/gameboard/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
||||||
|
self.dismiss_brief_if_present() # sign-gate brief intercepts create-game-btn
|
||||||
|
|
||||||
# Create room, deposit COIN — rails-click puts slot 1 in RESERVED;
|
# Create room, deposit COIN — rails-click puts slot 1 in RESERVED;
|
||||||
# OK btn fills it (debit_token sets coin.current_room = room).
|
# OK btn fills it (debit_token sets coin.current_room = room).
|
||||||
@@ -143,6 +155,13 @@ class CoinOnAStringTest(FunctionalTest):
|
|||||||
self.browser.find_element(By.ID, "id_create_game_btn").click()
|
self.browser.find_element(By.ID, "id_create_game_btn").click()
|
||||||
self.wait_for(lambda: self.assertIn("/gate/", self.browser.current_url))
|
self.wait_for(lambda: self.assertIn("/gate/", self.browser.current_url))
|
||||||
self.browser.find_element(By.ID, "id_kit_btn").click()
|
self.browser.find_element(By.ID, "id_kit_btn").click()
|
||||||
|
# Wait for the dialog's max-width animation (landscape: 0→5rem,
|
||||||
|
# portrait: max-height 0→5rem) to finish — clicking the COIN .token
|
||||||
|
# mid-animation fails scrollIntoView on the 0-dimension container.
|
||||||
|
self.wait_for(lambda: self.assertGreater(
|
||||||
|
self.browser.find_element(By.ID, "id_kit_bag_dialog").rect["width"],
|
||||||
|
50,
|
||||||
|
))
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(
|
lambda: self.browser.find_element(
|
||||||
By.CSS_SELECTOR,
|
By.CSS_SELECTOR,
|
||||||
|
|||||||
@@ -103,6 +103,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// html.bud-open: slide the panel out, fade the kit btn away.
|
// html.bud-open: slide the panel out, fade the kit btn away.
|
||||||
|
// Kit fade is PORTRAIT-only: in landscape kit lives at the TOP of the
|
||||||
|
// right sidebar + bud_panel sits at the BOTTOM (full-width strip), so
|
||||||
|
// they don't visually compete + kit can stay visible.
|
||||||
html.bud-open {
|
html.bud-open {
|
||||||
#id_bud_panel {
|
#id_bud_panel {
|
||||||
transform: scaleX(1);
|
transform: scaleX(1);
|
||||||
@@ -110,9 +113,11 @@ html.bud-open {
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#id_kit_btn {
|
@media (orientation: portrait) {
|
||||||
opacity: 0;
|
#id_kit_btn {
|
||||||
pointer-events: none;
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 4.2rem;
|
bottom: 4.2rem;
|
||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
z-index: 318;
|
// Match .gear-btn page-level z (314) — keeps burger BELOW kit_btn +
|
||||||
|
// bud_btn (both z-318) + bud_panel (z-317) so those naturally cover
|
||||||
|
// burger when they overlap. Dialog (z-316) covers burger too.
|
||||||
|
z-index: 314;
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: rgba(var(--secUser), 1);
|
color: rgba(var(--secUser), 1);
|
||||||
@@ -60,7 +63,7 @@
|
|||||||
left: 2rem; // burger left (0.5) + half-btn (1.5)
|
left: 2rem; // burger left (0.5) + half-btn (1.5)
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
z-index: 317; // below #id_burger_btn so the burger stays clickable
|
z-index: 313; // below #id_burger_btn (314) so burger stays clickable
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
@media (orientation: landscape) {
|
@media (orientation: landscape) {
|
||||||
@@ -119,3 +122,9 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No explicit bud_panel / kit_dialog hide rules needed — burger's z=314
|
||||||
|
// sits BELOW kit_btn / bud_btn (318), bud_panel (317), + kit_dialog (316),
|
||||||
|
// so all those naturally cover the burger when they overlap. Earlier
|
||||||
|
// iterations explicitly faded the burger via opacity; rendered redundant
|
||||||
|
// by the z-index drop.
|
||||||
|
|||||||
@@ -48,10 +48,10 @@
|
|||||||
border: none;
|
border: none;
|
||||||
border-top: 0.1rem solid rgba(var(--quaUser), 1);
|
border-top: 0.1rem solid rgba(var(--quaUser), 1);
|
||||||
background: rgba(var(--priUser), 0.97);
|
background: rgba(var(--priUser), 0.97);
|
||||||
// Above the burger btn (z-318) so the dialog covers it when open in
|
// Below kit_btn + burger_btn (both z-318) so those btns stay visible
|
||||||
// BOTH orientations — burger sits in the bottom-left corner in
|
// when the dialog opens — user can re-click kit_btn to close + the
|
||||||
// portrait + the right sidebar in landscape; the dialog lands atop.
|
// burger fan stays accessible alongside an open dialog.
|
||||||
z-index: 319;
|
z-index: 316;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
// Closed state — portrait: grow from below the footer (max-height)
|
// Closed state — portrait: grow from below the footer (max-height)
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
// Opaque in landscape — the dialog covers the sidebar btns + the
|
// Opaque in landscape — the dialog covers the sidebar btns + the
|
||||||
// burger + bud area when open; transparency would let those btns
|
// burger + bud area when open; transparency would let those btns
|
||||||
// bleed through the dialog content.
|
// bleed through the dialog content.
|
||||||
background: rgba(var(--priUser), 1);
|
background: rgba(var(--priUser), 0.97);
|
||||||
|
|
||||||
// Closed state — width animates from 0 (grows leftward from right)
|
// Closed state — width animates from 0 (grows leftward from right)
|
||||||
max-width: 0;
|
max-width: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user