fix(as-S5a): fixup from booth-dev's gate — release asks, fail-closed words, ids that cannot collide
From booth-dev's hulda bug-hunt with heid's second voice (BRINGA, thread 01M3MVGQ7QSCCK8WT59TQ4J469): - The booth page's "kept — release" asks by name, as the Desk's does. - WORDS has no prototype: data-confirm="__proto__" or "constructor" is an unknown word, and asks, instead of throwing before preventDefault. - The confirm helper moved into <head>: its capture listener exists before any form, so a click during load is asked too (the inline confirm() it replaced had that property). - shown() also marks U+2028/U+2029 and the zero-width characters. - Derived ids take ':' (bk-ask-<id>-<key>:prompt, bk-ask-<id>:title), which no id or key can contain; '-prompt' and '-title' collided with valid keys. booth-dev's chip test now looks its fragment up by [id=...]. - human_dur says "—" for a value that is not finite, instead of raising. - The tile's copy of a note drops its id (booth-dev: mark-<id> is the panel's article). - Four guards that asserted source patterns now also hold on computed effects: embed rings, rings inside clipping containers, the withdraw × on both axes, and question-level notes fields. Contract: as_antislop S5a (fixup). Falsifiers: antislop.toml 102/102 with r2_flow.toml 24/24 proved; the full gate follows.
This commit is contained in:
+56
-2
@@ -329,6 +329,12 @@ def _s5_booth(data):
|
||||
write_note(b, "a.png", "soft edges")
|
||||
write_note(b, None, "about the booth")
|
||||
declare_pick(b, "p1", {"title": "Round one", "prompt": "Which?", "options": ["North", "South"]})
|
||||
# S5a fixup: a multi-question ask with per-question notes, and keys that end
|
||||
# like the ids S5a derives from them (`-prompt`, `title`)
|
||||
declare_pick(b, "p2", {"title": "Round two", "questions": [
|
||||
{"key": "x-prompt", "prompt": "First?", "options": ["keep", "cut"], "notes": True},
|
||||
{"key": "x", "prompt": "Second?", "options": ["keep", "cut"], "notes": True},
|
||||
{"key": "title", "prompt": "Third?", "options": ["keep", "cut"]}]})
|
||||
_s5_board(data)
|
||||
return b
|
||||
|
||||
@@ -415,7 +421,7 @@ def test_fields_are_named(client):
|
||||
def test_radio_groups_are_named_and_ids_are_unique(client):
|
||||
c, data = client
|
||||
_s5_booth(data)
|
||||
(m,) = c.get("/b/b/embed.json").json()["marks"]
|
||||
(m,) = [m for m in c.get("/b/b/embed.json").json()["marks"] if m["id"] == "p1"]
|
||||
# The embed places an ask one of two ways: `whole` (title, questions and
|
||||
# submit in one piece), or its questions one by one plus `submit`. Never both.
|
||||
placements = {"whole": m["whole"], "parts": "".join(q["html"] for q in m["questions"]) + m["submit"]}
|
||||
@@ -466,7 +472,7 @@ def test_wipe_now_asks_by_name(client):
|
||||
page = c.get("/b/b/").text
|
||||
form = re.search(r'<form class="wipe wipe-lg"[^>]*>', page, flags=re.S).group(0)
|
||||
assert 'data-booth="b"' in form and 'data-confirm="wipe"' in form and "onsubmit" not in form, form
|
||||
assert "var WORDS = {" in page, "the shared confirm helper is on the booth page"
|
||||
assert "var WORDS = Object.create(null);" in page, "the shared confirm helper is on the booth page"
|
||||
|
||||
|
||||
def test_human_dur_rolls_up_to_days():
|
||||
@@ -497,3 +503,51 @@ def test_a_truncated_why_carries_its_full_text(client):
|
||||
(b / ".booth.json").write_text('{"handle": "design-dev", "why": "a long reason that the Desk truncates with an ellipsis"}')
|
||||
page = c.get("/").text
|
||||
assert re.search(r'<span class="prov-why" title="a long reason that the Desk truncates with an ellipsis">', page)
|
||||
|
||||
|
||||
|
||||
# ---- S5a fixup: booth-dev's gate (hulda bug-hunt + heid BRINGA, thread 01M3MVGQ7QSCCK8WT59TQ4J469) ----
|
||||
|
||||
def test_no_id_repeats_on_any_page(client):
|
||||
"""An id names one element. The tile's copy of a note used to repeat the
|
||||
panel article's `mark-<id>` (booth-dev: the id is the article's); a question
|
||||
key ending in `-prompt`, or named `title`, repeated the ids S5a derived."""
|
||||
c, data = client
|
||||
_s5_booth(data)
|
||||
pages = _pages(c)
|
||||
for m in c.get("/b/b/embed.json").json()["marks"]:
|
||||
pages[f"embed {m['id']} whole"] = m["whole"]
|
||||
pages[f"embed {m['id']} parts"] = "".join(q["html"] for q in m["questions"]) + m["submit"]
|
||||
for url, page in pages.items():
|
||||
ids = re.findall(r'\sid="([^"]+)"', _markup(page))
|
||||
dupes = sorted({i for i in ids if ids.count(i) > 1})
|
||||
assert not dupes, (url, dupes[:5])
|
||||
for ref in re.findall(r'aria-labelledby="([^"]+)"', page):
|
||||
assert f'id="{ref}"' in page, (url, ref)
|
||||
|
||||
|
||||
def test_release_on_the_booth_page_asks_by_name(client):
|
||||
c, data = client
|
||||
b = data / "k"
|
||||
b.mkdir()
|
||||
(b / "x.txt").write_text("x")
|
||||
(b / ".forever").write_text("")
|
||||
page = c.get("/b/k/").text
|
||||
form = re.search(r'<form class="keep-lg"[^>]*>', page).group(0)
|
||||
assert 'data-booth="k"' in form and 'data-confirm="release"' in form, form
|
||||
|
||||
|
||||
def test_the_confirm_helper_is_listening_before_the_body_exists(client):
|
||||
"""A click while the page is still loading must be asked too: the capture
|
||||
listener is registered in <head>, not after the footer."""
|
||||
c, data = client
|
||||
_s5_booth(data)
|
||||
for url in ("/", "/b/b/"):
|
||||
page = c.get(url).text
|
||||
assert page.index("function shown(n)") < page.index("</head>"), url
|
||||
|
||||
|
||||
def test_human_dur_never_raises():
|
||||
from booth.app import human_dur
|
||||
for bad in (float("nan"), float("inf"), float("-inf")):
|
||||
assert isinstance(human_dur(bad), str), bad
|
||||
|
||||
Reference in New Issue
Block a user