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:
191
src/lsp/document_edge_tests.rs
Normal file
191
src/lsp/document_edge_tests.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
//! Edge case tests for document functionality
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lsp::document::Document;
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_unicode() {
|
||||
let doc = Document::new("character Café { age: 7 }".to_string());
|
||||
|
||||
// Test finding "Café"
|
||||
let word = doc.word_at_offset(10);
|
||||
assert_eq!(word, Some("Café".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_underscore() {
|
||||
let doc = Document::new("character snake_case { }".to_string());
|
||||
|
||||
let word = doc.word_at_offset(12);
|
||||
assert_eq!(word, Some("snake_case".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_at_start() {
|
||||
let doc = Document::new("character Alice { }".to_string());
|
||||
|
||||
let word = doc.word_at_offset(0);
|
||||
assert_eq!(word, Some("character".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_at_end() {
|
||||
let doc = Document::new("character Alice".to_string());
|
||||
|
||||
let word = doc.word_at_offset(14);
|
||||
assert_eq!(word, Some("Alice".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_out_of_bounds() {
|
||||
let doc = Document::new("test".to_string());
|
||||
|
||||
let word = doc.word_at_offset(1000);
|
||||
assert_eq!(word, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_on_whitespace() {
|
||||
let doc = Document::new("character Alice".to_string());
|
||||
|
||||
let word = doc.word_at_offset(9); // Space between character and Alice
|
||||
assert_eq!(word, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_at_offset_on_punctuation() {
|
||||
let doc = Document::new("character Alice { }".to_string());
|
||||
|
||||
let word = doc.word_at_offset(16); // On '{'
|
||||
assert_eq!(word, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_clears_old_symbols() {
|
||||
let mut doc = Document::new("character Alice {}".to_string());
|
||||
|
||||
assert!(doc.name_table.resolve_name("Alice").is_some());
|
||||
|
||||
doc.update("character Bob {}".to_string());
|
||||
|
||||
assert!(doc.name_table.resolve_name("Alice").is_none());
|
||||
assert!(doc.name_table.resolve_name("Bob").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_with_invalid_syntax() {
|
||||
let mut doc = Document::new("character Alice {}".to_string());
|
||||
|
||||
assert!(doc.ast.is_some());
|
||||
assert!(doc.parse_errors.is_empty());
|
||||
|
||||
doc.update("invalid { }".to_string());
|
||||
|
||||
assert!(doc.ast.is_none());
|
||||
assert!(!doc.parse_errors.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_document_has_no_symbols() {
|
||||
let doc = Document::new("".to_string());
|
||||
|
||||
assert_eq!(doc.name_table.all_entries().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbol_table_with_duplicates() {
|
||||
let source = r#"
|
||||
character Alice { age: 7 }
|
||||
character Alice { age: 8 }
|
||||
"#;
|
||||
let doc = Document::new(source.to_string());
|
||||
|
||||
// Duplicate declarations should be caught during resolution
|
||||
// NameTable from_file will fail, so we'll have an empty table and
|
||||
// resolve_errors
|
||||
assert!(
|
||||
!doc.resolve_errors.is_empty(),
|
||||
"Should have validation error for duplicate"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_declaration_types() {
|
||||
let source = r#"
|
||||
species Human {}
|
||||
character Alice: Human {}
|
||||
template Child {}
|
||||
enum Mood { Happy, Sad }
|
||||
location Home {}
|
||||
relationship Friends { Alice as friend {} Bob as friend {} }
|
||||
"#;
|
||||
let doc = Document::new(source.to_string());
|
||||
|
||||
assert!(doc.name_table.resolve_name("Human").is_some());
|
||||
assert!(doc.name_table.resolve_name("Alice").is_some());
|
||||
assert!(doc.name_table.resolve_name("Child").is_some());
|
||||
assert!(doc.name_table.resolve_name("Mood").is_some());
|
||||
assert!(doc.name_table.resolve_name("Home").is_some());
|
||||
assert!(doc.name_table.resolve_name("Friends").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_life_arc_symbol_extraction() {
|
||||
let source = r#"
|
||||
life_arc Growing {
|
||||
state child {}
|
||||
state teen {}
|
||||
state adult {}
|
||||
}
|
||||
"#;
|
||||
let doc = Document::new(source.to_string());
|
||||
|
||||
assert!(doc.name_table.resolve_name("Growing").is_some());
|
||||
let growing = doc.name_table.resolve_name("Growing").unwrap();
|
||||
assert_eq!(growing.kind, crate::resolve::names::DeclKind::LifeArc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_schedule_symbol_extraction() {
|
||||
let source = r#"
|
||||
schedule Daily {
|
||||
08:00 -> 09:00: breakfast {}
|
||||
09:00 -> 12:00: work {}
|
||||
}
|
||||
"#;
|
||||
let doc = Document::new(source.to_string());
|
||||
|
||||
assert!(doc.name_table.resolve_name("Daily").is_some());
|
||||
let daily = doc.name_table.resolve_name("Daily").unwrap();
|
||||
assert_eq!(daily.kind, crate::resolve::names::DeclKind::Schedule);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_institution_symbol_extraction() {
|
||||
let source = "institution School { type: education }";
|
||||
let doc = Document::new(source.to_string());
|
||||
|
||||
assert!(doc.name_table.resolve_name("School").is_some());
|
||||
let school = doc.name_table.resolve_name("School").unwrap();
|
||||
assert_eq!(school.kind, crate::resolve::names::DeclKind::Institution);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_very_long_identifier() {
|
||||
let long_name = "a".repeat(1000);
|
||||
let source = format!("character {} {{}}", long_name);
|
||||
let doc = Document::new(source);
|
||||
|
||||
assert!(doc.name_table.resolve_name(&long_name).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiline_document() {
|
||||
let source = "\n\n\n\ncharacter Alice {\n\n\n age: 7\n\n\n}";
|
||||
let doc = Document::new(source.to_string());
|
||||
|
||||
assert!(doc.ast.is_some());
|
||||
assert!(doc.name_table.resolve_name("Alice").is_some());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user