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:
282
src/lsp/completion_tests.rs
Normal file
282
src/lsp/completion_tests.rs
Normal 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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user