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:
2026-02-13 21:52:03 +00:00
parent 80332971b8
commit 16deb5d237
290 changed files with 90316 additions and 5827 deletions

282
src/lsp/completion_tests.rs Normal file
View File

@@ -0,0 +1,282 @@
//! Tests for context-aware completion
#[cfg(test)]
mod tests {
use tower_lsp::lsp_types::{
CompletionParams,
Position,
TextDocumentIdentifier,
TextDocumentPositionParams,
Url,
};
use crate::lsp::{
completion,
document::Document,
};
fn make_params(line: u32, character: u32) -> CompletionParams {
CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier {
uri: Url::parse("file:///test.sb").unwrap(),
},
position: Position { line, character },
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
context: None,
}
}
#[test]
fn test_top_level_completions() {
let doc = Document::new("".to_string());
let params = make_params(0, 0);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have top-level keywords
assert!(items.iter().any(|item| item.label == "character"));
assert!(items.iter().any(|item| item.label == "template"));
assert!(items.iter().any(|item| item.label == "behavior"));
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_field_block_completions() {
let source = "character Alice {\n \n}";
let doc = Document::new(source.to_string());
// Position inside the character block (line 1, after spaces)
let params = make_params(1, 4);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have field-related keywords
assert!(items.iter().any(|item| item.label == "from"));
assert!(items.iter().any(|item| item.label == "age"));
assert!(items.iter().any(|item| item.label == "bond"));
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_completions_include_templates() {
// Test that templates show up in completions
let source = "template Child { age: number }\n";
let doc = Document::new(source.to_string());
let params = make_params(1, 0);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should include Child template in completions
assert!(
items.iter().any(|item| item.label == "Child"),
"Should have Child template in completions"
);
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_behavior_completions() {
let source = "behavior Test {\n \n}";
let doc = Document::new(source.to_string());
// Position inside behavior block
let params = make_params(1, 4);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have behavior tree keywords
assert!(items.iter().any(|item| item.label == "?"));
assert!(items.iter().any(|item| item.label == ">"));
assert!(items.iter().any(|item| item.label == "*"));
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_life_arc_completions() {
let source = "life_arc Growing {\n \n}";
let doc = Document::new(source.to_string());
// Position inside life arc block
let params = make_params(1, 4);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have life arc keywords
assert!(items.iter().any(|item| item.label == "state"));
assert!(items.iter().any(|item| item.label == "on"));
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_relationship_completions() {
let source = "relationship Friends {\n \n}";
let doc = Document::new(source.to_string());
// Position inside relationship block
let params = make_params(1, 4);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have relationship keywords
assert!(items.iter().any(|item| item.label == "as"));
assert!(items.iter().any(|item| item.label == "self"));
assert!(items.iter().any(|item| item.label == "other"));
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_type_suggestions_in_completions() {
// More complete example with proper syntax
let source = r#"template Child { age: number }
species Human {}
character Alice: Child {}
character Bob {}"#;
let doc = Document::new(source.to_string());
// Just check that templates and species are in completions
let params = make_params(0, 0);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Should have Child and Human in completions
assert!(items.iter().any(|item| item.label == "Child"));
assert!(items.iter().any(|item| item.label == "Human"));
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_behavior_reference_in_symbols() {
// Check that behaviors are in symbol table and show up in completions
let source = "behavior WalkAround { patrol }\nbehavior Main { idle }";
let doc = Document::new(source.to_string());
let params = make_params(0, 0);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Behaviors should be in completions
assert!(
items.iter().any(|item| item.label.contains("WalkAround")),
"Should have WalkAround in completions"
);
assert!(
items.iter().any(|item| item.label.contains("Main")),
"Should have Main in completions"
);
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_snippet_format_in_completions() {
let doc = Document::new("".to_string());
let params = make_params(0, 0);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Check that snippets have proper format
let char_item = items.iter().find(|item| item.label == "character");
assert!(char_item.is_some());
if let Some(item) = char_item {
assert!(item.insert_text.is_some());
assert!(item.insert_text_format.is_some());
// Should contain snippet placeholders
assert!(item.insert_text.as_ref().unwrap().contains("${"));
}
},
| _ => panic!("Expected array response"),
}
}
}
#[test]
fn test_no_duplicate_completions() {
let source = "character Alice {}\ncharacter Alice {}"; // Duplicate name
let doc = Document::new(source.to_string());
let params = make_params(0, 0);
// Duplicate definitions cause NameTable::from_file() to fail,
// resulting in an empty name table and no completions.
// This is correct - duplicates should be caught as validation errors.
assert!(
!doc.resolve_errors.is_empty(),
"Should have validation error for duplicate"
);
let result = completion::get_completions(&doc, &params);
assert!(result.is_some());
if let Some(response) = result {
match response {
| tower_lsp::lsp_types::CompletionResponse::Array(items) => {
// Count how many times "Alice" appears
let alice_count = items.iter().filter(|item| item.label == "Alice").count();
assert_eq!(
alice_count, 0,
"Should have no completions when there are duplicate definitions"
);
},
| _ => panic!("Expected array response"),
}
}
}
}