tooltips app TDD spike + kit bag refactor to .tt

- New apps.tooltips: TooltipContent model, {% tooltip data %} inclusion
  tag, _tooltip.html partial with .tt/.tt-title/.tt-description etc.
  class contract; 34 tests green
- Kit bag panel (_kit_bag_panel.html): .token-tooltip → .tt + child
  class renames (tt-title, tt-description, tt-shoptalk, tt-expiry)
- game-kit.js attachTooltip: .token-tooltip → .tt selector
- SCSS: .tt added alongside .token-tooltip for display:none default +
  hover rules in _wallet-tokens.scss and _game-kit.scss

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-15 22:16:50 -04:00
parent 71ef3dcb7f
commit de4ac60aec
20 changed files with 520 additions and 29 deletions

View File

View File

@@ -0,0 +1,127 @@
from django.test import TestCase
from django.db import IntegrityError
from apps.tooltips.models import TooltipContent
class TooltipContentModelTest(TestCase):
# ── Required fields ───────────────────────────────────────────────────────
def test_can_create_with_slug_and_title_only(self):
tt = TooltipContent.objects.create(slug='sun', title='Sun')
self.assertEqual(TooltipContent.objects.count(), 1)
self.assertEqual(tt.slug, 'sun')
self.assertEqual(tt.title, 'Sun')
def test_slug_is_unique(self):
TooltipContent.objects.create(slug='sun', title='Sun')
with self.assertRaises(IntegrityError):
TooltipContent.objects.create(slug='sun', title='Another Sun')
def test_str_returns_title(self):
tt = TooltipContent(slug='moon', title='Moon')
self.assertEqual(str(tt), 'Moon')
# ── Optional text fields ──────────────────────────────────────────────────
def test_optional_text_fields_default_blank(self):
tt = TooltipContent.objects.create(slug='mars', title='Mars')
for field in ('type_label', 'symbol', 'degree_str',
'description', 'shoptalk', 'expiry', 'effect'):
self.assertEqual(getattr(tt, field), '', msg=f'{field} should default blank')
def test_can_set_all_text_fields(self):
tt = TooltipContent.objects.create(
slug='jupiter',
title='Jupiter',
type_label='Planet',
symbol='',
degree_str='14° 22 Scorpio',
description='Planet of expansion and fortune.',
shoptalk='Ruler of Sagittarius.',
expiry='',
effect='Amplifies whichever house it occupies.',
)
self.assertEqual(tt.type_label, 'Planet')
self.assertEqual(tt.symbol, '')
self.assertEqual(tt.degree_str, '14° 22 Scorpio')
# ── extras JSONField ──────────────────────────────────────────────────────
def test_extras_defaults_to_empty_dict(self):
tt = TooltipContent.objects.create(slug='venus', title='Venus')
self.assertEqual(tt.extras, {})
def test_extras_stores_dignities(self):
tt = TooltipContent.objects.create(
slug='saturn',
title='Saturn',
extras={
'dignities': {
'Domicile': 'Capricorn / Aquarius',
'Exalted': 'Libra',
'Exile': 'Cancer / Leo',
'Fallen': 'Aries',
},
},
)
tt.refresh_from_db()
self.assertEqual(tt.extras['dignities']['Exalted'], 'Libra')
def test_extras_stores_aspects(self):
tt = TooltipContent.objects.create(
slug='mercury',
title='Mercury',
extras={
'aspects': [
{'symbol': '', 'type': 'Trine', 'body': 'Venus', 'orb': '2° 14'},
{'symbol': '', 'type': 'Square', 'body': 'Mars', 'orb': '0° 42'},
],
},
)
tt.refresh_from_db()
self.assertEqual(len(tt.extras['aspects']), 2)
self.assertEqual(tt.extras['aspects'][0]['type'], 'Trine')
def test_extras_stores_keywords(self):
tt = TooltipContent.objects.create(
slug='the-schizo',
title='The Schizo, Leavened',
extras={
'keywords_up': ['willpower', 'skill', 'resourcefulness'],
'keywords_rev': ['hubris', 'overreach'],
},
)
tt.refresh_from_db()
self.assertIn('willpower', tt.extras['keywords_up'])
def test_extras_stores_cautions(self):
tt = TooltipContent.objects.create(
slug='the-schizo-cautions',
title='The Schizo, Leavened',
extras={
'cautions': [
{
'title': 'Caution!',
'type_label': 'Rival Interaction',
'shoptalk': '[Shoptalk forthcoming]',
'effect': 'This card will reverse into I. The Pervert...',
},
],
},
)
tt.refresh_from_db()
self.assertEqual(tt.extras['cautions'][0]['type_label'], 'Rival Interaction')
def test_extras_stores_nav(self):
"""PRV/NXT nav dict for paired or sequenced tooltips."""
tt = TooltipContent.objects.create(
slug='house-01',
title='House of Self',
extras={
'nav': {'prv': 'house-12', 'nxt': 'house-02'},
},
)
tt.refresh_from_db()
self.assertEqual(tt.extras['nav']['nxt'], 'house-02')

View File

@@ -0,0 +1,172 @@
from django.template import Context, Template
from django.test import TestCase
def render_tooltip(data):
"""Helper: render the {% tooltip %} tag with the given data dict."""
tpl = Template("{% load tooltip_tags %}{% tooltip data %}")
return tpl.render(Context({'data': data}))
class TooltipTagRequiredFieldsTest(TestCase):
def test_renders_title(self):
html = render_tooltip({'title': 'Sun'})
self.assertIn('Sun', html)
self.assertIn('class="tt-title"', html)
def test_wrapper_has_tt_class(self):
html = render_tooltip({'title': 'Moon'})
self.assertIn('class="tt"', html)
class TooltipTagOptionalTextFieldsTest(TestCase):
def test_renders_type_label_when_present(self):
html = render_tooltip({'title': 'Sun', 'type_label': 'Planet'})
self.assertIn('Planet', html)
self.assertIn('tt-type', html)
def test_omits_type_label_when_absent(self):
html = render_tooltip({'title': 'Sun'})
self.assertNotIn('tt-type', html)
def test_renders_symbol_when_present(self):
html = render_tooltip({'title': 'Sun', 'symbol': ''})
self.assertIn('', html)
self.assertIn('tt-symbol', html)
def test_omits_symbol_when_absent(self):
html = render_tooltip({'title': 'Sun'})
self.assertNotIn('tt-symbol', html)
def test_renders_degree_str_when_present(self):
html = render_tooltip({'title': 'Sun', 'degree_str': '14° 22 Scorpio'})
self.assertIn('14° 22 Scorpio', html)
self.assertIn('tt-degree', html)
def test_omits_degree_str_when_absent(self):
html = render_tooltip({'title': 'Sun'})
self.assertNotIn('tt-degree', html)
def test_renders_description_when_present(self):
html = render_tooltip({'title': 'Sun', 'description': 'Planet of vitality.'})
self.assertIn('Planet of vitality.', html)
self.assertIn('tt-description', html)
def test_renders_shoptalk_in_em_when_present(self):
html = render_tooltip({'title': 'Sun', 'shoptalk': 'Ruler of Leo.'})
self.assertIn('Ruler of Leo.', html)
self.assertIn('tt-shoptalk', html)
self.assertIn('<em>', html)
def test_renders_expiry_when_present(self):
html = render_tooltip({'title': 'Pass', 'expiry': 'no expiry'})
self.assertIn('no expiry', html)
self.assertIn('tt-expiry', html)
def test_renders_effect_when_present(self):
html = render_tooltip({'title': 'Pass', 'effect': 'Admit All Entry'})
self.assertIn('Admit All Entry', html)
self.assertIn('tt-effect', html)
class TooltipTagExtrasTest(TestCase):
def test_renders_dignities_table_when_present(self):
data = {
'title': 'Saturn',
'extras': {
'dignities': {
'Domicile': 'Capricorn',
'Exalted': 'Libra',
},
},
}
html = render_tooltip(data)
self.assertIn('tt-table--dignities', html)
self.assertIn('Capricorn', html)
self.assertIn('Exalted', html)
def test_omits_dignities_when_absent(self):
html = render_tooltip({'title': 'Sun', 'extras': {}})
self.assertNotIn('tt-table--dignities', html)
def test_renders_aspects_list_when_present(self):
data = {
'title': 'Mercury',
'extras': {
'aspects': [
{'symbol': '', 'type': 'Trine', 'body': 'Venus', 'orb': '2° 14'},
],
},
}
html = render_tooltip(data)
self.assertIn('tt-aspects', html)
self.assertIn('Trine', html)
self.assertIn('Venus', html)
def test_omits_aspects_when_absent(self):
html = render_tooltip({'title': 'Sun', 'extras': {}})
self.assertNotIn('tt-aspects', html)
def test_renders_keyword_lists_when_present(self):
data = {
'title': 'The Schizo',
'extras': {
'keywords_up': ['willpower', 'skill'],
'keywords_rev': ['hubris'],
},
}
html = render_tooltip(data)
self.assertIn('tt-keywords', html)
self.assertIn('willpower', html)
self.assertIn('hubris', html)
def test_omits_keywords_when_absent(self):
html = render_tooltip({'title': 'Sun', 'extras': {}})
self.assertNotIn('tt-keywords', html)
def test_renders_fyi_cautions_section_when_present(self):
data = {
'title': 'The Schizo',
'extras': {
'cautions': [
{
'title': 'Caution!',
'type_label': 'Rival Interaction',
'shoptalk': '[Shoptalk forthcoming]',
'effect': 'This card will reverse...',
},
],
},
}
html = render_tooltip(data)
self.assertIn('tt-fyi', html)
self.assertIn('tt-fyi--cautions', html)
self.assertIn('Rival Interaction', html)
def test_omits_cautions_section_when_absent(self):
html = render_tooltip({'title': 'Sun', 'extras': {}})
self.assertNotIn('tt-fyi--cautions', html)
def test_renders_nav_when_present(self):
data = {
'title': 'House of Self',
'extras': {
'nav': {'prv': 'house-12', 'nxt': 'house-02'},
},
}
html = render_tooltip(data)
self.assertIn('tt-nav', html)
self.assertIn('house-02', html)
self.assertIn('house-12', html)
def test_omits_nav_when_absent(self):
html = render_tooltip({'title': 'Sun', 'extras': {}})
self.assertNotIn('tt-nav', html)
def test_no_extras_key_does_not_error(self):
"""Tag should handle data with no 'extras' key gracefully."""
html = render_tooltip({'title': 'Sun'})
self.assertIn('Sun', html)