BL-024 - the last 21 native dialogs, onto the shared kit
S1 counted 79 native dialogs app-wide and its tasks removed 58; the audit found the rest on surfaces no S1 task named: admin.js (6), users.js (10), the launcher's inline script (5). All 21 now go through wp-dialog.js - the T7.9 kit extracted as a self-injecting shared component: markup and styles land on first use, styles are theme tokens only with its own wp-dlg-* class names (the consoles' existing .modal styles are untouched), 44px targets on coarse pointers, and the whole file is guarded so the creator's inline copy - which owns the same-id markup in its HTML - still wins on its own page. The kit's toast comes along (S10 role rules), since none of the three pages had one. Conversion follows the T7.9 precedent: confirms -> wpConfirmDialog with named ok-labels, the password prompt -> wpPromptDialog whose validate() finally enforces min-12 AT the input (it was label-text-only before, server-enforced), API failures with detail -> wpAlertDialog, small info/validation messages -> the announced toast. New probe console_dialogs_check (17): counts pinned at 0, kit guarded and loaded by all three pages, and the users console driven live with natives poisoned - reset a password end to end (short refused inline, good one accepted by the server and announced), cancel a delete and prove nothing died. Items: BL-024 (closed), S1 completed to zero app-wide, C1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
149
tests/console_dialogs_check.py
Normal file
149
tests/console_dialogs_check.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Are the consoles' and launcher's 21 native dialogs gone? — BL-024, 2026-08-20.
|
||||
|
||||
S1 counted 79 native dialogs app-wide; its two tasks (T5.8 wizard, T7.9
|
||||
creator) removed 58 and the audit found the remaining 21 on surfaces no S1
|
||||
task named: admin.js (6), users.js (10), the launcher's inline script (5).
|
||||
They now go through `wp-dialog.js` — the T7.9 kit extracted as a shared,
|
||||
self-injecting component (guarded so the creator's inline copy still wins on
|
||||
its own page).
|
||||
|
||||
Static half greps the counts; browser half drives the password-reset prompt on
|
||||
the users console with natives poisoned, and proves validate() answers AT the
|
||||
input while the server round-trip completes end to end.
|
||||
|
||||
Boots its own throwaway SQLite + uvicorn + headless browser; run it alone.
|
||||
Exit 0 all passed, 1 a failure, 2 could not run.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import cdp # noqa: E402
|
||||
from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402
|
||||
from qa_gate_check import api # noqa: E402
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
HTML = os.path.join(ROOT, "html")
|
||||
|
||||
|
||||
def ascii_(v, n=240):
|
||||
return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n]
|
||||
|
||||
|
||||
def strip_js(src):
|
||||
src = re.sub(r"/\*.*?\*/", "", src, flags=re.S)
|
||||
return "\n".join(re.sub(r"(?<![:'\"])//.*$", "", ln) for ln in src.split("\n"))
|
||||
|
||||
|
||||
def natives(name):
|
||||
code = strip_js(io.open(os.path.join(HTML, name), encoding="utf-8").read())
|
||||
return len(re.findall(r"(?<![\w.$])(alert|confirm|prompt)\(", code))
|
||||
|
||||
|
||||
def main():
|
||||
exe = cdp.find_browser()
|
||||
if not exe:
|
||||
print("no headless-capable browser found; set WP_BROWSER.")
|
||||
return 2
|
||||
|
||||
print("\n1. the counts (baseline 6 + 10 + 5 = 21)")
|
||||
for name in ("admin.js", "users.js", "index.html"):
|
||||
chk("%s: 0 native dialogs" % name, natives(name) == 0, natives(name))
|
||||
chk("wp-dialog.js exists, has the guard, and no natives of its own",
|
||||
natives("wp-dialog.js") == 0
|
||||
and "typeof global.wpConfirmDialog === 'function'" in
|
||||
io.open(os.path.join(HTML, "wp-dialog.js"), encoding="utf-8").read())
|
||||
for page in ("index.html", "admin.html", "users.html"):
|
||||
chk("%s loads the kit" % page,
|
||||
'src="wp-dialog.js"' in io.open(os.path.join(HTML, page), encoding="utf-8").read())
|
||||
chk("the creator keeps its own copy (it owns the same-id markup in its HTML)",
|
||||
"function wpConfirmDialog" in
|
||||
io.open(os.path.join(HTML, "wp-creation-app.js"), encoding="utf-8").read())
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="wpsuite-condlg-")
|
||||
db_path = os.path.join(tmpdir, "check.db")
|
||||
server = None
|
||||
browser = None
|
||||
try:
|
||||
tok = seed(db_path)
|
||||
port = cdp.free_port()
|
||||
base = "http://127.0.0.1:%d" % port
|
||||
server = start_server(port, db_path)
|
||||
|
||||
print("\n2. the users console, natives poisoned")
|
||||
browser = cdp.Browser(exe)
|
||||
page = browser.page()
|
||||
page.clear_cookies()
|
||||
page.set_cookie("wp_session", tok["root"])
|
||||
page.viewport(1440, 900)
|
||||
page.goto(base + "/users.html")
|
||||
time.sleep(2.0)
|
||||
page.eval("window.alert=()=>{throw new Error('native alert reached')};"
|
||||
"window.confirm=()=>{throw new Error('native confirm reached')};"
|
||||
"window.prompt=()=>{throw new Error('native prompt reached')};")
|
||||
chk("the console booted with a user table",
|
||||
page.eval("!!document.querySelector('table')"))
|
||||
|
||||
page.eval("void resetPw('user_pat','pat')")
|
||||
time.sleep(0.4)
|
||||
chk("the reset prompt is the kit's modal, open, focused at the input",
|
||||
page.eval("(() => { const o=document.getElementById('wp-dlg-overlay');"
|
||||
" return !!o && o.classList.contains('open')"
|
||||
" && document.activeElement.id==='wp-dlg-input'; })()"))
|
||||
page.eval("document.getElementById('wp-dlg-input').value='short';"
|
||||
"document.getElementById('wp-dlg-ok').click()")
|
||||
chk("a short password is refused AT the input - dialog stays, error says why",
|
||||
page.eval("(() => { const o=document.getElementById('wp-dlg-overlay');"
|
||||
" return o.classList.contains('open')"
|
||||
" && /12 characters/.test(document.getElementById('wp-dlg-err').textContent); })()"))
|
||||
page.eval("document.getElementById('wp-dlg-input').value='CorrectHorseBattery10';"
|
||||
"document.getElementById('wp-dlg-ok').click()")
|
||||
time.sleep(1.2)
|
||||
chk("a good answer closes the dialog and the server accepts it",
|
||||
page.eval("!document.getElementById('wp-dlg-overlay').classList.contains('open')"))
|
||||
chk("...announced through the kit's toast (role=status)",
|
||||
page.eval("(() => { const t=document.getElementById('toast');"
|
||||
" return !!t && t.getAttribute('role')==='status'"
|
||||
" && /Password reset for pat/.test(t.textContent); })()"))
|
||||
st, _ = api(base, "/api/auth/login", "x", "POST",
|
||||
{"username": "pat", "password": "CorrectHorseBattery10"})
|
||||
chk("...and the new password actually works", st == 200, st)
|
||||
|
||||
print("\n3. destroy needs a real yes")
|
||||
page.eval("void deleteUser('user_bob','bob')")
|
||||
time.sleep(0.4)
|
||||
chk("the delete asks through the kit, spelling out what goes with it",
|
||||
page.eval("(() => { const o=document.getElementById('wp-dlg-overlay');"
|
||||
" return o.classList.contains('open')"
|
||||
" && /cannot be undone/.test(document.getElementById('wp-dlg-msg').textContent); })()"))
|
||||
page.eval("document.getElementById('wp-dlg-cancel').click()")
|
||||
time.sleep(0.6)
|
||||
st, users = api(base, "/api/auth/users", tok["root"])
|
||||
chk("cancel means no: bob is still an account",
|
||||
st == 200 and any(u.get("username") == "bob" for u in (users or [])),
|
||||
ascii_([u.get("username") for u in (users or [])]))
|
||||
errs = [e for e in page.js_errors() if "beforeunload" not in e]
|
||||
chk("no JavaScript errors, and no path reached a native dialog (they throw here)",
|
||||
not errs, ascii_(errs[:3]))
|
||||
finally:
|
||||
if browser:
|
||||
browser.close()
|
||||
if server:
|
||||
server.terminate()
|
||||
|
||||
print("\n" + "-" * 54)
|
||||
print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL)))
|
||||
for f in _FAIL:
|
||||
print(" - " + f)
|
||||
return 1 if _FAIL else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user