Expand description
§Testing API reference
Headless test driver - drag-and-drop in CI, no browser: DragSimProbe
captures a provider’s drag world and DragSim drives whole pointer
interactions through the production delivery path inside a VirtualDom.
Concept guide: docs/concepts/testing.md.
The drag state machine is plain Rust over signals, so a whole pointer
interaction can run headless: pick up, hover, drop, assert. The one thing a
headless run lacks is layout, so you place the zone rects - which makes
tests deterministic instead of flaky. Mount a DragSimProbe inside the
provider under test, grab the DragSim it captured, and drive:
use dioxus_dnd::test::{drag_sim, rerender, DragSimProbe};
fn test_app() -> Element {
rsx! {
DndProvider::<Card> {
DragSimProbe::<Card> {}
ShelfApp {} // the component you're testing
}
}
}
let mut dom = VirtualDom::new(test_app);
dom.rebuild_in_place();
let mut sim = drag_sim::<Card>();
sim.place(&dom, SHELF, Rect::new(0.0, 100.0, 200.0, 80.0));
sim.pick_up(&dom, card.clone());
sim.move_to(&dom, Point::new(100.0, 140.0));
assert_eq!(sim.over(&dom), Some(SHELF));
rerender(&mut dom);
assert!(dioxus_ssr::render(&dom).contains("data-over"));
assert_eq!(sim.release(&dom), Some(SHELF)); // your on_drop just ranOr as one line for the common arc: simulate_drag.
§DragSimProbe
Captures a DragSim<T> for the enclosing provider. Mount one inside the
DndProvider<T> of your test app; it renders nothing. Retrieve the
handle with drag_sim after rebuild_in_place.
| Prop | Type | Default | What it does |
|---|---|---|---|
phantom | PhantomData<T> | default | Internal type marker; never set this. |
Captured handles live in a thread-local slot keyed by payload type: one
slot per type per thread, and the most recently mounted probe wins. That is
exactly right for one VirtualDom per test; with two providers of the same
T (multi-window tests), mount the probe in the dom you intend to drive,
or mount it last.
§drag_sim
pub fn drag_sim<T: Clone + PartialEq + 'static>() -> DragSim<T>Returns the handle the most recent DragSimProbe<T> captured. Panics when
no probe for T has mounted - add DragSimProbe::<T> {} inside the
provider and rebuild_in_place first.
§DragSim
Headless driver for one provider’s drag world. Copy and Clone; the
gesture methods take &mut self, so bind it let mut sim. Every method
takes the VirtualDom so the underlying signal operations run inside its
runtime; call rerender between actions and markup assertions.
Placing geometry:
| Method | Returns | What it does |
|---|---|---|
place(&dom, zone, rect) | () | Gives a zone its client Rect - the headless stand-in for layout. Panics when no zone with this id is registered. |
place_in(&dom, window, zone, rect) | () | place for a zone living in another joined window’s registry; rect is in that window’s client px. Panics when this sim’s provider joined no world, the window is unknown, or the zone is not registered there. |
Driving the gesture:
| Method | Returns | What it does |
|---|---|---|
pick_up(&dom, payload) | () | Begins a pointer drag carrying payload, from no particular zone. |
pick_up_from(&dom, payload, from) | () | Begins a pointer drag reporting from: Option<ZoneId> as the source zone; it arrives in DropOutcome::from. |
move_to(&dom, point) | () | Moves the pointer: updates the tracked position and enters or leaves zones by hit-testing the placed rects, the same logic the pointer gesture runs per pointermove. |
release(&dom) | Option<ZoneId> | Releases at the current pointer position with the Move effect. Returns the zone that received the drop, or None when the drag cancelled. |
release_as(&dom, effect) | Option<ZoneId> | release with an explicit DropEffect - simulate the Ctrl-held copy drop with DropEffect::Copy. |
cancel(&dom) | () | Aborts the drag, as Escape or a pointer cancel would. |
A sim drag starts at the coordinate origin with a zero grab offset and a
Move base effect; move_to sets every position after that.
Observing state:
| Method | Returns | What it does |
|---|---|---|
over(&dom) | Option<ZoneId> | The zone currently hovered. |
dragging(&dom) | bool | Is a drag in flight? |
payload(&dom) | Option<T> | The in-flight payload, if any. |
announcement(&dom) | String | The latest screen-reader announcement. |
completions(&dom) | Vec<bool> | Exactly-once source completion results observed by the simulated source: true per delivered drop, false per cancel. What a Draggable’s on_drag_end would see. |
window_key() | Option<WindowKey> | The key this sim’s provider joined its world under, when it did. |
§Release semantics
Drops go through the production delivery path - acceptance filters,
DropOutcome construction, closest-edge enrichment, settle routing -
shared with Draggable itself, not a reimplementation. Releases mirror the
pointer gesture: the last acceptable exact hit in registry order wins and
rejecting overlaps are skipped; otherwise the drop snaps to the closest
acceptable zone whose edge is within 48px (the touch forgiveness), else the
drag cancels and release returns None.
Not simulated: pointer capture, auto-scroll, and the re-measure that precedes the real snap (headless rects are wherever you placed them). The crate covers those browser behaviors with its Playwright suite; see the concept guide.
§Cross-window simulation
When the sim’s provider joined a DndWorld (see
docs/api/multi-window.md), the sim is world-aware and
moves and releases resolve across windows, like the gesture:
pick_upanchors the world drag to this sim’s window.move_toresolves through the world first: a zone hit in any joined window is authoritative, a point inside a foreign window but outside its zones clears the hover, and an unresolved point falls back to the local registry.- A
releasethe world resolves into a foreign window delivers there, with the 48px snap running in the target window’s own CSS px.
Build one VirtualDom per simulated window, share the world via
with_root_context, feed each window’s geometry by hand, and place
foreign zones with place_in. tests/multiwindow.rs shows the full
pattern, including a rerender per dom before markup assertions.
§rerender
pub fn rerender(dom: &mut VirtualDom)Flushes pending reactivity so the tree reflects the simulated state - call
between driver actions and markup assertions (dioxus_ssr::render).
§simulate_drag
pub fn simulate_drag<T: Clone + PartialEq + 'static>(
dom: &mut VirtualDom,
payload: T,
from: Option<ZoneId>,
path: &[Point],
) -> Option<ZoneId>One whole pointer drag: picks payload up (from from), glides through
path, releases at its last point, re-rendering between steps so zone
reactions run just as they would live. Returns the receiving zone, or
None when the drag cancelled. Needs a mounted DragSimProbe<T>; an
empty path releases at the pickup point.
let landed = simulate_drag(&mut dom, card, Some(READING), &[Point::new(100.0, 140.0)]);
assert_eq!(landed, Some(FINISHED));§Where the rest lives
Point, Rect, ZoneId, WindowKey and the registry:
docs/api/core.md. DropEffect and the modifier convention:
docs/api/drop-effects.md. DndWorld, window geometry
and joining: docs/api/multi-window.md. The delivered
DropOutcome: docs/api/drag-and-drop.md.
Re-exports§
pub use DragSimProbe_completions::Component::DragSimProbe;
Structs§
- DragSim
- Headless driver for one provider’s drag world. Every method takes the
VirtualDomso the underlying signal operations run inside its runtime; callrerenderbetween actions and markup assertions. - Drag
SimProbe Props - Properties for the
DragSimProbecomponent.
Functions§
- Drag
SimProbe - Captures a
DragSimfor the enclosing provider. Mount one inside theDndProvider<T>of your test app (it renders nothing), then retrieve the handle withdrag_simafterrebuild_in_place. - drag_
sim - The handle the most recent
DragSimProbe<T>captured. - rerender
- Flush pending reactivity so the tree reflects the simulated state -
call between driver actions and markup assertions
(
dioxus_ssr::render). - simulate_
drag - One whole pointer drag: pick
payloadup (fromfrom), glide throughpath, release at its last point, re-rendering between steps so zone reactions run just as they would live. Returns the receiving zone, orNonewhen the drag cancelled. Needs a mountedDragSimProbe<T>; an emptypathreleases at the pickup point.