role-select UX: tray timing delays, seat/circle state polish, 394 ITs green
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

- _animationPending set before fetch (not in .then()) — blocks WS turn advance during in-flight request
- _placeCardDelay (3s) + _postTrayDelay (3s) give gamer time to see each step; both zeroed by _testReset()
- .role-confirmed class: full-opacity chair after placeCard completes; server-rendered on reload
- Slot circles disappear in join order (slot 1 first) via count-based logic, not role-label matching
- data-active-slot on card-stack; handleTurnChanged writes it for selectRole() to read
- #id_tray_wrap not rendered during gate phase ({% if room.table_status %})
- Tray slide/arc-in slowed to 1s for diagnostics; wobble kept at 0.45s
- Obsolete test_roles_revealed_simultaneously FT removed; T8 tray FT uses ROLE_SELECT room
- Jasmine macrotask flush pattern: await new Promise(r => setTimeout(r, 0)) after fetch .then()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-03-31 00:01:04 -04:00
parent a8592aeaec
commit 736b59b5c0
13 changed files with 833 additions and 400 deletions

View File

@@ -607,20 +607,20 @@ class PositionIndicatorsTest(FunctionalTest):
)
# ------------------------------------------------------------------ #
# Test P1 — 6 position indicators present while gatekeeper is open #
# Test P1 — 6 position circles present in strip alongside gatekeeper #
# ------------------------------------------------------------------ #
def test_position_indicators_visible_alongside_gatekeeper(self):
self.browser.get(self.gate_url)
# Gatekeeper modal is open
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-overlay")
)
# Six .table-position elements are rendered outside the modal
positions = self.browser.find_elements(By.CSS_SELECTOR, ".table-position")
self.assertEqual(len(positions), 6)
for pos in positions:
self.assertTrue(pos.is_displayed())
# Six .gate-slot elements are rendered in .position-strip, outside modal
strip = self.browser.find_element(By.CSS_SELECTOR, ".position-strip")
slots = strip.find_elements(By.CSS_SELECTOR, ".gate-slot")
self.assertEqual(len(slots), 6)
for slot in slots:
self.assertTrue(slot.is_displayed())
# ------------------------------------------------------------------ #
# Test P2 — URL drops /gate/ after pick_roles #
@@ -631,63 +631,57 @@ class PositionIndicatorsTest(FunctionalTest):
"founder@test.io", "amigo@test.io", "bud@test.io",
"pal@test.io", "dude@test.io", "bro@test.io",
])
# Simulate pick_roles having fired: room advances to ROLE_SELECT
self.room.table_status = Room.ROLE_SELECT
self.room.save()
# Navigating to the /gate/ URL should redirect to the plain room URL
self.browser.get(self.gate_url)
expected_url = (
f"{self.live_server_url}/gameboard/room/{self.room.id}/"
)
expected_url = f"{self.live_server_url}/gameboard/room/{self.room.id}/"
self.wait_for(
lambda: self.assertEqual(self.browser.current_url, expected_url)
)
# ------------------------------------------------------------------ #
# Test P3 — Each position has a chair icon and correct role label #
# Test P3 — Gate-slot circles live outside the modal #
# ------------------------------------------------------------------ #
def test_position_shows_chair_icon_and_role_label(self):
SLOT_ROLE_LABELS = {1: "PC", 2: "NC", 3: "EC", 4: "SC", 5: "AC", 6: "BC"}
def test_position_circles_outside_gatekeeper_modal(self):
"""The numbered position circles must NOT be descendants of .gate-modal —
they live in .position-strip which sits above the backdrop."""
self.browser.get(self.gate_url)
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-overlay")
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-modal")
)
for slot_number, role_label in SLOT_ROLE_LABELS.items():
pos = self.browser.find_element(
By.CSS_SELECTOR, f".table-position[data-slot='{slot_number}']"
)
# Chair icon present
self.assertTrue(pos.find_elements(By.CSS_SELECTOR, ".fa-chair"))
# Role label attribute and visible text
self.assertEqual(pos.get_attribute("data-role-label"), role_label)
label_el = pos.find_element(By.CSS_SELECTOR, ".position-role-label")
self.assertEqual(label_el.text.strip(), role_label)
# No .gate-slot inside the modal
modal_slots = self.browser.find_elements(
By.CSS_SELECTOR, ".gate-modal .gate-slot"
)
self.assertEqual(len(modal_slots), 0)
# All 6 live in .position-strip
strip_slots = self.browser.find_elements(
By.CSS_SELECTOR, ".position-strip .gate-slot"
)
self.assertEqual(len(strip_slots), 6)
# ------------------------------------------------------------------ #
# Test P4 — Unoccupied position shows ban icon #
# Test P4 — Each circle displays its slot number #
# ------------------------------------------------------------------ #
def test_unoccupied_position_shows_ban_icon(self):
def test_position_circle_shows_slot_number(self):
self.browser.get(self.gate_url)
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-overlay")
lambda: self.browser.find_element(By.CSS_SELECTOR, ".position-strip")
)
# All slots are empty — every position should have a ban icon
positions = self.browser.find_elements(By.CSS_SELECTOR, ".table-position")
for pos in positions:
self.assertTrue(
pos.find_elements(By.CSS_SELECTOR, ".fa-ban"),
f"Expected .fa-ban on slot {pos.get_attribute('data-slot')}",
for n in range(1, 7):
slot_el = self.browser.find_element(
By.CSS_SELECTOR, f".position-strip .gate-slot[data-slot='{n}']"
)
self.assertIn(str(n), slot_el.text)
# ------------------------------------------------------------------ #
# Test P5 — Occupied position shows check icon after token confirmed #
# Test P5 — Filled slot carries .filled class in strip #
# ------------------------------------------------------------------ #
def test_occupied_position_shows_check_icon_after_token_confirmed(self):
# Slot 1 is filled via ORM
def test_filled_slot_shown_in_strip(self):
from apps.epic.models import GateSlot
slot = self.room.gate_slots.get(slot_number=1)
slot.gamer = self.founder
@@ -696,10 +690,13 @@ class PositionIndicatorsTest(FunctionalTest):
self.browser.get(self.gate_url)
self.wait_for(
lambda: self.browser.find_element(By.CSS_SELECTOR, ".gate-overlay")
lambda: self.browser.find_element(By.CSS_SELECTOR, ".position-strip")
)
pos1 = self.browser.find_element(
By.CSS_SELECTOR, ".table-position[data-slot='1']"
slot1 = self.browser.find_element(
By.CSS_SELECTOR, ".position-strip .gate-slot[data-slot='1']"
)
self.assertTrue(pos1.find_elements(By.CSS_SELECTOR, ".fa-circle-check"))
self.assertFalse(pos1.find_elements(By.CSS_SELECTOR, ".fa-ban"))
self.assertIn("filled", slot1.get_attribute("class"))
slot2 = self.browser.find_element(
By.CSS_SELECTOR, ".position-strip .gate-slot[data-slot='2']"
)
self.assertIn("empty", slot2.get_attribute("class"))