room SCROLL applet: live async refresh over WebSocket (not just on page reload)
The room's scroll-of-events feed only updated on refresh — a gamer watching the SCROLL view never saw a co-player's deposit / role pick / sig appear. Now every recorded GameEvent nudges all open room sockets to re-fetch the feed. - drama.models.record() broadcasts a `scroll_update` to the `room_<id>` group via transaction.on_commit — so the live re-fetch sees the committed row, and a rolled-back TestCase never fires it (zero overhead / channel-layer traffic for the plain IT suite). _broadcast_scroll_update is fully guarded: a missing/unreachable channel layer must NEVER break event recording (falls back to refresh-to-update). One central hook covers every event writer, current + future. - RoomConsumer gains a `scroll_update` relay handler (same one-liner shape as gate_update / turn_changed). - New `scroll_status` view + url (epic:scroll_status, room/<id>/scroll/status) renders JUST core/_partials/_scroll.html with the same events/viewer/scroll_position context as room_view's inline paint, so the swapped feed is identical. - room-scroll.js listens for `room:scroll_update`, fetches the partial, swaps #id_drama_scroll, then re-applies the saved Frame/Redact filter + restarts the buffer dots on the fresh nodes. URL comes from .room-page[data-scroll-status-url]. Refactored the dots + filter into re-runnable helpers; existing behavior (title reel IO, filter form, localStorage) preserved. TDD: - drama RecordBroadcast ITs: record() schedules the broadcast on commit (captureOnCommitCallbacks execute=True) and NOT before commit. - RoomConsumer relays scroll_update (InMemory layer, WebsocketCommunicator). - ScrollStatusViewTest: endpoint renders the feed section, reflects the latest events, is the bare partial (no navbar/aperture chrome). 544 drama+epic ITs green — the on_commit hook is inert under TestCase, so no existing event-writer test regressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3493,6 +3493,47 @@ class RoomScrollOfEventsTest(TestCase):
|
||||
self.assertNotIn("gr-word--scroll", content)
|
||||
|
||||
|
||||
class ScrollStatusViewTest(TestCase):
|
||||
"""`scroll_status` renders JUST the scroll-of-events feed partial — the
|
||||
endpoint room-scroll.js re-fetches on a `scroll_update` WS nudge to refresh
|
||||
the SCROLL applet live (no reload). Same feed the room page paints inline."""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(email="founder@test.io", username="disco")
|
||||
self.client.force_login(self.user)
|
||||
self.room = Room.objects.create(
|
||||
name="Willawonky", owner=self.user,
|
||||
gate_status=Room.OPEN, table_status=Room.ROLE_SELECT,
|
||||
)
|
||||
GameEvent.objects.create(
|
||||
room=self.room, actor=self.user, verb=GameEvent.SLOT_FILLED,
|
||||
data={"token_type": "carte", "token_display": "Carte Blanche",
|
||||
"slot_number": 1, "renewal_days": 7},
|
||||
)
|
||||
self.url = reverse("epic:scroll_status", kwargs={"room_id": self.room.id})
|
||||
|
||||
def test_renders_the_scroll_feed_section(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "id_drama_scroll")
|
||||
self.assertContains(response, "drama-event")
|
||||
|
||||
def test_feed_reflects_latest_events(self):
|
||||
# A freshly-recorded event shows up on the next fetch (the live-refresh
|
||||
# contract) without re-rendering the whole room page.
|
||||
from apps.drama.models import record
|
||||
record(self.room, GameEvent.ROLE_SELECTED, actor=self.user, role="PC")
|
||||
content = self.client.get(self.url).content.decode()
|
||||
# One `.drama-event-body` per row (the bare class appears 3×/row).
|
||||
self.assertEqual(content.count("drama-event-body"), 2)
|
||||
|
||||
def test_only_partial_not_full_page(self):
|
||||
# It is the bare feed section — no room chrome (navbar / aperture).
|
||||
content = self.client.get(self.url).content.decode()
|
||||
self.assertNotIn("room-aperture", content)
|
||||
self.assertNotIn("navbar", content)
|
||||
|
||||
|
||||
class RoomViewsCarouselTest(TestCase):
|
||||
"""The scroll pane becomes a horizontal carousel of 5 views (ATLAS /
|
||||
SCROLL / POST / CHAT / PULSE — [[project-room-game-views-carousel]]).
|
||||
|
||||
Reference in New Issue
Block a user