billboard applets: drop billboard- prefix from partials & ids; Most Recent → Most Recent Scroll; room_scroll → scroll — TDD
- Slug renames (mig 0004): billboard-my-scrolls → my-scrolls; billboard-my-contacts → my-contacts; billboard-most-recent → most-recent-scroll (name → "Most Recent Scroll"); billboard-notes → notes - Partial filenames lose the `billboard-` token to mirror dashboard/gameboard convention; element ids follow (id_applet_my_scrolls, id_applet_my_contacts, id_applet_most_recent_scroll, id_applet_notes, id_applet_scroll); .applet-billboard-scroll → .applet-scroll - View fn billboard.views.room_scroll → scroll; template apps/billboard/room_scroll.html → scroll.html (URL name `billboard:scroll` already correct) - ITs + FTs updated to new identifiers; SCSS selectors retitled 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:
@@ -0,0 +1,56 @@
|
|||||||
|
"""Drop the legacy `billboard-` slug prefix from billboard applets and
|
||||||
|
rename Most Recent → Most Recent Scroll.
|
||||||
|
|
||||||
|
The `billboard-` prefix snuck into seed migration 0003 against intent — no
|
||||||
|
other context (dashboard, gameboard, wallet, game-kit) prefixes its applet
|
||||||
|
slugs with the context name, and slugs need to stay portable so users can
|
||||||
|
later rearrange which page hosts which applet.
|
||||||
|
"""
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
RENAMES = [
|
||||||
|
# (old_slug, new_slug, new_name_or_None)
|
||||||
|
('billboard-my-scrolls', 'my-scrolls', None),
|
||||||
|
('billboard-my-contacts', 'my-contacts', None),
|
||||||
|
('billboard-most-recent', 'most-recent-scroll', 'Most Recent Scroll'),
|
||||||
|
('billboard-notes', 'notes', None),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _apply(apps, mapping):
|
||||||
|
Applet = apps.get_model('applets', 'Applet')
|
||||||
|
for old_slug, new_slug, new_name in mapping:
|
||||||
|
try:
|
||||||
|
applet = Applet.objects.get(slug=old_slug)
|
||||||
|
except Applet.DoesNotExist:
|
||||||
|
continue
|
||||||
|
applet.slug = new_slug
|
||||||
|
fields = ['slug']
|
||||||
|
if new_name is not None:
|
||||||
|
applet.name = new_name
|
||||||
|
fields.append('name')
|
||||||
|
applet.save(update_fields=fields)
|
||||||
|
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
_apply(apps, RENAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def backward(apps, schema_editor):
|
||||||
|
inverse = [
|
||||||
|
(new, old, 'Most Recent' if old == 'billboard-most-recent' else None)
|
||||||
|
for (old, new, _) in RENAMES
|
||||||
|
]
|
||||||
|
_apply(apps, inverse)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('applets', '0003_seed_applets'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, backward),
|
||||||
|
]
|
||||||
@@ -12,9 +12,9 @@ from apps.lyric.models import User
|
|||||||
|
|
||||||
def _seed_billboard_applets():
|
def _seed_billboard_applets():
|
||||||
for slug, name, cols, rows in [
|
for slug, name, cols, rows in [
|
||||||
("billboard-my-scrolls", "My Scrolls", 4, 3),
|
("my-scrolls", "My Scrolls", 4, 3),
|
||||||
("billboard-my-contacts", "Contacts", 4, 3),
|
("my-contacts", "Contacts", 4, 3),
|
||||||
("billboard-most-recent", "Most Recent", 8, 6),
|
("most-recent-scroll", "Most Recent Scroll", 8, 6),
|
||||||
]:
|
]:
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
slug=slug,
|
slug=slug,
|
||||||
@@ -36,9 +36,9 @@ class BillboardViewTest(TestCase):
|
|||||||
response = self.client.get("/billboard/")
|
response = self.client.get("/billboard/")
|
||||||
self.assertIn("applets", response.context)
|
self.assertIn("applets", response.context)
|
||||||
slugs = [e["applet"].slug for e in response.context["applets"]]
|
slugs = [e["applet"].slug for e in response.context["applets"]]
|
||||||
self.assertIn("billboard-my-scrolls", slugs)
|
self.assertIn("my-scrolls", slugs)
|
||||||
self.assertIn("billboard-my-contacts", slugs)
|
self.assertIn("my-contacts", slugs)
|
||||||
self.assertIn("billboard-most-recent", slugs)
|
self.assertIn("most-recent-scroll", slugs)
|
||||||
|
|
||||||
def test_passes_my_rooms_context(self):
|
def test_passes_my_rooms_context(self):
|
||||||
room = Room.objects.create(name="Test Room", owner=self.user)
|
room = Room.objects.create(name="Test Room", owner=self.user)
|
||||||
@@ -107,25 +107,25 @@ class ToggleBillboardAppletsTest(TestCase):
|
|||||||
def test_toggle_hides_unchecked_applets(self):
|
def test_toggle_hides_unchecked_applets(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("billboard:toggle_applets"),
|
reverse("billboard:toggle_applets"),
|
||||||
{"applets": ["billboard-my-scrolls"]},
|
{"applets": ["my-scrolls"]},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
from apps.applets.models import UserApplet
|
from apps.applets.models import UserApplet
|
||||||
contacts = Applet.objects.get(slug="billboard-my-contacts")
|
contacts = Applet.objects.get(slug="my-contacts")
|
||||||
ua = UserApplet.objects.get(user=self.user, applet=contacts)
|
ua = UserApplet.objects.get(user=self.user, applet=contacts)
|
||||||
self.assertFalse(ua.visible)
|
self.assertFalse(ua.visible)
|
||||||
|
|
||||||
def test_toggle_returns_partial_on_htmx(self):
|
def test_toggle_returns_partial_on_htmx(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("billboard:toggle_applets"),
|
reverse("billboard:toggle_applets"),
|
||||||
{"applets": ["billboard-my-scrolls"]},
|
{"applets": ["my-scrolls"]},
|
||||||
HTTP_HX_REQUEST="true",
|
HTTP_HX_REQUEST="true",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertTemplateUsed(response, "apps/billboard/_partials/_applets.html")
|
self.assertTemplateUsed(response, "apps/billboard/_partials/_applets.html")
|
||||||
|
|
||||||
def test_htmx_toggle_response_renders_most_recent_with_real_events(self):
|
def test_htmx_toggle_response_renders_most_recent_scroll_with_real_events(self):
|
||||||
# Seed a room + event so Most Recent should render prose, not the empty fallback.
|
# Seed a room + event so Most Recent Scroll renders prose, not the empty fallback.
|
||||||
room = Room.objects.create(name="Sound Chamber", owner=self.user)
|
room = Room.objects.create(name="Sound Chamber", owner=self.user)
|
||||||
record(
|
record(
|
||||||
room, GameEvent.SLOT_FILLED, actor=self.user,
|
room, GameEvent.SLOT_FILLED, actor=self.user,
|
||||||
@@ -135,9 +135,9 @@ class ToggleBillboardAppletsTest(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("billboard:toggle_applets"),
|
reverse("billboard:toggle_applets"),
|
||||||
{"applets": [
|
{"applets": [
|
||||||
"billboard-my-scrolls",
|
"my-scrolls",
|
||||||
"billboard-my-contacts",
|
"my-contacts",
|
||||||
"billboard-most-recent",
|
"most-recent-scroll",
|
||||||
]},
|
]},
|
||||||
HTTP_HX_REQUEST="true",
|
HTTP_HX_REQUEST="true",
|
||||||
)
|
)
|
||||||
@@ -153,7 +153,7 @@ class ToggleBillboardAppletsTest(TestCase):
|
|||||||
# menu exactly once (the wrapper) — never two siblings of the same id.
|
# menu exactly once (the wrapper) — never two siblings of the same id.
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("billboard:toggle_applets"),
|
reverse("billboard:toggle_applets"),
|
||||||
{"applets": ["billboard-my-scrolls"]},
|
{"applets": ["my-scrolls"]},
|
||||||
HTTP_HX_REQUEST="true",
|
HTTP_HX_REQUEST="true",
|
||||||
)
|
)
|
||||||
body = response.content.decode("utf-8")
|
body = response.content.decode("utf-8")
|
||||||
@@ -165,28 +165,28 @@ class ToggleBillboardAppletsTest(TestCase):
|
|||||||
reverse("billboard:toggle_applets"),
|
reverse("billboard:toggle_applets"),
|
||||||
{"applets": [
|
{"applets": [
|
||||||
"new-post", "my-posts",
|
"new-post", "my-posts",
|
||||||
"billboard-my-scrolls",
|
"my-scrolls",
|
||||||
"billboard-most-recent",
|
"most-recent-scroll",
|
||||||
]},
|
]},
|
||||||
HTTP_HX_REQUEST="true",
|
HTTP_HX_REQUEST="true",
|
||||||
)
|
)
|
||||||
# Second toggle: hide Most Recent additionally — Contacts must stay hidden.
|
# Second toggle: hide Most Recent Scroll additionally — Contacts must stay hidden.
|
||||||
self.client.post(
|
self.client.post(
|
||||||
reverse("billboard:toggle_applets"),
|
reverse("billboard:toggle_applets"),
|
||||||
{"applets": [
|
{"applets": [
|
||||||
"new-post", "my-posts",
|
"new-post", "my-posts",
|
||||||
"billboard-my-scrolls",
|
"my-scrolls",
|
||||||
]},
|
]},
|
||||||
HTTP_HX_REQUEST="true",
|
HTTP_HX_REQUEST="true",
|
||||||
)
|
)
|
||||||
from apps.applets.models import UserApplet
|
from apps.applets.models import UserApplet
|
||||||
contacts = Applet.objects.get(slug="billboard-my-contacts")
|
contacts = Applet.objects.get(slug="my-contacts")
|
||||||
most_recent = Applet.objects.get(slug="billboard-most-recent")
|
most_recent_scroll = Applet.objects.get(slug="most-recent-scroll")
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
UserApplet.objects.get(user=self.user, applet=contacts).visible
|
UserApplet.objects.get(user=self.user, applet=contacts).visible
|
||||||
)
|
)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
UserApplet.objects.get(user=self.user, applet=most_recent).visible
|
UserApplet.objects.get(user=self.user, applet=most_recent_scroll).visible
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -201,9 +201,9 @@ class BillscrollViewTest(TestCase):
|
|||||||
token_display="Coin-on-a-String", renewal_days=7,
|
token_display="Coin-on-a-String", renewal_days=7,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_uses_room_scroll_template(self):
|
def test_uses_scroll_template(self):
|
||||||
response = self.client.get(f"/billboard/room/{self.room.id}/scroll/")
|
response = self.client.get(f"/billboard/room/{self.room.id}/scroll/")
|
||||||
self.assertTemplateUsed(response, "apps/billboard/room_scroll.html")
|
self.assertTemplateUsed(response, "apps/billboard/scroll.html")
|
||||||
|
|
||||||
def test_passes_events_context(self):
|
def test_passes_events_context(self):
|
||||||
response = self.client.get(f"/billboard/room/{self.room.id}/scroll/")
|
response = self.client.get(f"/billboard/room/{self.room.id}/scroll/")
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ urlpatterns = [
|
|||||||
path("note/<slug:slug>/set-palette", views.note_set_palette, name="note_set_palette"),
|
path("note/<slug:slug>/set-palette", views.note_set_palette, name="note_set_palette"),
|
||||||
path("note/<slug:slug>/don", views.don_title, name="don_title"),
|
path("note/<slug:slug>/don", views.don_title, name="don_title"),
|
||||||
path("note/<slug:slug>/doff", views.doff_title, name="doff_title"),
|
path("note/<slug:slug>/doff", views.doff_title, name="doff_title"),
|
||||||
path("room/<uuid:room_id>/scroll/", views.room_scroll, name="scroll"),
|
path("room/<uuid:room_id>/scroll/", views.scroll, name="scroll"),
|
||||||
path("room/<uuid:room_id>/scroll-position/", views.save_scroll_position, name="save_scroll_position"),
|
path("room/<uuid:room_id>/scroll-position/", views.save_scroll_position, name="save_scroll_position"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -85,11 +85,11 @@ def toggle_billboard_applets(request):
|
|||||||
|
|
||||||
|
|
||||||
@login_required(login_url="/")
|
@login_required(login_url="/")
|
||||||
def room_scroll(request, room_id):
|
def scroll(request, room_id):
|
||||||
room = Room.objects.get(id=room_id)
|
room = Room.objects.get(id=room_id)
|
||||||
events = room.events.select_related("actor").all()
|
events = room.events.select_related("actor").all()
|
||||||
sp = ScrollPosition.objects.filter(user=request.user, room=room).first()
|
sp = ScrollPosition.objects.filter(user=request.user, room=room).first()
|
||||||
return render(request, "apps/billboard/room_scroll.html", {
|
return render(request, "apps/billboard/scroll.html", {
|
||||||
"room": room,
|
"room": room,
|
||||||
"events": events,
|
"events": events,
|
||||||
"viewer": request.user,
|
"viewer": request.user,
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class StargazerNoteFromDashboardTest(FunctionalTest):
|
|||||||
defaults={"name": "Palettes", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
defaults={"name": "Palettes", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
||||||
)
|
)
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
slug="billboard-notes",
|
slug="notes",
|
||||||
defaults={"name": "Note", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
defaults={"name": "Note", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
||||||
)
|
)
|
||||||
self.gamer = User.objects.create(email="stargazer@test.io")
|
self.gamer = User.objects.create(email="stargazer@test.io")
|
||||||
@@ -323,7 +323,7 @@ class StargazerNoteFromSkyPageTest(FunctionalTest):
|
|||||||
defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
||||||
)
|
)
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
slug="billboard-notes",
|
slug="notes",
|
||||||
defaults={"name": "Note", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
defaults={"name": "Note", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
||||||
)
|
)
|
||||||
self.gamer = User.objects.create(email="stargazer@test.io")
|
self.gamer = User.objects.create(email="stargazer@test.io")
|
||||||
@@ -413,7 +413,7 @@ class NoteEquipTitleTest(FunctionalTest):
|
|||||||
super().setUp()
|
super().setUp()
|
||||||
self.browser.set_window_size(800, 1200)
|
self.browser.set_window_size(800, 1200)
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
slug="billboard-notes",
|
slug="notes",
|
||||||
defaults={"name": "My Notes", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
defaults={"name": "My Notes", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
||||||
)
|
)
|
||||||
self.gamer = User.objects.create(email="stargazer@test.io")
|
self.gamer = User.objects.create(email="stargazer@test.io")
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ class BillboardScrollTest(FunctionalTest):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
for slug, name, cols, rows in [
|
for slug, name, cols, rows in [
|
||||||
("billboard-my-scrolls", "My Scrolls", 4, 3),
|
("my-scrolls", "My Scrolls", 4, 3),
|
||||||
("billboard-my-contacts", "Contacts", 4, 3),
|
("my-contacts", "Contacts", 4, 3),
|
||||||
("billboard-most-recent", "Most Recent", 8, 6),
|
("most-recent-scroll", "Most Recent Scroll", 8, 6),
|
||||||
]:
|
]:
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
slug=slug,
|
slug=slug,
|
||||||
@@ -193,7 +193,7 @@ class BillscrollPositionTest(FunctionalTest):
|
|||||||
class BillboardAppletsTest(FunctionalTest):
|
class BillboardAppletsTest(FunctionalTest):
|
||||||
"""
|
"""
|
||||||
FT: billboard page renders three applets in the grid — My Scrolls,
|
FT: billboard page renders three applets in the grid — My Scrolls,
|
||||||
My Contacts, and Most Recent — with a functioning gear menu.
|
My Contacts, and Most Recent Scroll — with a functioning gear menu.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -202,9 +202,9 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
self.founder = User.objects.create(email="founder@test.io")
|
self.founder = User.objects.create(email="founder@test.io")
|
||||||
self.room = Room.objects.create(name="Arcane Assembly", owner=self.founder)
|
self.room = Room.objects.create(name="Arcane Assembly", owner=self.founder)
|
||||||
for slug, name, cols, rows in [
|
for slug, name, cols, rows in [
|
||||||
("billboard-my-scrolls", "My Scrolls", 4, 3),
|
("my-scrolls", "My Scrolls", 4, 3),
|
||||||
("billboard-my-contacts", "Contacts", 4, 3),
|
("my-contacts", "Contacts", 4, 3),
|
||||||
("billboard-most-recent", "Most Recent", 8, 6),
|
("most-recent-scroll", "Most Recent Scroll", 8, 6),
|
||||||
]:
|
]:
|
||||||
Applet.objects.get_or_create(
|
Applet.objects.get_or_create(
|
||||||
slug=slug,
|
slug=slug,
|
||||||
@@ -217,10 +217,10 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
self.browser.get(self.live_server_url + "/billboard/")
|
self.browser.get(self.live_server_url + "/billboard/")
|
||||||
# 2. Assert all three applet sections present
|
# 2. Assert all three applet sections present
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(By.ID, "id_applet_billboard_my_scrolls")
|
lambda: self.browser.find_element(By.ID, "id_applet_my_scrolls")
|
||||||
)
|
)
|
||||||
self.browser.find_element(By.ID, "id_applet_billboard_my_contacts")
|
self.browser.find_element(By.ID, "id_applet_my_contacts")
|
||||||
self.browser.find_element(By.ID, "id_applet_billboard_most_recent")
|
self.browser.find_element(By.ID, "id_applet_most_recent_scroll")
|
||||||
|
|
||||||
def test_billboard_my_scrolls_lists_rooms(self):
|
def test_billboard_my_scrolls_lists_rooms(self):
|
||||||
# 1. Log in, navigate to billboard
|
# 1. Log in, navigate to billboard
|
||||||
@@ -230,7 +230,7 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.assertIn(
|
lambda: self.assertIn(
|
||||||
"Arcane Assembly",
|
"Arcane Assembly",
|
||||||
self.browser.find_element(By.ID, "id_applet_billboard_my_scrolls").text,
|
self.browser.find_element(By.ID, "id_applet_my_scrolls").text,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
self.wait_for_slow(lambda: self.assertTrue(menu.is_displayed()))
|
self.wait_for_slow(lambda: self.assertTrue(menu.is_displayed()))
|
||||||
|
|
||||||
def test_toggling_applets_keeps_content_and_persists_per_applet(self):
|
def test_toggling_applets_keeps_content_and_persists_per_applet(self):
|
||||||
# Seed an event so Most Recent renders prose, not the empty fallback
|
# Seed an event so Most Recent Scroll renders prose, not the empty fallback
|
||||||
record(
|
record(
|
||||||
self.room, GameEvent.SLOT_FILLED, actor=self.founder,
|
self.room, GameEvent.SLOT_FILLED, actor=self.founder,
|
||||||
slot_number=1, token_type="coin",
|
slot_number=1, token_type="coin",
|
||||||
@@ -260,15 +260,15 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
self.create_pre_authenticated_session("founder@test.io")
|
self.create_pre_authenticated_session("founder@test.io")
|
||||||
self.browser.get(self.live_server_url + "/billboard/")
|
self.browser.get(self.live_server_url + "/billboard/")
|
||||||
|
|
||||||
# All three applets visible; Most Recent shows event prose, My Scrolls shows the room
|
# All three applets visible; Most Recent Scroll shows event prose, My Scrolls shows the room
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(By.ID, "id_applet_billboard_most_recent")
|
lambda: self.browser.find_element(By.ID, "id_applet_most_recent_scroll")
|
||||||
)
|
)
|
||||||
most_recent = self.browser.find_element(By.ID, "id_applet_billboard_most_recent")
|
most_recent_scroll = self.browser.find_element(By.ID, "id_applet_most_recent_scroll")
|
||||||
self.assertIn("Coin-on-a-String", most_recent.text)
|
self.assertIn("Coin-on-a-String", most_recent_scroll.text)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"Arcane Assembly",
|
"Arcane Assembly",
|
||||||
self.browser.find_element(By.ID, "id_applet_billboard_my_scrolls").text,
|
self.browser.find_element(By.ID, "id_applet_my_scrolls").text,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Open gear, uncheck Contacts, click OK
|
# Open gear, uncheck Contacts, click OK
|
||||||
@@ -278,26 +278,26 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
lambda: self.browser.find_element(By.ID, "id_billboard_applet_menu")
|
lambda: self.browser.find_element(By.ID, "id_billboard_applet_menu")
|
||||||
)
|
)
|
||||||
contacts_cb = menu.find_element(
|
contacts_cb = menu.find_element(
|
||||||
By.CSS_SELECTOR, "input[value='billboard-my-contacts']"
|
By.CSS_SELECTOR, "input[value='my-contacts']"
|
||||||
)
|
)
|
||||||
self.browser.execute_script("arguments[0].click()", contacts_cb)
|
self.browser.execute_script("arguments[0].click()", contacts_cb)
|
||||||
menu.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
|
menu.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
|
||||||
|
|
||||||
# Contacts is hidden; Most Recent + My Scrolls keep their content (bug #2)
|
# Contacts is hidden; Most Recent Scroll + My Scrolls keep their content (bug #2)
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.assertEqual(
|
lambda: self.assertEqual(
|
||||||
self.browser.find_elements(By.ID, "id_applet_billboard_my_contacts"),
|
self.browser.find_elements(By.ID, "id_applet_my_contacts"),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
most_recent = self.browser.find_element(By.ID, "id_applet_billboard_most_recent")
|
most_recent_scroll = self.browser.find_element(By.ID, "id_applet_most_recent_scroll")
|
||||||
self.assertIn("Coin-on-a-String", most_recent.text)
|
self.assertIn("Coin-on-a-String", most_recent_scroll.text)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"Arcane Assembly",
|
"Arcane Assembly",
|
||||||
self.browser.find_element(By.ID, "id_applet_billboard_my_scrolls").text,
|
self.browser.find_element(By.ID, "id_applet_my_scrolls").text,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Second toggle: hide Most Recent. Contacts must NOT come back (bug #1)
|
# Second toggle: hide Most Recent Scroll. Contacts must NOT come back (bug #1)
|
||||||
gear = self.browser.find_element(By.CSS_SELECTOR, ".billboard-page .gear-btn")
|
gear = self.browser.find_element(By.CSS_SELECTOR, ".billboard-page .gear-btn")
|
||||||
self.browser.execute_script("arguments[0].click()", gear)
|
self.browser.execute_script("arguments[0].click()", gear)
|
||||||
menu = self.wait_for(
|
menu = self.wait_for(
|
||||||
@@ -305,38 +305,38 @@ class BillboardAppletsTest(FunctionalTest):
|
|||||||
)
|
)
|
||||||
# The freshly-rendered menu must reflect DB state (Contacts unchecked)
|
# The freshly-rendered menu must reflect DB state (Contacts unchecked)
|
||||||
contacts_cb = menu.find_element(
|
contacts_cb = menu.find_element(
|
||||||
By.CSS_SELECTOR, "input[value='billboard-my-contacts']"
|
By.CSS_SELECTOR, "input[value='my-contacts']"
|
||||||
)
|
)
|
||||||
self.assertFalse(contacts_cb.is_selected())
|
self.assertFalse(contacts_cb.is_selected())
|
||||||
most_recent_cb = menu.find_element(
|
most_recent_scroll_cb = menu.find_element(
|
||||||
By.CSS_SELECTOR, "input[value='billboard-most-recent']"
|
By.CSS_SELECTOR, "input[value='most-recent-scroll']"
|
||||||
)
|
)
|
||||||
self.browser.execute_script("arguments[0].click()", most_recent_cb)
|
self.browser.execute_script("arguments[0].click()", most_recent_scroll_cb)
|
||||||
menu.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
|
menu.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
|
||||||
|
|
||||||
# Most Recent gone; Contacts still hidden (the stale-form bug would re-show it)
|
# Most Recent Scroll gone; Contacts still hidden (the stale-form bug would re-show it)
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.assertEqual(
|
lambda: self.assertEqual(
|
||||||
self.browser.find_elements(By.ID, "id_applet_billboard_most_recent"),
|
self.browser.find_elements(By.ID, "id_applet_most_recent_scroll"),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.browser.find_elements(By.ID, "id_applet_billboard_my_contacts"),
|
self.browser.find_elements(By.ID, "id_applet_my_contacts"),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
# And after a hard refresh both stay hidden, menu reflects DB
|
# And after a hard refresh both stay hidden, menu reflects DB
|
||||||
self.browser.refresh()
|
self.browser.refresh()
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(By.ID, "id_applet_billboard_my_scrolls")
|
lambda: self.browser.find_element(By.ID, "id_applet_my_scrolls")
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.browser.find_elements(By.ID, "id_applet_billboard_my_contacts"),
|
self.browser.find_elements(By.ID, "id_applet_my_contacts"),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.browser.find_elements(By.ID, "id_applet_billboard_most_recent"),
|
self.browser.find_elements(By.ID, "id_applet_most_recent_scroll"),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ class BillscrollAppletsTest(FunctionalTest):
|
|||||||
)
|
)
|
||||||
# 2. The full-width applet section is present
|
# 2. The full-width applet section is present
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(By.ID, "id_applet_billboard_scroll")
|
lambda: self.browser.find_element(By.ID, "id_applet_scroll")
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_billscroll_applet_contains_drama_events(self):
|
def test_billscroll_applet_contains_drama_events(self):
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ INVITEE_EMAIL = "invitee@test.io"
|
|||||||
def _applets():
|
def _applets():
|
||||||
for slug, name, ctx in [
|
for slug, name, ctx in [
|
||||||
("my-posts", "My Posts", "billboard"),
|
("my-posts", "My Posts", "billboard"),
|
||||||
("billboard-my-scrolls", "My Scrolls","billboard"),
|
("my-scrolls", "My Scrolls","billboard"),
|
||||||
]:
|
]:
|
||||||
Applet.objects.get_or_create(slug=slug, defaults={"name": name, "context": ctx})
|
Applet.objects.get_or_create(slug=slug, defaults={"name": name, "context": ctx})
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ body.page-billscroll {
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
|
|
||||||
// The single scroll applet stretches to fill the remaining aperture
|
// The single scroll applet stretches to fill the remaining aperture
|
||||||
.applet-billboard-scroll {
|
.applet-scroll {
|
||||||
@extend %applet-box;
|
@extend %applet-box;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -113,20 +113,20 @@ body.page-billscroll {
|
|||||||
|
|
||||||
// ── Billboard applet placement ─────────────────────────────────────────────
|
// ── Billboard applet placement ─────────────────────────────────────────────
|
||||||
// Left column (4-wide): My Scrolls → Contacts → Notes stacked.
|
// Left column (4-wide): My Scrolls → Contacts → Notes stacked.
|
||||||
// Right column (8-wide): Most Recent spans full height.
|
// Right column (8-wide): Most Recent Scroll spans full height.
|
||||||
// Portrait override (container query) restores stacked full-width layout.
|
// Portrait override (container query) restores stacked full-width layout.
|
||||||
|
|
||||||
#id_billboard_applets_container {
|
#id_billboard_applets_container {
|
||||||
#id_applet_billboard_my_scrolls { grid-column: 1 / span 4; grid-row: 1 / span 3; }
|
#id_applet_my_scrolls { grid-column: 1 / span 4; grid-row: 1 / span 3; }
|
||||||
#id_applet_billboard_my_contacts { grid-column: 1 / span 4; grid-row: 4 / span 3; }
|
#id_applet_my_contacts { grid-column: 1 / span 4; grid-row: 4 / span 3; }
|
||||||
#id_applet_billboard_notes { grid-column: 1 / span 4; grid-row: 7 / span 4; }
|
#id_applet_notes { grid-column: 1 / span 4; grid-row: 7 / span 4; }
|
||||||
#id_applet_billboard_most_recent { grid-column: 5 / span 8; grid-row: 1 / span 10; }
|
#id_applet_most_recent_scroll { grid-column: 5 / span 8; grid-row: 1 / span 10; }
|
||||||
|
|
||||||
@container (max-width: 550px) {
|
@container (max-width: 550px) {
|
||||||
#id_applet_billboard_my_scrolls,
|
#id_applet_my_scrolls,
|
||||||
#id_applet_billboard_my_contacts,
|
#id_applet_my_contacts,
|
||||||
#id_applet_billboard_notes,
|
#id_applet_notes,
|
||||||
#id_applet_billboard_most_recent {
|
#id_applet_most_recent_scroll {
|
||||||
grid-column: 1 / span 12;
|
grid-column: 1 / span 12;
|
||||||
grid-row: span var(--applet-rows, 3);
|
grid-row: span var(--applet-rows, 3);
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ body.page-billscroll {
|
|||||||
|
|
||||||
// ── Notes applet — vertical title ─────────────────────────────────────────
|
// ── Notes applet — vertical title ─────────────────────────────────────────
|
||||||
|
|
||||||
#id_applet_billboard_notes {
|
#id_applet_notes {
|
||||||
h2 {
|
h2 {
|
||||||
writing-mode: vertical-rl;
|
writing-mode: vertical-rl;
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
@@ -147,9 +147,9 @@ body.page-billscroll {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Most Recent applet — scrollable drama feed ─────────────────────────────
|
// ── Most Recent Scroll applet — scrollable drama feed ─────────────────────
|
||||||
|
|
||||||
#id_applet_billboard_most_recent {
|
#id_applet_most_recent_scroll {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ body.page-billscroll {
|
|||||||
|
|
||||||
// ── My Scrolls list ────────────────────────────────────────────────────────
|
// ── My Scrolls list ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#id_applet_billboard_my_scrolls {
|
#id_applet_my_scrolls {
|
||||||
.scroll-list {
|
.scroll-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{% load lyric_extras %}
|
{% load lyric_extras %}
|
||||||
<section
|
<section
|
||||||
id="id_applet_billboard_most_recent"
|
id="id_applet_most_recent_scroll"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2>Most Recent</h2>
|
<h2>Most Recent Scroll</h2>
|
||||||
{% if recent_room %}
|
{% if recent_room %}
|
||||||
<a href="{% url 'billboard:scroll' recent_room.id %}" class="most-recent-room-link">{{ recent_room.name }}</a>
|
<a href="{% url 'billboard:scroll' recent_room.id %}" class="most-recent-room-link">{{ recent_room.name }}</a>
|
||||||
<section id="id_drama_scroll" class="drama-scroll">
|
<section id="id_drama_scroll" class="drama-scroll">
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<section
|
<section
|
||||||
id="id_applet_billboard_my_contacts"
|
id="id_applet_my_contacts"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2>Contacts</h2>
|
<h2>Contacts</h2>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<section
|
<section
|
||||||
id="id_applet_billboard_my_scrolls"
|
id="id_applet_my_scrolls"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2>My Scrolls</h2>
|
<h2>My Scrolls</h2>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<section
|
<section
|
||||||
id="id_applet_billboard_notes"
|
id="id_applet_notes"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2><a href="/billboard/my-notes/">My Notes</a></h2>
|
<h2><a href="/billboard/my-notes/">My Notes</a></h2>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<section id="id_applet_billboard_scroll" class="applet-billboard-scroll">
|
<section id="id_applet_scroll" class="applet-scroll">
|
||||||
<h2>{{ room.name }}</h2>
|
<h2>{{ room.name }}</h2>
|
||||||
{% include "core/_partials/_scroll.html" %}
|
{% include "core/_partials/_scroll.html" %}
|
||||||
</section>
|
</section>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% include "apps/billboard/_partials/_applet-billboard-scroll.html" %}
|
{% include "apps/billboard/_partials/_applet-scroll.html" %}
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
Reference in New Issue
Block a user