From 5845930cd37e18687f5338550dc544af83208ef3 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Tue, 26 May 2026 01:03:58 +0200 Subject: [PATCH] test(techdemo): render slots from hemplate views Migrate techdemo effect paths and tests from explicit SafeHtml/html payload plumbing to RenderSlotExt view helpers. req: view/001 req: html_safety/002 --- examples/techdemo/src/lib.rs | 13 ++------ examples/techdemo/src/main.rs | 63 ++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/examples/techdemo/src/lib.rs b/examples/techdemo/src/lib.rs index fd86211..c6350b4 100644 --- a/examples/techdemo/src/lib.rs +++ b/examples/techdemo/src/lib.rs @@ -5,7 +5,7 @@ pub mod ui {} mod tests { use super::ui; use hemplate::Hemplate; - use slhx::{Effect, IntoEffect, Payload, SafeHtml}; + use slhx::{Effect, IntoEffect, Payload, RenderSlotExt}; use slhx_test::inspect; #[derive(Hemplate)] @@ -28,7 +28,7 @@ mod tests { fn techdemo_uses_generated_slots_for_multi_target_updates() { fn update() -> impl IntoEffect { ( - ui::control_center::slots::hero_metrics.html(render_fast_metric("fast")), + ui::control_center::slots::hero_metrics.render_view(&FastMetric { label: "fast" }), ui::control_center::slots::notice.text("typed"), ) } @@ -38,15 +38,6 @@ mod tests { assert!(batch.has_slot(ui::control_center::slots::notice)); } - fn render_fast_metric(label: &'static str) -> SafeHtml { - // req: html_safety/002 req: view/001 - let mut html = String::new(); - FastMetric { label } - .render_into(&mut html) - .expect("techdemo fast metric renders"); - SafeHtml::trusted(html) - } - // req: examples/001 req: form/001 req: form/004 req: form/006 req: derive_handler/003 #[test] fn techdemo_form_handler_is_checked_against_hemplate_form() { diff --git a/examples/techdemo/src/main.rs b/examples/techdemo/src/main.rs index ea1eca0..ecd0101 100644 --- a/examples/techdemo/src/main.rs +++ b/examples/techdemo/src/main.rs @@ -4,7 +4,7 @@ use axum::routing::get; use axum::Router; use futures_util::{stream, StreamExt}; use hemplate::Hemplate; -use slhx::{CssClass, CssClasses, Handle, IntoEffect, SafeHtml}; +use slhx::{CssClass, CssClasses, Handle, IntoEffect, RenderSlotExt, SafeHtml}; use slhx_axum::{ runtime_js, sse, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm, PageRequest, @@ -263,13 +263,13 @@ async fn interact( // req: push/001 req: push/003 req: examples/001 async fn events(Query(params): Query>) -> impl IntoResponse { if params.contains_key("once") { - let effect = ui::control_center::slots::live_feed.html(live_feed(1)); + let effect = ui::control_center::slots::live_feed.render_view(&LiveFeed { tick: 1 }); return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed()); } let batches = stream::unfold(1_u64, |tick| async move { tokio::time::sleep(Duration::from_secs(4)).await; - let effect = ui::control_center::slots::live_feed.html(live_feed(tick)); + let effect = ui::control_center::slots::live_feed.render_view(&LiveFeed { tick }); Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), tick + 1)) }) .boxed(); @@ -378,8 +378,10 @@ fn registry(shared: Arc) -> HandlerRegistry { let mut demo = shared.demo.lock().unwrap(); demo.log("Simulated push event produced the same EffectBatch shape"); ( - ui::control_center::slots::live_feed.html(live_feed(demo.activity.len() as u64)), - ui::control_center::slots::activity.html(render_activity(&demo)), + ui::control_center::slots::live_feed.render_view(&LiveFeed { + tick: demo.activity.len() as u64, + }), + ui::control_center::slots::activity.render_view(&activity_view(&demo)), ui::control_center::slots::notice.text("Push simulated ยท no client app code"), ) } @@ -405,10 +407,10 @@ fn update_work(demo: &mut DemoState, id: Option<&str>, update: impl FnOnce(&mut fn demo_effects(demo: &DemoState, notice: &'static str) -> impl IntoEffect { ( - ui::control_center::slots::hero_metrics.html(render_hero(demo)), - ui::control_center::slots::board.html(render_board(demo)), - ui::control_center::slots::activity.html(render_activity(demo)), - ui::control_center::slots::inspector.html(render_inspector(demo)), + ui::control_center::slots::hero_metrics.render_view(&hero_view(demo)), + ui::control_center::slots::board.render_view(&board_view(demo)), + ui::control_center::slots::activity.render_view(&activity_view(demo)), + ui::control_center::slots::inspector.render_view(&inspector_view(demo)), ui::control_center::slots::notice.text(notice), ui::control_center::forms::launch_work.clear("title"), ) @@ -433,20 +435,25 @@ fn shell(body: String) -> String { render_template(&AppShell { body: SafeHtml::trusted(body) }) } -fn render_hero(demo: &DemoState) -> SafeHtml { +fn hero_view(demo: &DemoState) -> HeroMetrics { // req: html_safety/002 req: view/001 let shipped = demo.work.iter().filter(|item| item.stage == Stage::Shipped).count(); let active = demo.work.iter().filter(|item| item.stage == Stage::Active).count(); let impact: u64 = demo.work.iter().map(|item| item.impact as u64).sum(); - render_html(&HeroMetrics { + HeroMetrics { resource_count: 13, active_count: active, shipped_count: shipped, impact_score: impact, - }) + } } -fn render_board(demo: &DemoState) -> SafeHtml { +fn render_hero(demo: &DemoState) -> SafeHtml { + // req: html_safety/002 req: view/001 + render_html(&hero_view(demo)) +} + +fn board_view(demo: &DemoState) -> BoardLanes { // req: html_safety/002 req: view/001 let lanes = LANES .iter() @@ -466,7 +473,12 @@ fn render_board(demo: &DemoState) -> SafeHtml { }) .collect::>(); - render_html(&BoardLanes { lanes }) + BoardLanes { lanes } +} + +fn render_board(demo: &DemoState) -> SafeHtml { + // req: html_safety/002 req: view/001 + render_html(&board_view(demo)) } fn issue_card(item: &WorkItem, selected: bool) -> IssueCard { @@ -489,14 +501,19 @@ fn issue_card(item: &WorkItem, selected: bool) -> IssueCard { } } -fn render_activity(demo: &DemoState) -> SafeHtml { +fn activity_view(demo: &DemoState) -> ActivityFeed { // req: html_safety/002 req: view/001 - render_html(&ActivityFeed { + ActivityFeed { items: demo.activity.iter().cloned().collect(), - }) + } } -fn render_inspector(demo: &DemoState) -> SafeHtml { +fn render_activity(demo: &DemoState) -> SafeHtml { + // req: html_safety/002 req: view/001 + render_html(&activity_view(demo)) +} + +fn inspector_view(demo: &DemoState) -> InspectorPanel { // req: html_safety/002 req: view/001 let selected = demo .selected_id @@ -512,15 +529,15 @@ fn render_inspector(demo: &DemoState) -> SafeHtml { }) }, ); - render_html(&InspectorPanel { + InspectorPanel { selected, spotlight: demo.spotlight.clone(), - }) + } } -fn live_feed(tick: u64) -> SafeHtml { +fn render_inspector(demo: &DemoState) -> SafeHtml { // req: html_safety/002 req: view/001 - render_html(&LiveFeed { tick }) + render_html(&inspector_view(demo)) } fn architecture_hero() -> SafeHtml { @@ -674,7 +691,7 @@ mod tests { // req: html_safety/002 req: view/001 req: test/005 #[test] fn live_feed_payload_is_rendered_by_a_hemplate_view() { - let html = live_feed(7); + let html = render_html(&LiveFeed { tick: 7 }); let document = Html::parse_fragment(html.as_str()); let row = document .select(&selector(".live-row"))