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:
@@ -1116,26 +1116,45 @@ class MySeaViewWithSavedDrawTest(TestCase):
|
||||
self.assertNotIn("my-sea-sign-gate", html)
|
||||
self.assertIn('data-phase="picker"', html)
|
||||
|
||||
def test_view_renders_brief_banner_when_active_draw_exists(self):
|
||||
def test_view_triggers_brief_banner_when_active_draw_exists(self):
|
||||
# Brief is rendered client-side via Brief.showBanner (standard
|
||||
# `.note-banner` w. Gaussian-glass bg, portaled atop the h2 —
|
||||
# same UX as my-notes / my-sign default-deck-warning Briefs).
|
||||
# Server emits a `window._showFreeDrawLockedBrief("<iso>")` call
|
||||
# gated on active_draw; ISO timestamp (`|date:'c'`) is re-used
|
||||
# as both `created_at` AND the source for the human-formatted
|
||||
# display string note.js renders in the `.note-banner__timestamp`
|
||||
# slot — single source of truth, no "Invalid Date" on bad input.
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertContains(response, "my-sea-brief")
|
||||
self.assertContains(response, "my-sea-brief__timestamp")
|
||||
self.assertContains(response, "my-sea-brief__nvm")
|
||||
# Match the call form w. opening quote — the bare token
|
||||
# `_showFreeDrawLockedBrief(` also appears in the function
|
||||
# definition emitted unconditionally inside the picker IIFE.
|
||||
self.assertContains(response, 'window._showFreeDrawLockedBrief("')
|
||||
# The ISO format produced by Django's `|date:'c'` starts with the
|
||||
# full year + ISO-style T separator — pin a representative token.
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
expected_year = (timezone.now() + timedelta(hours=24)).strftime("%Y")
|
||||
self.assertContains(response, '_showFreeDrawLockedBrief("' + expected_year)
|
||||
|
||||
def test_brief_banner_hidden_without_active_draw(self):
|
||||
# Markup is rendered unconditionally so JS can un-hide it on LOCK
|
||||
# HAND POST success without a page reload. When no active_draw,
|
||||
# the wrapping div carries `[hidden]` so the banner is invisible.
|
||||
def test_view_does_not_trigger_brief_banner_without_active_draw(self):
|
||||
# Definition of `_showFreeDrawLockedBrief` is always emitted;
|
||||
# only the CALL is gated on active_draw. Pin the call form.
|
||||
from apps.gameboard.models import MySeaDraw
|
||||
MySeaDraw.objects.all().delete()
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertContains(response, '<div class="my-sea-brief" hidden>')
|
||||
self.assertNotContains(response, 'window._showFreeDrawLockedBrief("')
|
||||
|
||||
def test_view_renders_del_guard_portal_when_active_draw_exists(self):
|
||||
def test_view_wires_del_button_to_shared_guard_portal_when_active_draw(self):
|
||||
# No my-sea-specific guard markup — the picker IIFE calls
|
||||
# `window.showGuard(delBtn, "Are you sure?", confirmFn)` which
|
||||
# targets the shared #id_guard_portal from base.html (same
|
||||
# tooltip the room gear-menu uses; standard OK/NVM button pair).
|
||||
# Server emits the call site; we pin the call form + the delete
|
||||
# URL it POSTs to.
|
||||
response = self.client.get(reverse("my_sea"))
|
||||
self.assertContains(response, 'id="id_my_sea_del_portal"')
|
||||
self.assertContains(response, "my-sea-del-portal__confirm")
|
||||
self.assertContains(response, "my-sea-del-portal__nvm")
|
||||
self.assertContains(response, "window.showGuard(")
|
||||
self.assertContains(response, reverse("my_sea_delete"))
|
||||
|
||||
def test_saved_hand_renders_as_filled_slots_in_picker(self):
|
||||
# Each saved position's slot is server-rendered as `--filled` w.
|
||||
|
||||
Reference in New Issue
Block a user