diff options
| author | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-13 07:29:41 -0400 |
|---|---|---|
| committer | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-13 07:29:41 -0400 |
| commit | 4c54b05dc15b81e449dc8ac53da8bae536fa2798 (patch) | |
| tree | f81c5e627dcc4dd81d2fe2fd3b2b584e72a73eca /test-suite/challenges | |
| parent | 5951b3fb04aaf53b875afe265a57a944d6fce641 (diff) | |
pi-chrome: snapshot pre-migration state
Diffstat (limited to 'test-suite/challenges')
| -rw-r--r-- | test-suite/challenges/34-dialog-handling.html | 28 | ||||
| -rw-r--r-- | test-suite/challenges/35-target-blank-popup.html | 35 | ||||
| -rw-r--r-- | test-suite/challenges/36-modal-focus-trap.html | 49 | ||||
| -rw-r--r-- | test-suite/challenges/37-autocomplete-combobox.html | 42 | ||||
| -rw-r--r-- | test-suite/challenges/38-spa-route-change.html | 40 |
5 files changed, 194 insertions, 0 deletions
diff --git a/test-suite/challenges/34-dialog-handling.html b/test-suite/challenges/34-dialog-handling.html new file mode 100644 index 0000000..25845a7 --- /dev/null +++ b/test-suite/challenges/34-dialog-handling.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset="utf-8"> +<title>34 dialog handling</title> +<link rel="stylesheet" href="../_style.css"> +<script src="../_lib.js"></script> +<body> +<main> + <p>Goal: handle browser dialogs. Click Prompt, enter <code>pi-chrome</code>, accept it; then click Confirm and accept it. Browser automation must expose/dismiss native dialogs.</p> + <button id="promptBtn">Prompt</button> + <button id="confirmBtn">Confirm</button> + <button id="alertBtn">Alert smoke</button> + <pre id="rep" class="code"></pre> +</main> +<script> +Challenge.init({ id: "dialog-handling", instructions: "accept prompt('pi-chrome') and confirm()" }); +const state = { prompt: null, confirm: null, alert: false }; +function paint(){ rep.textContent = JSON.stringify(state, null, 2); } +function check(){ + paint(); + if (state.prompt === "pi-chrome" && state.confirm === true) Challenge.pass("prompt and confirm handled with expected values"); +} +promptBtn.addEventListener("click", () => { state.prompt = prompt("Enter token", ""); Challenge.log("prompt-result", { value: state.prompt }); check(); }); +confirmBtn.addEventListener("click", () => { state.confirm = confirm("Accept benchmark confirm?"); Challenge.log("confirm-result", { value: state.confirm }); check(); }); +alertBtn.addEventListener("click", () => { alert("pi-chrome alert smoke"); state.alert = true; Challenge.log("alert-dismissed", {}); paint(); }); +window.addEventListener("beforeunload", e => { e.preventDefault(); e.returnValue = "benchmark beforeunload"; }); +paint(); +</script> +</body> diff --git a/test-suite/challenges/35-target-blank-popup.html b/test-suite/challenges/35-target-blank-popup.html new file mode 100644 index 0000000..0813625 --- /dev/null +++ b/test-suite/challenges/35-target-blank-popup.html @@ -0,0 +1,35 @@ +<!doctype html> +<meta charset="utf-8"> +<title>35 target blank popup</title> +<link rel="stylesheet" href="../_style.css"> +<script src="../_lib.js"></script> +<body> +<main> + <p>Goal: click <code>target=_blank</code> link, detect new tab, switch to it, and verify child page reports PASS.</p> + <a id="open" target="_blank" rel="opener" href="35-target-blank-popup.html?child=1" style="font-size:18px">Open child tab</a> + <p id="msg"></p> +</main> +<script> +Challenge.init({ id: "target-blank-popup", instructions: "open child tab and inspect it" }); +const qs = new URLSearchParams(location.search); +if (qs.get("child") === "1") { + document.getElementById("msg").textContent = "Child tab opened."; + Challenge.pass("child tab loaded on same origin"); + try { localStorage.setItem("pi-chrome-suite:target-blank-popup", JSON.stringify({ id:"target-blank-popup", verdict:"PASS", reason:["✓ child tab loaded"], ts: Date.now() })); } catch {} +} else { + document.getElementById("msg").textContent = "Parent page. Link should open new tab."; + function maybePassFromStorage(raw) { + if (!raw) return; + try { + const v = JSON.parse(raw); + if (v.verdict === "PASS") Challenge.pass("child reported PASS via storage event/localStorage"); + } catch {} + } + window.addEventListener("storage", e => { + if (e.key === "pi-chrome-suite:target-blank-popup") maybePassFromStorage(e.newValue); + }); + maybePassFromStorage(localStorage.getItem("pi-chrome-suite:target-blank-popup")); + document.getElementById("open").addEventListener("click", e => Challenge.log("popup-click", { trusted: e.isTrusted })); +} +</script> +</body> diff --git a/test-suite/challenges/36-modal-focus-trap.html b/test-suite/challenges/36-modal-focus-trap.html new file mode 100644 index 0000000..811207c --- /dev/null +++ b/test-suite/challenges/36-modal-focus-trap.html @@ -0,0 +1,49 @@ +<!doctype html> +<meta charset="utf-8"> +<title>36 modal focus trap</title> +<link rel="stylesheet" href="../_style.css"> +<script src="../_lib.js"></script> +<body> +<main> + <p>Goal: open modal, use Tab to cycle focus inside modal only, then Escape closes it and focus returns to opener.</p> + <button id="open">Open modal</button> + <div id="modal" role="dialog" aria-modal="true" aria-label="Focus trap modal" hidden style="position:fixed;inset:20%;background:#222;border:2px solid #6cf;border-radius:10px;padding:20px;z-index:5"> + <button id="first">First</button> + <input id="middle" aria-label="Modal input"> + <button id="last">Last</button> + </div> +</main> +<script> +Challenge.init({ id: "modal-focus-trap", instructions: "open modal, Tab cycles inside, Escape closes" }); +const order = []; +const modal = document.getElementById("modal"); +const nodes = [first, middle, last]; +let escaped = false, modalOpened = false, outsideFocusWhileOpen = false; +for (const el of [open, ...nodes]) el.addEventListener("focus", () => { order.push(el.id); Challenge.log("focus", { id: el.id }); }); +document.addEventListener("focusin", e => { + if (modalOpened && !modal.hidden && !modal.contains(e.target)) outsideFocusWhileOpen = true; +}); +open.addEventListener("click", () => { modal.hidden = false; modalOpened = true; first.focus(); }); +document.addEventListener("keydown", e => { + if (modal.hidden) return; + if (e.key === "Tab") { + const i = nodes.indexOf(document.activeElement); + if (i >= 0) { e.preventDefault(); nodes[(i + (e.shiftKey ? -1 : 1) + nodes.length) % nodes.length].focus(); } + } + if (e.key === "Escape") { modal.hidden = true; escaped = true; open.focus(); queueMicrotask(check); } +}); +function hasAdjacent(seq, a, b) { + return seq.some((x, i) => x === a && seq[i + 1] === b); +} +function check(){ + const afterOpen = order.slice(order.indexOf("first")); + const bad = []; + if (!escaped) bad.push("Escape did not close modal"); + if (document.activeElement !== open) bad.push("focus did not return to opener"); + if (!afterOpen.includes("middle") || !afterOpen.includes("last")) bad.push(`focus cycle incomplete: ${afterOpen.join(">")}`); + if (!hasAdjacent(afterOpen, "last", "first") && !hasAdjacent(afterOpen, "first", "last")) bad.push(`no wrap-around focus trap observed: ${afterOpen.join(">")}`); + if (outsideFocusWhileOpen) bad.push("focus escaped outside modal while open"); + if (bad.length) Challenge.fail(...bad); else Challenge.pass(`modal focus trap ok: ${afterOpen.join(">")}`); +} +</script> +</body> diff --git a/test-suite/challenges/37-autocomplete-combobox.html b/test-suite/challenges/37-autocomplete-combobox.html new file mode 100644 index 0000000..91f35c2 --- /dev/null +++ b/test-suite/challenges/37-autocomplete-combobox.html @@ -0,0 +1,42 @@ +<!doctype html> +<meta charset="utf-8"> +<title>37 autocomplete combobox</title> +<link rel="stylesheet" href="../_style.css"> +<script src="../_lib.js"></script> +<body> +<main> + <p>Goal: type <code>as</code>, use ArrowDown then Enter to choose <code>Aster Lamp</code> from ARIA combobox.</p> + <label>Product <input id="combo" role="combobox" aria-autocomplete="list" aria-controls="list" aria-expanded="false" autocomplete="off"></label> + <ul id="list" role="listbox" style="border:1px solid #555;max-width:260px;padding:6px;display:none"></ul> +</main> +<script> +Challenge.init({ id: "autocomplete-combobox", instructions: "type 'as', ArrowDown, Enter to choose Aster Lamp" }); +const items = ["Aster Lamp", "Aspen Desk", "Cable Kit", "Desk Mat", "Red Mug"]; +let active = -1, opened = false, keyPath = []; +combo.addEventListener("input", renderList); +combo.addEventListener("keydown", e => { + keyPath.push(e.key); + const opts = [...list.querySelectorAll('[role="option"]')]; + if (e.key === "ArrowDown") { e.preventDefault(); active = Math.min(active + 1, opts.length - 1); paintActive(opts); } + if (e.key === "Enter" && active >= 0 && opts[active]) { e.preventDefault(); combo.value = opts[active].textContent; close(); check(e); } +}); +function renderList(){ + const q = combo.value.toLowerCase(); + const matches = items.filter(x => x.toLowerCase().includes(q)); + list.innerHTML = matches.map((x,i)=>`<li role="option" id="opt-${i}" style="padding:4px;cursor:pointer">${x}</li>`).join(""); + list.style.display = matches.length ? "block" : "none"; + combo.setAttribute("aria-expanded", matches.length ? "true" : "false"); + active = -1; opened ||= matches.length > 0; +} +function paintActive(opts){ opts.forEach((o,i)=>o.style.background = i===active ? "#2b4b6b" : ""); if(opts[active]) combo.setAttribute("aria-activedescendant", opts[active].id); } +function close(){ list.style.display="none"; combo.setAttribute("aria-expanded","false"); } +function check(e){ + const bad=[]; + if (!opened) bad.push("listbox never opened"); + if (combo.value !== "Aster Lamp") bad.push(`selected ${combo.value}`); + if (!keyPath.includes("ArrowDown") || !keyPath.includes("Enter")) bad.push(`missing keyboard selection path: ${keyPath.join(",")}`); + if (!e.isTrusted) bad.push("Enter key isTrusted=false"); + if (bad.length) Challenge.fail(...bad); else Challenge.pass("ARIA combobox selected via keyboard"); +} +</script> +</body> diff --git a/test-suite/challenges/38-spa-route-change.html b/test-suite/challenges/38-spa-route-change.html new file mode 100644 index 0000000..533c38d --- /dev/null +++ b/test-suite/challenges/38-spa-route-change.html @@ -0,0 +1,40 @@ +<!doctype html> +<meta charset="utf-8"> +<title>38 SPA route change</title> +<link rel="stylesheet" href="../_style.css"> +<script src="../_lib.js"></script> +<body> +<main> + <p>Goal: navigate within SPA using links/buttons. URL must change via <code>history.pushState</code> without full reload. Then go Back to Settings, forward to Billing, and Save.</p> + <nav><a href="/settings" id="settings">Settings</a> <a href="/settings/billing" id="billing">Billing</a></nav> + <section id="view"></section> +</main> +<script> +Challenge.init({ id: "spa-route-change", instructions: "Settings → Billing → Back → Billing → Save, no full reload" }); +let navs = 0, popstates = 0; +window.__spaBootId = window.__spaBootId || Math.random().toString(36).slice(2); +const bootId = window.__spaBootId; +const navigationEntriesAtBoot = performance.getEntriesByType("navigation").length; +function route(path){ history.pushState({}, "", path); navs++; render(); Challenge.log("route", { path, navs }); } +settings.addEventListener("click", e => { e.preventDefault(); route("/settings"); }); +billing.addEventListener("click", e => { e.preventDefault(); route("/settings/billing"); }); +window.addEventListener("popstate", () => { popstates++; render(); Challenge.log("popstate", { path: location.pathname, popstates }); }); +function render(){ + if (location.pathname.endsWith("/settings/billing")) view.innerHTML = `<h2>Billing</h2><button id="save">Save billing settings</button>`; + else if (location.pathname.endsWith("/settings")) view.innerHTML = `<h2>Settings</h2><p>Choose Billing.</p>`; + else view.innerHTML = `<h2>Home</h2>`; + const save = document.getElementById("save"); + if (save) save.addEventListener("click", e => { + const bad=[]; + if (!location.pathname.endsWith("/settings/billing")) bad.push(`wrong route ${location.pathname}`); + if (navs < 2) bad.push(`only ${navs} SPA route changes`); + if (popstates < 1) bad.push("browser Back/popstate was not observed"); + if (window.__spaBootId !== bootId) bad.push("window sentinel changed; reload detected"); + if (performance.getEntriesByType("navigation").length !== navigationEntriesAtBoot) bad.push("navigation entry count changed; reload suspected"); + if (!e.isTrusted) bad.push("save click isTrusted=false"); + if (bad.length) Challenge.fail(...bad); else Challenge.pass("SPA pushState flow completed without reload"); + }); +} +render(); +</script> +</body> |
