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
This commit is contained in:
slhx agent
2026-05-26 00:39:47 +02:00
parent efabd3ff66
commit 062c184e42
6 changed files with 50 additions and 19 deletions
+1 -1
View File
@@ -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());
}
}
+4 -1
View File
@@ -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()
+15 -15
View File
@@ -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}"]"#)
}
+1 -1
View File
@@ -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();
+24
View File
@@ -730,6 +730,12 @@ impl<T> Clone for Slot<T> {
impl<T> Copy for Slot<T> {}
impl<T> core::fmt::Display for Slot<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.id.id.fmt(f)
}
}
impl<T> Slot<T> {
pub const fn new(id: u32) -> Self {
Self {
@@ -778,6 +784,12 @@ impl<K, T> Clone for KeyedSlot<K, T> {
impl<K, T> Copy for KeyedSlot<K, T> {}
impl<K, T> core::fmt::Display for KeyedSlot<K, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.id.id.fmt(f)
}
}
impl<K, T> KeyedSlot<K, T>
where
K: ToString,
@@ -878,6 +890,12 @@ impl<T> Clone for Atom<T> {
impl<T> Copy for Atom<T> {}
impl<T> core::fmt::Display for Atom<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.id.id.fmt(f)
}
}
impl<T> Atom<T> {
pub const fn new(id: u32) -> Self {
Self {
@@ -951,6 +969,12 @@ impl<T> Clone for Form<T> {
impl<T> Copy for Form<T> {}
impl<T> core::fmt::Display for Form<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.id.id.fmt(f)
}
}
impl<T> Form<T> {
pub const fn new(id: u32) -> Self {
Self {
+5 -1
View File
@@ -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::<String>::new(10).to_string(), "10");
assert_eq!(KeyedSlot::<u64, String>::new(11).to_string(), "11");
assert_eq!(Atom::<String>::new(12).to_string(), "12");
assert_eq!(Handle::<()>::new(42).to_string(), "42");
assert_eq!(Form::<()>::new(13).to_string(), "13");
}
#[test]