136 lines
7.0 KiB
Python
136 lines
7.0 KiB
Python
|
|
"""Default-structure FTs for the Wristband trinket.
|
|||
|
|
|
|||
|
|
BAND is the non-admin variant of PASS — same magical 'Admit All Entry' tooltip
|
|||
|
|
and same never-deposited, stays-equipped behavior, but admin-assigned (not
|
|||
|
|
auto-granted on user creation) and not gated behind `is_staff`. Like PASS, it
|
|||
|
|
doesn't go through the `.token-rails` deposit flow that CARTE / COIN use, so
|
|||
|
|
there's no `data-current-room-name` / In-Use parity work to do.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|||
|
|
from selenium.webdriver.common.by import By
|
|||
|
|
|
|||
|
|
from .base import FunctionalTest
|
|||
|
|
from apps.applets.models import Applet
|
|||
|
|
from apps.lyric.models import Token, User
|
|||
|
|
|
|||
|
|
|
|||
|
|
class WristbandTest(FunctionalTest):
|
|||
|
|
"""BAND is admin-awarded to a non-staff user — coverage pins the
|
|||
|
|
awarded-but-not-auto-equipped state + tooltip prose + DON/DOFF parity."""
|
|||
|
|
|
|||
|
|
def setUp(self):
|
|||
|
|
super().setUp()
|
|||
|
|
for slug, name, cols, rows in [
|
|||
|
|
("new-game", "New Game", 6, 3),
|
|||
|
|
("my-games", "My Games", 6, 3),
|
|||
|
|
("game-kit", "Game Kit", 6, 3),
|
|||
|
|
]:
|
|||
|
|
Applet.objects.get_or_create(
|
|||
|
|
slug=slug,
|
|||
|
|
defaults={
|
|||
|
|
"name": name, "grid_cols": cols,
|
|||
|
|
"grid_rows": rows, "context": "gameboard",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
# Non-staff user — BAND is admin-awarded after creation, NOT auto-granted
|
|||
|
|
# by the post_save signal (the way PASS is for staff users).
|
|||
|
|
self.gamer = User.objects.create(email="bro@test.io", is_staff=False)
|
|||
|
|
self.band = Token.objects.create(user=self.gamer, token_type=Token.BAND)
|
|||
|
|
|
|||
|
|
# ── Test 1 ───────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def test_band_not_auto_equipped_after_award(self):
|
|||
|
|
"""Award diverges from PASS — BAND lands in the wallet but the user
|
|||
|
|
must DON it manually. Confirms `create_wallet_and_tokens` doesn't
|
|||
|
|
auto-equip a freshly-minted BAND (the way it auto-equips PASS for
|
|||
|
|
staff at user-creation)."""
|
|||
|
|
self.gamer.refresh_from_db()
|
|||
|
|
self.assertNotEqual(self.gamer.equipped_trinket_id, self.band.pk)
|
|||
|
|
|
|||
|
|
# ── Test 2 ───────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def test_band_tooltip_renders_full_prose(self):
|
|||
|
|
"""Hover the Wristband token in the Game Kit applet → main tooltip
|
|||
|
|
shows title, description, shoptalk, and expiry."""
|
|||
|
|
self.create_pre_authenticated_session("bro@test.io")
|
|||
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
|||
|
|
|
|||
|
|
band_el = self.browser.find_element(By.ID, "id_kit_wristband")
|
|||
|
|
self.browser.execute_script(
|
|||
|
|
"arguments[0].scrollIntoView({block: 'center'})", band_el
|
|||
|
|
)
|
|||
|
|
ActionChains(self.browser).move_to_element(band_el).perform()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
|||
|
|
)
|
|||
|
|
portal = self.browser.find_element(By.ID, "id_tooltip_portal")
|
|||
|
|
self.assertIn("Wristband", portal.text)
|
|||
|
|
self.assertIn("Admit All Entry", portal.text)
|
|||
|
|
self.assertIn("Unlimited free entry", portal.text) # shoptalk
|
|||
|
|
self.assertIn("BYOB", portal.text)
|
|||
|
|
self.assertIn("no expiry", portal.text)
|
|||
|
|
|
|||
|
|
# ── Test 3 ───────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def test_band_uses_fa_ring_icon(self):
|
|||
|
|
"""Wristband renders w. the fa-ring icon — distinct from PASS's
|
|||
|
|
fa-clipboard, CARTE's fa-money-check, COIN's fa-medal."""
|
|||
|
|
self.create_pre_authenticated_session("bro@test.io")
|
|||
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
|||
|
|
|
|||
|
|
band_el = self.browser.find_element(By.ID, "id_kit_wristband")
|
|||
|
|
icon = band_el.find_element(By.CSS_SELECTOR, "i")
|
|||
|
|
self.assertIn("fa-ring", icon.get_attribute("class"))
|
|||
|
|
|
|||
|
|
# ── Test 4 ───────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def test_equipped_band_shows_equipped_mini_tooltip(self):
|
|||
|
|
"""After DON-ing BAND, mini portal under the main tooltip says
|
|||
|
|
'Equipped' (same shape as PASS's equipped-state mini portal)."""
|
|||
|
|
self.gamer.equipped_trinket = self.band
|
|||
|
|
self.gamer.save()
|
|||
|
|
self.create_pre_authenticated_session("bro@test.io")
|
|||
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
|||
|
|
|
|||
|
|
band_el = self.browser.find_element(By.ID, "id_kit_wristband")
|
|||
|
|
ActionChains(self.browser).move_to_element(band_el).perform()
|
|||
|
|
self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
|||
|
|
)
|
|||
|
|
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
|
|||
|
|
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
|
|||
|
|
self.assertIn("Equipped", mini.text)
|
|||
|
|
|
|||
|
|
# ── Test 5 ───────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def test_equipped_band_shows_doff_active_don_disabled(self):
|
|||
|
|
"""Equipped BAND apparatus: DOFF active (clickable), DON is × disabled.
|
|||
|
|
Symmetric to PASS's equipped state."""
|
|||
|
|
self.gamer.equipped_trinket = self.band
|
|||
|
|
self.gamer.save()
|
|||
|
|
self.create_pre_authenticated_session("bro@test.io")
|
|||
|
|
self.browser.get(self.live_server_url + "/gameboard/")
|
|||
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
|||
|
|
|
|||
|
|
band_el = self.browser.find_element(By.ID, "id_kit_wristband")
|
|||
|
|
ActionChains(self.browser).move_to_element(band_el).perform()
|
|||
|
|
portal = self.wait_for(
|
|||
|
|
lambda: self.browser.find_element(By.ID, "id_tooltip_portal")
|
|||
|
|
)
|
|||
|
|
don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
|
|||
|
|
doff = portal.find_element(By.CSS_SELECTOR, ".btn-unequip")
|
|||
|
|
self.assertIn("btn-disabled", don.get_attribute("class"))
|
|||
|
|
self.assertEqual(don.text, "×")
|
|||
|
|
self.assertNotIn("btn-disabled", doff.get_attribute("class"))
|
|||
|
|
self.assertEqual(doff.text, "DOFF")
|
|||
|
|
|
|||
|
|
# TODO — BAND deposit/admit flow:
|
|||
|
|
# Like PASS, BAND auto-admits any gate w.o going through the rails like
|
|||
|
|
# CARTE / COIN. When the gatekeeper UX for never-deposited trinkets is
|
|||
|
|
# formalized (PASS still has the same TODO open), add tests for the
|
|||
|
|
# "BAND admits 1 slot, stays equipped" flow on both room.html + my_sea.html.
|