refactor(v0): render page swap with hemplate

Move the v0 docs/page-swap partial and EffectBatch content payload from raw HTML strings into hemplate views, with scraper assertions for rendered structure.

req: html_safety/002

req: view/001

req: test/005
This commit is contained in:
slhx agent
2026-05-25 21:49:13 +02:00
parent bd35347fa2
commit f8d46e1a40
3 changed files with 74 additions and 16 deletions
+71 -14
View File
@@ -37,6 +37,18 @@ struct TodoItem {
title: String, title: String,
} }
#[derive(Hemplate)]
struct PageSwap {
content: SafeHtml,
title: &'static str,
}
#[derive(Hemplate)]
#[hemplate = "partials"]
struct DocsContent {
message: &'static str,
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let state = Arc::new(ExampleState::default()); let state = Arc::new(ExampleState::default());
@@ -63,16 +75,7 @@ async fn home(request: PageRequest) -> impl IntoResponse {
// req: page_swap/002, req: examples/001 // req: page_swap/002, req: examples/001
async fn docs(request: PageRequest) -> impl IntoResponse { async fn docs(request: PageRequest) -> impl IntoResponse {
let partial = ui::page_swap::lower_html( let partial = render_page_swap("Docs", "This page was swapped without a full reload.");
r#"<main data-slhx-root="docs">
<nav data-slhx-slot="nav">
<a href="/" data-slhx-nav="">Examples</a>
<a href="/docs" data-slhx-nav="">Docs</a>
</nav>
<article data-slhx-slot="content"><h1>Docs</h1><p>This page was swapped without a full reload.</p></article>
<title data-slhx-slot="title">Docs</title>
</main>"#,
);
request request
.page(partial, shell) .page(partial, shell)
.title("Docs") .title("Docs")
@@ -155,8 +158,8 @@ fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
.register(ui::page_swap::handles::load_docs.id().id, |_| { .register(ui::page_swap::handles::load_docs.id().id, |_| {
// req: page_swap/002, req: examples/001 // req: page_swap/002, req: examples/001
( (
ui::page_swap::slots::content.html(SafeHtml::trusted( ui::page_swap::slots::content.html(render_docs_content(
"<h1>Docs</h1><p>This content came from an EffectBatch.</p>", "This content came from an EffectBatch.",
)), )),
ui::page_swap::slots::title.text("Docs"), ui::page_swap::slots::title.text("Docs"),
push("/docs"), push("/docs"),
@@ -170,7 +173,7 @@ fn all_examples() -> String {
ui::todos::lower_html(include_str!("../templates/todos.heml")), ui::todos::lower_html(include_str!("../templates/todos.heml")),
ui::wizard::lower_html(include_str!("../templates/wizard.heml")), ui::wizard::lower_html(include_str!("../templates/wizard.heml")),
ui::auth::lower_html(include_str!("../templates/auth.heml")), ui::auth::lower_html(include_str!("../templates/auth.heml")),
ui::page_swap::lower_html(include_str!("../templates/page_swap.heml")), render_page_swap("Welcome", "Welcome"),
ui::notifications::lower_html(include_str!("../templates/notifications.heml")), ui::notifications::lower_html(include_str!("../templates/notifications.heml")),
] ]
.join("\n") .join("\n")
@@ -214,8 +217,25 @@ fn render_todos(todos: &[Todo]) -> SafeHtml {
}) })
} }
fn render_page_swap(title: &'static str, message: &'static str) -> String {
// req: html_safety/002 req: view/001
ui::page_swap::lower_html(render_template(&PageSwap {
content: render_docs_content(message),
title,
}))
}
fn render_docs_content(message: &'static str) -> SafeHtml {
// req: html_safety/002 req: view/001
render_html(&DocsContent { message })
}
fn render_html(template: &impl Hemplate) -> SafeHtml { fn render_html(template: &impl Hemplate) -> SafeHtml {
SafeHtml::trusted(template.render().expect("v0 hemplate partial renders")) SafeHtml::trusted(render_template(template))
}
fn render_template(template: &impl Hemplate) -> String {
template.render().expect("v0 hemplate view renders")
} }
#[cfg(test)] #[cfg(test)]
@@ -227,6 +247,43 @@ mod tests {
Selector::parse(value).expect("test selector parses") Selector::parse(value).expect("test selector parses")
} }
// req: html_safety/002 req: view/001 req: test/005
#[test]
fn docs_content_payload_is_rendered_by_a_hemplate_view() {
let html = render_docs_content("This content came from an EffectBatch.");
let document = Html::parse_fragment(html.as_str());
assert_eq!(
document
.select(&selector("h1"))
.next()
.map(|heading| heading.text().collect::<String>()),
Some("Docs".to_owned())
);
assert_eq!(
document
.select(&selector("p"))
.next()
.map(|paragraph| paragraph.text().collect::<String>()),
Some("This content came from an EffectBatch.".to_owned())
);
}
// req: html_safety/002 req: view/001 req: test/005
#[test]
fn docs_page_partial_is_rendered_by_a_hemplate_view() {
let html = render_page_swap("Docs", "This page was swapped without a full reload.");
let document = Html::parse_fragment(&html);
assert_eq!(document.select(&selector("main[data-slhx-root=\"docs\"]")).count(), 1);
assert_eq!(document.select(&selector("article[data-sid]")).count(), 1);
assert_eq!(
document
.select(&selector("article h1"))
.next()
.map(|heading| heading.text().collect::<String>()),
Some("Docs".to_owned())
);
}
// req: html_safety/002 req: view/001 req: test/005 // req: html_safety/002 req: view/001 req: test/005
#[test] #[test]
fn todos_payload_is_rendered_by_a_hemplate_view() { fn todos_payload_is_rendered_by_a_hemplate_view() {
+2 -2
View File
@@ -2,6 +2,6 @@
<nav data-slhx-slot="nav"> <nav data-slhx-slot="nav">
<a href="/docs" data-slhx-nav="" data-slhx-handle="load_docs">Docs</a> <a href="/docs" data-slhx-nav="" data-slhx-handle="load_docs">Docs</a>
</nav> </nav>
<article data-slhx-slot="content">Welcome</article> <article data-slhx-slot="content">{+= self.content =+}</article>
<title data-slhx-slot="title">Welcome</title> <title data-slhx-slot="title">{+ self.title +}</title>
</main> </main>
@@ -0,0 +1 @@
<h1>Docs</h1><p>{+ self.message +}</p>