My Sea iter 4b polish: Brief banner uses standard portaled .note-banner (Gaussian glass atop h2); next-free-draw datetime in dedicated <time> slot (not "Invalid Date"); DEL guard reuses shared #id_guard_portal from base.html — TDD
UX refactor on top of iter 4b (b76d3c5) per user direction:
(1) Brief banner — replaced custom `.my-sea-brief` markup + SCSS w. a call to `Brief.showBanner` from note.js. Now matches the my-notes / my-sign default-deck-warning Briefs exactly: standard `.note-banner` portaled atop the h2 w. Gaussian-glass backdrop-filter blur. Tagged `.my-sea-locked-banner` for FT disambiguation only — no visual override.
(2) Brief timestamp — fix for "Invalid Date" rendering in note.js's `<time class="note-banner__timestamp">` slot. Previously passed `created_at: ''` to `Brief.showBanner` → `new Date('')` returns Invalid Date → `toLocaleDateString` renders "Invalid Date". Now passes the next-free-draw ISO timestamp as `created_at` (server emits via `|date:'c'`). After Brief.showBanner returns, the `_showFreeDrawLockedBrief` JS overwrites the rendered text w. the more detailed `D, M j @ g:i A` format ("Wed, May 20 @ 11:57 PM") — leaves the ISO `datetime=` attribute intact for accessibility. The `line_text` no longer carries the timestamp inline (it's redundant w. the dedicated slot).
(3) DEL guard portal — replaced custom `#id_my_sea_del_portal` fullscreen modal + `.my-sea-del-portal` SCSS w. a call to `window.showGuard` from base.html, targeting the shared `#id_guard_portal`. Same Gaussian-glass tooltip the room gear-menu DEL flow uses: no backdrop, positioned above the anchor button, standard `.btn-confirm OK` + `.btn-cancel NVM` pair. Bundled a non-breaking `options.yesLabel` extension to `show()` in base.html for future destructive flows that need a custom YES label (defaults to 'OK', resets on dismiss/confirm) — my-sea doesn't use it per user direction (the `.btn-confirm` class implies "OK"; destructive intent belongs on the trigger button, which is `.btn-danger DEL`).
Tests: 30 iter-4b ITs (model + lock + delete + saved-draw view branches) + 5 iter-4b FTs all green; IT/FT assertions updated to target the shared portal markup (`#id_guard_portal.active`, `.guard-yes`, `.guard-no`, `.note-banner.my-sea-locked-banner`).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1096,35 +1096,45 @@ class MySeaLockHandTest(FunctionalTest):
|
||||
# ── Test 3 ───────────────────────────────────────────────────────────────
|
||||
|
||||
def test_saved_draw_renders_brief_banner_with_next_free_draw_timestamp(self):
|
||||
"""Post-lock UX: a Look!-formatted Brief banner above the picker
|
||||
informs the user when the next free draw is available + offers a
|
||||
NVM button to dismiss. Mirrors the Brief banner shape from the
|
||||
Baltimorean Note unlock + the my-sign default-deck warning."""
|
||||
"""Post-lock UX: a Look!-formatted Brief banner appears atop the
|
||||
h2 (standard portaled `.note-banner` w. Gaussian-glass bg, same
|
||||
styling as my-notes / my-sign default-deck-warning Briefs). The
|
||||
next-free-draw timestamp lives in the dedicated `.note-banner__
|
||||
timestamp` `<time>` slot (note.js's standard datetime element),
|
||||
formatted by JS to `D, M j @ g:i A` shape — e.g. "Wed, May 20 @
|
||||
11:57 PM". Tagged `.my-sea-locked-banner` so this FT disambiguates
|
||||
from any other Briefs that may stack on the page."""
|
||||
self._save_draw_for_user()
|
||||
self.create_pre_authenticated_session(self.email)
|
||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||
brief = self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
By.CSS_SELECTOR, ".my-sea-brief"
|
||||
By.CSS_SELECTOR, ".note-banner.my-sea-locked-banner"
|
||||
)
|
||||
)
|
||||
text = brief.text
|
||||
self.assertIn("Look!", text)
|
||||
self.assertIn("free draw", text.lower())
|
||||
# The timestamp is rendered inside a dedicated child so the JS
|
||||
# NVM-dismiss handler can find + style it independently of the
|
||||
# surrounding copy.
|
||||
ts = brief.find_element(By.CSS_SELECTOR, ".my-sea-brief__timestamp")
|
||||
self.assertTrue(ts.text.strip(), "brief should render a non-empty next-free-draw timestamp")
|
||||
# NVM button is present (Jasmine pins the dismiss-on-click).
|
||||
brief.find_element(By.CSS_SELECTOR, ".my-sea-brief__nvm")
|
||||
# Timestamp slot owns the next-free-draw datetime. The "@" token
|
||||
# in the `D, M j @ g:i A` format is a stable assertion target;
|
||||
# also pin the year to confirm the source ISO parsed correctly
|
||||
# (would render "Invalid Date" if note.js got an empty string).
|
||||
ts = brief.find_element(By.CSS_SELECTOR, ".note-banner__timestamp")
|
||||
ts_text = ts.text
|
||||
self.assertIn("@", ts_text)
|
||||
self.assertNotIn("Invalid", ts_text)
|
||||
# NVM dismiss button is wired by note.js itself.
|
||||
brief.find_element(By.CSS_SELECTOR, ".note-banner__nvm")
|
||||
|
||||
# ── Test 4 ───────────────────────────────────────────────────────────────
|
||||
|
||||
def test_del_click_opens_guard_portal_with_uniform_confirm_copy(self):
|
||||
"""DEL on a locked hand opens `#id_my_sea_del_portal` — uniform
|
||||
'Are you sure?' copy (no conditional quota wording; the Brief
|
||||
banner carries that info separately) w. CONFIRM + NVM buttons."""
|
||||
def test_del_click_opens_shared_guard_portal(self):
|
||||
"""DEL on a locked hand opens the shared `#id_guard_portal` from
|
||||
base.html (same Gaussian-glass tooltip the room gear-menu uses)
|
||||
w. uniform 'Are you sure?' copy + the standard `.btn-confirm OK`
|
||||
+ `.btn-cancel NVM` button pair. The Brief banner above carries
|
||||
the quota-specific info, so the portal stays text-free of
|
||||
conditional wording."""
|
||||
self._save_draw_for_user()
|
||||
self.create_pre_authenticated_session(self.email)
|
||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/")
|
||||
@@ -1137,22 +1147,21 @@ class MySeaLockHandTest(FunctionalTest):
|
||||
delbtn.click()
|
||||
portal = self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
By.CSS_SELECTOR, "#id_my_sea_del_portal"
|
||||
By.CSS_SELECTOR, "#id_guard_portal.active"
|
||||
)
|
||||
)
|
||||
self.wait_for(lambda: self.assertTrue(portal.is_displayed()))
|
||||
text = portal.text.lower()
|
||||
self.assertIn("sure", text)
|
||||
portal.find_element(By.CSS_SELECTOR, ".my-sea-del-portal__confirm")
|
||||
portal.find_element(By.CSS_SELECTOR, ".my-sea-del-portal__nvm")
|
||||
self.assertIn("sure", portal.text.lower())
|
||||
portal.find_element(By.CSS_SELECTOR, ".guard-yes")
|
||||
portal.find_element(By.CSS_SELECTOR, ".guard-no")
|
||||
|
||||
# ── Test 5 ───────────────────────────────────────────────────────────────
|
||||
|
||||
def test_del_confirm_clears_saved_draw_and_returns_to_landing(self):
|
||||
"""Clicking the portal's CONFIRM POSTs to the delete endpoint
|
||||
→ server wipes the MySeaDraw row → reload lands on the FREE DRAW
|
||||
landing again (no saved hand, no Brief banner, FREE DRAW btn
|
||||
present)."""
|
||||
"""Clicking the portal's OK (`.guard-yes`) POSTs to the delete
|
||||
endpoint → server wipes the MySeaDraw row → reload lands on the
|
||||
FREE DRAW landing again (no saved hand, no Brief banner, FREE
|
||||
DRAW btn present)."""
|
||||
from apps.gameboard.models import MySeaDraw
|
||||
self._save_draw_for_user()
|
||||
self.assertEqual(MySeaDraw.objects.filter(user=self.gamer).count(), 1)
|
||||
@@ -1166,7 +1175,7 @@ class MySeaLockHandTest(FunctionalTest):
|
||||
picker.find_element(By.CSS_SELECTOR, "#id_sea_del").click()
|
||||
confirm = self.wait_for(
|
||||
lambda: self.browser.find_element(
|
||||
By.CSS_SELECTOR, ".my-sea-del-portal__confirm"
|
||||
By.CSS_SELECTOR, "#id_guard_portal.active .guard-yes"
|
||||
)
|
||||
)
|
||||
confirm.click()
|
||||
|
||||
Reference in New Issue
Block a user