Skip to content

Testing

The Domain Foundation has 414+ tests across 12 categories, covering model behavior, operation semantics, integration, composition, acceptance, concurrency, property-based, stress, serde round-trip, and App Engine lifecycle.


Test Organization

All Domain Foundation tests live under tests/domain_foundation_tests/ as submodules of a single test binary:

tests/
└── domain_foundation_tests/
    ├── main.rs                            ← entry point, declares all submodules
    ├── canvas_2d_test.rs                 ← model tests
    ├── canvas_2d_operations_test.rs       ← operation tests
    ├── canvas_history_integration_test.rs ← undo/redo, transactions
    ├── canvas_command_handler_test.rs     ← command handler
    ├── relation_definition_test.rs        ← relation builder, criteria
    ├── relation_registry_test.rs         ← registry, lookup, clear
    ├── relation_evaluation_test.rs       ← evaluation, value store
    ├── relation_propagation_test.rs       ← cascading, batch, cycles
    ├── relation_value_source_test.rs      ← PropertyValueSource/Sink
    ├── timeline_test.rs
    ├── timeline_operations_test.rs
    ├── layers_test.rs
    ├── layers_operations_test.rs
    ├── node_graph_test.rs
    ├── node_graph_operations_test.rs
    ├── node_graph_canvas_integration_test.rs
    ├── viewport_3d_test.rs
    ├── viewport_3d_operations_test.rs
    ├── table_test.rs
    ├── table_operations_test.rs
    ├── text_editor_test.rs
    ├── text_editor_operations_test.rs
    ├── filtering_test.rs
    ├── querying_test.rs
    ├── tagging_test.rs
    ├── sorting_test.rs
    ├── grouping_test.rs
    ├── validation_test.rs
    ├── batch_test.rs
    ├── concurrency_test.rs               ← Send + Sync, threads
    ├── proptest_test.rs                  ← property-based
    ├── stress_test.rs                    ← 100K objects
    ├── serde_roundtrip_test.rs           ← serialize → deserialize → assert_eq
    ├── app_engine_lifecycle_test.rs      ← full AppEngine lifecycle
    ├── domain_foundation_test.rs         ← public API smoke
    ├── domain_composition_test.rs        ← boundaries, composition
    └── domain_foundation_acceptance_test.rs ← end-to-end acceptance

Entry point (main.rs)

//! Phase 2–12 integration tests.

mod canvas_2d_test;
mod canvas_2d_operations_test;
mod relation_definition_test;
// ... all modules ...
mod domain_foundation_test;
mod domain_composition_test;
mod domain_foundation_acceptance_test;

#[cfg(feature = "serde")]
mod serde_roundtrip_test;

Run tests with:

# Regular tests
cargo test --test domain_foundation_tests

# With serde (enables round-trip tests)
cargo test --features serde --test domain_foundation_tests

# Specific module
cargo test --test domain_foundation_tests canvas_2d_test

# Stress tests only
cargo test --test domain_foundation_tests stress

# Benchmarks
cargo bench

Test Categories

Model Tests

Verify each environment's types, queries, and constraints:

#[test]
fn create_object_returns_unique_ids() {
    let mut canvas = Canvas::new();
    let a = canvas.create_object(Transform::identity());
    let b = canvas.create_object(Transform::identity());
    assert_ne!(a, b);
}
Test file Tests What it covers
canvas_2d_test.rs 15+ Create, contains, iter, clear, z-order, spatial queries, Vec2 math
timeline_test.rs 18+ Tracks, clips, keyframes, overlap, clips_at, move_track, split
layers_test.rs 18+ Hierarchy, visibility, locked, reparent, move_up/down, layer_path
node_graph_test.rs 15+ Connect, validation, topo sort, cycles, max_inputs, is_connected
viewport_3d_test.rs 17+ Transforms, parent-child, world_transform, spatial queries, Vec3 math
table_test.rs 18+ Cells, merge, swap, ordering, IntoIterator
text_editor_test.rs 30+ Unicode, find/replace, line/col, ranges, Position arithmetic

Operation Tests

Verify each operation variant, error cases, and composition:

#[test]
fn create_operation_adds_an_object() {
    let mut canvas = Canvas::new();
    let outcome = CanvasOperation::Create { transform: Transform::identity() }
        .apply(&mut canvas)
        .unwrap();
    assert!(matches!(outcome, CanvasOutcome::Created { .. }));
    assert_eq!(canvas.len(), 1);
}
Test file Tests
canvas_2d_operations_test.rs 11 operations + z-order undo
timeline_operations_test.rs 11 operations (trim, split, set keyframe)
layers_operations_test.rs 10 operations (duplicate, clear)
node_graph_operations_test.rs 13 operations (rename port, duplicate, clear)
viewport_3d_operations_test.rs 8 operations (set parent, clear)
table_operations_test.rs 13 operations (merge, unmerge, clear)
text_editor_operations_test.rs 7 operations (append, delete line, replace line)

Integration Tests

Verify DomainEngine + App Engine (undo/redo, events, transactions):

#[test]
fn full_undo_redo_cycle() {
    let mut engine = DomainEngine::new();
    // Create + Move + Transform
    engine.execute(Create { ... }).unwrap();
    engine.execute(Move { ... }).unwrap();
    engine.execute(Transform { ... }).unwrap();

    // Undo all 3
    engine.undo().unwrap();
    engine.undo().unwrap();
    engine.undo().unwrap();
    assert!(!engine.canvas().contains(id));

    // Redo all 3
    engine.redo().unwrap();
    engine.redo().unwrap();
    engine.redo().unwrap();
    assert!(engine.canvas().contains(id));
}
Test file Tests
canvas_history_integration_test.rs Undo/redo, transactions, rollback, compound, dirty tracking
domain_operation_integration_test.rs Execute through DomainEngine for each environment
canvas_command_handler_test.rs CanvasCommandHandler definition and execution

Composition Tests

Verify architectural boundaries:

#[test]
fn canvas_2d_works_without_other_environments() {
    let mut canvas = Canvas::new();
    let id = canvas.create_object(Transform::identity());
    assert!(canvas.contains(id));
}

#[test]
fn node_graph_and_canvas_are_composed_not_coupled() {
    let mut graph = NodeGraph::new();
    let n = graph.create_node("N".into(), Transform::identity());
    graph.move_node(n, Vec2::new(50.0, 50.0)).unwrap();
    assert_eq!(graph.node_count(), 1);   // Graph unchanged
    assert_eq!(graph.canvas().len(), 1); // Canvas changed
}
Test file Tests
domain_composition_test.rs Each environment standalone, NodeGraph→Canvas2D, Relations independence, utilities orthogonality, App Engine domain-agnostic

Acceptance Tests

End-to-end tests simulating real Domain Engines:

#[test]
fn vector_editor_composes_canvas_layers_and_relations() {
    // Simulates a vector editor using Canvas2D + Layers + Relations + Filtering
    let mut canvas = Canvas::new();
    let mut layers = Layers::new();
    let mut registry = RelationRegistry::new();
    // ... full setup ...
}
Test file Simulated Domain Engine Environments used
domain_foundation_acceptance_test.rs Vector Editor Canvas2D + Layers + Relations + Filtering
Spreadsheet Table + Tagging + Validation + Relations
Text Editor TextEditor only
Video Editor Timeline + Layers

Concurrency Tests

Verify Send + Sync bounds and thread-safe shared mutation:

#[test]
fn canvas_is_send_sync() {
    fn assert_send_sync<T: Send + Sync>() {}
    assert_send_sync::<Canvas>();
}

#[test]
fn canvas_shared_across_threads() {
    let canvas = Arc::new(Mutex::new(Canvas::new()));
    let handles: Vec<_> = (0..4).map(|i| {
        let canvas = Arc::clone(&canvas);
        thread::spawn(move || {
            let mut c = canvas.lock().unwrap();
            c.create_object(Transform::from_translation(Vec2::new(i as f64, i as f64)));
        })
    }).collect();
    for h in handles { h.join().unwrap(); }
    assert_eq!(canvas.lock().unwrap().len(), 4);
}
Test file Tests
concurrency_test.rs Send + Sync for all environments + RelationRegistry, shared mutation across threads

Property-Based Tests

Use proptest to verify invariants across random inputs:

proptest! {
    #[test]
    fn insert_then_delete_restores_text(
        original in "[a-z]{0,50}",
        pos in 0u32..50,
        inserted in "[a-z]{0,20}"
    ) {
        let mut doc = Document::from_str(&original);
        let len = doc.len();
        if pos <= len {
            doc.insert(Position::new(pos), &inserted).unwrap();
            let inserted_len = inserted.chars().count() as u32;
            doc.delete(Range::new(
                Position::new(pos),
                Position::new(pos + inserted_len),
            )).unwrap();
            prop_assert_eq!(doc.text(), original);
        }
    }

    #[test]
    fn undo_redo_preserves_canvas_state(ops in prop::collection::vec(canvas_op_strategy(), 1..10)) {
        let mut engine = DomainEngine::new();
        // Execute, undo all, redo all, verify same state
    }
}
Test file Properties tested
proptest_test.rs Insert-then-delete restores text, replace preserves length, undo-redo preserves canvas state, canvas count invariant, table set-then-clear returns None

Stress Tests

Large-scale operations to verify performance and correctness at scale:

#[test]
fn canvas_100k_objects() {
    let mut canvas = Canvas::new();
    for i in 0..100_000u64 {
        canvas.create_object(Transform::from_translation(Vec2::new(i as f64, i as f64)));
    }
    assert_eq!(canvas.len(), 100_000);
    assert_eq!(canvas.draw_order().len(), 100_000);
}

#[test]
fn document_100k_chars() {
    let text = "x".repeat(100_000);
    let doc = Document::from_str(&text);
    assert_eq!(doc.len(), 100_000);  // O(1) due to caching
    assert_eq!(doc.byte_count(), 100_000);
}
Test Scale
canvas_100k_objects 100,000 objects
table_10k_cells 10,000 cells (100 rows × 100 columns)
document_100k_chars 100,000 characters
timeline_10k_clips 10,000 clips on one track

Serde Round-Trip Tests

Serialize → Deserialize → assert_eq:

#[cfg(feature = "serde")]
#[test]
fn canvas_roundtrip() {
    let mut canvas = Canvas::new();
    canvas.create_object(Transform::from_translation(Vec2::new(1.0, 2.0)));
    let json = serde_json::to_string(&canvas).unwrap();
    let restored: Canvas = serde_json::from_str(&json).unwrap();
    assert_eq!(canvas, restored);
}
Test What survives round-trip
canvas_roundtrip Objects, draw_order, next_id
timeline_roundtrip Tracks, clips, keyframes, interpolation
table_structure_roundtrip Rows, columns (cells don't — JSON limitation)
document_roundtrip Text, char_count

App Engine Lifecycle Test

Full AppEngine lifecycle with DomainEngine:

#[test]
fn app_engine_lifecycle_works() {
    let mut engine = AppEngine::new().unwrap();
    assert_eq!(engine.state(), LifecycleState::Created);

    engine.initialize().unwrap();
    assert_eq!(engine.state(), LifecycleState::Initialized);

    engine.start().unwrap();
    assert_eq!(engine.state(), LifecycleState::Running);

    assert!(engine.history_store().is_empty());

    engine.stop().unwrap();
    assert_eq!(engine.state(), LifecycleState::Stopped);

    engine.dispose().unwrap();
    assert_eq!(engine.state(), LifecycleState::Disposed);
}

Testing Conventions

Struct literal in match

Never write match Enum::Variant { fields }.method() { ... } — the parser can't disambiguate the {. Bind to a variable first:

// WRONG — won't compile
let id = match CanvasOperation::Create { transform }.apply(&mut canvas).unwrap() { ... };

// CORRECT
let outcome = CanvasOperation::Create { transform }.apply(&mut canvas).unwrap();
let id = match outcome {
    CanvasOutcome::Created { id } => id,
    _ => unreachable!(),
};

Import conventions

// Test files import via the crate name
use app_shell::domain_foundation::environments::canvas_2d::{
    Canvas, CanvasError, Object, ObjectId, Transform, Vec2,
};

// For traits that provide methods (like FilterCriteria), import the trait
use app_shell::domain_foundation::utilities::filtering::{criteria, FilterCriteria};

Test file naming

Test files use _test suffix (e.g., canvas_2d_test.rs) matching the _test suffix on the module name in main.rs.


Test Count by Category

Category Test files Tests
Model 7 ~120
Operations 7 ~80
Integration 3 ~25
Composition 1 ~10
Acceptance 1 ~5
API 1 ~15
Concurrency 1 ~10
Proptest 1 ~5
Stress 1 4
Serde 1 ~4
Lifecycle 1 1
Relations 4 ~30
Utilities 7 ~35
Total 35+ 414+

Next Steps