feat(build): make generated page helper canonical

Move canonical examples and docs snippets from raw render/page calls to generated ui::page helpers, hide generated render behind doc(hidden), and let form resources participate in generated target inspection for tests.

req: canonical_authoring/005

req: dx/006

req: codegen/002

req: public_api/002

req: test/001
This commit is contained in:
slhx agent
2026-06-12 07:31:39 +02:00
parent 4a8442c5f9
commit 7be1c9c649
7 changed files with 49 additions and 43 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ use ui::board::{forms, targets};
targets::card.replace(card.id, &CardView::from(card));
forms::create_card.clear("title");
ui::render(&BoardView::from(board));
ui::page(&BoardView::from(board));
```
No string desync. No manual ids. The generated module owns the names.
+1 -1
View File
@@ -37,7 +37,7 @@ mod tests {
// req: html_safety/002 req: view/001 req: test/005
#[test]
fn kanban_board_test_payload_is_rendered_by_a_hemplate_view() {
let html = super::ui::render(&empty_board());
let html = super::ui::page(&empty_board());
let document = Html::parse_fragment(html.as_str());
assert_eq!(document.select(&selector(".columns")).count(), 1);
}
+6 -6
View File
@@ -281,15 +281,15 @@ fn parse_column(value: Option<&str>) -> usize {
fn page_html(board: &BoardState) -> Html {
// req: html_safety/002 req: view/001
board_ui::render(&Board {
board_ui::page(&Board {
options: render_options(),
board: ui::render(&board_view(board)),
board: ui::page(&board_view(board)),
})
}
fn shell(body: Html) -> Html {
// req: html_safety/001 req: html_safety/002 req: axum_integration/001
hemx::page(&AppShell {
ui::page(&AppShell {
runtime_src: runtime_js_path(),
body,
})
@@ -297,7 +297,7 @@ fn shell(body: Html) -> Html {
fn render_options() -> Html {
// req: html_safety/002 req: view/001
ui::render(&ColumnOptions {
ui::page(&ColumnOptions {
options: COLUMNS
.iter()
.map(|(id, title)| ColumnOption { id, title })
@@ -385,7 +385,7 @@ mod tests {
}],
};
let html = ui::render(&board_view(&board));
let html = ui::page(&board_view(&board));
let document = Html::parse_fragment(html.as_str());
assert_eq!(
document
@@ -417,7 +417,7 @@ mod tests {
// req: html_safety/002 req: view/001 req: test/005
#[test]
fn presence_payload_is_rendered_by_a_hemplate_view() {
let html = ui::render(&Presence { count: 7 });
let html = ui::page(&Presence { count: 7 });
let document = Html::parse_fragment(html.as_str());
assert_eq!(
document
+15 -19
View File
@@ -254,18 +254,18 @@ pub struct AppShell {
}
pub fn home_page(ctx: &AppContext) -> Html {
hemx::page(&AppShell {
ui::page(&AppShell {
title: "hemx SaaS tutorial",
runtime_src: runtime_js_path(),
body: hemx::page(&Dashboard::from_context(ctx)),
body: ui::page(&Dashboard::from_context(ctx)),
})
}
pub fn settings_page(ctx: &AppContext) -> Html {
hemx::page(&AppShell {
ui::page(&AppShell {
title: "hemx SaaS tutorial settings",
runtime_src: runtime_js_path(),
body: hemx::page(&Dashboard::settings(ctx)),
body: ui::page(&Dashboard::settings(ctx)),
})
}
@@ -330,7 +330,7 @@ fn project_summary(total: usize) -> String {
mod tests {
use super::*;
use hemx_axum::{InteractionForm, InteractionRequest};
use hemx_test::{inspect, inspect_batch};
use hemx_test::{any_root_selector, inspect, inspect_batch, target_selector};
use scraper::{Html as ParsedHtml, Selector};
fn form<I>(handle: hemx::Handle<I>, fields: &[(&str, &str)]) -> InteractionForm {
@@ -353,15 +353,13 @@ mod tests {
let html = home_page(&ctx);
let document = ParsedHtml::parse_document(html.as_str());
assert_eq!(document.select(&selector(any_root_selector())).count(), 1);
assert_eq!(
document
.select(&selector("[data-hemx-root='dashboard']"))
.count(),
1
);
assert_eq!(
document
.select(&selector("form[data-hemx-form='new_project']"))
.select(&selector(&format!(
"form{}",
target_selector(dashboard::new_project)
)))
.count(),
1
);
@@ -389,15 +387,13 @@ mod tests {
let html = settings_page(&ctx);
let document = ParsedHtml::parse_document(html.as_str());
assert_eq!(document.select(&selector(any_root_selector())).count(), 1);
assert_eq!(
document
.select(&selector("[data-hemx-root='dashboard']"))
.count(),
1
);
assert_eq!(
document
.select(&selector("[data-hemx-slot='page_panel'] .settings-page"))
.select(&selector(&format!(
"{} .settings-page",
target_selector(dashboard::page_panel)
)))
.count(),
1
);
+10 -10
View File
@@ -268,18 +268,18 @@ fn registry(state: Arc<ExampleState>) -> Registry {
fn all_examples() -> Html {
// req: html_safety/001 req: html_safety/002 req: component/003
Html::join([
counter::render(&Counter),
hemx::page(&todos_view(&[])),
wizard::render(&Wizard),
auth::render(&Auth),
counter::page(&Counter),
ui::page(&todos_view(&[])),
wizard::page(&Wizard),
auth::page(&Auth),
render_page_swap("Welcome", "Welcome"),
notifications::render(&Notifications),
notifications::page(&Notifications),
])
}
fn shell(body: Html) -> Html {
// req: html_safety/001 req: html_safety/002 req: axum_integration/001 req: component/003
hemx::page(&AppShell {
ui::page(&AppShell {
runtime_src: runtime_js_path(),
body,
})
@@ -447,7 +447,7 @@ fn todo_summary(todos: &[TodoRecord]) -> String {
fn render_page_swap(title: &'static str, message: &'static str) -> Html {
// req: html_safety/002 req: view/001 req: component/003
page_swap::render(&PageSwap {
page_swap::page(&PageSwap {
content: render_docs_content(message),
title,
})
@@ -455,7 +455,7 @@ fn render_page_swap(title: &'static str, message: &'static str) -> Html {
fn render_docs_content(message: &'static str) -> Html {
// req: html_safety/002 req: view/001 req: component/003
hemx::page(&DocsContent { message })
ui::page(&DocsContent { message })
}
#[cfg(test)]
@@ -570,7 +570,7 @@ mod tests {
title: "<b>Ship v0</b>".to_owned(),
}];
let html = hemx::page(&todos_view(&todos));
let html = ui::page(&todos_view(&todos));
let document = Html::parse_fragment(html.as_str());
let rows = document
.select(&selector(&keyed_items_selector("li")))
@@ -590,7 +590,7 @@ mod tests {
// req: html_safety/002 req: view/001 req: test/005
#[test]
fn empty_todos_payload_is_rendered_by_a_hemplate_view() {
let html = hemx::page(&todos_view(&[]));
let html = ui::page(&todos_view(&[]));
let document = Html::parse_fragment(html.as_str());
let rows = document
.select(&selector(&list_item_selector("")))
+10 -6
View File
@@ -732,6 +732,7 @@ impl Resources {
"{pad} ::hemx::__private::html_trusted(lower(html))\n"
));
out.push_str(&format!("{pad}}}\n\n"));
out.push_str(&format!("{pad}#[doc(hidden)]\n"));
out.push_str(&format!(
"{pad}pub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html {{\n"
));
@@ -1733,9 +1734,9 @@ mod tests {
));
assert!(generated.contains("#[doc(hidden)]\npub fn lower_html"));
assert!(generated.contains("pub fn static_fragment(html: &'static str) -> ::hemx::Html"));
assert!(
generated.contains("pub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")
);
assert!(generated.contains(
"#[doc(hidden)]\npub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html"
));
assert!(generated.contains("pub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html"));
assert!(generated.contains(
"#[doc(hidden)]\npub fn render_html(view: &impl ::hemplate::Hemplate) -> ::hemx::Html"
@@ -1846,9 +1847,9 @@ mod tests {
));
assert!(generated.contains("\n#[doc(hidden)]\npub fn lower_html"));
assert!(generated.contains("\npub fn static_fragment(html: &'static str) -> ::hemx::Html"));
assert!(
generated.contains("\npub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")
);
assert!(generated.contains(
"\n#[doc(hidden)]\npub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html"
));
assert!(
generated.contains("\npub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")
);
@@ -1865,6 +1866,9 @@ mod tests {
assert!(generated.contains(" pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains(" pub use self::targets::todos;"));
assert!(generated.contains(" #[doc(hidden)]\n pub fn lower_html"));
assert!(generated.contains(
" #[doc(hidden)]\n pub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html"
));
assert!(
generated.contains(" pub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")
);
+6
View File
@@ -1165,6 +1165,12 @@ impl<T> core::fmt::Display for Form<T> {
}
}
impl<T> GeneratedTarget for Form<T> {
fn __hemx_resource_id(self) -> ResourceId {
self.id
}
}
impl<T> Form<T> {
pub const fn new(id: u32) -> Self {
Self {