PICK SKY DEL btn: JS-inject after wheel paints so a blank modal carries no DEL action — TDD

Previously the DEL btn was always template-rendered inside .sky-wheel-col, which on a fresh PICK SKY modal (form pristine, schedulePreview not yet fired) put a red DEL btn floating in the empty wheel area suggesting there's something to delete when the user hasn't even seen a wheel yet. Refactored: drop the <button id="id_sky_delete_btn"> from _sky_overlay.html, lazily create it in JS via _ensureDelBtn() called from the schedulePreview success handler (right after SkyWheel.draw/redraw); the existing DEL click handler now also removes the btn from the DOM after clearing the SVG, so the next preview re-injects it. PickSkyRenderingTest.test_no_sky_delete_btn_in_blank_sky_select_modal IT asserts `id="id_sky_delete_btn"` doesn't appear in the rendered HTML for a SKY_SELECT room (the literal identifier still lives inside the inline <script> that does the injection — assertion targets the HTML-attribute-syntax form so the JS reference doesn't trip it). Existing PickSkyDelTest FT still green: it fires preview before clicking DEL, so the btn is present at click time.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-08 14:56:43 -04:00
parent 301b4e8201
commit 8a8d1536b1
2 changed files with 39 additions and 13 deletions

View File

@@ -1792,6 +1792,19 @@ class PickSkyRenderingTest(TestCase):
self.assertContains(response, 'id="id_pick_sky_btn"') self.assertContains(response, 'id="id_pick_sky_btn"')
self.assertContains(response, 'style="display:none"') self.assertContains(response, 'style="display:none"')
def test_no_sky_delete_btn_in_blank_sky_select_modal(self):
"""A fresh PICK SKY modal (no preview wheel rendered yet) must not
carry the DEL btn — it would otherwise float in the empty wheel area
suggesting there's something to delete when the user has only seen
the form. The JS schedulePreview success handler is the contract that
injects the btn after the wheel paints — so the rendered HTML should
carry no <button id="id_sky_delete_btn"> markup. (The literal string
does still appear inside the inline <script> that does the injection,
so the assertion targets the rendered attribute syntax, not the bare
identifier.)"""
response = self.client.get(self.url)
self.assertNotContains(response, 'id="id_sky_delete_btn"')
# ── SEA_SELECT rendering ────────────────────────────────────────────────────── # ── SEA_SELECT rendering ──────────────────────────────────────────────────────

View File

@@ -87,10 +87,12 @@
</div> </div>
{# ── Wheel column ─────────────────────────────────────── #} {# ── Wheel column ─────────────────────────────────────── #}
{# DEL btn is JS-injected after the wheel paints (see schedule #}
{# Preview success handler) — keeping it out of the template #}
{# means a blank PICK SKY modal can never show a DEL action #}
{# against a non-existent wheel. #}
<div class="sky-wheel-col"> <div class="sky-wheel-col">
<svg id="id_sky_svg" class="sky-svg"></svg> <svg id="id_sky_svg" class="sky-svg"></svg>
<button type="button" id="id_sky_delete_btn"
class="btn btn-danger">DEL</button>
</div> </div>
</div>{# /.sky-modal-body #} </div>{# /.sky-modal-body #}
@@ -343,6 +345,7 @@
} else { } else {
SkyWheel.draw(svgEl, data); SkyWheel.draw(svgEl, data);
} }
_ensureDelBtn();
}) })
.catch(err => { .catch(err => {
setStatus(`Could not fetch chart: ${err.message}`, 'error'); setStatus(`Could not fetch chart: ${err.message}`, 'error');
@@ -401,20 +404,26 @@
window.location.reload(); window.location.reload();
} }
// ── DEL btn — clears wheel + form + localStorage (no server hit) ──────── // ── DEL btn — JS-injected after the wheel paints; absent on a blank modal
// PICK SKY's wheel is a live preview; un-saved data lives only in LS_KEY. // PICK SKY's wheel is a live preview; un-saved data lives only in LS_KEY.
// Match the My Sky applet / Dashsky pattern but skip the server delete — // The btn is created lazily after the first SkyWheel.draw so a blank modal
// there's no Character draft created during preview (sky_save fires only // can never offer a DEL action against a non-existent wheel; clearing the
// on SAVE SKY click w. action='confirm'). // SVG removes the btn from the DOM entirely (re-injected on next preview).
// window.showGuard is assigned in a base.html script that loads BELOW the let _delBtn = null;
// content block — defer the readiness check to click-time so the listener function _ensureDelBtn() {
// bind happens regardless of inline-script execution order. if (_delBtn) return;
const delBtn = document.getElementById('id_sky_delete_btn'); const wheelCol = document.querySelector('.sky-wheel-col');
if (delBtn) { if (!wheelCol) return;
delBtn.addEventListener('click', () => { _delBtn = document.createElement('button');
_delBtn.type = 'button';
_delBtn.id = 'id_sky_delete_btn';
_delBtn.className = 'btn btn-danger';
_delBtn.textContent = 'DEL';
wheelCol.appendChild(_delBtn);
_delBtn.addEventListener('click', () => {
if (!window.showGuard) return; if (!window.showGuard) return;
window.showGuard(delBtn, 'Forget sky?', () => { window.showGuard(_delBtn, 'Forget sky?', () => {
while (svgEl.firstChild) svgEl.removeChild(svgEl.firstChild); while (svgEl.firstChild) svgEl.removeChild(svgEl.firstChild);
form.reset(); form.reset();
latInput.value = ''; latInput.value = '';
@@ -425,6 +434,10 @@
confirmBtn.disabled = true; confirmBtn.disabled = true;
setStatus(''); setStatus('');
try { localStorage.removeItem(LS_KEY); } catch (_) {} try { localStorage.removeItem(LS_KEY); } catch (_) {}
if (_delBtn) {
_delBtn.remove();
_delBtn = null;
}
}); });
}); });
} }