PICK SKY: natal wheel planet tooltips + FT modernisation

- natus-wheel.js: per-planet <g> group with data-planet/sign/degree/retrograde
  attrs; mouseover/mouseout on group (pointer-events:none on child text/℞ so
  the whole apparatus triggers hover); tooltip uses .tt-title/.tt-description;
  in-sign degree via _inSignDeg() (ecliptic % 30); D3 switched from CDN to
  local d3.min.js
- _natus.scss: .nw-planet--hover glow; #id_natus_tooltip position:fixed z-200
- _natus_overlay.html: tooltip div uses .tt; local d3.min.js script tag
- T3/T4/T5 converted from Selenium execute_script to Jasmine unit tests
  (NatusWheelSpec.js) — NatusWheel was never defined in headless GeckoDriver;
  SpecRunner.html updated to load D3 + natus-wheel.js
- test_pick_sky.py: NatusWheelTooltipTest removed (replaced by Jasmine)
- test_component_cards_tarot / test_trinket_carte_blanche: equip assertions
  updated from legacy .equip-deck-btn/.equip-trinket-btn mini-tooltip pattern
  to current DON|DOFF (.btn-equip in main portal); mini-portal text assertions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-16 01:57:02 -04:00
parent db9ac9cb24
commit 2910012b67
12 changed files with 370 additions and 59 deletions

View File

@@ -272,11 +272,12 @@ class GameKitDeckSelectionTest(FunctionalTest):
self.assertIn("Earthman", portal.text)
self.assertIn("108", portal.text)
# Mini tooltip shows Equip button — Earthman is NOT currently equipped
# Mini shows "Not Equipped"; DON button is active in the main portal
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
equip_btn = mini.find_element(By.CSS_SELECTOR, ".equip-deck-btn")
self.assertEqual(equip_btn.text, "Equip Deck?")
self.assertIn("Not Equipped", mini.text)
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")
@@ -299,7 +300,7 @@ class GameKitDeckSelectionTest(FunctionalTest):
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
self.assertIn("Equipped", mini.text)
# ── Hover back to Earthman and click Equip ────────────────────────
# ── Hover back to Earthman and click DON ─────────────────────────
ActionChains(self.browser).move_to_element(earthman_el).perform()
self.wait_for(
lambda: self.assertIn(
@@ -307,24 +308,21 @@ class GameKitDeckSelectionTest(FunctionalTest):
self.browser.find_element(By.ID, "id_tooltip_portal").text,
)
)
portal = self.browser.find_element(By.ID, "id_tooltip_portal")
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
mini.find_element(By.CSS_SELECTOR, ".equip-deck-btn").click()
portal.find_element(By.CSS_SELECTOR, ".btn-equip").click()
# Both portals close after equip
# DON becomes disabled; mini updates to "Equipped"; data attr set optimistically
self.wait_for(
lambda: self.assertFalse(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
lambda: self.assertIn(
"btn-disabled",
portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
)
)
# Game Kit data attribute now reflects Earthman's id
self.assertIn("Equipped", self.browser.find_element(By.ID, "id_mini_tooltip_portal").text)
game_kit = self.browser.find_element(By.ID, "id_game_kit")
self.wait_for(
lambda: self.assertNotEqual(
game_kit.get_attribute("data-equipped-deck-id"), ""
)
)
self.assertNotEqual(game_kit.get_attribute("data-equipped-deck-id"), "")
# ------------------------------------------------------------------ #
# Test 6 — new user's Game Kit shows only the default Earthman deck #

View File

@@ -52,10 +52,10 @@ class PickSkyLocalStorageTest(FunctionalTest):
def _fill_form(self):
"""Set date, lat, lon directly (bypasses Nominatim network call)."""
self.browser.execute_script(
"document.getElementById('id_nf_date').value = '1990-02-28';"
"document.getElementById('id_nf_lat').value = '39.8244';"
"document.getElementById('id_nf_lon').value = '-74.9970';"
"document.getElementById('id_nf_place').value = 'Lindenwold, NJ';"
"document.getElementById('id_nf_date').value = '2008-05-27';"
"document.getElementById('id_nf_lat').value = '38.3754';"
"document.getElementById('id_nf_lon').value = '-76.6955';"
"document.getElementById('id_nf_place').value = 'Morganza, MD';"
"document.getElementById('id_nf_tz').value = 'America/New_York';"
)
# Fire input events so the save listener triggers
@@ -95,10 +95,10 @@ class PickSkyLocalStorageTest(FunctionalTest):
self._open_overlay()
values = self._field_values()
self.assertEqual(values["date"], "1990-02-28")
self.assertEqual(values["lat"], "39.8244")
self.assertEqual(values["lon"], "-74.9970")
self.assertEqual(values["place"], "Lindenwold, NJ")
self.assertEqual(values["date"], "2008-05-27")
self.assertEqual(values["lat"], "38.3754")
self.assertEqual(values["lon"], "-76.6955")
self.assertEqual(values["place"], "Morganza, MD")
self.assertEqual(values["tz"], "America/New_York")
# ------------------------------------------------------------------ #
@@ -118,8 +118,10 @@ class PickSkyLocalStorageTest(FunctionalTest):
self._open_overlay()
values = self._field_values()
self.assertEqual(values["date"], "1990-02-28")
self.assertEqual(values["lat"], "39.8244")
self.assertEqual(values["lon"], "-74.9970")
self.assertEqual(values["place"], "Lindenwold, NJ")
self.assertEqual(values["date"], "2008-05-27")
self.assertEqual(values["lat"], "38.3754")
self.assertEqual(values["lon"], "-76.6955")
self.assertEqual(values["place"], "Morganza, MD")
self.assertEqual(values["tz"], "America/New_York")

View File

@@ -106,7 +106,7 @@ class CarteBlancheTest(FunctionalTest):
)
)
# 3. Hover Carte Blanche — main tooltip present; mini tooltip shows "Equip Trinket?"
# 3. Hover Carte Blanche — main tooltip present; mini shows "Not Equipped"; DON active
carte_el = self.browser.find_element(By.ID, "id_kit_carte_blanche")
self.browser.execute_script(
"arguments[0].scrollIntoView({block: 'center'})", carte_el
@@ -122,14 +122,16 @@ class CarteBlancheTest(FunctionalTest):
self.assertIn("no expiry", portal.text)
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
equip_btn = mini.find_element(By.CSS_SELECTOR, ".equip-trinket-btn")
self.assertEqual(equip_btn.text, "Equip Trinket?")
self.assertIn("Not Equipped", mini.text)
don = portal.find_element(By.CSS_SELECTOR, ".btn-equip")
self.assertNotIn("btn-disabled", don.get_attribute("class"))
# 4. Click "Equip Trinket?" — DB switches; both portals close
equip_btn.click()
# 4. Click DON — DON becomes disabled; data-equipped-id set optimistically
don.click()
self.wait_for(
lambda: self.assertFalse(
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
lambda: self.assertIn(
"btn-disabled",
portal.find_element(By.CSS_SELECTOR, ".btn-equip").get_attribute("class"),
)
)
@@ -141,13 +143,8 @@ class CarteBlancheTest(FunctionalTest):
str(self.carte.pk),
)
)
# NOTE: re-hovering carte_el here to assert "Equipped" in mini is unreliable in
# headless GeckoDriver — move_to_element uses a different scroll-into-view algorithm
# than scrollIntoView({block:'center'}), so the computed element centre can match the
# cursor's current position and no mousemove fires. The equip round-trip is validated
# implicitly by the DB-side check below (step 6: Pass now shows "Equip Trinket?").
# 6. Hover Backstage Pass — mini tooltip shows "Equip Trinket?" (Pass no longer equipped)
# 6. Hover Backstage Pass — mini shows "Not Equipped" (Pass no longer equipped)
pass_el = self.browser.find_element(By.ID, "id_kit_pass")
ActionChains(self.browser).move_to_element(pass_el).perform()
self.wait_for(
@@ -158,7 +155,7 @@ class CarteBlancheTest(FunctionalTest):
)
mini = self.browser.find_element(By.ID, "id_mini_tooltip_portal")
self.wait_for(lambda: self.assertTrue(mini.is_displayed()))
self.assertTrue(mini.find_element(By.CSS_SELECTOR, ".equip-trinket-btn").is_displayed())
self.assertIn("Not Equipped", mini.text)
# ── GATEKEEPER PHASE ─────────────────────────────────────────────────