rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
"""Functional tests for the Note system.
|
|
|
|
|
|
|
|
|
|
|
|
Note is Earthman's achievement analogue — account-level unlocks earned when
|
|
|
|
|
|
the socius observes a qualifying action. These tests cover the Stargazer Note:
|
|
|
|
|
|
earned on the first valid personal sky save (dashboard My Sky applet or sky page),
|
|
|
|
|
|
outside of any game room.
|
|
|
|
|
|
|
|
|
|
|
|
Two test classes — one per surface that can trigger the unlock — each asserting both
|
|
|
|
|
|
the negative (disabled/incomplete save does nothing) and positive (first valid save
|
|
|
|
|
|
fires the banner) conditions.
|
|
|
|
|
|
|
|
|
|
|
|
T2 (Dashboard full flow) is split across three focused tests:
|
|
|
|
|
|
T2a — save → banner → FYI → recognition page item
|
|
|
|
|
|
T2b — palette modal flow on recognition page
|
|
|
|
|
|
T2c — dashboard palette applet reflects Note palette unlock
|
|
|
|
|
|
"""
|
|
|
|
|
|
import json as _json
|
|
|
|
|
|
|
2026-04-23 03:12:24 -04:00
|
|
|
|
from django.test import tag
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
from django.utils import timezone
|
2026-02-07 19:44:47 -05:00
|
|
|
|
from selenium.webdriver.common.by import By
|
2026-02-17 23:07:12 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
from apps.applets.models import Applet
|
|
|
|
|
|
from apps.drama.models import Note
|
|
|
|
|
|
from apps.lyric.models import User
|
|
|
|
|
|
|
2026-02-01 20:06:01 -05:00
|
|
|
|
from .base import FunctionalTest
|
2026-02-17 23:07:12 -05:00
|
|
|
|
|
2026-02-01 20:06:01 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
# Shared natal chart fixture — same birth data as test_applet_my_sky.py.
|
|
|
|
|
|
_CHART_FIXTURE = {
|
|
|
|
|
|
"planets": {
|
|
|
|
|
|
"Sun": {"sign": "Gemini", "degree": 66.7, "retrograde": False},
|
|
|
|
|
|
"Moon": {"sign": "Taurus", "degree": 43.0, "retrograde": False},
|
|
|
|
|
|
"Mercury": {"sign": "Taurus", "degree": 55.0, "retrograde": False},
|
|
|
|
|
|
"Venus": {"sign": "Gemini", "degree": 63.3, "retrograde": False},
|
|
|
|
|
|
"Mars": {"sign": "Leo", "degree": 132.0, "retrograde": False},
|
|
|
|
|
|
"Jupiter": {"sign": "Capricorn", "degree": 292.0, "retrograde": True},
|
|
|
|
|
|
"Saturn": {"sign": "Virgo", "degree": 153.0, "retrograde": False},
|
|
|
|
|
|
"Uranus": {"sign": "Pisces", "degree": 322.0, "retrograde": False},
|
|
|
|
|
|
"Neptune": {"sign": "Aquarius", "degree": 323.0, "retrograde": True},
|
|
|
|
|
|
"Pluto": {"sign": "Sagittarius", "degree": 269.0, "retrograde": True},
|
|
|
|
|
|
},
|
|
|
|
|
|
"houses": {
|
|
|
|
|
|
"cusps": [180, 210, 240, 270, 300, 330, 0, 30, 60, 90, 120, 150],
|
|
|
|
|
|
"asc": 180.0, "mc": 90.0,
|
|
|
|
|
|
},
|
|
|
|
|
|
"elements": {"Fire": 1, "Water": 0, "Stone": 2, "Air": 4, "Time": 1, "Space": 1},
|
|
|
|
|
|
"aspects": [],
|
|
|
|
|
|
"distinctions": {
|
|
|
|
|
|
"1": 0, "2": 0, "3": 2, "4": 0, "5": 0, "6": 0,
|
|
|
|
|
|
"7": 1, "8": 0, "9": 2, "10": 1, "11": 1, "12": 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
"house_system": "O",
|
|
|
|
|
|
"timezone": "Europe/London",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _mock_preview_js(fixture):
|
|
|
|
|
|
"""Intercepts /sky/preview with a fixture; lets /sky/save reach the real server."""
|
|
|
|
|
|
return f"""
|
|
|
|
|
|
const FIXTURE = {_json.dumps(fixture)};
|
|
|
|
|
|
window._origFetch = window.fetch;
|
|
|
|
|
|
window.fetch = function(url, opts) {{
|
|
|
|
|
|
if (url.includes('/sky/preview')) {{
|
|
|
|
|
|
return Promise.resolve({{
|
|
|
|
|
|
ok: true,
|
|
|
|
|
|
json: () => Promise.resolve(FIXTURE),
|
|
|
|
|
|
}});
|
|
|
|
|
|
}}
|
|
|
|
|
|
return window._origFetch(url, opts);
|
|
|
|
|
|
}};
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fill_valid_sky_form(browser):
|
|
|
|
|
|
"""Populate form fields and fire input events to trigger schedulePreview."""
|
|
|
|
|
|
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_tz').value = 'Europe/London';
|
|
|
|
|
|
document.getElementById('id_nf_date').dispatchEvent(
|
|
|
|
|
|
new Event('input', {bubbles: true})
|
|
|
|
|
|
);
|
|
|
|
|
|
""")
|
2026-02-01 20:18:42 -05:00
|
|
|
|
|
2026-02-07 19:44:47 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
# Surface A — My Sky applet on the Dashboard
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
class StargazerNoteFromDashboardTest(FunctionalTest):
|
|
|
|
|
|
"""Stargazer Note triggered from the My Sky applet."""
|
|
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
|
self.browser.set_window_size(800, 1200)
|
|
|
|
|
|
Applet.objects.get_or_create(
|
|
|
|
|
|
slug="my-sky",
|
|
|
|
|
|
defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
|
|
|
|
|
)
|
|
|
|
|
|
Applet.objects.get_or_create(
|
|
|
|
|
|
slug="palette",
|
|
|
|
|
|
defaults={"name": "Palettes", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
|
|
|
|
|
)
|
|
|
|
|
|
Applet.objects.get_or_create(
|
2026-05-03 23:22:01 -04:00
|
|
|
|
slug="notes",
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
defaults={"name": "Note", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
|
|
|
|
|
)
|
|
|
|
|
|
self.gamer = User.objects.create(email="stargazer@test.io")
|
|
|
|
|
|
|
|
|
|
|
|
# ── T1 ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def test_disabled_save_button_does_not_unlock_note(self):
|
|
|
|
|
|
"""SAVE SKY is disabled before a valid preview fires.
|
|
|
|
|
|
No Note banner appears while the button remains disabled."""
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
2026-02-01 20:18:42 -05:00
|
|
|
|
self.browser.get(self.live_server_url)
|
2026-02-07 19:44:47 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
confirm_btn = self.wait_for(
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_sky_confirm")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
)
|
|
|
|
|
|
self.assertIsNotNone(confirm_btn.get_attribute("disabled"))
|
|
|
|
|
|
self.assertFalse(self.browser.find_elements(By.CSS_SELECTOR, ".note-banner"))
|
2026-02-07 19:44:47 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
# ── T2a ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def test_first_valid_save_from_applet_fires_banner_and_leads_to_note_page(self):
|
|
|
|
|
|
"""First valid SAVE SKY from the My Sky applet fires the Stargazer banner.
|
|
|
|
|
|
FYI button navigates to /billboard/my-notes/ showing the Stargazer item."""
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url)
|
|
|
|
|
|
|
|
|
|
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_applet_sky_form_wrap"))
|
|
|
|
|
|
self.browser.execute_script(_mock_preview_js(_CHART_FIXTURE))
|
|
|
|
|
|
_fill_valid_sky_form(self.browser)
|
|
|
|
|
|
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.wait_for(lambda: self.assertIsNone(confirm_btn.get_attribute("disabled")))
|
|
|
|
|
|
confirm_btn.click()
|
|
|
|
|
|
|
|
|
|
|
|
# Banner slides in below the Dash h2
|
|
|
|
|
|
banner = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-banner")
|
2026-02-07 19:44:47 -05:00
|
|
|
|
)
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Stargazer",
|
|
|
|
|
|
banner.find_element(By.CSS_SELECTOR, ".note-banner__title").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
banner.find_element(By.CSS_SELECTOR, ".note-banner__description")
|
|
|
|
|
|
banner.find_element(By.CSS_SELECTOR, ".note-banner__timestamp")
|
brief sprint C3.a: Note unlock spawns Line + Brief on the user's per-category Post; banner JS consumes the new brief payload (FYI → post detail, square → my-notes) — TDD
Wires the C2 Brief model into the existing Note-unlock pipeline. Per the per-category Post model: each user has a single Post(owner=user, kind=NOTE_UNLOCK) titled by the header Line "Look! — new Note unlocked"; each unlock appends a per-event Line ("Stargazer, 5:21:00 PM") + spawns a Brief FK'd to that Line.
Server:
- billboard.Post gains a `kind` enum (NOTE_UNLOCK / USER_POST / SHARE_INVITE, default USER_POST) so the per-category Post is deterministically discoverable via (owner, kind=NOTE_UNLOCK).
- drama.Note.grant_if_new now returns (note, created, brief) — backwards-incompat tuple shape; the new third slot is None on idempotent re-grants. On a fresh grant it: get-or-creates the user's Note Unlocks Post; ensures the header Line; appends a per-event Line w. timestamp; creates a Brief(kind=NOTE_UNLOCK, owner, post, line, title=note.display_title).
- dashboard.views.sky_save's response shape flips {note: {...}|null} → {brief: {...}|null}. The new helper _brief_to_banner_dict exposes {id, kind, title, line_text, post_url, square_url, created_at}; for NOTE_UNLOCK kind, square_url = reverse('billboard:my_notes').
Banner JS (apps/dashboard/note.js):
- Module renamed Note → Brief (Note kept as an alias during the C3 sprint; will retire in C3.e). showBanner now consumes the Brief shape: title from brief.title, body from brief.line_text, time from brief.created_at, FYI href = brief.post_url, NVM dismisses, .note-banner__image is rendered as a clickable <a href=brief.square_url> when square_url is set. The CSS class names (.note-banner, .note-banner__title, etc.) stay so all existing SCSS + FT selectors keep working.
- sky.html + _applet-my-sky.html flip Note.handleSaveResponse → Brief.handleSaveResponse.
Tests:
- new IT class GrantIfNewSpawnsBriefTest (5 tests): first grant creates Post+Line+Brief, idempotent on second grant returns no brief, two different slugs share one Post, line text carries note title, post.kind == NOTE_UNLOCK. PostKindFieldTest (2 tests): default user_post + all three choices present.
- existing drama test_models.GrantIfNew tests updated to unpack the third tuple element.
- dashboard.tests.integrated.test_sky_views: 5 tests flipped from data["note"] → data["brief"] w/ the new payload shape (kind=note_unlock, title=Stargazer, line_text contains Stargazer, post_url under /billboard/post/, square_url=/billboard/my-notes/).
- NoteSpec.js (Jasmine): 12 specs rewritten for the Brief shape — SAMPLE_BRIEF carries the seven fields, T6 asserts the square is a clickable <a>, T8 asserts FYI points at brief.post_url (was hardcoded /billboard/my-notes/).
- functional_tests.test_applet_my_notes: T2 split — FYI now navigates to /billboard/post/<uuid>/ (the post detail / mark-read contract), the .note-banner__image square preserves the legacy jump direct to /billboard/my-notes/.
billboard/0003_post_kind migration auto-generated. 808 ITs + 9 my_notes FTs + 1 Jasmine FT all green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:00:01 -04:00
|
|
|
|
# Per the Brief sprint, .note-banner__image is now a clickable <a> for
|
|
|
|
|
|
# NOTE_UNLOCK kind — square goes to my_notes.html (the legacy FYI
|
|
|
|
|
|
# behavior preserved on the square).
|
|
|
|
|
|
square = banner.find_element(By.CSS_SELECTOR, ".note-banner__image")
|
|
|
|
|
|
self.assertEqual(square.tag_name, "a")
|
|
|
|
|
|
self.assertEqual(square.get_attribute("href").rstrip("/"),
|
|
|
|
|
|
self.live_server_url + "/billboard/my-notes")
|
2026-04-22 22:43:50 -04:00
|
|
|
|
banner.find_element(By.CSS_SELECTOR, ".btn.btn-cancel") # NVM
|
2026-04-28 20:22:19 -04:00
|
|
|
|
fyi = banner.find_element(By.CSS_SELECTOR, ".btn.btn-info") # FYI
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
|
brief sprint C3.a: Note unlock spawns Line + Brief on the user's per-category Post; banner JS consumes the new brief payload (FYI → post detail, square → my-notes) — TDD
Wires the C2 Brief model into the existing Note-unlock pipeline. Per the per-category Post model: each user has a single Post(owner=user, kind=NOTE_UNLOCK) titled by the header Line "Look! — new Note unlocked"; each unlock appends a per-event Line ("Stargazer, 5:21:00 PM") + spawns a Brief FK'd to that Line.
Server:
- billboard.Post gains a `kind` enum (NOTE_UNLOCK / USER_POST / SHARE_INVITE, default USER_POST) so the per-category Post is deterministically discoverable via (owner, kind=NOTE_UNLOCK).
- drama.Note.grant_if_new now returns (note, created, brief) — backwards-incompat tuple shape; the new third slot is None on idempotent re-grants. On a fresh grant it: get-or-creates the user's Note Unlocks Post; ensures the header Line; appends a per-event Line w. timestamp; creates a Brief(kind=NOTE_UNLOCK, owner, post, line, title=note.display_title).
- dashboard.views.sky_save's response shape flips {note: {...}|null} → {brief: {...}|null}. The new helper _brief_to_banner_dict exposes {id, kind, title, line_text, post_url, square_url, created_at}; for NOTE_UNLOCK kind, square_url = reverse('billboard:my_notes').
Banner JS (apps/dashboard/note.js):
- Module renamed Note → Brief (Note kept as an alias during the C3 sprint; will retire in C3.e). showBanner now consumes the Brief shape: title from brief.title, body from brief.line_text, time from brief.created_at, FYI href = brief.post_url, NVM dismisses, .note-banner__image is rendered as a clickable <a href=brief.square_url> when square_url is set. The CSS class names (.note-banner, .note-banner__title, etc.) stay so all existing SCSS + FT selectors keep working.
- sky.html + _applet-my-sky.html flip Note.handleSaveResponse → Brief.handleSaveResponse.
Tests:
- new IT class GrantIfNewSpawnsBriefTest (5 tests): first grant creates Post+Line+Brief, idempotent on second grant returns no brief, two different slugs share one Post, line text carries note title, post.kind == NOTE_UNLOCK. PostKindFieldTest (2 tests): default user_post + all three choices present.
- existing drama test_models.GrantIfNew tests updated to unpack the third tuple element.
- dashboard.tests.integrated.test_sky_views: 5 tests flipped from data["note"] → data["brief"] w/ the new payload shape (kind=note_unlock, title=Stargazer, line_text contains Stargazer, post_url under /billboard/post/, square_url=/billboard/my-notes/).
- NoteSpec.js (Jasmine): 12 specs rewritten for the Brief shape — SAMPLE_BRIEF carries the seven fields, T6 asserts the square is a clickable <a>, T8 asserts FYI points at brief.post_url (was hardcoded /billboard/my-notes/).
- functional_tests.test_applet_my_notes: T2 split — FYI now navigates to /billboard/post/<uuid>/ (the post detail / mark-read contract), the .note-banner__image square preserves the legacy jump direct to /billboard/my-notes/.
billboard/0003_post_kind migration auto-generated. 808 ITs + 9 my_notes FTs + 1 Jasmine FT all green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:00:01 -04:00
|
|
|
|
# FYI now navigates to the underlying Brief's Post detail
|
|
|
|
|
|
# (/billboard/post/<uuid>/) — the GET render is the mark-read contract.
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
fyi.click()
|
2026-02-07 19:44:47 -05:00
|
|
|
|
self.wait_for(
|
brief sprint C3.a: Note unlock spawns Line + Brief on the user's per-category Post; banner JS consumes the new brief payload (FYI → post detail, square → my-notes) — TDD
Wires the C2 Brief model into the existing Note-unlock pipeline. Per the per-category Post model: each user has a single Post(owner=user, kind=NOTE_UNLOCK) titled by the header Line "Look! — new Note unlocked"; each unlock appends a per-event Line ("Stargazer, 5:21:00 PM") + spawns a Brief FK'd to that Line.
Server:
- billboard.Post gains a `kind` enum (NOTE_UNLOCK / USER_POST / SHARE_INVITE, default USER_POST) so the per-category Post is deterministically discoverable via (owner, kind=NOTE_UNLOCK).
- drama.Note.grant_if_new now returns (note, created, brief) — backwards-incompat tuple shape; the new third slot is None on idempotent re-grants. On a fresh grant it: get-or-creates the user's Note Unlocks Post; ensures the header Line; appends a per-event Line w. timestamp; creates a Brief(kind=NOTE_UNLOCK, owner, post, line, title=note.display_title).
- dashboard.views.sky_save's response shape flips {note: {...}|null} → {brief: {...}|null}. The new helper _brief_to_banner_dict exposes {id, kind, title, line_text, post_url, square_url, created_at}; for NOTE_UNLOCK kind, square_url = reverse('billboard:my_notes').
Banner JS (apps/dashboard/note.js):
- Module renamed Note → Brief (Note kept as an alias during the C3 sprint; will retire in C3.e). showBanner now consumes the Brief shape: title from brief.title, body from brief.line_text, time from brief.created_at, FYI href = brief.post_url, NVM dismisses, .note-banner__image is rendered as a clickable <a href=brief.square_url> when square_url is set. The CSS class names (.note-banner, .note-banner__title, etc.) stay so all existing SCSS + FT selectors keep working.
- sky.html + _applet-my-sky.html flip Note.handleSaveResponse → Brief.handleSaveResponse.
Tests:
- new IT class GrantIfNewSpawnsBriefTest (5 tests): first grant creates Post+Line+Brief, idempotent on second grant returns no brief, two different slugs share one Post, line text carries note title, post.kind == NOTE_UNLOCK. PostKindFieldTest (2 tests): default user_post + all three choices present.
- existing drama test_models.GrantIfNew tests updated to unpack the third tuple element.
- dashboard.tests.integrated.test_sky_views: 5 tests flipped from data["note"] → data["brief"] w/ the new payload shape (kind=note_unlock, title=Stargazer, line_text contains Stargazer, post_url under /billboard/post/, square_url=/billboard/my-notes/).
- NoteSpec.js (Jasmine): 12 specs rewritten for the Brief shape — SAMPLE_BRIEF carries the seven fields, T6 asserts the square is a clickable <a>, T8 asserts FYI points at brief.post_url (was hardcoded /billboard/my-notes/).
- functional_tests.test_applet_my_notes: T2 split — FYI now navigates to /billboard/post/<uuid>/ (the post detail / mark-read contract), the .note-banner__image square preserves the legacy jump direct to /billboard/my-notes/.
billboard/0003_post_kind migration auto-generated. 808 ITs + 9 my_notes FTs + 1 Jasmine FT all green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:00:01 -04:00
|
|
|
|
lambda: self.assertRegex(self.browser.current_url, r"/billboard/post/[0-9a-f-]+/")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
brief sprint C3.a: Note unlock spawns Line + Brief on the user's per-category Post; banner JS consumes the new brief payload (FYI → post detail, square → my-notes) — TDD
Wires the C2 Brief model into the existing Note-unlock pipeline. Per the per-category Post model: each user has a single Post(owner=user, kind=NOTE_UNLOCK) titled by the header Line "Look! — new Note unlocked"; each unlock appends a per-event Line ("Stargazer, 5:21:00 PM") + spawns a Brief FK'd to that Line.
Server:
- billboard.Post gains a `kind` enum (NOTE_UNLOCK / USER_POST / SHARE_INVITE, default USER_POST) so the per-category Post is deterministically discoverable via (owner, kind=NOTE_UNLOCK).
- drama.Note.grant_if_new now returns (note, created, brief) — backwards-incompat tuple shape; the new third slot is None on idempotent re-grants. On a fresh grant it: get-or-creates the user's Note Unlocks Post; ensures the header Line; appends a per-event Line w. timestamp; creates a Brief(kind=NOTE_UNLOCK, owner, post, line, title=note.display_title).
- dashboard.views.sky_save's response shape flips {note: {...}|null} → {brief: {...}|null}. The new helper _brief_to_banner_dict exposes {id, kind, title, line_text, post_url, square_url, created_at}; for NOTE_UNLOCK kind, square_url = reverse('billboard:my_notes').
Banner JS (apps/dashboard/note.js):
- Module renamed Note → Brief (Note kept as an alias during the C3 sprint; will retire in C3.e). showBanner now consumes the Brief shape: title from brief.title, body from brief.line_text, time from brief.created_at, FYI href = brief.post_url, NVM dismisses, .note-banner__image is rendered as a clickable <a href=brief.square_url> when square_url is set. The CSS class names (.note-banner, .note-banner__title, etc.) stay so all existing SCSS + FT selectors keep working.
- sky.html + _applet-my-sky.html flip Note.handleSaveResponse → Brief.handleSaveResponse.
Tests:
- new IT class GrantIfNewSpawnsBriefTest (5 tests): first grant creates Post+Line+Brief, idempotent on second grant returns no brief, two different slugs share one Post, line text carries note title, post.kind == NOTE_UNLOCK. PostKindFieldTest (2 tests): default user_post + all three choices present.
- existing drama test_models.GrantIfNew tests updated to unpack the third tuple element.
- dashboard.tests.integrated.test_sky_views: 5 tests flipped from data["note"] → data["brief"] w/ the new payload shape (kind=note_unlock, title=Stargazer, line_text contains Stargazer, post_url under /billboard/post/, square_url=/billboard/my-notes/).
- NoteSpec.js (Jasmine): 12 specs rewritten for the Brief shape — SAMPLE_BRIEF carries the seven fields, T6 asserts the square is a clickable <a>, T8 asserts FYI points at brief.post_url (was hardcoded /billboard/my-notes/).
- functional_tests.test_applet_my_notes: T2 split — FYI now navigates to /billboard/post/<uuid>/ (the post detail / mark-read contract), the .note-banner__image square preserves the legacy jump direct to /billboard/my-notes/.
billboard/0003_post_kind migration auto-generated. 808 ITs + 9 my_notes FTs + 1 Jasmine FT all green.
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:00:01 -04:00
|
|
|
|
# Square (.note-banner__image) preserves the jump-direct-to-my-notes
|
|
|
|
|
|
# behavior. Reload the dashboard and re-fire the banner via a stash.
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/billboard/my-notes/")
|
|
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
# Note page: one Stargazer item
|
|
|
|
|
|
item = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-list .note-item")
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Stargazer",
|
|
|
|
|
|
item.find_element(By.CSS_SELECTOR, ".note-item__title").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
item.find_element(By.CSS_SELECTOR, ".note-item__description")
|
|
|
|
|
|
item.find_element(By.CSS_SELECTOR, ".note-item__image-box")
|
|
|
|
|
|
|
|
|
|
|
|
# ── T2b ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-22 23:54:05 -04:00
|
|
|
|
def _open_modal_and_click_bardo(self):
|
|
|
|
|
|
"""Helper: navigate to /billboard/my-notes/, open modal, click bardo swatch body.
|
|
|
|
|
|
Returns (modal, confirm_menu) after the confirm bar is visible."""
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.browser.get(self.live_server_url + "/billboard/my-notes/")
|
|
|
|
|
|
image_box = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-item__image-box")
|
|
|
|
|
|
)
|
|
|
|
|
|
image_box.click()
|
|
|
|
|
|
modal = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-palette-modal")
|
|
|
|
|
|
)
|
|
|
|
|
|
bardo_body = modal.find_element(By.CSS_SELECTOR, ".palette-bardo .note-swatch-body")
|
|
|
|
|
|
self.browser.execute_script(
|
|
|
|
|
|
"arguments[0].dispatchEvent(new MouseEvent('click', {bubbles: true}))",
|
|
|
|
|
|
bardo_body,
|
|
|
|
|
|
)
|
2026-04-22 23:54:05 -04:00
|
|
|
|
confirm_menu = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-palette-confirm")
|
|
|
|
|
|
)
|
|
|
|
|
|
return modal, confirm_menu
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
|
2026-04-22 23:54:05 -04:00
|
|
|
|
def test_note_page_swatch_previews_palette_sitewide_and_ok_persists(self):
|
|
|
|
|
|
"""Clicking a swatch previews that palette on the whole body.
|
|
|
|
|
|
OK commits it — Note.palette and user.palette both saved — so it
|
|
|
|
|
|
survives navigation to a new page."""
|
|
|
|
|
|
Note.objects.create(
|
|
|
|
|
|
user=self.gamer, slug="stargazer", earned_at=timezone.now(),
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
)
|
2026-04-22 23:54:05 -04:00
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
|
2026-04-22 23:54:05 -04:00
|
|
|
|
modal, confirm = self._open_modal_and_click_bardo()
|
|
|
|
|
|
|
|
|
|
|
|
# Swatch click previews bardo on the whole body
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn(
|
|
|
|
|
|
"palette-bardo",
|
|
|
|
|
|
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
|
|
|
|
|
|
)
|
2026-02-07 19:44:47 -05:00
|
|
|
|
)
|
2026-04-22 23:54:05 -04:00
|
|
|
|
# Modal still open
|
|
|
|
|
|
self.assertTrue(self.browser.find_elements(By.CSS_SELECTOR, ".note-palette-modal"))
|
2026-02-01 20:18:42 -05:00
|
|
|
|
|
2026-04-22 23:54:05 -04:00
|
|
|
|
# OK → modal closes, ? box replaced by bardo swatch
|
|
|
|
|
|
confirm.find_element(By.CSS_SELECTOR, ".btn.btn-confirm").click()
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.wait_for(lambda: self.assertFalse(
|
|
|
|
|
|
self.browser.find_elements(By.CSS_SELECTOR, ".note-palette-modal")
|
|
|
|
|
|
))
|
|
|
|
|
|
item = self.browser.find_element(By.CSS_SELECTOR, ".note-item")
|
2026-04-22 23:54:05 -04:00
|
|
|
|
self.assertTrue(item.find_elements(By.CSS_SELECTOR, ".note-item__palette.palette-bardo"))
|
|
|
|
|
|
self.assertFalse(item.find_elements(By.CSS_SELECTOR, ".note-item__image-box"))
|
|
|
|
|
|
|
|
|
|
|
|
# Navigate away — palette persists (user.palette was saved)
|
|
|
|
|
|
self.browser.get(self.live_server_url)
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn(
|
|
|
|
|
|
"palette-bardo",
|
|
|
|
|
|
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
|
|
|
|
|
|
)
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
)
|
2026-04-22 23:54:05 -04:00
|
|
|
|
|
|
|
|
|
|
def test_note_swatch_nvm_reverts_body_palette(self):
|
|
|
|
|
|
"""NVM in the confirm bar reverts the sitewide body palette back to
|
|
|
|
|
|
what it was before the swatch was clicked."""
|
|
|
|
|
|
Note.objects.create(
|
|
|
|
|
|
user=self.gamer, slug="stargazer", earned_at=timezone.now(),
|
|
|
|
|
|
)
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
|
|
|
|
|
|
|
|
|
|
|
# Record the original palette before opening the modal
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/billboard/my-notes/")
|
|
|
|
|
|
original_classes = self.browser.find_element(
|
|
|
|
|
|
By.TAG_NAME, "body"
|
|
|
|
|
|
).get_attribute("class")
|
|
|
|
|
|
original_palette = next(
|
|
|
|
|
|
(c for c in original_classes.split() if c.startswith("palette-")), None
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
modal, confirm = self._open_modal_and_click_bardo()
|
|
|
|
|
|
|
|
|
|
|
|
# Bardo is previewed
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn(
|
|
|
|
|
|
"palette-bardo",
|
|
|
|
|
|
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# NVM reverts
|
|
|
|
|
|
confirm.find_element(By.CSS_SELECTOR, ".btn.btn-cancel").click()
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertNotIn(
|
|
|
|
|
|
"palette-bardo",
|
|
|
|
|
|
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
|
|
|
|
|
|
)
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
)
|
2026-04-22 23:54:05 -04:00
|
|
|
|
if original_palette:
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
original_palette,
|
|
|
|
|
|
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
|
|
|
|
|
|
)
|
2026-04-23 01:31:19 -04:00
|
|
|
|
# Swatch modal remains open after NVM; only the confirm bar is hidden
|
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
|
self.browser.find_elements(By.CSS_SELECTOR, ".note-palette-modal")
|
|
|
|
|
|
)
|
|
|
|
|
|
confirm_el = self.browser.find_element(By.CSS_SELECTOR, ".note-palette-confirm")
|
|
|
|
|
|
self.assertFalse(confirm_el.is_displayed())
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
|
|
|
|
|
|
# ── T2c ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def test_dashboard_palette_applet_reflects_note_palette_unlock(self):
|
|
|
|
|
|
"""After palette unlock via Note, the Dashboard Palette applet shows
|
|
|
|
|
|
the palette swatch as unlocked with Stargazer shoptalk."""
|
|
|
|
|
|
Note.objects.create(
|
|
|
|
|
|
user=self.gamer, slug="stargazer", earned_at=timezone.now(),
|
|
|
|
|
|
palette="palette-bardo",
|
|
|
|
|
|
)
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
2026-02-01 20:18:42 -05:00
|
|
|
|
self.browser.get(self.live_server_url)
|
2026-02-07 19:44:47 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
palette_applet = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_applet_palette")
|
2026-02-07 19:44:47 -05:00
|
|
|
|
)
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
bardo = palette_applet.find_element(By.CSS_SELECTOR, ".swatch.palette-bardo")
|
|
|
|
|
|
self.assertNotIn("locked", bardo.get_attribute("class"))
|
|
|
|
|
|
bardo_ok = bardo.find_element(By.CSS_SELECTOR, ".palette-ok")
|
|
|
|
|
|
self.assertIn("btn-confirm", bardo_ok.get_attribute("class"))
|
|
|
|
|
|
self.assertNotIn("btn-disabled", bardo_ok.get_attribute("class"))
|
2026-04-26 21:17:42 -04:00
|
|
|
|
self.assertIn("Stargazer", bardo.get_attribute("data-description"))
|
2026-02-07 19:44:47 -05:00
|
|
|
|
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
# Surface B — /dashboard/sky/ standalone page
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
class StargazerNoteFromSkyPageTest(FunctionalTest):
|
|
|
|
|
|
"""Stargazer Note triggered from the standalone sky page.
|
|
|
|
|
|
|
|
|
|
|
|
T3 — disabled save on sky page does not fire Note.
|
|
|
|
|
|
T4 — first valid save fires banner; NVM dismisses it.
|
|
|
|
|
|
T5 — already-earned Note does not re-show banner on subsequent save.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
|
self.browser.set_window_size(800, 1200)
|
|
|
|
|
|
Applet.objects.get_or_create(
|
|
|
|
|
|
slug="my-sky",
|
|
|
|
|
|
defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
|
|
|
|
|
|
)
|
|
|
|
|
|
Applet.objects.get_or_create(
|
2026-05-03 23:22:01 -04:00
|
|
|
|
slug="notes",
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
defaults={"name": "Note", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
|
|
|
|
|
)
|
|
|
|
|
|
self.gamer = User.objects.create(email="stargazer@test.io")
|
|
|
|
|
|
self.sky_url = self.live_server_url + "/dashboard/sky/"
|
|
|
|
|
|
|
|
|
|
|
|
# ── T3 ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def test_disabled_save_on_sky_page_does_not_unlock_note(self):
|
|
|
|
|
|
"""On /dashboard/sky/, SAVE SKY is disabled until preview fires.
|
|
|
|
|
|
No Note banner appears while the button is disabled."""
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
|
|
|
|
|
self.browser.get(self.sky_url)
|
|
|
|
|
|
|
|
|
|
|
|
confirm_btn = self.wait_for(
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_sky_confirm")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
)
|
|
|
|
|
|
self.assertIsNotNone(confirm_btn.get_attribute("disabled"))
|
|
|
|
|
|
self.assertFalse(self.browser.find_elements(By.CSS_SELECTOR, ".note-banner"))
|
|
|
|
|
|
|
|
|
|
|
|
# ── T4 ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def test_first_valid_save_on_sky_page_fires_banner_and_nvm_dismisses(self):
|
|
|
|
|
|
"""First valid SAVE SKY on /dashboard/sky/ fires the Stargazer banner.
|
2026-04-22 22:43:50 -04:00
|
|
|
|
NVM (.btn.btn-cancel) dismisses it."""
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
|
|
|
|
|
self.browser.get(self.sky_url)
|
|
|
|
|
|
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_sky_confirm"))
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.browser.execute_script(_mock_preview_js(_CHART_FIXTURE))
|
|
|
|
|
|
_fill_valid_sky_form(self.browser)
|
|
|
|
|
|
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.wait_for(lambda: self.assertIsNone(confirm_btn.get_attribute("disabled")))
|
|
|
|
|
|
confirm_btn.click()
|
|
|
|
|
|
|
|
|
|
|
|
banner = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-banner")
|
2026-02-07 19:44:47 -05:00
|
|
|
|
)
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Stargazer",
|
|
|
|
|
|
banner.find_element(By.CSS_SELECTOR, ".note-banner__title").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-22 22:43:50 -04:00
|
|
|
|
banner.find_element(By.CSS_SELECTOR, ".btn.btn-cancel").click()
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.wait_for(lambda: self.assertFalse(
|
|
|
|
|
|
self.browser.find_elements(By.CSS_SELECTOR, ".note-banner")
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
# ── T5 ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def test_already_earned_note_does_not_show_banner_on_subsequent_save(self):
|
|
|
|
|
|
"""When Stargazer is already in the database for this user, a valid sky
|
|
|
|
|
|
save does not fire another Note banner."""
|
|
|
|
|
|
Note.objects.create(
|
|
|
|
|
|
user=self.gamer,
|
|
|
|
|
|
slug="stargazer",
|
|
|
|
|
|
earned_at=timezone.now(),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
|
|
|
|
|
self.browser.get(self.sky_url)
|
|
|
|
|
|
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
self.wait_for(lambda: self.browser.find_element(By.ID, "id_sky_confirm"))
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.browser.execute_script(_mock_preview_js(_CHART_FIXTURE))
|
|
|
|
|
|
_fill_valid_sky_form(self.browser)
|
|
|
|
|
|
|
rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:36:15 -04:00
|
|
|
|
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
|
rename: Note→Post/Line (dashboard); Recognition→Note (drama); new-post/my-posts to billboard
- dashboard: Note→Post, Item→Line across models, forms, views, API, urls & tests
- new-post (9×3) & my-posts (3×3) applets migrate from dashboard→billboard context; billboard view passes form & recent_posts
- drama: Recognition→Note, related_name notes; billboard URL /recognition/→/my-notes/, set-palette at /note/<slug>/set-palette
- recognition.js→note.js (module Note, data.note key); recognition-page.js→note-page.js; .recog-*→.note-*
- _recognition.scss→_note.scss; BillNotes page header; applet slug billboard-recognition→billboard-notes (My Notes)
- NoteSpec.js replaces RecognitionSpec.js; test_recognition.py→test_applet_my_notes.py
- 4 migrations applied: dashboard 0004, applets 0011+0012, drama 0005; 683 ITs green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:32:34 -04:00
|
|
|
|
self.wait_for(lambda: self.assertIsNone(confirm_btn.get_attribute("disabled")))
|
|
|
|
|
|
confirm_btn.click()
|
|
|
|
|
|
|
|
|
|
|
|
# Wait for save to complete (wheel renders) then assert no banner
|
|
|
|
|
|
self.wait_for(lambda: self.assertTrue(
|
|
|
|
|
|
self.browser.find_elements(By.CSS_SELECTOR, ".nw-root")
|
|
|
|
|
|
))
|
|
|
|
|
|
self.assertFalse(self.browser.find_elements(By.CSS_SELECTOR, ".note-banner"))
|
2026-04-23 01:44:58 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
# Title equip — DON/DOFF buttons on the note item
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-23 03:12:24 -04:00
|
|
|
|
@tag("sequential")
|
2026-04-23 01:44:58 -04:00
|
|
|
|
class NoteEquipTitleTest(FunctionalTest):
|
|
|
|
|
|
"""DON button equips the note title as the sitewide greeting; DOFF restores it."""
|
|
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
|
self.browser.set_window_size(800, 1200)
|
|
|
|
|
|
Applet.objects.get_or_create(
|
2026-05-03 23:22:01 -04:00
|
|
|
|
slug="notes",
|
2026-04-23 01:44:58 -04:00
|
|
|
|
defaults={"name": "My Notes", "grid_cols": 4, "grid_rows": 4, "context": "billboard"},
|
|
|
|
|
|
)
|
|
|
|
|
|
self.gamer = User.objects.create(email="stargazer@test.io")
|
|
|
|
|
|
|
|
|
|
|
|
def test_don_equips_title_greeting_and_doff_restores(self):
|
|
|
|
|
|
"""DON replaces 'Earthman' with the note title; DOFF restores 'Earthman'.
|
|
|
|
|
|
DON/DOFF follow the game-kit equip button pattern: the active btn is normal,
|
|
|
|
|
|
the inactive btn is × + btn-disabled."""
|
|
|
|
|
|
Note.objects.create(
|
|
|
|
|
|
user=self.gamer, slug="stargazer", earned_at=timezone.now(),
|
|
|
|
|
|
)
|
|
|
|
|
|
self.create_pre_authenticated_session("stargazer@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/billboard/my-notes/")
|
|
|
|
|
|
|
|
|
|
|
|
note_item = self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".note-item")
|
|
|
|
|
|
)
|
2026-04-28 02:00:22 -04:00
|
|
|
|
# Click the note to lock it — makes DON/DOFF opacity:1 and interactable
|
|
|
|
|
|
note_item.click()
|
2026-04-23 01:44:58 -04:00
|
|
|
|
don_btn = note_item.find_element(By.CSS_SELECTOR, ".btn-equip")
|
|
|
|
|
|
doff_btn = note_item.find_element(By.CSS_SELECTOR, ".btn-unequip")
|
|
|
|
|
|
|
|
|
|
|
|
# Initial state: DON active ("DON"), DOFF disabled ("×")
|
|
|
|
|
|
self.assertNotIn("btn-disabled", don_btn.get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(don_btn.text, "DON")
|
|
|
|
|
|
self.assertIn("btn-disabled", doff_btn.get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(doff_btn.text, "×")
|
|
|
|
|
|
|
|
|
|
|
|
# Click DON → greeting changes to the note title
|
|
|
|
|
|
don_btn.click()
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn(
|
|
|
|
|
|
"Stargazer",
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_greeting_name").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertIn("btn-disabled", don_btn.get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(don_btn.text, "×")
|
|
|
|
|
|
self.assertNotIn("btn-disabled", doff_btn.get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(doff_btn.text, "DOFF")
|
|
|
|
|
|
|
|
|
|
|
|
# Click DOFF → greeting restored to "Earthman"
|
|
|
|
|
|
doff_btn.click()
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertIn(
|
|
|
|
|
|
"Earthman",
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_greeting_name").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertNotIn("btn-disabled", don_btn.get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(don_btn.text, "DON")
|
|
|
|
|
|
self.assertIn("btn-disabled", doff_btn.get_attribute("class"))
|
|
|
|
|
|
self.assertEqual(doff_btn.text, "×")
|