"""Rendered-HTML assertions for pages converted to the Python layout/components.
These go beyond `test_paths_return_200`: they assert that the `Page()` document
wrapper and the Python component bodies emit the right structure, and — most
importantly — that nothing is double-escaped (the recurring failure mode when a
`SafeText` loses its safe marker and renders as `<tag>`).
"""
from datetime import datetime
from zoneinfo import ZoneInfo
from django.conf import settings
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from games.models import Game, GameStatusChange, Platform, Purchase, Session
ZONEINFO = ZoneInfo(settings.TIME_ZONE)
# If any of these appear in output, a SafeText lost its safe marker somewhere.
_ESCAPED_TAG_MARKERS = [
"<a",
"<div",
"<span",
"<button",
"<input",
"<li",
]
class RenderedPagesTest(TestCase):
def setUp(self) -> None:
self.user = User.objects.create_superuser(
username="testuser", email="test@example.com", password="testpass"
)
self.client.force_login(self.user)
self.platform = Platform.objects.create(name="Test Platform", icon="test")
self.game = Game.objects.create(name="Test Game", platform=self.platform)
self.purchase = Purchase.objects.create(
date_purchased=datetime(2022, 9, 26, 14, 58, tzinfo=ZONEINFO),
platform=self.platform,
)
self.purchase.games.add(self.game)
self.session = Session.objects.create(
game=self.game,
timestamp_start=datetime(2022, 9, 26, 15, 0, tzinfo=ZONEINFO),
timestamp_end=datetime(2022, 9, 26, 16, 0, tzinfo=ZONEINFO),
)
def get(self, url_name, *args):
return self.client.get(reverse(url_name, args=args), follow=True)
def assertNoEscapedTags(self, html):
for marker in _ESCAPED_TAG_MARKERS:
self.assertNotIn(
marker, html, f"Found double-escaped markup ({marker!r}) in output"
)
# --- scripts auto-collected from component media (Phase 4) ---------------
def test_list_page_auto_loads_widget_scripts(self):
"""The games list view passes no scripts= argument; the filter bar's
components declare their JS and Page() collects it."""
html = self.get("games:list_games").content.decode()
self.assertIn("js/filter_bar.js", html)
self.assertIn("js/search_select.js", html)
self.assertIn("js/range_slider.js", html)
def test_stats_page_auto_loads_datepicker(self):
"""YearPicker declares the datepicker UMD bundle as media; the stats
view no longer hoists it by hand."""
html = self.get("games:stats_alltime").content.decode()
self.assertIn("js/datepicker.umd.js", html)
# --- layout wrapper ------------------------------------------------------
def test_page_layout_wrapper(self):
"""A converted page is wrapped in the full Page() document."""
html = self.get("games:list_playevents").content.decode()
for marker in [
"",
"