From 062c184e42f3e46f2884eec6ebbb178f6ca29008 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Tue, 26 May 2026 00:39:47 +0200 Subject: [PATCH] feat(core): format generated resources for templates Implement Display for generated slots, keyed slots, atoms, and forms so public examples can pass typed resources directly to hemplate dynamic attributes and tests instead of extracting raw numeric ids. req: public_api/001 req: public_api/002 --- examples/kanban/src/lib.rs | 2 +- examples/techdemo/src/lib.rs | 5 ++++- examples/techdemo/tests/browser_e2e.rs | 30 +++++++++++++------------- examples/techdemo/tests/e2e.rs | 2 +- slhx-core/src/lib.rs | 24 +++++++++++++++++++++ slhx-core/tests/effect_batch.rs | 6 +++++- 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/examples/kanban/src/lib.rs b/examples/kanban/src/lib.rs index 75e4570..e487fa8 100644 --- a/examples/kanban/src/lib.rs +++ b/examples/kanban/src/lib.rs @@ -73,7 +73,7 @@ mod tests { // req: examples/001 req: form/002 req: codegen/003 #[test] fn kanban_template_exports_form_and_card_handles() { - assert_ne!(ui::board::handles::create_card.id().id, ui::board::handles::move_right.id().id); + assert_ne!(ui::board::handles::create_card.id(), ui::board::handles::move_right.id()); assert_eq!(ui::board::forms::create_card.field("title").resource, ui::board::forms::create_card.id()); } } diff --git a/examples/techdemo/src/lib.rs b/examples/techdemo/src/lib.rs index f3fdde5..0bf8fac 100644 --- a/examples/techdemo/src/lib.rs +++ b/examples/techdemo/src/lib.rs @@ -64,7 +64,10 @@ mod tests { // req: examples/001 req: form/002 req: codegen/003 #[test] fn techdemo_exports_form_and_interaction_handles() { - assert_ne!(ui::control_center::handles::launch_work.id().id, ui::control_center::handles::advance_work.id().id); + assert_ne!( + ui::control_center::handles::launch_work.id(), + ui::control_center::handles::advance_work.id() + ); assert_eq!( ui::control_center::forms::launch_work.field("title").resource, ui::control_center::forms::launch_work.id() diff --git a/examples/techdemo/tests/browser_e2e.rs b/examples/techdemo/tests/browser_e2e.rs index f040ca6..ad4396b 100644 --- a/examples/techdemo/tests/browser_e2e.rs +++ b/examples/techdemo/tests/browser_e2e.rs @@ -53,11 +53,11 @@ async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> { wait_for_runtime(&driver).await?; driver - .find(By::Css(&handle_selector(ui::control_center::handles::simulate_push.id().id))) + .find(By::Css(&handle_selector(ui::control_center::handles::simulate_push))) .await? .click() .await?; - wait_for_text(&driver, &slot_selector(ui::control_center::slots::notice.id().id), "Push simulated").await?; + wait_for_text(&driver, &slot_selector(ui::control_center::slots::notice), "Push simulated").await?; driver.find(By::Css("button.primary-action")).await?.click().await?; wait_for_text(&driver, ".work-card[data-key='4']", "Ship typed effects").await?; @@ -65,40 +65,40 @@ async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> { drag_card_to_lane(&driver, 2, "product").await?; wait_for_text(&driver, ".lane[data-lane='product'] .work-card[data-key='2']", "Shipped").await?; - wait_for_text(&driver, &slot_selector(ui::control_center::slots::notice.id().id), "Drag-and-drop move persisted").await?; + wait_for_text(&driver, &slot_selector(ui::control_center::slots::notice), "Drag-and-drop move persisted").await?; driver - .find(By::Css(&card_button_selector(ui::control_center::handles::spotlight_work.id().id, 2))) + .find(By::Css(&card_button_selector(ui::control_center::handles::spotlight_work, 2))) .await? .click() .await?; wait_for_text( &driver, - &slot_selector(ui::control_center::slots::inspector.id().id), + &slot_selector(ui::control_center::slots::inspector), "Stream typed presence · lane=Product · stage=Shipped · impact=7", ) .await?; driver - .find(By::Css(&card_button_selector(ui::control_center::handles::advance_work.id().id, 3))) + .find(By::Css(&card_button_selector(ui::control_center::handles::advance_work, 3))) .await? .click() .await?; wait_for_text(&driver, ".lane[data-lane='product'] .work-card[data-key='3']", "Active").await?; driver - .find(By::Css(&handle_selector(ui::control_center::handles::simulate_push.id().id))) + .find(By::Css(&handle_selector(ui::control_center::handles::simulate_push))) .await? .click() .await?; - wait_for_text(&driver, &slot_selector(ui::control_center::slots::live_feed.id().id), "SSE tick").await?; + wait_for_text(&driver, &slot_selector(ui::control_center::slots::live_feed), "SSE tick").await?; driver.find(By::Css("a[href='/architecture']")).await?.click().await?; - wait_for_text(&driver, &slot_selector(ui::control_center::slots::inspector.id().id), "Page swap").await?; + wait_for_text(&driver, &slot_selector(ui::control_center::slots::inspector), "Page swap").await?; assert!(driver.current_url().await?.as_str().ends_with("/architecture")); driver.goto(&format!("http://{APP_ADDR}/")).await?; - wait_for_text(&driver, &slot_selector(ui::control_center::slots::live_feed.id().id), "SSE tick").await?; + wait_for_text(&driver, &slot_selector(ui::control_center::slots::live_feed), "SSE tick").await?; Ok::<(), WebDriverError>(()) } @@ -119,15 +119,15 @@ fn wait_for_tcp(addr: &str) { panic!("timed out waiting for {addr}"); } -fn handle_selector(id: u32) -> String { - format!(r#"[data-hid="{id}"]"#) +fn handle_selector(handle: impl std::fmt::Display) -> String { + format!(r#"[data-hid="{handle}"]"#) } -fn slot_selector(id: u32) -> String { - format!(r#"[data-sid="{id}"]"#) +fn slot_selector(slot: impl std::fmt::Display) -> String { + format!(r#"[data-sid="{slot}"]"#) } -fn card_button_selector(handle_id: u32, work_id: u64) -> String { +fn card_button_selector(handle_id: impl std::fmt::Display, work_id: u64) -> String { format!(r#"[data-hid="{handle_id}"][data-work-id="{work_id}"]"#) } diff --git a/examples/techdemo/tests/e2e.rs b/examples/techdemo/tests/e2e.rs index 9881cdf..5eaf2c8 100644 --- a/examples/techdemo/tests/e2e.rs +++ b/examples/techdemo/tests/e2e.rs @@ -103,7 +103,7 @@ fn product_is_e2e_working_over_http() { let move_to_lane = post( "/", - &format!("__h={}&work_id=4&lane=runtime", ui::control_center::handles::move_to_lane.id().id), + &format!("__h={}&work_id=4&lane=runtime", ui::control_center::handles::move_to_lane), ); assert_effect_response(&move_to_lane); let move_to_lane_batch = move_to_lane.batch(); diff --git a/slhx-core/src/lib.rs b/slhx-core/src/lib.rs index d9e0a12..ab7ccdd 100644 --- a/slhx-core/src/lib.rs +++ b/slhx-core/src/lib.rs @@ -730,6 +730,12 @@ impl Clone for Slot { impl Copy for Slot {} +impl core::fmt::Display for Slot { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.id.id.fmt(f) + } +} + impl Slot { pub const fn new(id: u32) -> Self { Self { @@ -778,6 +784,12 @@ impl Clone for KeyedSlot { impl Copy for KeyedSlot {} +impl core::fmt::Display for KeyedSlot { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.id.id.fmt(f) + } +} + impl KeyedSlot where K: ToString, @@ -878,6 +890,12 @@ impl Clone for Atom { impl Copy for Atom {} +impl core::fmt::Display for Atom { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.id.id.fmt(f) + } +} + impl Atom { pub const fn new(id: u32) -> Self { Self { @@ -951,6 +969,12 @@ impl Clone for Form { impl Copy for Form {} +impl core::fmt::Display for Form { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.id.id.fmt(f) + } +} + impl Form { pub const fn new(id: u32) -> Self { Self { diff --git a/slhx-core/tests/effect_batch.rs b/slhx-core/tests/effect_batch.rs index 0aa95c8..865347f 100644 --- a/slhx-core/tests/effect_batch.rs +++ b/slhx-core/tests/effect_batch.rs @@ -102,9 +102,13 @@ fn component_refs_format_generated_component_names() { } #[test] -fn generated_handles_format_for_hemplate_dynamic_handle_attrs() { +fn generated_resources_format_for_hemplate_dynamic_attrs() { // req: public_api/001 req: public_api/002 + assert_eq!(Slot::::new(10).to_string(), "10"); + assert_eq!(KeyedSlot::::new(11).to_string(), "11"); + assert_eq!(Atom::::new(12).to_string(), "12"); assert_eq!(Handle::<()>::new(42).to_string(), "42"); + assert_eq!(Form::<()>::new(13).to_string(), "13"); } #[test]