import lxml.html
from datetime import timedelta
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from apps.applets.models import Applet, UserApplet
from apps.epic.models import DeckVariant, Room, TableSeat
from apps.lyric.models import Token, User
class GameboardViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="gamer@test.io")
self.client.force_login(self.user)
Applet.objects.get_or_create(slug="new-game", defaults={"name": "New Game", "context": "gameboard"})
Applet.objects.get_or_create(slug="my-games", defaults={"name": "My Games", "context": "gameboard"})
Applet.objects.get_or_create(slug="game-kit", defaults={"name": "Game Kit", "context": "gameboard"})
response = self.client.get("/gameboard/")
self.parsed = lxml.html.fromstring(response.content)
def test_gameboard_requires_login(self):
self.client.logout()
response = self.client.get("/gameboard/")
self.assertRedirects(
response, "/?next=/gameboard/", fetch_redirect_response=False
)
def test_gameboard_renders(self):
response = self.client.get("/gameboard/")
self.assertEqual(response.status_code, 200)
def test_gameboard_shows_my_games_applet(self):
[_] = self.parsed.cssselect("#id_applet_my_games")
def test_gameboard_shows_new_game_applet(self):
[_] = self.parsed.cssselect("#id_applet_new_game")
def test_gameboard_shows_my_sea_applet(self):
# Sprint 3 of the My Sea roadmap — applet shell only; sigs/sea/draw
# flow lands in later sprints. Seeded via migration 0008.
[_] = self.parsed.cssselect("#id_applet_my_sea")
def test_my_sea_applet_renders_sign_gate_for_user_without_sig(self):
# Sprint 4b — user with no significator sees the Look!-formatted
# gate (mirror of the standalone page), not the draw UX.
[_gate] = self.parsed.cssselect(
"#id_applet_my_sea .my-sea-sign-gate--applet"
)
# Draw-state nodes are suppressed while the gate is up.
self.assertEqual(
len(self.parsed.cssselect("#id_applet_my_sea .my-sea-empty")), 0,
)
self.assertEqual(
len(self.parsed.cssselect("#id_applet_my_sea .my-sea-card")), 0,
)
def test_my_sea_applet_renders_empty_state_for_user_with_sig_no_draws(self):
# Sig set + no saved draws → the scroll container hosts a single
# placeholder line ("No draws yet."), no card cells, no gate.
from apps.epic.models import personal_sig_cards
sig_pile = personal_sig_cards(self.user)
self.user.significator = sig_pile[0]
self.user.save()
response = self.client.get("/gameboard/")
parsed = lxml.html.fromstring(response.content)
[empty] = parsed.cssselect("#id_applet_my_sea .my-sea-empty")
self.assertIn("No draws yet", empty.text_content())
self.assertEqual(
len(parsed.cssselect("#id_applet_my_sea .my-sea-card")), 0,
)
self.assertEqual(
len(parsed.cssselect("#id_applet_my_sea .my-sea-sign-gate--applet")),
0,
)
def test_my_sea_applet_header_links_to_my_sea_page(self):
[link] = self.parsed.cssselect("#id_applet_my_sea h2 a")
self.assertEqual(link.get("href"), reverse("my_sea"))
def test_gameboard_shows_game_kit(self):
[_] = self.parsed.cssselect("#id_game_kit")
def test_gameboard_shows_game_gear(self):
[_] = self.parsed.cssselect(".gear-btn")
def test_my_games_has_no_game_items_for_new_user(self):
game_items = self.parsed.cssselect("#id_applet_my_games .game-item")
self.assertEqual(len(game_items), 0)
def test_my_games_row_shows_latest_event_prose_and_ts(self):
from apps.drama.models import GameEvent, record
room = Room.objects.create(name="StampedRoom", owner=self.user)
record(
room, GameEvent.SLOT_FILLED, actor=self.user,
slot_number=1, token_type="coin",
token_display="Coin-on-a-String", renewal_days=7,
)
response = self.client.get("/gameboard/")
body = response.content.decode()
self.assertIn("StampedRoom", body)
# Row carries a ts cell from the recorded event
self.assertRegex(
body,
r'#id_applet_my_games|class="[^"]*row-ts'.replace("#", ""),
)
# A .row-body cell carries some event prose
self.assertRegex(body, r'