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,
}
#[derive(Hemplate)]
struct PageSwap {
content: SafeHtml,
title: &'static str,
}
#[derive(Hemplate)]
#[hemplate = "partials"]
struct DocsContent {
message: &'static str,
}
#[tokio::main]
async fn main() {
let state = Arc::new(ExampleState::default());
@@ -63,16 +75,7 @@ async fn home(request: PageRequest) -> impl IntoResponse {
// req: page_swap/002, req: examples/001
async fn docs(request: PageRequest) -> impl IntoResponse {
let partial = ui::page_swap::lower_html(
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>"#,
);
let partial = render_page_swap("Docs", "This page was swapped without a full reload.");
request
.page(partial, shell)
.title("Docs")
@@ -155,8 +158,8 @@ fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
.register(ui::page_swap::handles::load_docs.id().id, |_| {
// req: page_swap/002, req: examples/001
(
ui::page_swap::slots::content.html(SafeHtml::trusted(
"<h1>Docs</h1><p>This content came from an EffectBatch.</p>",
ui::page_swap::slots::content.html(render_docs_content(
"This content came from an EffectBatch.",
)),
ui::page_swap::slots::title.text("Docs"),
push("/docs"),
@@ -170,7 +173,7 @@ fn all_examples() -> String {
ui::todos::lower_html(include_str!("../templates/todos.heml")),
ui::wizard::lower_html(include_str!("../templates/wizard.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")),
]
.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 {
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)]
@@ -227,6 +247,43 @@ mod tests {
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
#[test]
fn todos_payload_is_rendered_by_a_hemplate_view() {
+2 -2
View File
@@ -2,6 +2,6 @@
<nav data-slhx-slot="nav">
<a href="/docs" data-slhx-nav="" data-slhx-handle="load_docs">Docs</a>
</nav>
<article data-slhx-slot="content">Welcome</article>
<title data-slhx-slot="title">Welcome</title>
<article data-slhx-slot="content">{+= self.content =+}</article>
<title data-slhx-slot="title">{+ self.title +}</title>
</main>
@@ -0,0 +1 @@
<h1>Docs</h1><p>{+ self.message +}</p>