release: Storybook v0.2.0 - Major syntax and features update
BREAKING CHANGES: - Relationship syntax now requires blocks for all participants - Removed self/other perspective blocks from relationships - Replaced 'guard' keyword with 'if' for behavior tree decorators Language Features: - Add tree-sitter grammar with improved if/condition disambiguation - Add comprehensive tutorial and reference documentation - Add SBIR v0.2.0 binary format specification - Add resource linking system for behaviors and schedules - Add year-long schedule patterns (day, season, recurrence) - Add behavior tree enhancements (named nodes, decorators) Documentation: - Complete tutorial series (9 chapters) with baker family examples - Complete reference documentation for all language features - SBIR v0.2.0 specification with binary format details - Added locations and institutions documentation Examples: - Convert all examples to baker family scenario - Add comprehensive working examples Tooling: - Zed extension with LSP integration - Tree-sitter grammar for syntax highlighting - Build scripts and development tools Version Updates: - Main package: 0.1.0 → 0.2.0 - Tree-sitter grammar: 0.1.0 → 0.2.0 - Zed extension: 0.1.0 → 0.2.0 - Storybook editor: 0.1.0 → 0.2.0
This commit is contained in:
370
tests/validate_examples.rs
Normal file
370
tests/validate_examples.rs
Normal file
@@ -0,0 +1,370 @@
|
||||
//! Example Validation Tests
|
||||
//!
|
||||
//! These tests validate that example storybooks load correctly and demonstrate
|
||||
//! documented features. Unlike unit/integration tests that validate specific
|
||||
//! functionality, these tests ensure that:
|
||||
//! 1. Examples parse and resolve successfully
|
||||
//! 2. Examples demonstrate features as documented
|
||||
//! 3. Examples serve as reliable educational resources
|
||||
//!
|
||||
//! Cross-references:
|
||||
//! - examples/baker-family/README.md
|
||||
//! - examples/alice-in-wonderland/README.md
|
||||
//! - DEVELOPER-GUIDE.md
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use storybook::Project;
|
||||
|
||||
/// Helper to load an example project from the examples/ directory
|
||||
fn load_example(name: &str) -> Project {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("examples");
|
||||
path.push(name);
|
||||
|
||||
assert!(path.exists(), "Example '{}' not found at {:?}", name, path);
|
||||
|
||||
Project::load(&path).expect(&format!("Failed to load example '{}'", name))
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Baker Family Example Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_loads_successfully() {
|
||||
let project = load_example("baker-family");
|
||||
|
||||
// Should have exactly 3 characters
|
||||
let char_count = project.characters().count();
|
||||
assert_eq!(char_count, 3, "Baker family should have 3 characters");
|
||||
|
||||
// Verify all characters exist
|
||||
assert!(
|
||||
project.find_character("Martha").is_some(),
|
||||
"Martha should exist"
|
||||
);
|
||||
assert!(
|
||||
project.find_character("Jane").is_some(),
|
||||
"Jane should exist"
|
||||
);
|
||||
assert!(
|
||||
project.find_character("Emma").is_some(),
|
||||
"Emma should exist"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_martha_has_all_template_fields() {
|
||||
let project = load_example("baker-family");
|
||||
let martha = project
|
||||
.find_character("Martha")
|
||||
.expect("Martha should exist");
|
||||
|
||||
// Fields from Person template (base)
|
||||
assert!(
|
||||
martha.fields.contains_key("age"),
|
||||
"Should have age from Person"
|
||||
);
|
||||
assert!(
|
||||
martha.fields.contains_key("energy"),
|
||||
"Should have energy from Person"
|
||||
);
|
||||
assert!(
|
||||
martha.fields.contains_key("mood"),
|
||||
"Should have mood from Person"
|
||||
);
|
||||
|
||||
// Fields from Worker template (extends Person)
|
||||
assert!(
|
||||
martha.fields.contains_key("occupation"),
|
||||
"Should have occupation from Worker"
|
||||
);
|
||||
assert!(
|
||||
martha.fields.contains_key("work_ethic"),
|
||||
"Should have work_ethic from Worker"
|
||||
);
|
||||
|
||||
// Fields from Baker template (extends Worker)
|
||||
assert!(
|
||||
martha.fields.contains_key("specialty"),
|
||||
"Should have specialty from Baker"
|
||||
);
|
||||
assert!(
|
||||
martha.fields.contains_key("baking_skill"),
|
||||
"Should have baking_skill from Baker"
|
||||
);
|
||||
assert!(
|
||||
martha.fields.contains_key("customer_relations"),
|
||||
"Should have customer_relations from Baker"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_field_values() {
|
||||
let project = load_example("baker-family");
|
||||
let martha = project.find_character("Martha").unwrap();
|
||||
|
||||
// Martha sets age: 34 (overriding Person template range 0..100)
|
||||
if let Some(storybook::syntax::ast::Value::Int(age)) = martha.fields.get("age") {
|
||||
assert_eq!(*age, 34, "Martha's age should be 34");
|
||||
} else {
|
||||
panic!("age should be an Int");
|
||||
}
|
||||
|
||||
// Martha sets specialty: "sourdough" (overriding Baker template default
|
||||
// "bread")
|
||||
if let Some(storybook::syntax::ast::Value::String(specialty)) = martha.fields.get("specialty") {
|
||||
assert_eq!(
|
||||
specialty, "sourdough",
|
||||
"Martha's specialty should be sourdough"
|
||||
);
|
||||
} else {
|
||||
panic!("specialty should be a String");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_has_expected_behaviors() {
|
||||
let project = load_example("baker-family");
|
||||
|
||||
let behaviors: Vec<_> = project.behaviors().collect();
|
||||
let behavior_names: Vec<_> = behaviors.iter().map(|b| b.name.as_str()).collect();
|
||||
|
||||
// Core behaviors for the family
|
||||
let expected = vec![
|
||||
"BakingWork",
|
||||
"PrepKitchen",
|
||||
"BasicNeeds",
|
||||
"SocialInteraction",
|
||||
"BakingSkills",
|
||||
"CustomerService",
|
||||
"PlayBehavior",
|
||||
"LearnBehavior",
|
||||
];
|
||||
|
||||
for name in expected {
|
||||
assert!(
|
||||
behavior_names.contains(&name),
|
||||
"Should have behavior '{}', got: {:?}",
|
||||
name,
|
||||
behavior_names
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_has_expected_schedules() {
|
||||
let project = load_example("baker-family");
|
||||
|
||||
let schedules: Vec<_> = project.schedules().collect();
|
||||
assert!(!schedules.is_empty(), "Should have schedules");
|
||||
|
||||
let schedule_names: Vec<_> = schedules.iter().map(|s| s.name.as_str()).collect();
|
||||
|
||||
// Should have work schedules
|
||||
assert!(
|
||||
schedule_names
|
||||
.iter()
|
||||
.any(|&n| n.contains("Schedule") || n.contains("Week")),
|
||||
"Should have schedule definitions, got: {:?}",
|
||||
schedule_names
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_multi_file_structure() {
|
||||
// Baker family is organized across multiple files:
|
||||
// - schema/templates.sb
|
||||
// - behaviors/baker_behaviors.sb
|
||||
// - schedules/work_schedules.sb
|
||||
// - characters/*.sb
|
||||
|
||||
let project = load_example("baker-family");
|
||||
|
||||
// If all declaration types are present, multi-file loading works
|
||||
assert!(project.characters().count() > 0, "Should have characters");
|
||||
assert!(project.behaviors().count() > 0, "Should have behaviors");
|
||||
assert!(project.schedules().count() > 0, "Should have schedules");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Alice in Wonderland Example Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_loads_successfully() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
// Should have multiple whimsical characters
|
||||
let char_count = project.characters().count();
|
||||
assert!(
|
||||
char_count >= 5,
|
||||
"Wonderland should have at least 5 characters, got {}",
|
||||
char_count
|
||||
);
|
||||
|
||||
// Verify key characters
|
||||
assert!(
|
||||
project.find_character("Alice").is_some(),
|
||||
"Alice should exist"
|
||||
);
|
||||
assert!(
|
||||
project.find_character("WhiteRabbit").is_some(),
|
||||
"WhiteRabbit should exist"
|
||||
);
|
||||
assert!(
|
||||
project.find_character("CheshireCat").is_some(),
|
||||
"CheshireCat should exist"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_alice_details() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
let alice = project.find_character("Alice").expect("Alice should exist");
|
||||
|
||||
// Alice should have age field
|
||||
assert!(alice.fields.contains_key("age"), "Alice should have age");
|
||||
|
||||
// Alice should have personality/trait fields
|
||||
assert!(
|
||||
!alice.fields.is_empty(),
|
||||
"Alice should have character fields"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_has_behaviors() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
let behavior_count = project.behaviors().count();
|
||||
assert!(behavior_count > 0, "Wonderland should have behaviors");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_has_schedules() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
let schedule_count = project.schedules().count();
|
||||
assert!(schedule_count > 0, "Wonderland should have schedules");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_multi_file_organization() {
|
||||
// Alice in Wonderland is organized into:
|
||||
// - schema/ (templates, enums)
|
||||
// - world/characters/ (character definitions)
|
||||
// - world/behaviors/ (behavior trees)
|
||||
// - world/schedules/ (schedules)
|
||||
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
// Verify all major declaration types loaded
|
||||
assert!(
|
||||
project.characters().count() >= 5,
|
||||
"Should have multiple characters"
|
||||
);
|
||||
assert!(project.behaviors().count() > 0, "Should have behaviors");
|
||||
assert!(project.schedules().count() > 0, "Should have schedules");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_all_characters_have_names_and_fields() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
for character in project.characters() {
|
||||
assert!(!character.name.is_empty(), "Character should have a name");
|
||||
assert!(
|
||||
!character.fields.is_empty(),
|
||||
"Character {} should have fields",
|
||||
character.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_schedules_have_valid_blocks() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
for schedule in project.schedules() {
|
||||
if schedule.blocks.is_empty() {
|
||||
continue; // Some schedules might be empty/templates
|
||||
}
|
||||
|
||||
for block in &schedule.blocks {
|
||||
// Time ranges should be valid: start < end (or midnight wrap)
|
||||
let start_mins = block.start.hour as u32 * 60 + block.start.minute as u32;
|
||||
let end_mins = block.end.hour as u32 * 60 + block.end.minute as u32;
|
||||
|
||||
assert!(
|
||||
start_mins < end_mins || end_mins == 0,
|
||||
"Block in {} should have valid time: {:02}:{:02}-{:02}:{:02}",
|
||||
schedule.name,
|
||||
block.start.hour,
|
||||
block.start.minute,
|
||||
block.end.hour,
|
||||
block.end.minute
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alice_wonderland_behaviors_have_valid_structure() {
|
||||
let project = load_example("alice-in-wonderland");
|
||||
|
||||
for behavior in project.behaviors() {
|
||||
assert!(!behavior.name.is_empty(), "Behavior should have a name");
|
||||
// Just verify root node exists
|
||||
let _ = &behavior.root;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cross-Example Consistency Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_all_examples_load_without_errors() {
|
||||
// Test that all examples in the examples/ directory load successfully
|
||||
let examples = vec!["baker-family", "alice-in-wonderland"];
|
||||
|
||||
for name in examples {
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
load_example(name);
|
||||
});
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Example '{}' should load without panicking",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_have_consistent_structure() {
|
||||
// All examples should have characters and at least one other declaration type
|
||||
let examples = vec!["baker-family", "alice-in-wonderland"];
|
||||
|
||||
for name in examples {
|
||||
let project = load_example(name);
|
||||
|
||||
assert!(
|
||||
project.characters().count() > 0,
|
||||
"Example '{}' should have characters",
|
||||
name
|
||||
);
|
||||
|
||||
let has_other_decls = project.behaviors().count() > 0 ||
|
||||
project.schedules().count() > 0 ||
|
||||
project.life_arcs().count() > 0;
|
||||
|
||||
assert!(
|
||||
has_other_decls,
|
||||
"Example '{}' should have behaviors, schedules, or life arcs",
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user