hypen-engine 0.4.92

A Rust implementation of the Hypen engine
Documentation
# Hypen Engine Test Utilities

This directory contains common test utilities for writing tests across the hypen-engine codebase.

## Overview

The `common/` module provides three main categories of utilities:

1. **Fixtures** (`fixtures.rs`) - Pre-built elements, components, modules, and test data
2. **Matchers** (`matchers.rs`) - Assertion helpers for common patterns
3. **Helpers** (`helpers.rs`) - Utility functions for tree traversal, paths, and debugging

## Quick Start

```rust
// In your test file
mod common;
use common::*;

#[test]
fn test_example() {
    // GIVEN: Create test elements using fixtures
    let element = column_with_children(vec![
        text_element("Hello"),
        text_element("World"),
    ]);

    // THEN: Use matchers to assert properties
    assert_element_type(&element, "Column");
    assert_has_children(&element, 2);
}
```

## Fixtures

### Element Fixtures

Create common element types with ease:

```rust
// Basic elements
let text = text_element("Hello");
let img = image_element("https://example.com/image.jpg");
let btn = button_element("Click me");

// Elements with state bindings
let dynamic_text = text_element_with_binding("user.name");
// Renders as: Text { text: @{state.user.name} }

// Containers
let column = column_with_children(vec![
    text_element("First"),
    text_element("Second"),
]);

// Elements with actions
let action_btn = button_with_action("Submit", "submitForm");

// Keyed elements (for reconciliation)
let keyed = keyed_text_element("Item 1", "item-1");
```

### Tree Fixtures

Create complex tree structures for testing:

```rust
// Nested tree: Column { Text("A"), Row { Text("B"), Text("C") }, Text("D") }
let nested = nested_tree();

// Deep tree (for performance testing)
let deep = deep_tree(100); // 100 levels deep

// Wide tree (for stress testing)
let wide = wide_tree(1000); // 1000 children

// Keyed list
let items = keyed_list(&["Item 1", "Item 2", "Item 3"]);
```

### Component Fixtures

```rust
// Simple component that always renders the same content
let comp = simple_component("Button", "Click me");

// Component that uses props
let comp = component_with_prop("Greeting", "name");

// Container component
let comp = container_component("Card");
```

### Module Fixtures

```rust
// Simple module
let module = simple_module("ProfilePage");

// Module with initial state
let module = module_instance_with_state("UserModule", json!({
    "user": {"name": "Alice"}
}));

// Pre-built modules with common state patterns
let user_mod = user_module();
let counter_mod = counter_module();
let list_mod = list_module();
```

### State Fixtures

```rust
// Common state patterns
let state = user_state();  // { user: { id, name, email, profile: {...} } }
let state = cart_state();  // { cart: { items: [...], total: 45.0 } }
let state = form_state();  // { form: { name, email, message, errors } }
```

### Callback Fixtures

```rust
// Capture patches for assertion
let (patches, callback) = patch_capture();
engine.set_render_callback(callback);
// ... render something ...
let captured = patches.lock().unwrap();
assert_eq!(captured.len(), 5);

// Count function calls
let (count, callback) = call_counter();
// ... use callback ...
assert_eq!(*count.lock().unwrap(), 3);
```

## Matchers

### Patch Matchers

```rust
// Assert specific patch types
assert_create_patch(&patch, "Text");
assert_set_prop_patch(&patch, "color");
assert_set_prop_value(&patch, "color", &json!("red"));
assert_set_text_patch(&patch, "Hello");
assert_insert_patch(&patch);
assert_move_patch(&patch);
assert_remove_patch(&patch);

// Count patches by type
let creates = count_creates(&patches);
let set_props = count_set_props(&patches);
let inserts = count_inserts(&patches);
let removes = count_removes(&patches);
let moves = count_moves(&patches);

// Collection assertions
assert_has_create(&patches);  // At least one Create patch
assert_no_changes(&patches);  // No SetProp patches
assert_patch_count(&patches, 10);  // Exactly 10 patches
```

### Element Matchers

```rust
// Element structure
assert_element_type(&element, "Column");
assert_has_children(&element, 3);
assert_has_key(&element, "item-1");

// Props
assert_has_prop(&element, "color");
assert_prop_static_value(&element, "text", &json!("Hello"));
```

### Value Matchers

```rust
// Value type assertions
assert_static_value(&value, &json!("Hello"));
assert_binding_value(&value, "user.name");
assert_action_value(&value, "submitForm");
```

### JSON Matchers

```rust
// JSON path assertions
assert_json_path(&value, "user.profile.avatar");
assert_json_path_value(&value, "user.name", &json!("Alice"));
```

## Helpers

### Tree Helpers

```rust
// Count nodes in a tree
let count = count_tree_nodes(&tree);

// Get all node IDs
let ids = collect_node_ids(&tree);

// Get tree depth
let depth = tree_depth(&tree);

// Find node by type
if let Some(node_id) = find_node_by_type(&tree, "Button") {
    // ...
}
```

### Path Helpers

```rust
// Split paths
let parts = split_path("user.profile.name");  // ["user", "profile", "name"]

// Check path prefix
assert!(is_path_prefix("user", "user.name"));

// Get parent path
let parent = parent_path("user.profile.name");  // Some("user.profile")
```

### Timing Helpers

```rust
// Measure execution time
let (result, duration) = measure_time(|| {
    engine.render(&element);
});

// Assert completion within time limit
let result = assert_completes_within(Duration::from_millis(100), || {
    reconcile_large_tree();
});
```

### Debug Helpers

```rust
// Print tree structure (for debugging)
print_tree(&tree);
// Output:
//   Column (id: NodeId(...), key: None)
//     Text (id: NodeId(...), key: None)
//     Text (id: NodeId(...), key: None)

// Print patches (for debugging)
print_patches(&patches);
// Output:
//   Patches (3):
//     [0] Create { element_type: "Column", ... }
//     [1] Create { element_type: "Text", ... }
//     [2] Insert { parent_id: "...", ... }

// Tree snapshot for comparison
let snapshot = tree_snapshot(&tree);
```

## Writing Tests with Given/When/Then

All tests should follow the Given/When/Then pattern:

```rust
#[test]
fn test_feature_name() {
    // GIVEN: Setup initial state
    let mut engine = Engine::new();
    let element = text_element("Hello");
    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Perform action
    engine.render(&element);

    // THEN: Verify expectations
    let captured = patches.lock().unwrap();
    assert_has_create(&captured);
    assert_patch_count(&captured, 2);  // Create + Insert
}
```

## Examples

See `tests/test_common.rs` for working examples of all utilities.

### Example: Testing Element Creation

```rust
#[test]
fn test_column_with_children() {
    // GIVEN: Create a column with two text elements
    let element = column_with_children(vec![
        text_element("First"),
        text_element("Second"),
    ]);

    // THEN: It has the correct structure
    assert_element_type(&element, "Column");
    assert_has_children(&element, 2);
    assert_element_type(&element.children[0], "Text");
    assert_element_type(&element.children[1], "Text");
}
```

### Example: Testing State Bindings

```rust
#[test]
fn test_state_binding_resolution() {
    // GIVEN: Element with binding and module with state
    let element = text_element_with_binding("user.name");
    let module = module_instance_with_state("UserModule", json!({
        "user": {"name": "Alice"}
    }));

    // WHEN: Render with state
    let mut engine = Engine::new();
    engine.set_module(module);
    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);
    engine.render(&element);

    // THEN: Binding is resolved
    let captured = patches.lock().unwrap();
    // Check that SetProp patch has value "Alice"
}
```

### Example: Testing Reconciliation

```rust
#[test]
fn test_reconcile_children_reordered() {
    // GIVEN: Rendered tree with keyed children
    let mut engine = Engine::new();
    let initial = column_with_children(keyed_list(&["A", "B", "C"]));
    engine.render(&initial);

    let (patches, callback) = patch_capture();
    engine.set_render_callback(callback);

    // WHEN: Render with reordered children
    let reordered = column_with_children(keyed_list(&["C", "B", "A"]));
    engine.render(&reordered);

    // THEN: Move patches generated
    let captured = patches.lock().unwrap();
    assert_eq!(count_moves(&captured), 2);  // Moved C and A
}
```

## Adding New Utilities

When adding new test utilities:

1. **Fixtures**: Add to `fixtures.rs` if it's a commonly-used test object
2. **Matchers**: Add to `matchers.rs` if it's an assertion helper
3. **Helpers**: Add to `helpers.rs` if it's a utility function

Follow the existing patterns:
- Use clear, descriptive names
- Add doc comments with examples
- Keep functions focused and composable
- Export via `mod.rs` for easy access

## Running Tests

```bash
# Run all integration tests
cargo test --test test_common

# Run tests with output
cargo test --test test_common -- --nocapture

# Run specific test
cargo test --test test_common test_nested_tree_fixture
```