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
This commit is contained in:
slhx agent
2026-05-26 01:03:58 +02:00
parent 852053fd04
commit 5845930cd3
2 changed files with 42 additions and 34 deletions
+2 -11
View File
@@ -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() {
+40 -23
View File
@@ -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<BTreeMap<String, String>>) -> 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<Shared>) -> 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::<Vec<_>>();
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"))