FTs: unified Sig-stage felt/stat-block (single-browser) + live SCROLL refresh (channels)

End-to-end coverage for this session's two shipped features.

SigStageUnifiedTest (FunctionalTest, no WS — both pass locally):
- the sig stage renders INSIDE .room-hex-pane on green --duoUser felt
  (.has-sig-stage), the overlay is a descendant of the hex pane, the dark
  .sig-backdrop is gone, and the overlay bg is not a translucent-black wash;
- OK'ing a card freezes the stage and reveals the DRY _stat_face.html —
  .stat-face-title + .stat-chip-rank populate (the old reduced block had
  neither; proves the populateStatExtras wiring).

RoomScrollLiveRefreshTest (ChannelsFunctionalTest, @tag channels):
- with the room open, a server-side record() of a new GameEvent grows the
  feed (#id_drama_scroll .drama-event 1 → 2) WITHOUT a reload, via the
  record() on_commit broadcast → RoomConsumer.scroll_update relay →
  room-scroll.js re-fetch+swap. Validated in the CI channels stage (needs a
  cross-process channel layer); the plumbing is already green via the
  consumer-relay + record-hook + scroll_status ITs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-03 03:19:12 -04:00
parent 8ca3f79561
commit 0e4101ce95
2 changed files with 120 additions and 2 deletions

View File

@@ -6,11 +6,12 @@ the hex. No partial scroll — `scroll-snap-stop: always` enforces the binary.
FT bucket: game_room. Built to DRY-template onto my_sea next.
"""
from django.test import tag
from selenium.webdriver.common.by import By
from .base import FunctionalTest
from .base import FunctionalTest, ChannelsFunctionalTest
from .room_page import _equip_earthman_deck, _fill_room_via_orm
from apps.drama.models import GameEvent
from apps.drama.models import GameEvent, record
from apps.epic.models import Room
from apps.lyric.models import User
@@ -189,3 +190,54 @@ class RoomScrollOfEventsTest(FunctionalTest):
By.CSS_SELECTOR,
"#id_drama_scroll .drama-event[data-label='frame']",
).is_displayed())
@tag("channels")
class RoomScrollLiveRefreshTest(ChannelsFunctionalTest):
"""The SCROLL applet refreshes live over WebSocket — no page reload. A
GameEvent recorded by any actor nudges every open room socket (record() →
on_commit broadcast → RoomConsumer.scroll_update relay → room-scroll.js
re-fetches `core/_partials/_scroll.html` + swaps #id_drama_scroll), so an
open feed grows on its own. Needs daphne + a real channel layer (channels
stage)."""
EMAILS = [
"disco@test.io", "amigo@test.io", "bud@test.io",
"pal@test.io", "dude@test.io", "bro@test.io",
]
def setUp(self):
super().setUp()
self.viewer = User.objects.create(email="disco@test.io", username="disco")
_equip_earthman_deck(self.viewer)
self.room = Room.objects.create(name="Willawonky", owner=self.viewer)
_fill_room_via_orm(self.room, self.EMAILS)
# One seed line so the feed isn't empty at open.
record(self.room, GameEvent.SLOT_FILLED, actor=self.viewer,
slot_number=1, token_type="carte", token_display="Carte Blanche",
renewal_days=7)
self.room.table_status = Room.ROLE_SELECT
self.room.gate_status = Room.OPEN
self.room.save()
def _rows(self):
return len(self.browser.find_elements(
By.CSS_SELECTOR, "#id_drama_scroll .drama-event"))
def test_feed_grows_live_when_an_event_is_recorded(self):
self.create_pre_authenticated_session("disco@test.io")
self.browser.set_window_size(820, 600)
self.browser.get(self.live_server_url + f"/gameboard/room/{self.room.id}/")
# Wait for the room WebSocket to actually open (room.js sets _roomSocket).
self.wait_for(lambda: self.assertEqual(
self.browser.execute_script(
"return window._roomSocket ? window._roomSocket.readyState : -1;"),
1))
self.wait_for(lambda: self.assertEqual(self._rows(), 1))
# Another action is recorded server-side → broadcasts scroll_update on
# commit; the open feed must grow WITHOUT a reload.
record(self.room, GameEvent.SLOT_FILLED, actor=self.viewer,
slot_number=2, token_type="carte", token_display="Carte Blanche",
renewal_days=7)
self.wait_for(lambda: self.assertEqual(self._rows(), 2))