Files
python-tdd/src/functional_tests/test_dash_wallet.py

244 lines
12 KiB
Python
Raw Normal View History

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
feat: wallet Tokens applet shows CARTE + BAND + COIN + PASS independently — Chunk 1 of the Shop applet rollout per [[project-wallet-shop-expansion]]. Pre-Chunk-1 the `_applet-wallet-tokens.html` template used a `{% if pass_token %} ... {% elif band %} ... {% elif coin %}` chain that suppressed 2-of-3 trinkets from the wallet whenever the user held a higher-priority one — bad UX since the equip slot is now the user's opt-in for trinket-as-token use per [[feedback-equip-slot-gates-trinket-use]], so ALL owned trinkets need visibility. Fix: dropped the elif chain → independent `{% if %}` blocks for PASS / BAND / COIN; added a new CARTE block w. `fa-money-check` icon mirroring the Game Kit's render. View context (`apps.dashboard.views.wallet` + `:toggle_wallet_applets`) now passes `carte = user.tokens.filter(token_type=Token.CARTE).first()` alongside the existing pass/band/coin keys (no `is_staff` filter — CARTE has no admin gate). TDD — new `WalletTokensAppletAllTrinketsVisibleTest` (9 ITs): 6 pin individual `#id_<token>` visibility for a staff user holding all 5 types, 2 pin view-context shape (`carte` + `band` keys), 1 pins CARTE-on-non-staff. New FT `test_wallet_tokens_applet_shows_all_owned_trinket_types` reads BAND/CARTE `.tt` `innerHTML` directly (no hover ceremony — already covered by the COIN/FREE hover paths in `test_new_user_wallet_shows_starting_balances`) to pin the new template blocks server-render full tooltip prose. **Trap caught mid-build**: initial multi-line `{# ... #}` Django comment leaked as plain text into the rendered DOM (Django's hash-comment is single-line only), pushing the COIN tile off-screen + breaking the existing hover FT. Switched to `{% comment %}...{% endcomment %}`. Captured in [[feedback-django-comments-single-line-only]] — symptom signature: previously-passing Selenium hover times out + screendump shows literal `{# ...` text near the broken element. 1169 IT/UT + 6 wallet FTs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:07:42 -04:00
from apps.lyric.models import Token, User
class WalletDisplayTest(FunctionalTest):
def setUp(self):
super().setUp()
Applet.objects.get_or_create(slug="wallet", defaults={"name": "Wallet"})
for slug, name, cols, rows in [
("wallet-balances", "Wallet Balances", 3, 3),
("wallet-tokens", "Wallet Tokens", 3, 3),
("wallet-payment", "Payment Methods", 6, 3),
]:
Applet.objects.get_or_create(
slug=slug,
defaults={"name": name, "grid_cols": cols, "grid_rows": rows, "context": "wallet"},
)
def test_new_user_wallet_shows_starting_balances(self):
# 1. Log in as new user
self.create_pre_authenticated_session("capman@test.io")
# 2. Navigate to dashboard
self.browser.get(self.live_server_url)
# 3. Find wallet applet summary card
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_applet_wallet")
)
# 4. Click thru to full wallet page via manage link
self.browser.find_element(
By.CSS_SELECTOR, "#id_applet_wallet a.wallet-manage-link"
).click()
# 5. Assert user landed on wallet page
self.wait_for(
lambda: self.assertRegex(self.browser.current_url, r"/dashboard/wallet/$")
)
# 6. Assert writs balance shows 144 (complimentary bundle on signup)
self.wait_for(
lambda: self.assertEqual(
"144",
self.browser.find_element(By.ID, "id_writs_balance").text,
)
)
# 7. Assert esteem balance shows 0
self.assertEqual(
"0",
self.browser.find_element(By.ID, "id_esteem_balance").text,
)
# 8. Assert Coin-on-a-String token element present
coin = self.browser.find_element(By.ID, "id_coin_on_a_string")
# 9. Hover over it; assert tooltip appears w. name, entry text, 'no expiry'
ActionChains(self.browser).move_to_element(coin).perform()
self.wait_for(
lambda: self.assertTrue(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
)
)
coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
self.assertIn("Coin-on-a-String", coin_tooltip)
self.assertIn("Admit 1 Entry", coin_tooltip)
self.assertIn("no expiry", coin_tooltip)
# 10. Assert ×1 Free Token present (complimentary on signup)
free_token = self.browser.find_element(By.ID, "id_free_token")
# 11. Hover over it; assert tooltip shows name, entry text, expiry date
ActionChains(self.browser).move_to_element(free_token).perform()
self.wait_for(
lambda: self.assertTrue(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
)
)
free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
self.assertIn("Free Token", free_tooltip)
self.assertIn("Admit 1 Entry", free_tooltip)
self.assertIn("Expires", free_tooltip)
def test_wallet_payment_section_renders(self):
# 1. Log in, navigate directly to wallet page
self.create_pre_authenticated_session("capman@test.io")
self.browser.get(self.live_server_url + "/dashboard/wallet/")
# 2. Assert saved payment methods section present
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_payment_methods")
)
# 3. Assert the add-payment-method button is visible
self.browser.find_element(By.ID, "id_add_payment_method")
# 4. Assert Stripe Payment Element mount point exists
self.browser.find_element(By.ID, "id_stripe_payment_element")
def test_user_can_save_a_payment_method(self):
# 1. Log in, navigate to wallet page
self.create_pre_authenticated_session("capman@test.io")
self.browser.get(self.live_server_url + "/dashboard/wallet/")
# 2. Click add-payment-method btn
self.browser.find_element(By.ID, "id_add_payment_method").click()
# 3. Wait for Stripe Payment Element iframe to appear inside mount point
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, "#id_stripe_payment_element iframe"
)
)
# 4. Switch into Stripe iframe to interact w. card fields
stripe_frame = self.browser.find_element(
By.CSS_SELECTOR, "#id_stripe_payment_element iframe"
)
self.browser.switch_to.frame(stripe_frame)
# 4a. Wait for card inputs to render inside iframe
self.wait_for(
lambda: self.browser.find_element(
By.CSS_SELECTOR, 'input[placeholder="1234 1234 1234 1234"]'
)
)
# 5. Fill in Stripe test card details
self.browser.find_element(
By.CSS_SELECTOR, 'input[placeholder="1234 1234 1234 1234"]'
).send_keys("4242424242424242")
self.browser.find_element(
By.CSS_SELECTOR, 'input[placeholder="MM / YY"]'
).send_keys("12 / 26")
self.browser.find_element(
By.CSS_SELECTOR, 'input[placeholder="CVC"]'
).send_keys("424")
self.browser.find_element(
By.CSS_SELECTOR, 'input[placeholder="12345"]'
).send_keys("42424")
# 6. Return to main doc & submit form
self.browser.switch_to.default_content()
self.wait_for(
lambda: self.assertFalse(
self.browser.find_element(By.ID, "id_save_payment_method").get_attribute("hidden")
)
)
self.browser.find_element(By.ID, "id_save_payment_method").click()
# 7. Wait for saved card to appear in payment methods list
# Assert last 4 digits shown (Stripe confirmSetup + server round-trip can be slow)
self.wait_for_slow(
lambda: self.assertIn(
"4242",
self.browser.find_element(By.ID, "id_payment_methods").text,
)
)
def test_user_can_cancel_adding_payment_method(self):
# 1. Log in, navigate to wallet page
self.create_pre_authenticated_session("capman@test.io")
self.browser.get(self.live_server_url + "/dashboard/wallet/")
# 2. Click Add Payment Method
self.wait_for_slow(
lambda: self.browser.find_element(By.ID, "id_add_payment_method")
).click()
# 3. Wait for Cancel button to appear (visible after setup-intent fetch returns)
self.wait_for_slow(
lambda: self.assertFalse(
self.browser.find_element(By.ID, "id_cancel_payment_method").get_attribute("hidden")
)
)
# 3a. Assert applet expanded to 15 rows
rows = self.browser.execute_script(
"return document.getElementById('id_payment_methods')"
".style.getPropertyValue('--applet-rows').trim()"
)
self.assertEqual(rows, '15')
# 4. Click Cancel
self.browser.find_element(By.ID, "id_cancel_payment_method").click()
# 5. Assert Cancel + Save buttons are hidden again
self.wait_for_slow(
lambda: self.assertTrue(
self.browser.find_element(By.ID, "id_cancel_payment_method").get_attribute("hidden")
)
)
self.assertTrue(
self.browser.find_element(By.ID, "id_save_payment_method").get_attribute("hidden")
)
# 6. Assert applet collapses back to 3 grid rows
rows = self.browser.execute_script(
"return document.getElementById('id_payment_methods')"
".style.getPropertyValue('--applet-rows').trim()"
)
self.assertEqual(rows, '3')
feat: wallet Tokens applet shows CARTE + BAND + COIN + PASS independently — Chunk 1 of the Shop applet rollout per [[project-wallet-shop-expansion]]. Pre-Chunk-1 the `_applet-wallet-tokens.html` template used a `{% if pass_token %} ... {% elif band %} ... {% elif coin %}` chain that suppressed 2-of-3 trinkets from the wallet whenever the user held a higher-priority one — bad UX since the equip slot is now the user's opt-in for trinket-as-token use per [[feedback-equip-slot-gates-trinket-use]], so ALL owned trinkets need visibility. Fix: dropped the elif chain → independent `{% if %}` blocks for PASS / BAND / COIN; added a new CARTE block w. `fa-money-check` icon mirroring the Game Kit's render. View context (`apps.dashboard.views.wallet` + `:toggle_wallet_applets`) now passes `carte = user.tokens.filter(token_type=Token.CARTE).first()` alongside the existing pass/band/coin keys (no `is_staff` filter — CARTE has no admin gate). TDD — new `WalletTokensAppletAllTrinketsVisibleTest` (9 ITs): 6 pin individual `#id_<token>` visibility for a staff user holding all 5 types, 2 pin view-context shape (`carte` + `band` keys), 1 pins CARTE-on-non-staff. New FT `test_wallet_tokens_applet_shows_all_owned_trinket_types` reads BAND/CARTE `.tt` `innerHTML` directly (no hover ceremony — already covered by the COIN/FREE hover paths in `test_new_user_wallet_shows_starting_balances`) to pin the new template blocks server-render full tooltip prose. **Trap caught mid-build**: initial multi-line `{# ... #}` Django comment leaked as plain text into the rendered DOM (Django's hash-comment is single-line only), pushing the COIN tile off-screen + breaking the existing hover FT. Switched to `{% comment %}...{% endcomment %}`. Captured in [[feedback-django-comments-single-line-only]] — symptom signature: previously-passing Selenium hover times out + screendump shows literal `{# ...` text near the broken element. 1169 IT/UT + 6 wallet FTs green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:07:42 -04:00
def test_wallet_tokens_applet_shows_all_owned_trinket_types(self):
"""Wallet Tokens applet renders every owned trinket-as-token type
independently PASS (staff), BAND, COIN, CARTE alongside FREE
+ TITHE. Pre-2026-05-22 the template had an if/elif chain that
only showed ONE of {PASS, BAND, COIN}; that hid BAND + COIN +
CARTE entirely from the wallet for any staff user holding a PASS,
even though all three are usable at the gate. Chunk 1 of the
Shop applet rollout drops that exclusivity so each trinket gets
its own tooltipped icon in the row."""
# 1. Build a staff user holding every trinket-as-token type
staff = User.objects.create(email="ledger@test.io", is_staff=True)
# post_save signal already created COIN (auto-equipped) + FREE + PASS;
# mint the rest manually so we exercise the full inventory render.
Token.objects.create(user=staff, token_type=Token.BAND)
Token.objects.create(user=staff, token_type=Token.CARTE)
Token.objects.create(user=staff, token_type=Token.TITHE)
# 2. Log in + land on wallet page
self.create_pre_authenticated_session("ledger@test.io")
self.browser.get(self.live_server_url + "/dashboard/wallet/")
# 3. Every trinket-as-token icon is present (no if/elif suppression)
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pass_token"))
self.browser.find_element(By.ID, "id_band_token")
self.browser.find_element(By.ID, "id_coin_on_a_string")
self.browser.find_element(By.ID, "id_carte_token")
# 4. Consumable tokens still present
self.browser.find_element(By.ID, "id_free_token")
self.browser.find_element(By.ID, "id_tithe_token")
# 5. BAND tile carries its tooltip content in the DOM (the wallet's
# `initWalletTooltips` clones `.tt` innerHTML into the portal on
# hover — already exercised by the COIN/FREE hover paths in
# `test_new_user_wallet_shows_starting_balances`; here we just pin
# that the new BAND/CARTE template blocks server-render their full
# tooltip prose, which is the Chunk 1 contract).
band_tt = self.browser.find_element(By.CSS_SELECTOR, "#id_band_token .tt").get_attribute("innerHTML")
self.assertIn("Wristband", band_tt)
self.assertIn("Admit All Entry", band_tt)
self.assertIn("no expiry", band_tt)
# 6. CARTE tile carries its tooltip content too
carte_tt = self.browser.find_element(By.CSS_SELECTOR, "#id_carte_token .tt").get_attribute("innerHTML")
self.assertIn("Carte Blanche", carte_tt)
self.assertIn("Admit up to +6", carte_tt)
self.assertIn("no expiry", carte_tt)
def test_user_can_purchase_tithe_token_bundle(self):
# 1. Log in, navigate to wallet page
self.create_pre_authenticated_session("capman@test.io")
self.browser.get(self.live_server_url + "/dashboard/wallet/")
# 2. Assert Tithe Token purchase section present
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_tithe_token_shop")
)
# 3. Assert min. +1 bundle option is visible
bundle = self.browser.find_element(
By.CSS_SELECTOR, "#id_tithe_token_shop .token-bundle"
)
# 4. Assert ea. bundle shows token count & writ bonus placeholder
self.assertIn("Tithe Token", bundle.text)
self.assertIn("Writ", bundle.text)
# 5. (Placeholder) Purchase flow via Stripe not driven in this FT:
# Full charge assertion deferred until Stripe webhook handling implemented