Files
python-tdd/src/functional_tests/test_navbar.py
Disco DeDisco 0bcc7567bb
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
XL landscape polish: btn-primary sizing, tray from right, footer bg, layout fixes
- .btn-xl removed; .btn-primary absorbs 4rem sizing (same as PICK SIGS/PICK ROLES)
- Landscape navbar .btn-primary: 3rem → 4rem to match base; XL stays 4rem (consistent)
- _button-pad.scss XL: base .btn ×1.2 (2.4rem); .btn-xl block deleted
- _tray.scss XL (≥1800px): portrait-style tray (slides from right, z-95)
- tray.js: _isLandscape() returns false at ≥1800px; portrait code paths run throughout
- Footer sidebar: background-color added so opaque footer masks tray sliding behind it
- Copyright .footer-container: bottom → top in landscape sidebar
- #id_room_menu: right: 2.5rem override in _room.scss XL block (cascade fix)
- navbar-text XL: 0.65rem × 1.2 = 0.78rem
- All landscape media queries: max-width: 1440px cutoff removed (already done prior)
- btn-xl class stripped from all 5 templates; test_navbar.py assertion updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 03:02:37 -04:00

183 lines
7.0 KiB
Python

from django.urls import reverse
from selenium.webdriver.common.by import By
from apps.drama.models import GameEvent, record
from apps.epic.models import Room
from apps.lyric.models import User
from .base import FunctionalTest
def _guard_rect(browser):
"""Return the guard portal's bounding rect (reflects CSS transform)."""
return browser.execute_script(
"return document.getElementById('id_guard_portal').getBoundingClientRect().toJSON()"
)
def _elem_rect(browser, element):
"""Return an element's bounding rect."""
return browser.execute_script(
"return arguments[0].getBoundingClientRect().toJSON()", element
)
class NavbarByeTest(FunctionalTest):
"""
The BYE btn-abandon replaces LOG OUT in the identity group.
It should confirm before logging out and its tooltip must appear below
the button (not above, which would be off-screen in the navbar).
"""
def setUp(self):
super().setUp()
self.create_pre_authenticated_session("disco@test.io")
# ------------------------------------------------------------------ #
# T1 — BYE btn present; "Log Out" text gone #
# ------------------------------------------------------------------ #
def test_bye_btn_replaces_log_out(self):
self.browser.get(self.live_server_url)
self.wait_for(lambda: self.browser.find_element(By.ID, "id_logout"))
logout_btn = self.browser.find_element(By.ID, "id_logout")
self.assertEqual(logout_btn.text, "BYE")
self.assertIn("btn-abandon", logout_btn.get_attribute("class"))
self.assertNotIn("btn-primary", logout_btn.get_attribute("class"))
# Old "Log Out" text nowhere in navbar
navbar = self.browser.find_element(By.CSS_SELECTOR, ".navbar")
self.assertNotIn("Log Out", navbar.text)
# ------------------------------------------------------------------ #
# T2 — BYE tooltip appears below btn #
# ------------------------------------------------------------------ #
def test_bye_tooltip_appears_below_btn(self):
self.browser.get(self.live_server_url)
btn = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_logout")
)
btn_rect = _elem_rect(self.browser, btn)
# Click BYE — guard should become active
self.browser.execute_script("arguments[0].click()", btn)
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_guard_portal.active"
)
)
portal_rect = _guard_rect(self.browser)
self.assertGreaterEqual(
portal_rect["top"],
btn_rect["bottom"] - 2, # 2 px tolerance for sub-pixel rounding
"Guard portal should appear below the BYE btn, not above it",
)
# ------------------------------------------------------------------ #
# T3 — BYE btn logs out on confirm #
# ------------------------------------------------------------------ #
def test_bye_btn_logs_out_on_confirm(self):
self.browser.get(self.live_server_url)
btn = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_logout")
)
self.browser.execute_script("arguments[0].click()", btn)
self.confirm_guard()
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, "input[name=email]")
)
navbar = self.browser.find_element(By.CSS_SELECTOR, ".navbar")
self.assertNotIn("disco@test.io", navbar.text)
# ------------------------------------------------------------------ #
# T4 — No CONT GAME btn when user has no rooms with events #
# ------------------------------------------------------------------ #
def test_cont_game_btn_absent_without_recent_room(self):
self.browser.get(self.live_server_url)
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_logout")
)
cont_game_btns = self.browser.find_elements(By.ID, "id_cont_game")
self.assertEqual(
len(cont_game_btns), 0,
"CONT GAME btn should not appear when user has no rooms with events",
)
class NavbarContGameTest(FunctionalTest):
"""
When the authenticated user has at least one room with a game event the
CONT GAME btn-primary appears in the navbar and navigates to that
room on confirmation. Its tooltip must also appear below the button.
"""
def setUp(self):
super().setUp()
self.create_pre_authenticated_session("disco@test.io")
self.user = User.objects.get(email="disco@test.io")
self.room = Room.objects.create(name="Arena of Peril", owner=self.user)
record(
self.room, GameEvent.SLOT_FILLED, actor=self.user,
slot_number=1, token_type="coin",
token_display="Coin-on-a-String", renewal_days=7,
)
# ------------------------------------------------------------------ #
# T5 — CONT GAME btn present when recent room exists #
# ------------------------------------------------------------------ #
def test_cont_game_btn_present(self):
self.browser.get(self.live_server_url)
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_cont_game")
)
btn = self.browser.find_element(By.ID, "id_cont_game")
self.assertIn("btn-primary", btn.get_attribute("class"))
# ------------------------------------------------------------------ #
# T6 — CONT GAME tooltip appears below btn #
# ------------------------------------------------------------------ #
def test_cont_game_tooltip_appears_below_btn(self):
self.browser.get(self.live_server_url)
btn = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_cont_game")
)
btn_rect = _elem_rect(self.browser, btn)
self.browser.execute_script("arguments[0].click()", btn)
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_guard_portal.active"
)
)
portal_rect = _guard_rect(self.browser)
self.assertGreaterEqual(
portal_rect["top"],
btn_rect["bottom"] - 2,
"Guard portal should appear below the CONT GAME btn, not above it",
)
# ------------------------------------------------------------------ #
# T7 — CONT GAME navigates to the room on confirm #
# ------------------------------------------------------------------ #
def test_cont_game_navigates_to_room_on_confirm(self):
self.browser.get(self.live_server_url)
btn = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_cont_game")
)
self.browser.execute_script("arguments[0].click()", btn)
self.confirm_guard()
self.wait_for(
lambda: self.assertIn(str(self.room.id), self.browser.current_url)
)