feat(test): diagnose failed effect assertions
This commit is contained in:
@@ -52,7 +52,7 @@ Keep it stable. Prefer pointers to canonical sources over copied structure, file
|
||||
- Prefer links or pointers to canonical sources over copied lists.
|
||||
- Avoid project trees, architecture maps, generated inventories, current file sizes, issue lists, TODO inventories, and other snapshots that will rot.
|
||||
- Stable commands: `cargo run -p hemx-xtask -- test`, `cargo run -p hemx-xtask -- html-examples-smoke`, `cargo check --workspace`, `redgate health --strict`. Use the xtask runner for full verification so jobs are capped from local CPU and memory; use the html_examples smoke for focused repo-owned browser verification of the HTML pattern gallery, no-reload dynamic interactions, and no `/tmp` scripts. Keep fast crate tests, focused browser smoke, and full xtask authority distinct; the full path should stay within a documented 10 minute local timeout or be split into deterministic shards under the same wrapper. req: test/004 req: test/006 req: test/012 req: test/013 req: test/014 req: test/015 req: test/016
|
||||
- Example behavior tests should prefer `hemx_test` generated-resource assertions over raw slot constants or raw effect/payload matching; rendered target/handle assertions should name the generated resource in failure messages; keep browser selector helpers as test adapters only, not authoring APIs. req: test/008 req: test/009 req: test/010 req: test/017
|
||||
- Example behavior tests should prefer `hemx_test` generated-resource assertion methods over raw slot constants, raw effect/payload matching, or boolean predicates wrapped in opaque `assert!`; failures should include the expectation and actual effects, while rendered target/handle assertions should name the generated resource. Keep browser selector helpers as test adapters only, not authoring APIs. req: test/008 req: test/009 req: test/010 req: test/017 req: test/018
|
||||
- Run the workout product exemplar with `cargo run -p hemx-xtask -- workout dev` and open `http://127.0.0.1:3028`; set `HEMX_WORKOUT_ADDR=127.0.0.1:3030` if the default port is busy. Its durable visual direction and recovery expectations live in `examples/workout/DESIGN.md`. req: examples/008
|
||||
- Use the same Workout command surface for tests, production build, and mobile release: `cargo run -p hemx-xtask -- workout test`, `cargo run -p hemx-xtask -- workout build`, `HEMX_WORKOUT_ORIGIN=https://workout.example.com cargo run -p hemx-xtask -- workout mobile-release`, and `HEMX_WORKOUT_ORIGIN=https://workout.example.com cargo run -p hemx-xtask -- workout mobile-verify`; Android/iOS SDKs, store submission targets, and signing remain external blockers, not repo-owned secrets, and do not imply a broad `hemx-mobile` framework. req: examples/006 req: examples/011 req: examples/013
|
||||
- hemx core stays small: effects, typed ids, registries, and wire schema only; keep features in core only when they fit typed resources plus the closed EffectBatch op set, and treat DOM details as runtime lowering. Workspace crates stay separated, stable-Rust-compatible, and free of kitchen-sink boundaries; new primitives must delete special cases. Public identifiers should flow through typed wrappers over internal `ResourceId`/`ResourceRef`, not special-case opcodes. Wire output lowers symbolic authoring names to compact metadata and postcard/form-encoded envelopes, not JSON. ABI/schema versions and build fingerprints must guard runtime/server compatibility. v0 scope is the checked hypermedia core plus page/runtime/wire/diagnostic/test/axum proof, not optional sync/wasm/query/auth/router breadth. req: v0_scope/001 req: v0_scope/002 req: v0_scope/005 req: laws/001 req: invariant/001 req: invariant/005 req: typed_id/001 req: typed_id/003 req: effect_algebra/001 req: effect_algebra/006 req: wire/001 req: wire/002 req: wire/003 req: wire/004 req: wire/005 req: wire/006 req: abi/001 req: abi/002 req: abi/003 req: abi/004 req: abi/005 req: misc/001 req: misc/002 req: misc/003 req: misc/004 req: misc/005 req: misc/006 req: misc/007 req: misc/008 req: misc/009 req: misc/010
|
||||
|
||||
@@ -880,6 +880,9 @@ what a valid business email is. [north_star]
|
||||
### req: test/017
|
||||
0 017 Test helpers for rendered runtime ids report the generated handle or slot name, not only raw `data-hid` or `data-sid` selectors. [north_star]
|
||||
|
||||
### req: test/018
|
||||
0 018 `EffectInspector` assertion methods fail at the caller with the expected generated target and payload condition plus the actual effect operations, avoiding opaque boolean assertion failures. [north_star]
|
||||
|
||||
---
|
||||
|
||||
## check
|
||||
|
||||
@@ -856,10 +856,10 @@ mod tests {
|
||||
);
|
||||
// Component-level rendering now lowers contact_card-local handles, so the
|
||||
// effect contains the lowered handle id, not the source name.
|
||||
assert!(edit.updates_html_containing(
|
||||
edit.assert_updates_html_containing(
|
||||
gallery::contact_card,
|
||||
&format!("data-hid=\"{}\"", contact_card::save_contact),
|
||||
));
|
||||
);
|
||||
|
||||
let save = inspect_batch(
|
||||
InteractionRequest::from(form(
|
||||
@@ -871,7 +871,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.batch,
|
||||
);
|
||||
assert!(save.updates_html_containing(gallery::contact_card, "ada@hemx.test"));
|
||||
save.assert_updates_html_containing(gallery::contact_card, "ada@hemx.test");
|
||||
|
||||
let lazy = inspect_batch(
|
||||
InteractionRequest::from(form(gallery::lazy_load, &[("request", "lazy")]))
|
||||
|
||||
+19
-1
@@ -492,10 +492,28 @@ impl EffectInspector {
|
||||
})
|
||||
}
|
||||
|
||||
/// Assert that a generated target receives an HTML update containing text.
|
||||
/// Check that a generated target receives an HTML update containing text.
|
||||
/// req: test/001 req: dx/006
|
||||
pub fn updates_html_containing(&self, target: impl GeneratedTarget, needle: &str) -> bool {
|
||||
self.has_html_update_containing(target.__hemx_resource_id(), needle)
|
||||
}
|
||||
|
||||
/// Assert that a generated target receives an HTML update containing text.
|
||||
///
|
||||
/// Unlike wrapping [`Self::updates_html_containing`] in `assert!`, failures include the
|
||||
/// expected resource and payload fragment together with every actual effect operation.
|
||||
/// req: test/001 req: test/018 req: dx/006
|
||||
#[track_caller]
|
||||
pub fn assert_updates_html_containing(&self, target: impl GeneratedTarget, needle: &str) {
|
||||
let resource = target.__hemx_resource_id();
|
||||
assert!(
|
||||
self.has_html_update_containing(resource, needle),
|
||||
"expected an HTML update for {resource:?} containing {needle:?}; actual effects: {:#?}",
|
||||
self.batch.ops
|
||||
);
|
||||
}
|
||||
|
||||
fn has_html_update_containing(&self, resource: ResourceId, needle: &str) -> bool {
|
||||
self.batch.ops.iter().any(|op| {
|
||||
matches!(
|
||||
op,
|
||||
|
||||
@@ -17,6 +17,30 @@ fn inspects_tuple_effects() {
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn html_update_assertion_reports_expectation_and_actual_effects() {
|
||||
// req: test/018
|
||||
let target = TestTarget(ResourceKind::Slot, 42);
|
||||
let inspected = hemx_test::inspect(Effect::Put {
|
||||
target: ResourceRef::unscoped(ResourceId::new(ResourceKind::Slot, 7)),
|
||||
payload: Payload::Html(String::from("<p>actual</p>")),
|
||||
});
|
||||
|
||||
let panic = std::panic::catch_unwind(|| {
|
||||
inspected.assert_updates_html_containing(target, "expected fragment");
|
||||
})
|
||||
.expect_err("a mismatched target and payload must fail");
|
||||
let message = panic
|
||||
.downcast_ref::<String>()
|
||||
.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("ResourceId"), "{message}");
|
||||
assert!(message.contains("actual"), "{message}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_keyed_slot_targets() {
|
||||
let rows = KeyedSlot::<u32, String>::new(9);
|
||||
|
||||
Reference in New Issue
Block a user