From da8fb70168901f854a69e26e8bcd0d6389a84339 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Thu, 25 Jun 2026 18:32:22 +0200 Subject: [PATCH] test(xtask): cover revealed fallback smoke Add a deterministic html_examples browser smoke path that reloads the runtime with IntersectionObserver disabled and verifies data-hemx-revealed fallback dispatch. req: convention/014 req: test/006 --- hemx-xtask/src/main.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hemx-xtask/src/main.rs b/hemx-xtask/src/main.rs index b49e666..946f405 100644 --- a/hemx-xtask/src/main.rs +++ b/hemx-xtask/src/main.rs @@ -95,6 +95,19 @@ fn run_html_examples_smoke() -> ExitCode { println!("html_examples smoke ok: {name}"); } + // Re-load the runtime with IntersectionObserver disabled to exercise the + // deterministic revealed fallback path in a real browser. req: convention/014 req: test/006 + if let Err(code) = cdp_tab_goto(&url) { + return code; + } + if let Err(code) = cdp_wait_for_html_examples() { + return code; + } + if let Err(code) = cdp_assert("revealed fallback without IntersectionObserver", REVEALED_FALLBACK_SMOKE) { + return code; + } + println!("html_examples smoke ok: revealed fallback without IntersectionObserver"); + ExitCode::SUCCESS } @@ -229,6 +242,7 @@ const DELETE_ROW_SMOKE: &str = r#"(async()=>{const text=()=>document.body.textCo const LAZY_LOAD_SMOKE: &str = r#"(async()=>{const f=Array.from(document.querySelectorAll("form")).find(f=>f.textContent.includes("Load lazy content")); const panel=()=>f.nextElementSibling; const before=panel().textContent.trim(); f.querySelector("button,input[type=submit]").click(); await new Promise(r=>setTimeout(r,700)); const after=panel().textContent.trim(); if(!(after.includes("Lazy content loaded by server update #")&&after!==before)) throw new Error("lazy load did not visibly update"); return true;})()"#; const CLICK_TO_LOAD_SMOKE: &str = r#"(async()=>{const text=()=>document.body.textContent.replace(/\s+/g," "); const f=Array.from(document.querySelectorAll("form")).find(f=>f.textContent.includes("Load more")); f.querySelector("button,input[type=submit]").click(); await new Promise(r=>setTimeout(r,700)); if(!(text().includes("Loaded row 3")&&text().includes("Loaded row 4"))) throw new Error("click-to-load did not append rows"); return true;})()"#; const INFINITE_SCROLL_SMOKE: &str = r#"(async()=>{const text=()=>document.body.textContent.replace(/\s+/g," "); const f=Array.from(document.querySelectorAll("form")).find(f=>f.textContent.includes("Reveal more rows")); f.querySelector("button,input[type=submit]").click(); await new Promise(r=>setTimeout(r,700)); if(!(text().includes("Loaded row 4")&&text().includes("Loaded row 6"))) throw new Error("infinite scroll did not append rows"); return true;})()"#; +const REVEALED_FALLBACK_SMOKE: &str = r#"(async()=>{const text=()=>document.body.textContent.replace(/\s+/g," "); const runtime=document.querySelector('script[src*="hemx"][src$=".js"]'); if(!runtime) throw new Error("runtime script not found"); Object.defineProperty(window,"IntersectionObserver",{value:undefined,configurable:true}); const reloaded=document.createElement("script"); reloaded.src=runtime.src; document.head.appendChild(reloaded); for(let i=0;i<20;i++){if(text().includes("Lazy content loaded by server update #")&&text().includes("Loaded row 4")&&text().includes("Loaded row 6")) return true; await new Promise(r=>setTimeout(r,150));} throw new Error("revealed fallback did not dispatch lazy/infinite forms without a click");})()"#; const PROGRESS_SMOKE: &str = r#"(async()=>{const f=Array.from(document.querySelectorAll("form")).find(f=>f.textContent.includes("Tick progress")); const text=()=>document.querySelector("progress").textContent.trim(); const before=text(); f.querySelector("button,input[type=submit]").click(); await new Promise(r=>setTimeout(r,700)); const after=text(); if(!(after!==before&&after!=="100% complete"&&after.endsWith("% complete"))) throw new Error("progress click did not visibly tick"); return true;})()"#; const VALUE_SELECT_SMOKE: &str = r#"(async()=>{const f=Array.from(document.querySelectorAll("form")).find(f=>f.elements.category); f.elements.category.value="numbers"; f.elements.category.dispatchEvent(new Event("change",{bubbles:true,cancelable:true})); f.querySelector("button,input[type=submit]").click(); await new Promise(r=>setTimeout(r,700)); const options=Array.from(document.querySelectorAll("select[name=value] option")).map(option=>option.textContent.trim()); if(!(options.includes("One")&&options.includes("Two")&&!options.includes("Alpha"))) throw new Error("value select did not replace options"); return true;})()"#; const RESET_INPUT_SMOKE: &str = r#"(async()=>{const f=Array.from(document.querySelectorAll("form")).find(f=>f.elements.message); f.elements.message.value="hello reset"; f.querySelector("button,input[type=submit]").click(); await new Promise(r=>setTimeout(r,700)); if(!(document.body.textContent.includes("Sent: hello reset")&&f.elements.message.value==="")) throw new Error("reset user input did not clear field"); return true;})()"#;