FT helper: sig_page.py — _seed_earthman_sig_pile + _assign_sig — Sprint 4c of My Sea roadmap — TDD
Pure test-infra refactor. Sprint 5+ (sea-select / DRAW SEA / latest-draw rendering) all need a "user has sig" precondition without walking the picker — extracting it once now prevents Sprint 5's setUps from copy-pasting the inline assignment dance again. Two helpers in new `src/functional_tests/sig_page.py` (mirrors the `room_page.py` / `post_page.py` / `my_posts_page.py` convention — underscored to signal "test infrastructure, not API surface", public within `functional_tests/`): - `_seed_earthman_sig_pile()` — re-seeds Earthman DeckVariant + the 16 MIDDLE court cards (Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS) that `personal_sig_cards(user)` returns. Hoisted verbatim from the duplicate definitions in `test_bill_my_sign.py` + `test_game_my_sea.py` introduced in [[sprint_serialized_rollback_ft_fix_may19]]. Major 0/1 are deliberately NOT seeded — `_filter_major_unlocks` in `personal_sig_cards()` strips them for users w.o the matching Note unlocks, which is the default state in solo FTs. - `_assign_sig(user, card=None, reversed_flag=False)` — sets `user.significator` + `significator_reversed` directly, bypassing the picker UI. Returns the assigned card so downstream assertions can use it. `card=None` defaults to `personal_sig_cards(user)[0]` (the same target the picker happy-path FT uses). Call sites updated: - `test_bill_my_sign.py` — drops the local `_seed_earthman_sig_pile` definition (32 lines); imports from `sig_page`. `MySignClearTest.setUp` now uses `_assign_sig(self.gamer)` instead of the 4-line manual sig-assignment block. - `test_game_my_sea.py` — drops the local `_seed_earthman_sig_pile` definition (22 lines); imports from `sig_page`. `MySeaSignGateTest`'s two "user w. sig" tests (#4 + #6) swap their 2-line `self.gamer.significator = ... ; .save(...)` blocks for `_assign_sig(self.gamer, self.target_card)`. Diff stat: +69 lines (new helper module), -59 lines (duplicate code removed). Net +10 LOC but the duplication trap is closed — single source of truth for sig-state FT setup. 20/20 FT green across both files in 174s post-refactor. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
69
src/functional_tests/sig_page.py
Normal file
69
src/functional_tests/sig_page.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"""Sig-state FT helpers — sprint 4c of [[project-my-sea-roadmap]]. Two
|
||||||
|
public helpers:
|
||||||
|
|
||||||
|
- `_seed_earthman_sig_pile()` — restore the Earthman DeckVariant + 16
|
||||||
|
MIDDLE court cards that `personal_sig_cards(user)` returns. The
|
||||||
|
migration seed gets flushed by TransactionTestCase between tests
|
||||||
|
(see [[feedback_transactiontestcase_flush]]); each setUp that touches
|
||||||
|
the picker or its dependents must call this before creating a User.
|
||||||
|
|
||||||
|
- `_assign_sig(user, card=None, reversed_flag=False)` — set
|
||||||
|
`User.significator` (+ `significator_reversed`) directly, bypassing
|
||||||
|
the picker UI. Returns the assigned card so downstream assertions can
|
||||||
|
use it. Use this in any FT that needs a "user has a sig" precondition
|
||||||
|
without walking the SCAN SIGN → click thumb → OK → SAVE SIGN flow.
|
||||||
|
|
||||||
|
Naming follows the room_page.py / post_page.py / my_posts_page.py
|
||||||
|
convention: underscored to signal "test infrastructure, not API
|
||||||
|
surface"; public within `functional_tests/`.
|
||||||
|
|
||||||
|
Consumers:
|
||||||
|
test_bill_my_sign — both helpers
|
||||||
|
test_game_my_sea — both helpers (gate / no-gate branches)
|
||||||
|
"""
|
||||||
|
from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_earthman_sig_pile():
|
||||||
|
"""Re-seed Earthman DeckVariant + the 16 MIDDLE court cards that
|
||||||
|
`personal_sig_cards(user)` returns. Idempotent — `get_or_create` on
|
||||||
|
deck + each card slug.
|
||||||
|
|
||||||
|
The 16 cards are Maid/Jack/Queen/King × BRANDS/CROWNS/BLADES/GRAILS.
|
||||||
|
Major 0/1 (Nomad/Schizo) are *not* seeded here — `_filter_major_unlocks`
|
||||||
|
in `personal_sig_cards()` strips them for users without the matching
|
||||||
|
Note unlocks, which is the default state in solo FTs. If a future FT
|
||||||
|
needs the Major seed (Note-unlocked path), it should seed those rows
|
||||||
|
separately or extend this helper w. a flag."""
|
||||||
|
earthman, _ = DeckVariant.objects.get_or_create(
|
||||||
|
slug="earthman",
|
||||||
|
defaults={"name": "Earthman", "card_count": 106, "is_default": True},
|
||||||
|
)
|
||||||
|
_NAME = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"}
|
||||||
|
for suit in ("BRANDS", "CROWNS", "BLADES", "GRAILS"):
|
||||||
|
for number in (11, 12, 13, 14):
|
||||||
|
TarotCard.objects.get_or_create(
|
||||||
|
deck_variant=earthman,
|
||||||
|
slug=f"{_NAME[number].lower()}-of-{suit.lower()}-em",
|
||||||
|
defaults={"arcana": "MIDDLE", "suit": suit, "number": number,
|
||||||
|
"name": f"{_NAME[number]} of {suit.capitalize()}",
|
||||||
|
"levity_qualifier": "Elevated",
|
||||||
|
"gravity_qualifier": "Graven"},
|
||||||
|
)
|
||||||
|
return earthman
|
||||||
|
|
||||||
|
|
||||||
|
def _assign_sig(user, card=None, reversed_flag=False):
|
||||||
|
"""Assign `user.significator` (and optionally `significator_reversed`)
|
||||||
|
directly, bypassing the picker UI. Returns the assigned card.
|
||||||
|
|
||||||
|
If `card` is None, defaults to the first card in
|
||||||
|
`personal_sig_cards(user)` — the same card the picker happy-path FT
|
||||||
|
targets. Caller is responsible for ensuring the sig pile is seeded
|
||||||
|
(call `_seed_earthman_sig_pile()` before User.create if needed)."""
|
||||||
|
if card is None:
|
||||||
|
card = personal_sig_cards(user)[0]
|
||||||
|
user.significator = card
|
||||||
|
user.significator_reversed = reversed_flag
|
||||||
|
user.save(update_fields=["significator", "significator_reversed"])
|
||||||
|
return card
|
||||||
@@ -10,8 +10,9 @@ is branded "Sign" / "Game Sign".
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
|
from .sig_page import _assign_sig, _seed_earthman_sig_pile
|
||||||
from apps.applets.models import Applet
|
from apps.applets.models import Applet
|
||||||
from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards
|
from apps.epic.models import personal_sig_cards
|
||||||
from apps.lyric.models import User
|
from apps.lyric.models import User
|
||||||
|
|
||||||
|
|
||||||
@@ -23,29 +24,6 @@ def _seed_my_sign_applet():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _seed_earthman_sig_pile():
|
|
||||||
"""Re-seed Earthman DeckVariant + the 16 MIDDLE court cards that
|
|
||||||
personal_sig_cards() returns. TransactionTestCase flushes wipe the
|
|
||||||
migration seed between tests, so each setUp must restore them.
|
|
||||||
See [[feedback_transactiontestcase_flush]]."""
|
|
||||||
earthman, _ = DeckVariant.objects.get_or_create(
|
|
||||||
slug="earthman",
|
|
||||||
defaults={"name": "Earthman", "card_count": 106, "is_default": True},
|
|
||||||
)
|
|
||||||
_NAME = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"}
|
|
||||||
for suit in ("BRANDS", "CROWNS", "BLADES", "GRAILS"):
|
|
||||||
for number in (11, 12, 13, 14):
|
|
||||||
TarotCard.objects.get_or_create(
|
|
||||||
deck_variant=earthman,
|
|
||||||
slug=f"{_NAME[number].lower()}-of-{suit.lower()}-em",
|
|
||||||
defaults={"arcana": "MIDDLE", "suit": suit, "number": number,
|
|
||||||
"name": f"{_NAME[number]} of {suit.capitalize()}",
|
|
||||||
"levity_qualifier": "Elevated",
|
|
||||||
"gravity_qualifier": "Graven"},
|
|
||||||
)
|
|
||||||
return earthman
|
|
||||||
|
|
||||||
|
|
||||||
class MySignPickerTest(FunctionalTest):
|
class MySignPickerTest(FunctionalTest):
|
||||||
"""Happy-path picker: a user with the Earthman deck equipped lands at
|
"""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
|
/billboard/my-sign/, picks a card, clicks SAVE SIGN, and sees the sig
|
||||||
@@ -469,13 +447,8 @@ class MySignClearTest(FunctionalTest):
|
|||||||
_seed_my_sign_applet()
|
_seed_my_sign_applet()
|
||||||
self.email = "clear@test.io"
|
self.email = "clear@test.io"
|
||||||
self.gamer = User.objects.create(email=self.email)
|
self.gamer = User.objects.create(email=self.email)
|
||||||
sig_pile = personal_sig_cards(self.gamer)
|
# Pre-save a sig so the DEL affordance is visible on landing.
|
||||||
self.target_card = sig_pile[0] if sig_pile else None
|
self.target_card = _assign_sig(self.gamer)
|
||||||
self.assertIsNotNone(self.target_card)
|
|
||||||
# Pre-save a sig so the CLEAR affordance is visible on landing.
|
|
||||||
self.gamer.significator = self.target_card
|
|
||||||
self.gamer.significator_reversed = False
|
|
||||||
self.gamer.save(update_fields=["significator", "significator_reversed"])
|
|
||||||
|
|
||||||
# ── Test 1 ───────────────────────────────────────────────────────────────
|
# ── Test 1 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ mirrors the gate hint in its empty-state slot.
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
|
from .sig_page import _assign_sig, _seed_earthman_sig_pile
|
||||||
from apps.applets.models import Applet
|
from apps.applets.models import Applet
|
||||||
from apps.epic.models import DeckVariant, TarotCard, personal_sig_cards
|
from apps.epic.models import personal_sig_cards
|
||||||
from apps.lyric.models import User
|
from apps.lyric.models import User
|
||||||
|
|
||||||
|
|
||||||
@@ -32,29 +33,6 @@ def _seed_gameboard_applets():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _seed_earthman_sig_pile():
|
|
||||||
"""Re-seed Earthman DeckVariant + the 16 MIDDLE court cards that
|
|
||||||
personal_sig_cards() returns. TransactionTestCase flushes wipe the
|
|
||||||
migration seed between tests, so each setUp must restore them.
|
|
||||||
See [[feedback_transactiontestcase_flush]]."""
|
|
||||||
earthman, _ = DeckVariant.objects.get_or_create(
|
|
||||||
slug="earthman",
|
|
||||||
defaults={"name": "Earthman", "card_count": 106, "is_default": True},
|
|
||||||
)
|
|
||||||
_NAME = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"}
|
|
||||||
for suit in ("BRANDS", "CROWNS", "BLADES", "GRAILS"):
|
|
||||||
for number in (11, 12, 13, 14):
|
|
||||||
TarotCard.objects.get_or_create(
|
|
||||||
deck_variant=earthman,
|
|
||||||
slug=f"{_NAME[number].lower()}-of-{suit.lower()}-em",
|
|
||||||
defaults={"arcana": "MIDDLE", "suit": suit, "number": number,
|
|
||||||
"name": f"{_NAME[number]} of {suit.capitalize()}",
|
|
||||||
"levity_qualifier": "Elevated",
|
|
||||||
"gravity_qualifier": "Graven"},
|
|
||||||
)
|
|
||||||
return earthman
|
|
||||||
|
|
||||||
|
|
||||||
class MySeaSignGateTest(FunctionalTest):
|
class MySeaSignGateTest(FunctionalTest):
|
||||||
"""Sign-gate UX on the standalone /gameboard/my-sea/ page + the
|
"""Sign-gate UX on the standalone /gameboard/my-sea/ page + the
|
||||||
/gameboard/ My Sea applet. User without a saved sig sees a Look!-
|
/gameboard/ My Sea applet. User without a saved sig sees a Look!-
|
||||||
@@ -134,8 +112,7 @@ class MySeaSignGateTest(FunctionalTest):
|
|||||||
def test_with_sig_skips_gate_and_renders_draw_shell(self):
|
def test_with_sig_skips_gate_and_renders_draw_shell(self):
|
||||||
"""User w. saved significator → no .my-sea-sign-gate on the page;
|
"""User w. saved significator → no .my-sea-sign-gate on the page;
|
||||||
draw shell renders normally (Sprint 3 placeholder)."""
|
draw shell renders normally (Sprint 3 placeholder)."""
|
||||||
self.gamer.significator = self.target_card
|
_assign_sig(self.gamer, self.target_card)
|
||||||
self.gamer.save(update_fields=["significator"])
|
|
||||||
self.create_pre_authenticated_session(self.email)
|
self.create_pre_authenticated_session(self.email)
|
||||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
@@ -170,8 +147,7 @@ class MySeaSignGateTest(FunctionalTest):
|
|||||||
def test_with_sig_applet_renders_default_empty_state(self):
|
def test_with_sig_applet_renders_default_empty_state(self):
|
||||||
"""Applet w. saved sig → no gate, empty-state placeholder (until
|
"""Applet w. saved sig → no gate, empty-state placeholder (until
|
||||||
Sprint 7 wires up the latest-draw rendering)."""
|
Sprint 7 wires up the latest-draw rendering)."""
|
||||||
self.gamer.significator = self.target_card
|
_assign_sig(self.gamer, self.target_card)
|
||||||
self.gamer.save(update_fields=["significator"])
|
|
||||||
self.create_pre_authenticated_session(self.email)
|
self.create_pre_authenticated_session(self.email)
|
||||||
self.browser.get(self.live_server_url + "/gameboard/")
|
self.browser.get(self.live_server_url + "/gameboard/")
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
|
|||||||
Reference in New Issue
Block a user