#!/usr/bin/env python3 """Are the creator's 43 native dialogs gone, and did validation move inline? — S1, T7.9. wp-creation-app.js:1144 said "Subject and WP Type are required" in an alert(), without naming, highlighting or scrolling to the field, on a form ten cards deep. Now: errors AT the field (role=alert, announced), submit focuses and scrolls to the first invalid one - switching sections if needed - and the rail entry of every section holding an error is marked with a character, not only a colour. confirm()/prompt() became one promise-based modal with its own inline validation. Exit 0 all passed, 1 a failure, 2 could not run. """ import json 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 sections_check import set_sop # noqa: E402 from stepper_check import dismiss_dialogs # noqa: E402 ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) HTML = os.path.join(ROOT, "html") def ascii_(v, n=280): return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n] def settle(seconds=0.5): time.sleep(seconds) def strip_js(src): src = re.sub(r"/\*.*?\*/", "", src, flags=re.S) return "\n".join(re.sub(r"(? { const ov = document.getElementById('wp-dialog'); return {open: !!ov && ov.classList.contains('open'), title: (document.getElementById('wp-dialog-title')||{}).textContent || '', err: (document.getElementById('wp-dialog-err')||{}).textContent || ''}; })())""")) def main(): exe = cdp.find_browser() if not exe: print("no headless-capable browser found; set WP_BROWSER.") return 2 # ── 1. the count ────────────────────────────────────────────────────────── print("\n1. the count") creator = 0 total = {} for name in sorted(os.listdir(HTML)): if not name.endswith((".js", ".html")): continue n = native_count(open(os.path.join(HTML, name), encoding="utf-8").read()) if n: total[name] = n if name in ("wp-creation-app.js", "wp-creation-index.html"): creator += n grand = sum(total.values()) chk("no alert(), confirm() or prompt() remains in the creator; count is 0", creator == 0, creator) print(" app-wide native dialogs now: %d (wave 0 baseline: 79) — %s" % (grand, ascii_(total))) chk("the app-wide count is recorded, and it is far below the baseline of 79", grand < 79, grand) tmpdir = tempfile.mkdtemp(prefix="wpsuite-dlg-") db_path = os.path.join(tmpdir, "check.db") server = None browser = None try: tok = seed(db_path) set_sop(db_path, {}) port = cdp.free_port() base = "http://127.0.0.1:%d" % port server = start_server(port, db_path) browser = cdp.Browser(exe) page = browser.page() page.clear_cookies() page.set_cookie("wp_session", tok["root"]) page.viewport(1440, 900) page.goto(base + "/wp-creation-index.html?project=projA") dismiss_dialogs(page) chk("the creator boots", wait_creator(page)) settle(1.6) # If ANY code path still reaches a native, these throw and fail the run. 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')};""") # ── 2. inline validation on save ───────────────────────────────────── print("\n2. required fields validate inline") page.eval("gotoSection('signoff-card')") # start far from the errors settle(0.6) page.eval("void savePackage(false)") settle(0.8) errs = json.loads(page.eval("""JSON.stringify({ subj: (document.getElementById('wp_subject_err')||{}).textContent || '', type: (document.getElementById('wp_type_err')||{}).textContent || '', subjInvalid: (document.getElementById('wp_subject')||{getAttribute:()=>''}).getAttribute('aria-invalid'), })""")) chk("the errors render AT the fields, naming them", "Subject is required" in errs["subj"] and "WP type is required" in errs["type"], ascii_(errs)) chk("...with aria-invalid set", errs["subjInvalid"] == "true") chk("...and the error boxes are alert live regions", page.eval("(document.getElementById('wp_subject_err')||{getAttribute:()=>''})" ".getAttribute('role')") == "alert") chk("submit switched to the section holding the first error", page.eval("secCurrent") == "general-card", page.eval("secCurrent")) chk("...and focused the first invalid field", page.eval("document.activeElement && document.activeElement.id") == "wp_subject", page.eval("document.activeElement && document.activeElement.id")) chk("the rail marks the section that holds errors, with a character not just a colour", page.eval("""(() => { const b = document.querySelector('.sec-rail-item[data-sec="general-card"]'); return b && b.classList.contains('has-error') && (b.querySelector('.sec-err')||{}).textContent === '!'; })()""")) chk("the failure was announced (role=alert toast)", page.eval("(document.getElementById('toast')||{getAttribute:()=>''}).getAttribute('role')") == "alert") page.eval("document.getElementById('wp_subject').value='Horn strobe conduit'") page.eval("document.getElementById('wp_type').value='Conduit Install'") page.eval("void savePackage(false)") settle(0.8) chk("with the fields filled, the save goes through", page.eval("savedPackages.length") >= 1) chk("...the errors clear", page.eval( "!(document.getElementById('wp_subject_err')||{textContent:''}).textContent")) chk("...and the rail marks clear", page.eval("!document.querySelector('.sec-rail-item.has-error')")) # ── 3. the modal that replaced confirm() ───────────────────────────── print("\n3. the confirm modal") n0 = page.eval("savedPackages.length") page.eval("void deletePackage(0)") settle(0.5) d = dialog_state(page) chk("deleting asks through the modal, a real dialog element", d["open"] and "Delete" in d["title"], ascii_(d)) page.eval("wpDialogCancel()") settle(0.3) chk("cancel keeps the package", page.eval("savedPackages.length") == n0) page.eval("void deletePackage(0)") settle(0.4) page.eval("wpDialogOk()") settle(0.4) chk("confirm deletes it", page.eval("savedPackages.length") == n0 - 1) # ── 4. the modal that replaced prompt(), with inline validation ────── print("\n4. the prompt modal") page.eval("newPackage()") settle(0.5) page.eval("document.getElementById('wp_subject').value='Copy me'") page.eval("document.getElementById('wp_type').value='Conduit Install'") page.eval("void duplicateWP()") settle(0.5) d = dialog_state(page) chk("duplicating asks for the count through the modal", d["open"] and "Duplicate" in d["title"], ascii_(d)) page.eval("document.getElementById('wp-dialog-input').value='banana'") page.eval("wpDialogOk()") settle(0.3) d = dialog_state(page) chk("a bad answer is refused AT the input - the dialog stays, the error says why", d["open"] and "whole number" in d["err"], ascii_(d)) page.eval("document.getElementById('wp-dialog-input').value='2'") n0 = page.eval("savedPackages.length") page.eval("wpDialogOk()") settle(0.6) chk("a good answer proceeds", page.eval("savedPackages.length") == n0 + 2) js_errors = [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 js_errors, ascii_(js_errors[:2])) finally: if browser is not None: try: browser.close() except Exception: pass if server is not None: try: server.terminate() except Exception: pass 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())