//! 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 //! - 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).unwrap_or_else(|e| panic!("Failed to load example '{}': {:?}", name, e)) } // ============================================================================ // Baker Family Example Tests // ============================================================================ #[test] fn test_baker_family_loads_successfully() { let project = load_example("baker-family"); // Should have exactly 5 characters let char_count = project.characters().count(); assert_eq!(char_count, 5, "Baker family should have 5 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" ); assert!( project.find_character("Henry").is_some(), "Henry should exist" ); assert!( project.find_character("Roland").is_some(), "Roland 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::Number(age)) = martha.fields.get("age") { assert_eq!(*age, 34, "Martha's age should be 34"); } else { panic!("age should be a Number"); } // Martha sets specialty: Sourdough (overriding Baker template default Bread) if let Some(storybook::syntax::ast::Value::Identifier(parts)) = martha.fields.get("specialty") { assert_eq!( parts, &vec!["Sourdough".to_string()], "Martha's specialty should be Sourdough" ); } else { panic!("specialty should be an Identifier"); } } #[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/core_types.sb, baking_types.sb, world_types.sb, social_types.sb // - schema/templates.sb, life_arcs.sb // - behaviors/baker_behaviors.sb, person_behaviors.sb, child_behaviors.sb // - behaviors/competition_behaviors.sb, henry_behaviors.sb // - schedules/base_schedule.sb, baker_schedule.sb, school_schedule.sb, // retired_schedule.sb // - relationships/family_relationships.sb, bakery_relationships.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"); } // ============================================================================ // Generic Validation Tests // ============================================================================ #[test] fn test_baker_family_all_characters_have_names_and_fields() { let project = load_example("baker-family"); 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_baker_family_schedules_have_valid_blocks() { let project = load_example("baker-family"); 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_baker_family_behaviors_have_valid_structure() { let project = load_example("baker-family"); for behavior in project.behaviors() { assert!(!behavior.name.is_empty(), "Behavior should have a name"); // Just verify root node exists let _ = &behavior.root; } } // ============================================================================ // Example Validation // ============================================================================ #[test] fn test_baker_family_example_loads_without_errors() { // Test that the baker-family example loads successfully let result = std::panic::catch_unwind(|| { load_example("baker-family"); }); assert!( result.is_ok(), "Baker family example should load without panicking" ); }