Testing
Testing
Section titled “Testing”The repo has focused Rust tests under crates/lurq/tests. They are the best reference for expected behavior when changing runtime internals.
Run Tests
Section titled “Run Tests”cargo test -p lurq --features resourcesWith feature combinations:
cargo test -p lurq --features "image svg resources devtools"cargo test -p lurq --features "winit wgpu image svg resources devtools clipboard"The combinations above do not enable every subsystem. Check feature-specific behavior explicitly:
cargo test -p lurq --features query,tokio --test query_testscargo test -p lurq --features form,router,persistent_storage,i18n --lib --testscargo test -p lurq --all-features --doccargo test -p lurq --features canvas --test canvas_testscargo check --workspace --all-features --all-targets --lockedThe publish workflow in .github/workflows/publish-crates.yml defines release validation. Native GPU and window checks need the matching platform and a usable graphics environment; see Canvas 2D and Window lifecycle. Ignored hardware tests are not run by an ordinary cargo test.
On Windows, the render readback tests draw into a hidden window of their own and compare both native backends with CSS blending and with the box-shadow formula; the box-shadow capture check runs a themed scene through layout and both backends and requires identical captures:
cargo test -p lurq --features wgpu,dx12,raster,screenshot --lib readback -- --ignored --test-threads=1cargo run -p lurq --example box_shadow_capture_check --features screenshot,wgpu,dx12Run one area:
cargo test -p lurq layout::paddingcargo test -p lurq reactivity::signalcargo test -p lurq dnd::target_trackingLayout Tests
Section titled “Layout Tests”Layout tests usually create a Tree, install a static root, and run a layout pass with explicit constraints.
use lurq::{ app::Tree, layout::{Constraints, Size},};
let mut tree = Tree::new();tree.set_root(lurq::components::Spacer::new().size(100.0, 50.0));
let result = tree .pass_layout(Constraints::loose(Size::new(400.0, 400.0))) .unwrap();
assert_eq!(result.size.width, 100.0);assert_eq!(result.size.height, 50.0);This snippet runs inside the layout test modules, where use super::PassLayoutExt; imports the helper from tests/layout/mod.rs. pass_layout is not a public Tree method. Production app code normally lets Tree::pass drive layout.
Render Snapshot Tests
Section titled “Render Snapshot Tests”tests/support.rs defines a capturing render engine that records the generated render list.
Use this style when testing visual output without opening a real GPU window:
let mut tree = Tree::new();tree.set_root(lurq::components::Rect::new(100.0, 50.0).background("#22c55e"));
let snapshot = support::render_pass(&mut tree);assert_eq!(snapshot.rects.len(), 1);assert_eq!(snapshot.rects[0].width, 100.0);This is useful for border, radius, opacity, image order, SVG order, and render-list regressions. snapshot.layers lists the frame’s opacity layers (see Opacity); rects and glyphs record their order, so a test can tell which layer holds them. The pixels of layers are checked through both render engines by the opacity_layer_readback tests:
cargo test -p lurq --features wgpu,dx12,screenshot --lib opacity_layer_readback -- --ignored --test-threads=1Reactivity Tests
Section titled “Reactivity Tests”Reactivity tests validate state containers independently and through component dirty tracking.
let signal = lurq::core::Signal::new(0);signal.update(|value| *value += 1);assert_eq!(signal.get(), 1);Dirty tracking tests verify that:
- child signal updates do not rerender clean parents,
- parent signal updates do not rerender clean children unnecessarily,
- passed signals mark children dirty when the child reads them,
- prop changes rerender the affected child.
Input And DnD Tests
Section titled “Input And DnD Tests”Input tests drive the tree directly:
tree.mouse_move(20.0, 20.0);tree.mouse_down(20.0, 20.0, MouseButton::Left);tree.mouse_up(20.0, 20.0, MouseButton::Left);tree.scroll(20.0, 20.0, 0.0, -120.0, ScrollPhase::Scroll);tree.key_down("a".into(), "KeyA".into(), false, false, false);Click handlers run from a matching pointer down/up pair; tests should not inject a separate click event.
Use direct tree input for deterministic hover, active, focus, scroll, text input, selectable text, slider, checkbox, drag, and drop behavior.
Text input tests cover caret placement, Unicode-safe deletion, keyboard selection, multiline movement, undo/redo, and double/triple-click selection. Selectable text tests cover drag ranges, word and line selection, and transformed visual-coordinate hit testing.
Headless Passes And Focus
Section titled “Headless Passes And Focus”tree.pass_headless(&mut app) lays out the tree, overlays and modals included, without a window handle or render engine, so input, hit testing, focus, and Tab work in tests without the unsafe test surface. tree.focused_element() returns the focused control:
tree.set_root(Column::new().child(Button::new("Save").id("save").tab_index(0)));tree.pass_headless(&mut App::new());tree.key_down("Tab".into(), "Tab".into(), false, false, false);assert_eq!(tree.focused_element().and_then(|element| element.id()), Some("save"));Use the render snapshot helpers when a test asserts on drawn output. See Testing Focus.
Painted Output Without A Window
Section titled “Painted Output Without A Window”Some of what an element paints is not in the element tree: a control’s own fill, border or shadow, a text input’s caret and selection. After any pass, tree.painted_quads() returns what the last layout paints, in paint order, as lurq::layout::quad::Quads at absolute logical positions, without a window or render engine, so an application’s own tests can check it after pass_headless:
use lurq::{ app::{App, Tree, theme::PaletteColor}, components::Select, core::{ElementRef, Signal}, layout::quad::QuadContent, node::color::Color,};
let surface = Color::from_hex("#1e293b");let select = ElementRef::new();let mut app = App::new();app.theme().set_palette_color(PaletteColor::SurfaceInput, surface);let mut tree = Tree::new();tree.set_root(Select::new(Signal::new(1)).options([(1, "One")]).ref_element(select.clone()));tree.pass_headless(&mut app);
let bounds = select.bounds();let trigger_fill = tree.painted_quads().into_iter().find_map(|quad| match quad.content { QuadContent::Rect { color, .. } if (quad.x, quad.y, quad.width) == (bounds.x, bounds.y, bounds.width) => Some(color), _ => None,});assert_eq!(trigger_fill, Some(surface));Quads are the draw list before text shaping and batching; the render snapshot helpers above show the batched rects and glyphs a render engine receives.
Input Methods
Section titled “Input Methods”Drive input method composition through Tree::ime, as the winit shell does with the platform’s events, and check the result through the focused input:
use lurq::app::events::ImeEvent;
tree.get_element_by_id_mut("draft").unwrap().focus();tree.ime(ImeEvent::Preedit { text: "にほん".into(), cursor: Some((9, 9)) });assert!(tree.is_composing());let draft = tree.get_element_by_id_mut("draft").and_then(|element| element.as_text_input()).unwrap();assert_eq!(draft.composition().as_deref(), Some("にほん"));
// Windows reports the Enter that confirms as a "Process" press; it reaches no handler.tree.key_down("Process".into(), "Enter".into(), false, false, false);tree.ime(ImeEvent::Preedit { text: String::new(), cursor: None });tree.ime(ImeEvent::Commit("日本".into()));tests/input/text_input/ime.rs covers the preedit display, commits, cancellation, the Process keys withheld and the keys passed on during a composition on both platforms’ event orders, stale compositions, masked inputs and the candidate window area. What an input method does with the keys is up to the platform and is not exercised headlessly.
Element Lookup And Typed Interaction
Section titled “Element Lookup And Typed Interaction”Tag nodes with .id("...") in the tree under test, then address them directly instead of writing predicates:
tree.set_root( Column::new() .child(TextInput::new(value.clone()).id("email")) .child(Button::new("Save").id("save").on_click(on_save)),);run_pass(&mut tree);
// Signal-backed value write; does NOT fire on_input (DOM `el.value = x`).tree.get_element_by_id_mut("email").unwrap() .as_text_input().unwrap() .set_value("ada@example.com");
// DOM el.click(): fires the node's own on_click without hit-testing.tree.get_element_by_id_mut("save").unwrap().click();click() works even when the node is occluded. For pointer-fidelity coverage (hover, capture, hit testing) keep driving tree.mouse_down / tree.mouse_up, composing coordinates from the handle’s bounds().center().
DevTools Tests
Section titled “DevTools Tests”DevTools tests construct snapshots from the tree and assert collected metadata:
- tag names for built-ins and user components,
- recursive props from
DevtoolsInspectable, - signal/memo value history,
- effect metadata,
- context metadata,
- overlay selection behavior,
- pick mode and scroll-into behavior.
Run them with:
cargo test -p lurq --features devtoolsIf a test only fails with devtools, check trait bounds first. Props, signal values, stores, and memo outputs may need DevtoolsInspectable.
Benchmarks
Section titled “Benchmarks”Benchmarks live in crates/lurq/benches:
cargo bench -p lurqCurrent benches cover layout, tree build, render-list generation, and the Markdown-backed text pipeline. The text benchmark requires its feature explicitly:
cargo bench -p lurq --bench text_pipeline --features markdownIts fixtures include the root README.md, so documentation changes can alter workload size. Compare the same fixture and source/toolchain metadata before interpreting historical numbers. Use benchmarks when changing layout caching, smart relayout, retained-node reconciliation, render command generation, or text rasterization.
Text pipeline optimization notes and benchmark history live in Text Pipeline Optimization.