test(html-examples): cover interval progress path

Exercise the data-hemx-interval trigger path through the progress-bar example and browser smoke while clearing stale timers before each smoke run.

req: convention/005

req: convention/011

req: test/006
This commit is contained in:
slhx agent
2026-06-25 18:58:10 +02:00
parent 18af9d6eee
commit d7cf6fe9f6
3 changed files with 11 additions and 6 deletions
+1
View File
@@ -815,6 +815,7 @@ mod tests {
assert!(html.contains("data-hemx-form=\"validate_email\"")); assert!(html.contains("data-hemx-form=\"validate_email\""));
assert!(html.contains("data-hemx-revealed=\"true\"")); assert!(html.contains("data-hemx-revealed=\"true\""));
assert!(html.contains("data-hemx-handle=\"tick_progress\"")); assert!(html.contains("data-hemx-handle=\"tick_progress\""));
assert!(html.contains("data-hemx-interval=\"1000\""));
assert!(html.contains("data-hemx-slot=\"loaded_row\"")); assert!(html.contains("data-hemx-slot=\"loaded_row\""));
assert!(html.contains("data-hemx-slot=\"infinite_row\"")); assert!(html.contains("data-hemx-slot=\"infinite_row\""));
assert!(html.contains("data-hemx-slot=\"value_option\"")); assert!(html.contains("data-hemx-slot=\"value_option\""));
@@ -78,7 +78,7 @@
<section id="progress-bar" data-htmx-example="progress-bar" aria-labelledby="progress-heading"> <section id="progress-bar" data-htmx-example="progress-bar" aria-labelledby="progress-heading">
<h2 id="progress-heading">progress-bar</h2> <h2 id="progress-heading">progress-bar</h2>
<form data-hemx-handle="tick_progress" data-hemx-form="tick_progress"> <form data-hemx-handle="tick_progress" data-hemx-form="tick_progress" data-hemx-interval="1000">
<input type="hidden" name="request" value="tick"> <input type="hidden" name="request" value="tick">
<button type="submit">Tick progress</button> <button type="submit">Tick progress</button>
</form> </form>
+9 -5
View File
@@ -60,15 +60,20 @@ fn run_html_examples_smoke() -> ExitCode {
}; };
let addr = format!("127.0.0.1:{port}"); let addr = format!("127.0.0.1:{port}");
let url = format!("http://{addr}"); let url = format!("http://{addr}");
if let Err(code) = ensure_cdp_browser() {
return code;
}
// Clear any timers from a prior smoke page before a fresh server binds the port.
if let Err(code) = cdp_tab_goto("about:blank") {
return code;
}
let _server = match start_html_examples_server(&addr) { let _server = match start_html_examples_server(&addr) {
Ok(server) => server, Ok(server) => server,
Err(code) => return code, Err(code) => return code,
}; };
if let Err(code) = ensure_cdp_browser() {
return code;
}
let checks = [ let checks = [
("progress", PROGRESS_SMOKE),
("click-to-edit save", CLICK_TO_EDIT_SMOKE), ("click-to-edit save", CLICK_TO_EDIT_SMOKE),
("edit-row save", EDIT_ROW_SMOKE), ("edit-row save", EDIT_ROW_SMOKE),
("inline validation revalidation", INLINE_VALIDATION_SMOKE), ("inline validation revalidation", INLINE_VALIDATION_SMOKE),
@@ -77,7 +82,6 @@ fn run_html_examples_smoke() -> ExitCode {
("lazy load", LAZY_LOAD_SMOKE), ("lazy load", LAZY_LOAD_SMOKE),
("click-to-load load more", CLICK_TO_LOAD_SMOKE), ("click-to-load load more", CLICK_TO_LOAD_SMOKE),
("infinite/reveal rows", INFINITE_SCROLL_SMOKE), ("infinite/reveal rows", INFINITE_SCROLL_SMOKE),
("progress", PROGRESS_SMOKE),
("value select", VALUE_SELECT_SMOKE), ("value select", VALUE_SELECT_SMOKE),
("reset user input", RESET_INPUT_SMOKE), ("reset user input", RESET_INPUT_SMOKE),
]; ];
@@ -266,7 +270,7 @@ const LAZY_LOAD_SMOKE: &str = r#"(async()=>{const f=Array.from(document.querySel
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 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 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 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 PROGRESS_SMOKE: &str = r#"(async()=>{const f=Array.from(document.querySelectorAll("form")).find(f=>f.textContent.includes("Tick progress")); if(f.getAttribute("data-hemx-interval")!=="1000") throw new Error("progress form is not interval-driven"); const text=()=>document.querySelector("progress").textContent.trim(); const before=text(); await new Promise(r=>setTimeout(r,1300)); const after=text(); if(!(after!==before&&after!=="100% complete"&&after.endsWith("% complete"))) throw new Error("progress interval 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 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;})()"#; 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;})()"#;