MY SKY: dashboard applet + full-page natal chart — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

- User model: sky_birth_dt/lat/lon/place/house_system/chart_data fields (lyric migration 0018)
- Applet seed: my-sky (6×6, dashboard context) via applets migration 0009
- dashboard/sky/ — full monoapplet page: natus form + D3 wheel, LS key scoped to dashboard:sky; saves to User model
- dashboard/sky/preview/ — PySwiss proxy (same logic as epic:natus_preview, no seat check)
- dashboard/sky/save/ — persists 6 sky fields to User via update_fields
- _applet-my-sky.html: tile with h2 link to /dashboard/sky/
- 2 FTs (applet→link→form, localStorage persistence) + 12 ITs — all green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-16 03:03:19 -04:00
parent 127f4a092d
commit abf8be8861
9 changed files with 817 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
"""Functional tests for the My Sky dashboard feature.
My Sky is a dashboard applet linking to /dashboard/sky/ — a full-page
natus (natal chart) interface where the user can save their personal sky
to their account (stored on the User model, independent of any game room).
"""
from selenium.webdriver.common.by import By
from apps.applets.models import Applet
from apps.lyric.models import User
from .base import FunctionalTest
class MySkyAppletTest(FunctionalTest):
"""My Sky applet appears on the dashboard and links to the sky page."""
def setUp(self):
super().setUp()
Applet.objects.get_or_create(
slug="my-sky",
defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
)
self.gamer = User.objects.create(email="stargazer@test.io")
# ── T1 ───────────────────────────────────────────────────────────────────
def test_my_sky_applet_links_to_sky_page_with_form(self):
"""Applet is visible on dashboard; link leads to /dashboard/sky/ with
all natus form fields present."""
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.live_server_url)
# 1. Applet is on the dashboard
applet = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_applet_my_sky")
)
# 2. Heading contains a link whose text is "My Sky"
link = applet.find_element(By.CSS_SELECTOR, "h2 a")
self.assertIn("MY SKY", link.text.upper())
# 3. Clicking the link navigates to /dashboard/sky/
link.click()
self.wait_for(
lambda: self.assertRegex(self.browser.current_url, r"/dashboard/sky/$")
)
# 4. All natus form fields are present
self.browser.find_element(By.ID, "id_nf_date")
self.browser.find_element(By.ID, "id_nf_time")
self.browser.find_element(By.ID, "id_nf_place")
self.browser.find_element(By.ID, "id_nf_lat")
self.browser.find_element(By.ID, "id_nf_lon")
self.browser.find_element(By.ID, "id_nf_tz")
self.browser.find_element(By.ID, "id_natus_confirm")
class MySkyLocalStorageTest(FunctionalTest):
"""My Sky form fields persist to localStorage across visits."""
def setUp(self):
super().setUp()
Applet.objects.get_or_create(
slug="my-sky",
defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
)
self.gamer = User.objects.create(email="stargazer@test.io")
self.sky_url = self.live_server_url + "/dashboard/sky/"
def _fill_form(self):
"""Set date, lat, lon directly — bypasses Nominatim network call."""
self.browser.execute_script(
"document.getElementById('id_nf_date').value = '1990-06-15';"
"document.getElementById('id_nf_lat').value = '51.5074';"
"document.getElementById('id_nf_lon').value = '-0.1278';"
"document.getElementById('id_nf_place').value = 'London, UK';"
"document.getElementById('id_nf_tz').value = 'Europe/London';"
)
# Fire input events so the localStorage save listener triggers
self.browser.execute_script("""
['id_nf_date','id_nf_lat','id_nf_lon','id_nf_place','id_nf_tz'].forEach(id => {
document.getElementById(id)
.dispatchEvent(new Event('input', {bubbles: true}));
});
""")
def _field_values(self):
return self.browser.execute_script("""
return {
date: document.getElementById('id_nf_date').value,
lat: document.getElementById('id_nf_lat').value,
lon: document.getElementById('id_nf_lon').value,
place: document.getElementById('id_nf_place').value,
tz: document.getElementById('id_nf_tz').value,
};
""")
# ── T2 ───────────────────────────────────────────────────────────────────
def test_sky_form_fields_repopulated_after_page_refresh(self):
"""Form values survive a full page refresh via localStorage."""
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.sky_url)
self.wait_for(lambda: self.browser.find_element(By.ID, "id_nf_date"))
self._fill_form()
self.browser.refresh()
self.wait_for(lambda: self.browser.find_element(By.ID, "id_nf_date"))
values = self._field_values()
self.assertEqual(values["date"], "1990-06-15")
self.assertEqual(values["lat"], "51.5074")
self.assertEqual(values["lon"], "-0.1278")
self.assertEqual(values["place"], "London, UK")
self.assertEqual(values["tz"], "Europe/London")