A.3 my_sign.html image-rendering — first visible surface — TDD. Sprint A.3 of [[project-image-based-deck-face-rendering]]. When the user's equipped deck has has_card_images=True (Minchiate Fiorentine 1860-1890 today), the saved-sig stage card on /billboard/my-sign/ renders as an <img> over the irregular-shape transparent PNG with a contour-following arcana-colored stroke — not the text fan-card scaffold. First of 6 surfaces in the image-rendering rollout (my_sea + both billboard applets + room + game_kit follow in A.5+). New TarotCard.image_url property (consumes A.2's image_filename + DeckVariant.has_card_images + django.templatetags.static.static() to produce a full static-asset URL) — empty string when has_card_images=False so legacy text-only decks (Earthman, RWS) pass through transparently. my_sign.html picker grid .sig-card elements gain data-image-url + data-arcana-key attrs (the latter for stroke-color CSS selection); the .sig-stage-card scaffold gains a hidden <img class="sig-stage-card-img"> slot that JS swaps visible when image-mode is active. stage-card.js extends fromDataset to read image_url + arcana_key; new _setImageMode(stageCard, card) toggles the .sig-stage-card--image marker class + sets data-arcana-key on the stage card + populates the img src/alt; called from populateCard so all existing sig-stage flows pick up image rendering automatically (text-mode decks still pass through since image_url is empty). SCSS: new .sig-stage-card.sig-stage-card--image rule hides the .fan-card-corner + .fan-card-face text scaffold, strips the rectangular border/padding, and applies a 4-cardinal-direction filter: drop-shadow() stack to the <img> so the stroke FOLLOWS the alpha contour of the PNG instead of tracing a rectangular bounding box (per user spec 2026-05-25 PM clarification — early draft used a rectangular border which doesn't match the irregular-card aesthetic). Stroke color is driven by a CSS custom prop --img-stroke-color defaulting to rgba(var(--quiUser), 1) (cream — minor + middle arcana); [data-arcana-key="MAJOR"] override flips it to rgba(var(--terUser), 1) (gold) per Q2 lock. mobile-safe — filter on raster images works cross-browser (the [[feedback-mobile-svg-glow]] dead-end was specifically SVG glow, not raster drop-shadows). New _seed_minchiate_image_fixtures() helper in functional_tests/sig_page.py re-seeds the minimal Minchiate fixture (DeckVariant + Il Matto + Papa Uno) needed for image FTs after TransactionTestCase's flush wipes migration data — mirrors the existing _seed_earthman_sig_pile pattern per [[feedback-transactiontestcase-flush]]. New MySignImageRenderingTest.test_saved_sig_renders_as_img_for_image_deck FT seeds Minchiate + creates a superuser test gamer (superuser auto-gets super-nomad + super-schizo Notes via the User post_save signal, which _filter_major_unlocks then lets through to expose Il Matto in the picker grid — otherwise Minchiate's sig pool is empty since it has no MIDDLE arcana cards), equips Minchiate, saves Il Matto as sig, visits /billboard/my-sign/, asserts the stage card displays + contains an <img> w. src ending in the v2-convention filename minchiate-fiorentine-1860-1890-trumps-00-il-matto.png + carries .sig-stage-card--image marker class. Out of scope for this commit (deferred to A.3 follow-up polish + A.5+): the full stat-block restructure (top-left rank+suit chip Q♥ inline w. EMANATION/REVERSAL header; title in arcana-color font; keyword reposition; FYI panel re-anchor — per the locked Q3 spec) — image card-face ships now w. the existing stat-block layout to land the visible-win first. Tests: 1 new FT green; 15/15 my_sign FT class green (no regression on the 14 existing tests); 1289/1289 IT+UT total green (68s, unchanged from A.2 since no new ITs in this commit — FT covers the wiring end-to-end). Sprint A backend foundation (A.0+A.1+A.2) + first visible surface (A.3) all landed; 5 surfaces remain (A.5-A.8 + A.4's card-deck icon)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,9 @@ is branded "Sign" / "Game Sign".
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from .base import FunctionalTest
|
||||
from .sig_page import _assign_sig, _seed_earthman_sig_pile
|
||||
from .sig_page import _assign_sig, _seed_earthman_sig_pile, _seed_minchiate_image_fixtures
|
||||
from apps.applets.models import Applet
|
||||
from apps.epic.models import personal_sig_cards
|
||||
from apps.epic.models import TarotCard, personal_sig_cards
|
||||
from apps.lyric.models import User
|
||||
|
||||
|
||||
@@ -533,3 +533,83 @@ class MySignClearTest(FunctionalTest):
|
||||
self.gamer.refresh_from_db()
|
||||
self.assertIsNone(self.gamer.significator)
|
||||
self.assertFalse(self.gamer.significator_reversed)
|
||||
|
||||
|
||||
class MySignImageRenderingTest(FunctionalTest):
|
||||
"""Sprint A.3 — when the user's equipped deck has card images (Minchiate
|
||||
Fiorentine 1860-1890 today), the saved-sig stage card renders as an <img>
|
||||
pointing at the deck's image asset, not the text-only fan-card scaffold.
|
||||
|
||||
First visible-surface FT in the image-rendering rollout per
|
||||
[[project-image-based-deck-face-rendering]]. Other 5 surfaces (my_sea,
|
||||
both billboard applets, room, game_kit) follow in A.5+.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Earthman is auto-equipped by the User post_save signal — seed its
|
||||
# pile first so the signal succeeds, then override the equipped deck
|
||||
# to Minchiate (the image-deck under test).
|
||||
_seed_earthman_sig_pile()
|
||||
self.minchiate = _seed_minchiate_image_fixtures()
|
||||
Applet.objects.get_or_create(
|
||||
slug="my-sign",
|
||||
defaults={"name": "My Sign", "context": "billboard",
|
||||
"default_visible": True, "grid_cols": 4, "grid_rows": 6},
|
||||
)
|
||||
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 = "img-sig@test.io"
|
||||
# Superuser so post_save grants super-nomad + super-schizo Notes →
|
||||
# `_filter_major_unlocks` lets Il Matto (Major 0) through into the
|
||||
# picker grid. Without the Notes, Minchiate's sig pool is empty for
|
||||
# this user (no MIDDLE arcana cards + the 2 Major-0/1 cards filtered).
|
||||
self.gamer = User.objects.create(email=self.email, is_superuser=True)
|
||||
self.gamer.unlocked_decks.add(self.minchiate)
|
||||
self.gamer.equipped_deck = self.minchiate
|
||||
self.gamer.save(update_fields=["equipped_deck"])
|
||||
# Save Il Matto as the user's sig (bypass the picker UI — the FT is
|
||||
# about render output, not the pick flow).
|
||||
self.il_matto = TarotCard.objects.get(
|
||||
deck_variant=self.minchiate, slug="il-matto",
|
||||
)
|
||||
_assign_sig(self.gamer, card=self.il_matto)
|
||||
|
||||
def test_saved_sig_renders_as_img_for_image_deck(self):
|
||||
"""Visit /billboard/my-sign/ with a Minchiate sig saved → the stage
|
||||
card contains an <img> child whose src points at the deck's image
|
||||
asset under the v2 naming convention. The text fan-card scaffold is
|
||||
hidden in image mode."""
|
||||
self.create_pre_authenticated_session(self.email)
|
||||
self.browser.get(self.live_server_url + "/billboard/my-sign/")
|
||||
stage_card = self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
By.CSS_SELECTOR, ".my-sign-stage .sig-stage-card"
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
stage_card.is_displayed(),
|
||||
"Stage card should preview the saved sig on landing",
|
||||
)
|
||||
# <img> child renders w. the v2-convention filename for Il Matto.
|
||||
img = self.wait_for(
|
||||
lambda: stage_card.find_element(By.TAG_NAME, "img")
|
||||
)
|
||||
src = img.get_attribute("src") or ""
|
||||
self.assertIn(
|
||||
"minchiate-fiorentine-1860-1890-trumps-00-il-matto.png",
|
||||
src,
|
||||
f"Expected Minchiate Il Matto image src, got: {src}",
|
||||
)
|
||||
# Image mode toggle — the stage card carries a marker class so SCSS
|
||||
# can hide the fan-card text scaffold + show the <img>.
|
||||
self.assertIn(
|
||||
"sig-stage-card--image", stage_card.get_attribute("class"),
|
||||
"Stage card should carry .sig-stage-card--image class in image mode",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user