A.0 image-rendering schema + RWS rename + canonical-Earthman suit collapse — TDD. Sprint A.0 of [[project-image-based-deck-face-rendering]]. Adds three DeckVariant fields: has_card_images (BooleanField default=True — Earthman keeps False until its artwork ships, every new deck defaults True), family (CharField choices=[earthman, italian, english, playing] default=earthman — drives per-family display + filename slug mapping per [[reference-card-image-naming-convention]]), is_polarized (BooleanField default=False — Earthman is True today; Sprint A.4 game_kit applet will render "(×2)" in --terUser for polarized decks; Sprint C+B segment model uses it for segment-count logic). TarotCard.SUIT_CHOICES collapses from 8 values to 4 canonical Earthman values (BRANDS / CROWNS / GRAILS / BLADES); WANDS / CUPS / SWORDS / PENTACLES dropped — they were duplicative at the structural level since sig_deck_cards + levity/gravity_sig_cards already treated [WANDS, BRANDS, CROWNS] as one segment and [SWORDS, BLADES, CUPS, GRAILS] as another (so the project already *functionally* equated them; the lock just makes that explicit). Per-family display vocab (batons for Italian, wands for English, clubs for Playing) lives in Sprint A.2's display_suit_name property, not in the enum. Audit 2026-05-25 revealed the existing fiorentine-minchiate DeckVariant is actually 78-card RWS Tarot in disguise (22 majors numbered 0-21 w. RWS names: The Fool / The Magician / ... / The World; 56 minors in 4 suits × 14 cards) — NOT Minchiate (which has 40 trumps + 1 Il Matto + 56 minors = 97 cards). Migration 0012 renames the slug → tarot-rider-waite-smith, name → "Tarot (Rider-Waite-Smith)", sets family='english', has_card_images=False, is_polarized=False — and revocabs its 56 minor cards' suits in-place (WANDS→BRANDS, CUPS→GRAILS, SWORDS→BLADES, PENTACLES→CROWNS) so they match the new canonical enum. FKs (User.equipped_deck, User.unlocked_decks, TableSeat.deck_variant, etc.) survive untouched — slug-only changes don't break referential integrity. Earthman fields set explicitly in 0012 too (family=earthman, has_card_images=False, is_polarized=True). Companion code simplifications: sig_deck_cards + _sig_unique_cards_for_deck queries shrink from suit__in=[3 values] and [4 values] to [2 values] each (one per segment); TarotCard.suit_icon mapping shrinks from 8 entries to 4; gameboard.views.tarot_fan._suit_order shrinks from 8 keys to 4. Existing test files updated: test_game_room_tray.py (largest update — self.fiorentine → self.rws, id_kit_fiorentine_deck → id_kit_tarot_deck (template-id derives from deck.short_key = first slug segment), assertion "Fiorentine" → "Rider-Waite-Smith"); test_game_room_deck_contrib.py (same pattern, smaller); lyric/test_models.py + gameboard/test_views.py (slug literal swaps only); epic/test_models.py _make_sig_card test fixtures: "WANDS"→"BRANDS", "CUPS"→"GRAILS". 14 new ITs in DeckSchemaA0Test cover the schema additions + migration outcomes (field existence + choice values + earthman has all three fields set correctly + RWS rename verified + RWS cards use canonical suits + dropped enum values absent from SUIT_CHOICES). Tests: 14 new green; 1255/1255 IT+UT total green (38s); no regressions. Out of scope: Sprint A.1 will seed the actual Minchiate Fiorentine 1860-1890 (97-card) DeckVariant + TarotCard rows w. family='italian', has_card_images=True; A.2 adds the image_filename + display_suit_name properties that consume the new family field; A.3+ wires the render branches across 6 surfaces
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -182,16 +182,16 @@ class DeckInUseGameKitTest(FunctionalTest):
|
||||
def test_non_contributing_deck_has_normal_don_doff(self):
|
||||
"""A deck not assigned to any active seat shows the normal DON/DOFF apparatus."""
|
||||
gamer, earthman, room, seat = self._setup_in_use_deck()
|
||||
# Unlock Fiorentine for the gamer so it appears in Game Kit
|
||||
fiorentine, _ = DeckVariant.objects.get_or_create(
|
||||
slug="fiorentine-minchiate",
|
||||
defaults={"name": "Fiorentine Minchiate", "card_count": 97},
|
||||
# Unlock RWS for the gamer so it appears in Game Kit
|
||||
rws, _ = DeckVariant.objects.get_or_create(
|
||||
slug="tarot-rider-waite-smith",
|
||||
defaults={"name": "Tarot (Rider-Waite-Smith)", "card_count": 78},
|
||||
)
|
||||
gamer.unlocked_decks.add(fiorentine)
|
||||
gamer.unlocked_decks.add(rws)
|
||||
self.create_pre_authenticated_session(GAMER_EMAIL)
|
||||
self.browser.get(self.live_server_url + "/gameboard/")
|
||||
self.wait_for(
|
||||
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_fiorentine_deck")
|
||||
lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_kit_tarot_deck")
|
||||
).click()
|
||||
don_btn = self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
|
||||
@@ -527,17 +527,17 @@ class GameKitDeckSelectionTest(FunctionalTest):
|
||||
slug="earthman",
|
||||
defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
|
||||
)
|
||||
self.fiorentine, _ = DeckVariant.objects.get_or_create(
|
||||
slug="fiorentine-minchiate",
|
||||
defaults={"name": "Fiorentine Minchiate", "card_count": 78, "is_default": False},
|
||||
self.rws, _ = DeckVariant.objects.get_or_create(
|
||||
slug="tarot-rider-waite-smith",
|
||||
defaults={"name": "Tarot (Rider-Waite-Smith)", "card_count": 78, "is_default": False},
|
||||
)
|
||||
self.gamer = User.objects.create(email="gamer@deck.io")
|
||||
# Signal sets equipped_deck = earthman and unlocked_decks = [earthman].
|
||||
# Explicitly grant fiorentine too, then switch equipped_deck to it so
|
||||
# the test can exercise switching back to Earthman.
|
||||
self.gamer.refresh_from_db()
|
||||
self.gamer.unlocked_decks.add(self.fiorentine)
|
||||
self.gamer.equipped_deck = self.fiorentine
|
||||
self.gamer.unlocked_decks.add(self.rws)
|
||||
self.gamer.equipped_deck = self.rws
|
||||
self.gamer.save(update_fields=["equipped_deck"])
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
@@ -578,23 +578,23 @@ class GameKitDeckSelectionTest(FunctionalTest):
|
||||
don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
|
||||
self.assertNotIn("btn-disabled", don.get_attribute("class"))
|
||||
|
||||
# ── Hover over Fiorentine Minchiate deck ─────────────────────────
|
||||
fiorentine_el = self.browser.find_element(By.ID, "id_kit_fiorentine_deck")
|
||||
# ── Hover over Tarot (Rider-Waite-Smith) deck ────────────────────
|
||||
rws_el = self.browser.find_element(By.ID, "id_kit_tarot_deck")
|
||||
self.browser.execute_script(
|
||||
"arguments[0].scrollIntoView({block: 'center'})", fiorentine_el
|
||||
"arguments[0].scrollIntoView({block: 'center'})", rws_el
|
||||
)
|
||||
ActionChains(self.browser).move_to_element(fiorentine_el).perform()
|
||||
ActionChains(self.browser).move_to_element(rws_el).perform()
|
||||
|
||||
self.wait_for(
|
||||
lambda: self.assertIn(
|
||||
"Fiorentine",
|
||||
"Rider-Waite-Smith",
|
||||
self.browser.find_element(By.ID, "id_tooltip_portal").text,
|
||||
)
|
||||
)
|
||||
portal = self.browser.find_element(By.ID, "id_tooltip_portal")
|
||||
self.assertIn("78", portal.text)
|
||||
|
||||
# Mini tooltip shows "Equipped" — Fiorentine is the active deck
|
||||
# Mini tooltip shows "Equipped" — RWS is the active deck
|
||||
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)
|
||||
@@ -639,8 +639,8 @@ class GameKitDeckSelectionTest(FunctionalTest):
|
||||
deck_cards = self.browser.find_elements(By.CSS_SELECTOR, "#id_game_kit .deck-variant")
|
||||
self.assertEqual(len(deck_cards), 1)
|
||||
self.browser.find_element(By.ID, "id_kit_earthman_deck")
|
||||
fiorentine_cards = self.browser.find_elements(By.ID, "id_kit_fiorentine_deck")
|
||||
self.assertEqual(len(fiorentine_cards), 0)
|
||||
rws_cards = self.browser.find_elements(By.ID, "id_kit_tarot_deck")
|
||||
self.assertEqual(len(rws_cards), 0)
|
||||
|
||||
|
||||
class GameKitPageTest(FunctionalTest):
|
||||
|
||||
Reference in New Issue
Block a user