163 lines
7.1 KiB
Python
163 lines
7.1 KiB
Python
|
|
"""FTs for the Game Sign (a.k.a. My Significator) picker + billboard applet.
|
||
|
|
|
||
|
|
Sprint 4a of [[project-my-sea-roadmap]]. The picker lives at
|
||
|
|
`/billboard/my-sign/` — solo lift of the room's sig-select grid (no
|
||
|
|
countdown / polarity / multi-user). Selection persists on
|
||
|
|
User.significator + User.significator_reversed. "Significator" remains
|
||
|
|
the storage-layer term + room sig-select context; this billboard surface
|
||
|
|
is branded "Sign" / "Game Sign".
|
||
|
|
"""
|
||
|
|
from selenium.webdriver.common.by import By
|
||
|
|
|
||
|
|
from .base import FunctionalTest
|
||
|
|
from apps.applets.models import Applet
|
||
|
|
from apps.epic.models import personal_sig_cards
|
||
|
|
from apps.lyric.models import User
|
||
|
|
|
||
|
|
|
||
|
|
def _seed_my_sign_applet():
|
||
|
|
Applet.objects.get_or_create(
|
||
|
|
slug="my-sign",
|
||
|
|
defaults={"name": "Game Sign", "context": "billboard",
|
||
|
|
"default_visible": True, "grid_cols": 4, "grid_rows": 6},
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class MySignPickerTest(FunctionalTest):
|
||
|
|
"""Happy-path picker: a user with the Earthman deck equipped lands at
|
||
|
|
/billboard/my-sign/, picks a card, clicks SAVE SIGN, and sees the sig
|
||
|
|
propagate to the Game Sign applet on /billboard/."""
|
||
|
|
|
||
|
|
# StaticLiveServerTestCase → TransactionTestCase flushes DB between tests,
|
||
|
|
# wiping migration-seeded DeckVariant + TarotCard rows. Without this flag,
|
||
|
|
# personal_sig_cards(user) returns [] because the signal that auto-equips
|
||
|
|
# Earthman can't find the deck. See [[feedback_transactiontestcase_flush]].
|
||
|
|
serialized_rollback = True
|
||
|
|
|
||
|
|
def setUp(self):
|
||
|
|
super().setUp()
|
||
|
|
_seed_my_sign_applet()
|
||
|
|
# Seed the rest of the billboard applets so /billboard/ renders
|
||
|
|
# without missing-applet errors.
|
||
|
|
for slug, name in [
|
||
|
|
("my-scrolls", "My Scrolls"),
|
||
|
|
("my-buds", "My Buds"),
|
||
|
|
("most-recent-scroll", "Most Recent Scroll"),
|
||
|
|
]:
|
||
|
|
Applet.objects.get_or_create(
|
||
|
|
slug=slug, defaults={"name": name, "context": "billboard"},
|
||
|
|
)
|
||
|
|
self.email = "sig@test.io"
|
||
|
|
self.gamer = User.objects.create(email=self.email)
|
||
|
|
# post_save signal auto-equips Earthman. Picker uses personal_sig_cards
|
||
|
|
# (= 16 middle arcana + Major 0 & 1, filtered by Note unlocks) so the
|
||
|
|
# target must come from that subset, not the full deck.
|
||
|
|
sig_pile = personal_sig_cards(self.gamer)
|
||
|
|
self.target_card = sig_pile[0] if sig_pile else None
|
||
|
|
self.assertIsNotNone(
|
||
|
|
self.target_card,
|
||
|
|
"personal_sig_cards(user) returned no cards — check Earthman seed"
|
||
|
|
" + DeckVariant fixture availability in the FT DB.",
|
||
|
|
)
|
||
|
|
|
||
|
|
# ── Test 1 ───────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def test_picker_renders_card_grid_from_equipped_deck(self):
|
||
|
|
"""GET /billboard/my-sign/ → page renders w. a card grid + the page
|
||
|
|
wordmark reads "Game Sign", populated by the user's equipped_deck."""
|
||
|
|
self.create_pre_authenticated_session(self.email)
|
||
|
|
self.browser.get(self.live_server_url + "/billboard/my-sign/")
|
||
|
|
# Wordmark. The h2 letter-splitter (base.html) wraps each character
|
||
|
|
# in its own <span>, so Selenium's `.text` joins them w. newlines —
|
||
|
|
# strip whitespace before the substring check.
|
||
|
|
self.wait_for(
|
||
|
|
lambda: self.assertIn(
|
||
|
|
"GAMESIGN",
|
||
|
|
"".join(
|
||
|
|
self.browser.find_element(By.CSS_SELECTOR, "h2").text.upper().split()
|
||
|
|
),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
# Target card present in the grid. The data-card-id selector itself
|
||
|
|
# is the assertion — find_element raises if absent. Avoid asserting
|
||
|
|
# against `.fan-corner-rank .text` (CSS hides it via font-size or
|
||
|
|
# similar, so Selenium returns "").
|
||
|
|
self.wait_for(
|
||
|
|
lambda: self.browser.find_element(
|
||
|
|
By.CSS_SELECTOR,
|
||
|
|
f'.my-sign-deck-grid .sig-card[data-card-id="{self.target_card.id}"]',
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# ── Test 2 ───────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def test_pick_card_then_save_persists_choice_and_shows_in_applet(self):
|
||
|
|
"""Click card → SAVE SIGN btn enables → click → DB updated → applet
|
||
|
|
on /billboard/ shows the chosen card."""
|
||
|
|
self.create_pre_authenticated_session(self.email)
|
||
|
|
self.browser.get(self.live_server_url + "/billboard/my-sign/")
|
||
|
|
|
||
|
|
# Click target card
|
||
|
|
card_el = self.wait_for(
|
||
|
|
lambda: self.browser.find_element(
|
||
|
|
By.CSS_SELECTOR,
|
||
|
|
f'.my-sign-deck-grid .sig-card[data-card-id="{self.target_card.id}"]',
|
||
|
|
)
|
||
|
|
)
|
||
|
|
self.browser.execute_script("arguments[0].click()", card_el)
|
||
|
|
|
||
|
|
# SAVE SIGN should be enabled now
|
||
|
|
save_btn = self.wait_for(
|
||
|
|
lambda: self.browser.find_element(By.ID, "id_save_sign_btn")
|
||
|
|
)
|
||
|
|
self.wait_for(
|
||
|
|
lambda: self.assertFalse(
|
||
|
|
save_btn.get_attribute("disabled"),
|
||
|
|
"SAVE SIGN should be enabled after card click",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# Hidden card_id input should match the clicked card
|
||
|
|
self.assertEqual(
|
||
|
|
str(self.target_card.id),
|
||
|
|
self.browser.find_element(By.ID, "id_save_sign_card_id").get_attribute("value"),
|
||
|
|
)
|
||
|
|
|
||
|
|
# Save → DB updated
|
||
|
|
self.browser.execute_script("arguments[0].click()", save_btn)
|
||
|
|
self.wait_for(
|
||
|
|
lambda: self.gamer.refresh_from_db() or self.assertEqual(
|
||
|
|
self.gamer.significator_id, self.target_card.id,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# Navigate to /billboard/ → applet shows the saved card. Pin by
|
||
|
|
# data-card-id (same reasoning as test 1 re: CSS-hidden corner rank).
|
||
|
|
self.browser.get(self.live_server_url + "/billboard/")
|
||
|
|
self.wait_for(
|
||
|
|
lambda: self.browser.find_element(
|
||
|
|
By.CSS_SELECTOR,
|
||
|
|
f'#id_applet_my_sign .my-sign-applet-card[data-card-id="{self.target_card.id}"]',
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
# ── Test 3 ───────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def test_applet_renders_blank_state_when_no_sig_chosen(self):
|
||
|
|
"""Fresh user with no significator → applet shows the empty-state
|
||
|
|
copy, no card."""
|
||
|
|
self.create_pre_authenticated_session(self.email)
|
||
|
|
self.browser.get(self.live_server_url + "/billboard/")
|
||
|
|
empty = self.wait_for(
|
||
|
|
lambda: self.browser.find_element(
|
||
|
|
By.CSS_SELECTOR, "#id_applet_my_sign .my-sign-applet-empty"
|
||
|
|
)
|
||
|
|
)
|
||
|
|
self.assertIn("No sign chosen", empty.text)
|
||
|
|
self.assertEqual(
|
||
|
|
len(self.browser.find_elements(
|
||
|
|
By.CSS_SELECTOR, "#id_applet_my_sign .my-sign-applet-card"
|
||
|
|
)),
|
||
|
|
0,
|
||
|
|
)
|