feat(api): streamline generated app authoring
Move the canonical examples toward generated component-root helpers, typed form decoding, async/state handler registration, and derive-driven app/component registry wiring. Tighten requirements and diagnostics for the server-first, selectorless authoring path. Verified with cargo run -p slhx-xtask -- test, cargo check --workspace, redgate list, redgate refs, redgate health --strict, and git diff --check. req: canonical/001 req: canonical/003 req: canonical/004 req: dx/002 req: derive_app/001 req: component/003 req: form/004 req: axum_integration/003
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
use slhx_core::{Atom, Effect, KeyedSlot, Payload, ResourceRef, Slot};
|
||||
use slhx_core::{
|
||||
Atom, Effect, GeneratedTarget, KeyedSlot, Payload, ResourceId, ResourceKind, ResourceRef, Slot,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn inspects_tuple_effects() {
|
||||
let count = Slot::<u32>::new(1);
|
||||
let user = Atom::<String>::new(2);
|
||||
|
||||
let inspected = slhx_test::run(
|
||||
|value| (count.text(value), user.set("alice")),
|
||||
42,
|
||||
);
|
||||
let inspected = slhx_test::run(|value| (count.text(value), user.set("alice")), 42);
|
||||
|
||||
assert!(inspected.has_slot(count));
|
||||
assert!(inspected.has_atom(user));
|
||||
@@ -26,3 +25,116 @@ fn finds_keyed_slot_targets() {
|
||||
assert!(inspected.has_keyed_slot(rows));
|
||||
assert_eq!(inspected.ops().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_generated_handle_form_bodies() {
|
||||
let handle = slhx_core::Handle::<()>::new(7);
|
||||
|
||||
let body = slhx_test::handle_form_body(handle, &[("title", "hello world"), ("tag", "a&b")]);
|
||||
|
||||
assert_eq!(body, "__h=7&title=hello+world&tag=a%26b");
|
||||
assert_eq!(slhx_test::unknown_handle_form_body(99), "__h=99");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_authoring_boundary_selectors() {
|
||||
assert_eq!(
|
||||
slhx_test::root_selector("techdemo"),
|
||||
r#"[data-slhx-root="techdemo"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::island_selector("orbit"),
|
||||
r#"[data-slhx-island="orbit"]"#
|
||||
);
|
||||
assert_eq!(slhx_test::island_attribute_name(), "data-slhx-island");
|
||||
assert_eq!(slhx_test::island_event_name("orbit"), "slhx:island-orbit");
|
||||
assert_eq!(
|
||||
slhx_test::sse_endpoint_marker("/events"),
|
||||
r#"data-slhx-sse="/events""#
|
||||
);
|
||||
assert_eq!(slhx_test::any_root_selector(), "[data-slhx-root]");
|
||||
assert_eq!(
|
||||
slhx_test::root_element_selector("main", "docs"),
|
||||
r#"main[data-slhx-root="docs"]"#
|
||||
);
|
||||
assert_eq!(slhx_test::document_body_selector(), "body");
|
||||
assert_eq!(slhx_test::document_title_selector(), "title");
|
||||
assert_eq!(
|
||||
slhx_test::runtime_script_selector(),
|
||||
r#"script[src="/slhx.js"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::target_selector(TestTarget(ResourceKind::Slot, 42)),
|
||||
r#"[data-sid="42"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::handle_button_selector(slhx_core::Handle::<()>::new(7)),
|
||||
r#"button[data-hid="7"]"#
|
||||
);
|
||||
assert_eq!(slhx_test::article_selector(), "article");
|
||||
assert_eq!(slhx_test::strong_text_selector(), "strong");
|
||||
assert_eq!(slhx_test::small_text_selector(), "small");
|
||||
assert_eq!(slhx_test::escaped_markup_selector("b"), "b");
|
||||
assert_eq!(slhx_test::heading_selector("article", 1), "article h1");
|
||||
assert_eq!(slhx_test::list_item_selector("ul"), "ul li");
|
||||
assert_eq!(slhx_test::prose_selector("article"), "article p");
|
||||
assert_eq!(slhx_test::form_selector("header"), "header form");
|
||||
assert_eq!(
|
||||
slhx_test::select_options_selector("column"),
|
||||
r#"select[name="column"] > option"#
|
||||
);
|
||||
assert_eq!(slhx_test::class_selector("lane"), ".lane");
|
||||
assert_eq!(
|
||||
slhx_test::element_class_selector("span", "presence"),
|
||||
"span.presence"
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::class_child_selector("columns", "section", "column"),
|
||||
".columns > section.column"
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::class_descendant_selector("impact", "i"),
|
||||
".impact i"
|
||||
);
|
||||
assert_eq!(slhx_test::disabled_button_selector(), "button[disabled]");
|
||||
assert_eq!(
|
||||
slhx_test::nav_link_selector("/architecture"),
|
||||
r#"a[href="/architecture"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::page_nav_link_selector("/docs"),
|
||||
r#"a[href="/docs"][data-slhx-nav]:not([data-slhx-handle])"#
|
||||
);
|
||||
assert_eq!(slhx_test::island_snapshot_marker(), "data-island-snapshot=");
|
||||
assert_eq!(
|
||||
slhx_test::island_readout_selector(),
|
||||
"[data-island-readout]"
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::scoped_island_readout_selector("#probe-island"),
|
||||
"#probe-island [data-island-readout]"
|
||||
);
|
||||
assert_eq!(
|
||||
slhx_test::keyed_selector(".work-card", 4),
|
||||
r#".work-card[data-key="4"]"#
|
||||
);
|
||||
assert_eq!(slhx_test::keyed_items_selector("li"), "li[data-key]");
|
||||
|
||||
let probe = slhx_test::island_probe_script(
|
||||
"probe-island",
|
||||
"orbit",
|
||||
"1|1|1|probe waiting",
|
||||
"7|2|8|probe live",
|
||||
);
|
||||
assert!(probe.contains("probe-island"));
|
||||
assert!(probe.contains("probe live"));
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct TestTarget(ResourceKind, u32);
|
||||
|
||||
impl GeneratedTarget for TestTarget {
|
||||
fn __slhx_resource_id(self) -> ResourceId {
|
||||
ResourceId::new(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user