From 6b4bb97aaa3e4810759bad63883a49214ff7795e Mon Sep 17 00:00:00 2001 From: slhx agent Date: Mon, 13 Jul 2026 10:07:40 +0200 Subject: [PATCH] feat(test): bind text assertions to targets --- examples/html_examples/src/main.rs | 6 ++++-- hemx-test/src/lib.rs | 30 +++++++++++++++++++++++--- hemx-test/tests/inspector.rs | 34 ++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/examples/html_examples/src/main.rs b/examples/html_examples/src/main.rs index e6f2b78..50e49dc 100644 --- a/examples/html_examples/src/main.rs +++ b/examples/html_examples/src/main.rs @@ -880,8 +880,10 @@ mod tests { .unwrap() .batch, ); - assert!(lazy.updates_text(gallery::lazy_panel)); - assert!(lazy.payload_contains("Lazy content loaded by server update #1")); + lazy.assert_updates_text_containing( + gallery::lazy_panel, + "Lazy content loaded by server update #1", + ); let more = inspect_batch( InteractionRequest::from(form(gallery::load_more, &[("request", "more")])) diff --git a/hemx-test/src/lib.rs b/hemx-test/src/lib.rs index a2481fb..14d9fc6 100644 --- a/hemx-test/src/lib.rs +++ b/hemx-test/src/lib.rs @@ -462,17 +462,41 @@ impl EffectInspector { self.has_resource(target.__hemx_resource_id()) } - /// Assert that a generated target receives a text update, without matching raw effects. + /// Check that a generated target receives a text update, without matching raw effects. /// req: test/001 req: dx/006 pub fn updates_text(&self, target: impl GeneratedTarget) -> bool { + self.has_text_update_containing(target.__hemx_resource_id(), "") + } + + /// Check that a generated target receives a text update containing a fragment. + /// + /// This associates the payload condition with the intended target, unlike a separate global + /// [`Self::payload_contains`] check that can accidentally match another operation. + /// req: test/001 req: test/018 req: dx/006 + pub fn updates_text_containing(&self, target: impl GeneratedTarget, needle: &str) -> bool { + self.has_text_update_containing(target.__hemx_resource_id(), needle) + } + + /// Assert that a generated target receives a text update containing a fragment. + /// req: test/001 req: test/018 req: dx/006 + #[track_caller] + pub fn assert_updates_text_containing(&self, target: impl GeneratedTarget, needle: &str) { let resource = target.__hemx_resource_id(); + assert!( + self.has_text_update_containing(resource, needle), + "expected a text update for {resource:?} containing {needle:?}; actual effects: {:#?}", + self.batch.ops + ); + } + + fn has_text_update_containing(&self, resource: ResourceId, needle: &str) -> bool { self.batch.ops.iter().any(|op| { matches!( op, Effect::Put { target, - payload: Payload::Text(_), - } if target.resource == resource + payload: Payload::Text(text), + } if target.resource == resource && text.contains(needle) ) }) } diff --git a/hemx-test/tests/inspector.rs b/hemx-test/tests/inspector.rs index 4e8edc0..877211f 100644 --- a/hemx-test/tests/inspector.rs +++ b/hemx-test/tests/inspector.rs @@ -17,6 +17,40 @@ fn inspects_tuple_effects() { })); } +#[test] +fn text_update_condition_is_bound_to_the_expected_target() { + // req: test/018 + let expected_target = TestTarget(ResourceKind::Slot, 42); + let inspected = hemx_test::inspect(vec![ + Effect::Put { + target: ResourceRef::unscoped(ResourceId::new(ResourceKind::Slot, 42)), + payload: Payload::Text(String::from("wrong payload")), + }, + Effect::Put { + target: ResourceRef::unscoped(ResourceId::new(ResourceKind::Slot, 7)), + payload: Payload::Text(String::from("expected fragment")), + }, + ]); + + assert!(inspected.updates_text(expected_target)); + assert!(inspected.payload_contains("expected fragment")); + assert!(!inspected.updates_text_containing(expected_target, "expected fragment")); + + let panic = std::panic::catch_unwind(|| { + inspected.assert_updates_text_containing(expected_target, "expected fragment"); + }) + .expect_err("a payload on another target must not satisfy the assertion"); + let message = panic + .downcast_ref::() + .map(String::as_str) + .or_else(|| panic.downcast_ref::<&str>().copied()) + .expect("assertion panic must carry a message"); + + assert!(message.contains("expected fragment"), "{message}"); + assert!(message.contains("wrong payload"), "{message}"); + assert!(message.contains("ResourceId"), "{message}"); +} + #[test] fn html_update_assertion_reports_expectation_and_actual_effects() { // req: test/018