use crate::ast::*;
use crate::parser::*;
#[test]
fn test_simple_component() {
let input = "Text(\"Hello World\")";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
assert_eq!(component.arguments.arguments.len(), 1);
}
#[test]
fn test_component_without_arguments() {
let input = "Column";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.arguments.arguments.len(), 0);
}
#[test]
fn test_component_with_named_arguments() {
let input = r#"Text(text: "Hello", color: red)"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
assert_eq!(component.arguments.arguments.len(), 2);
let text_arg = component.arguments.get_named("text");
assert!(text_arg.is_some());
}
#[test]
fn test_component_with_children() {
let input = r#"
Column {
Text("First line")
Text("Second line")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.children.len(), 2);
assert_eq!(component.children[0].name, "Text");
assert_eq!(component.children[1].name, "Text");
}
#[test]
fn test_component_with_applicators() {
let input = r#"
Text("Styled text")
.backgroundColor(blue)
.padding(16)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
assert_eq!(component.applicators.len(), 2);
assert_eq!(component.applicators[0].name, "backgroundColor");
assert_eq!(component.applicators[1].name, "padding");
}
#[test]
fn test_nested_components() {
let input = r#"
Column {
Row {
Text("Left")
Text("Right")
}
Text("Bottom")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.children.len(), 2);
let row = &component.children[0];
assert_eq!(row.name, "Row");
assert_eq!(row.children.len(), 2);
assert_eq!(component.children[1].name, "Text");
}
#[test]
fn test_boolean_values() {
let input = "Component(enabled: true, disabled: false)";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let enabled = component.arguments.get_named("enabled");
let disabled = component.arguments.get_named("disabled");
assert!(matches!(enabled, Some(Value::Boolean(true))));
assert!(matches!(disabled, Some(Value::Boolean(false))));
}
#[test]
fn test_number_values() {
let input = "Component(width: 100, height: 50.5)";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let width = component.arguments.get_named("width");
let height = component.arguments.get_named("height");
assert!(matches!(width, Some(Value::Number(100.0))));
assert!(matches!(height, Some(Value::Number(50.5))));
}
#[test]
fn test_reference_values() {
let input = "Text(@state.username)";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
if let Some(Argument::Positioned { value, .. }) = component.arguments.arguments.first() {
assert!(matches!(value, Value::Reference(_)));
} else {
panic!("Expected positional argument with reference");
}
}
#[test]
fn test_resource_reference() {
let input = "Icon(@resources.heart)";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Icon");
if let Some(Argument::Positioned { value, .. }) = component.arguments.arguments.first() {
match value {
Value::Reference(r) => assert_eq!(r, "resources.heart"),
_ => panic!("Expected Reference, got {:?}", value),
}
} else {
panic!("Expected positional argument with reference");
}
}
#[test]
fn test_resource_reference_hyphenated() {
let input = "Icon(@resources.plus-square)";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
if let Some(Argument::Positioned { value, .. }) = component.arguments.arguments.first() {
match value {
Value::Reference(r) => assert_eq!(r, "resources.plus-square"),
_ => panic!("Expected Reference, got {:?}", value),
}
} else {
panic!("Expected positional argument with reference");
}
}
#[test]
fn test_list_values() {
let input = r#"Component(items: [1, 2, 3])"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let items = component.arguments.get_named("items");
assert!(matches!(items, Some(Value::List(_))));
}
#[test]
fn test_map_values() {
let input = r#"Component(config: {width: 100, height: 200})"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let config = component.arguments.get_named("config");
assert!(matches!(config, Some(Value::Map(_))));
}
#[test]
fn test_multiple_components() {
let input = r#"
Text("First")
Text("Second")
Text("Third")
"#;
let result = parse_components(input);
assert!(result.is_ok());
let components = result.unwrap();
assert_eq!(components.len(), 3);
}
#[test]
fn test_complex_example() {
let input = r#"
Column {
Text("Welcome, @state.username")
.fontSize(18)
.color(blue)
Button("@actions.signIn") {
Text("Sign In")
}
.padding(16)
Row {
Image(src: "logo.png")
.width(50)
.height(50)
Text("Logo")
}
}
.backgroundColor(white)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.applicators.len(), 1);
assert_eq!(component.applicators[0].name, "backgroundColor");
assert_eq!(component.children.len(), 3);
}
#[test]
fn test_empty_component() {
let input = "Component()";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Component");
assert_eq!(component.arguments.arguments.len(), 0);
}
#[test]
fn test_whitespace_handling() {
let input = " Text ( \"Hello\" ) ";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_empty_input() {
let input = "";
let result = parse_component(input);
assert!(result.is_err());
}
#[test]
fn test_empty_components_list() {
let input = "";
let result = parse_components(input);
assert!(result.is_err() || result.unwrap().is_empty());
}
#[test]
fn test_component_without_parens() {
let input = "Column";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert!(component.arguments.arguments.is_empty());
}
#[test]
fn test_component_with_empty_parens() {
let input = "Column()";
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert!(component.arguments.arguments.is_empty());
}
#[test]
fn test_special_chars_in_strings() {
let input = r#"Text("Hello, World!")"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_nested_maps() {
let input = r#"Component(config: {outer: {inner: value}})"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Component");
let config = component.arguments.get_named("config");
assert!(matches!(config, Some(Value::Map(_))));
}
#[test]
fn test_nested_lists() {
let input = r#"Component(items: [[1, 2], [3, 4]])"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let items = component.arguments.get_named("items");
assert!(matches!(items, Some(Value::List(_))));
}
#[test]
fn test_mixed_arguments() {
let input = r#"Component(first, key: "value", 123, enabled: true)"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.arguments.arguments.len(), 4);
assert!(matches!(
component.arguments.arguments[0],
Argument::Positioned { .. }
));
assert!(matches!(
component.arguments.arguments[1],
Argument::Named { .. }
));
}
#[test]
fn test_deeply_nested_components() {
let input = r#"
A {
B {
C {
D {
E {
Text("Deep")
}
}
}
}
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "A");
let flattened = component.flatten();
assert!(flattened.len() >= 6);
}
#[test]
fn test_component_with_block_and_applicators() {
let input = r#"
Column {
Text("Child")
}
.padding(16)
.backgroundColor(blue)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.children.len(), 1);
assert_eq!(component.applicators.len(), 2);
}
#[test]
fn test_multiple_siblings() {
let input = r#"
Column {
Text("First")
Text("Second")
Text("Third")
Text("Fourth")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.children.len(), 4);
}
#[test]
fn test_components_with_and_without_args() {
let input = r#"
Column {
Text("With args")
Spacer
Text("More args")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.children.len(), 3);
assert_eq!(component.children[1].name, "Spacer");
assert!(component.children[1].arguments.arguments.is_empty());
}
#[test]
fn test_error_unclosed_brace() {
let input = r#"
Column {
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_err());
}
#[test]
fn test_error_unclosed_paren() {
let input = r#"Text("Hello""#;
let result = parse_component(input);
assert!(result.is_err());
}
#[test]
fn test_error_invalid_syntax() {
let input = "Text(((";
let result = parse_component(input);
assert!(result.is_err());
}
#[test]
fn test_list_with_mixed_types() {
let input = r#"Component(items: [1, "text", true, {key: value}])"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let items = component.arguments.get_named("items");
assert!(matches!(items, Some(Value::List(_))));
if let Some(Value::List(list)) = items {
assert_eq!(list.len(), 4);
}
}
#[test]
fn test_trailing_commas() {
let input = r#"Component(a: 1, b: 2,)"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.arguments.arguments.len(), 2);
}
#[test]
fn test_list_trailing_comma() {
let input = r#"Component(items: [1, 2, 3,])"#;
let result = parse_component(input);
assert!(result.is_ok());
}
#[test]
fn test_map_trailing_comma() {
let input = r#"Component(config: {a: 1, b: 2,})"#;
let result = parse_component(input);
assert!(result.is_ok());
}
#[test]
fn test_component_name_case_sensitivity() {
let inputs = vec!["Text", "text", "TEXT", "MyComponent", "my_component"];
for name in inputs {
let input = format!("{}(\"test\")", name);
let result = parse_component(&input);
assert!(result.is_ok(), "Failed to parse: {}", name);
assert_eq!(result.unwrap().name, name);
}
}
#[test]
#[ignore]
fn test_applicators_with_children() {
let input = r#"
Container
.modifier {
Text("Inside modifier")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Container");
assert_eq!(component.applicators.len(), 1);
assert_eq!(component.applicators[0].name, "modifier");
assert_eq!(component.applicators[0].children.len(), 1);
}
#[test]
fn test_module_declaration() {
let input = r#"
module ProfilePage(userId: 123) {
Column {
Text("User Profile")
}
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "ProfilePage");
assert_eq!(component.declaration_type, DeclarationType::Module);
assert_eq!(component.arguments.arguments.len(), 1);
assert_eq!(component.children.len(), 1);
}
#[test]
fn test_component_declaration() {
let input = r#"
component Button(text: "Click Me") {
Text(@state.text)
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Button");
assert_eq!(
component.declaration_type,
DeclarationType::ComponentKeyword
);
assert_eq!(component.children.len(), 1);
}
#[test]
fn test_regular_component_no_keyword() {
let input = r#"Text("Hello")"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
assert_eq!(component.declaration_type, DeclarationType::Component);
}
#[test]
fn test_module_without_args() {
let input = r#"
module HomePage {
Text("Welcome")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "HomePage");
assert_eq!(component.declaration_type, DeclarationType::Module);
}
#[test]
fn test_component_without_block() {
let input = r#"component Divider"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Divider");
assert_eq!(
component.declaration_type,
DeclarationType::ComponentKeyword
);
}
#[test]
fn test_nested_module_and_components() {
let input = r#"
module App {
component Header {
Text("App Title")
}
Column {
Text("Content")
}
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.declaration_type, DeclarationType::Module);
assert_eq!(component.children.len(), 2);
assert_eq!(
component.children[0].declaration_type,
DeclarationType::ComponentKeyword
);
assert_eq!(
component.children[1].declaration_type,
DeclarationType::Component
);
}
#[test]
fn test_named_import() {
let input = r#"import { Button, Card } from "./components/ui""#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
let names = import.imported_names();
assert_eq!(names.len(), 2);
assert!(names.contains(&"Button".to_string()));
assert!(names.contains(&"Card".to_string()));
assert_eq!(import.source_path(), "./components/ui");
assert!(matches!(import.source, ImportSource::Local(_)));
}
#[test]
fn test_default_import() {
let input = r#"import HomePage from "./pages/HomePage""#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
assert!(matches!(import.clause, ImportClause::Default(_)));
let names = import.imported_names();
assert_eq!(names.len(), 1);
assert_eq!(names[0], "HomePage");
assert_eq!(import.source_path(), "./pages/HomePage");
}
#[test]
fn test_url_import() {
let input = r#"import { Header, Footer } from "https://cdn.example.com/ui""#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
assert!(matches!(import.source, ImportSource::Url(_)));
assert_eq!(import.source_path(), "https://cdn.example.com/ui");
}
#[test]
fn test_document_with_imports() {
let input = r#"
import { Button, Card } from "./components/ui"
import HomePage from "./pages/HomePage"
module App() {
Column {
HomePage()
Button(text: "Click me")
}
}
"#;
let result = parse_document(input);
assert!(result.is_ok());
let doc = result.unwrap();
assert_eq!(doc.imports.len(), 2);
assert_eq!(doc.components.len(), 1);
assert_eq!(doc.components[0].name, "App");
assert_eq!(doc.components[0].declaration_type, DeclarationType::Module);
}
#[test]
fn test_document_without_imports() {
let input = r#"
module App() {
Text("No imports")
}
"#;
let result = parse_document(input);
assert!(result.is_ok());
let doc = result.unwrap();
assert_eq!(doc.imports.len(), 0);
assert_eq!(doc.components.len(), 1);
}
#[test]
fn test_document_only_imports() {
let input = r#"
import { Button } from "./ui"
import Card from "./components"
"#;
let result = parse_document(input);
assert!(result.is_ok());
let doc = result.unwrap();
assert_eq!(doc.imports.len(), 2);
assert_eq!(doc.components.len(), 0);
}
#[test]
fn test_multiple_named_imports() {
let input = r#"import { A, B, C, D, E } from "./components""#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
let names = import.imported_names();
assert_eq!(names.len(), 5);
}
#[test]
fn test_import_trailing_comma() {
let input = r#"import { Button, Card, } from "./ui""#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
assert_eq!(import.imported_names().len(), 2);
}
#[test]
fn test_line_comment_before_component() {
let input = r#"
// This is a comment
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_line_comment_after_component() {
let input = r#"
Text("Hello")
// This is a comment
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_block_comment_before_component() {
let input = r#"
/* This is a block comment */
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_block_comment_after_component() {
let input = r#"
Text("Hello")
/* This is a block comment */
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_multiline_block_comment() {
let input = r#"
/*
* This is a multiline
* block comment
*/
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_comment_between_components() {
let input = r#"
Column {
Text("First")
// Comment between children
Text("Second")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.children.len(), 2);
}
#[test]
fn test_block_comment_between_components() {
let input = r#"
Column {
Text("First")
/* Comment between children */
Text("Second")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.children.len(), 2);
}
#[test]
fn test_comment_in_arguments() {
let input = r#"
Text(
// This is the text argument
"Hello",
// This is the color argument
color: blue
)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.arguments.arguments.len(), 2);
}
#[test]
fn test_block_comment_in_arguments() {
let input = r#"
Text(
/* text */ "Hello",
/* color */ color: blue
)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.arguments.arguments.len(), 2);
}
#[test]
fn test_comment_between_applicators() {
let input = r#"
Text("Styled text")
.backgroundColor(blue)
// Add padding
.padding(16)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.applicators.len(), 2);
}
#[test]
fn test_block_comment_between_applicators() {
let input = r#"
Text("Styled text")
.backgroundColor(blue)
/* Add padding */
.padding(16)
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.applicators.len(), 2);
}
#[test]
fn test_comment_in_list() {
let input = r#"
Component(items: [
1,
// Comment in list
2,
3
])
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let items = component.arguments.get_named("items");
assert!(matches!(items, Some(Value::List(_))));
if let Some(Value::List(list)) = items {
assert_eq!(list.len(), 3);
}
}
#[test]
fn test_comment_in_map() {
let input = r#"
Component(config: {
// Width setting
width: 100,
/* Height setting */
height: 200
})
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
let config = component.arguments.get_named("config");
assert!(matches!(config, Some(Value::Map(_))));
}
#[test]
fn test_comment_after_declaration_keyword() {
let input = r#"
module /* module name */ ProfilePage {
Text("Profile")
}
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "ProfilePage");
assert_eq!(component.declaration_type, DeclarationType::Module);
}
#[test]
fn test_comment_in_import() {
let input = r#"
// Import UI components
import { Button, Card } from "./ui"
"#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
assert_eq!(import.imported_names().len(), 2);
}
#[test]
fn test_comment_in_import_list() {
let input = r#"
import {
// Button component
Button,
/* Card component */
Card
} from "./ui"
"#;
let result = parse_import(input);
assert!(result.is_ok());
let import = result.unwrap();
assert_eq!(import.imported_names().len(), 2);
}
#[test]
fn test_comment_between_import_and_from() {
let input = r#"import { Button } /* from the ui module */ from "./ui""#;
let result = parse_import(input);
assert!(result.is_ok());
}
#[test]
fn test_document_with_comments() {
let input = r#"
// Import statements
import { Button } from "./ui"
/* Main application module */
module App {
// Welcome text
Text("Hello")
}
"#;
let result = parse_document(input);
assert!(result.is_ok());
let doc = result.unwrap();
assert_eq!(doc.imports.len(), 1);
assert_eq!(doc.components.len(), 1);
}
#[test]
fn test_multiple_comments_in_sequence() {
let input = r#"
// First comment
// Second comment
/* Block comment */
// Third comment
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_comment_with_special_chars() {
let input = r#"
// Comment with special chars: !@#$%^&*()
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
}
#[test]
fn test_block_comment_with_stars() {
let input = r#"
/***
*** Fancy comment
***/
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
}
#[test]
fn test_inline_comment_style() {
let input = r#"Text("Hello") // inline comment"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Text");
}
#[test]
fn test_complex_example_with_comments() {
let input = r#"
// Main application layout
Column {
/* Header section */
Text("Welcome")
.fontSize(24) // Large font
.color(blue) // Blue color
// Content section
Row {
// Left side
Text("Left")
// Right side
Text("Right")
}
.padding(16) /* Add padding around row */
}
.backgroundColor(white) // White background
"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.children.len(), 2);
assert_eq!(component.applicators.len(), 1);
}
#[test]
fn test_comment_inside_string_not_parsed_as_comment() {
let input = r#"Text("// this is not a comment")"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
if let Some(Argument::Positioned { value, .. }) = component.arguments.arguments.first() {
assert!(matches!(value, Value::String(s) if s.contains("// this is not a comment")));
} else {
panic!("Expected positional string argument");
}
}
#[test]
fn test_block_comment_inside_string_not_parsed_as_comment() {
let input = r#"Text("/* not a comment */")"#;
let result = parse_component(input);
assert!(result.is_ok());
let component = result.unwrap();
if let Some(Argument::Positioned { value, .. }) = component.arguments.arguments.first() {
assert!(matches!(value, Value::String(s) if s.contains("/* not a comment */")));
} else {
panic!("Expected positional string argument");
}
}
#[test]
fn test_unclosed_block_comment_error() {
let input = r#"/* unclosed comment Text("Hello")"#;
let result = parse_component(input);
assert!(result.is_err());
}
#[test]
fn test_empty_line_comment() {
let input = r#"
//
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
}
#[test]
fn test_empty_block_comment() {
let input = r#"
/**/
Text("Hello")
"#;
let result = parse_component(input);
assert!(result.is_ok());
}
#[test]
fn test_empty_braces_with_applicators() {
let input = r#"Column { }.padding(16)"#;
let result = parse_component(input);
println!("Result: {:?}", result);
assert!(
result.is_ok(),
"Should parse empty braces with applicators: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert!(component.children.is_empty());
assert_eq!(component.applicators.len(), 1);
assert_eq!(component.applicators[0].name, "padding");
}
#[test]
fn test_empty_braces_with_custom_applicator_and_identifier_arg() {
let input = r#"Column { }.test(something)"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse empty braces with .test(something): {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert!(component.children.is_empty());
assert_eq!(component.applicators.len(), 1);
assert_eq!(component.applicators[0].name, "test");
assert_eq!(
component.applicators[0].arguments.get_positioned(0),
Some(&Value::String("something".to_string()))
);
}
#[test]
fn test_empty_braces_no_applicators() {
let input = r#"Column { }"#;
let result = parse_component(input);
println!("Result: {:?}", result);
assert!(result.is_ok(), "Should parse empty braces: {:?}", result);
let component = result.unwrap();
assert_eq!(component.name, "Column");
assert!(component.children.is_empty());
}
#[test]
fn test_breakpoint_value_map_simple() {
let input = r#"Text("Hello").fontSize({default: 16, md: 18, lg: 24})"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse breakpoint value map: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.applicators.len(), 1);
assert_eq!(component.applicators[0].name, "fontSize");
let arg = component.applicators[0].arguments.get_positioned(0);
match arg {
Some(Value::Map(map)) => {
assert_eq!(map.get("default"), Some(&Value::Number(16.0)));
assert_eq!(map.get("md"), Some(&Value::Number(18.0)));
assert_eq!(map.get("lg"), Some(&Value::Number(24.0)));
}
_ => panic!("Expected Map value, got: {:?}", arg),
}
}
#[test]
fn test_breakpoint_value_map_multiple_applicators() {
let input = r#"Text("Hello").fontSize({default: 16, md: 18}).padding({default: 8, lg: 32})"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse multiple breakpoint maps: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.applicators.len(), 2);
assert_eq!(component.applicators[0].name, "fontSize");
assert_eq!(component.applicators[1].name, "padding");
assert!(matches!(
component.applicators[0].arguments.get_positioned(0),
Some(Value::Map(_))
));
assert!(matches!(
component.applicators[1].arguments.get_positioned(0),
Some(Value::Map(_))
));
}
#[test]
fn test_breakpoint_value_map_with_strings() {
let input = r#"Text("Hello").color({default: "black", dark: "white"})"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse breakpoint map with strings: {:?}",
result
);
let component = result.unwrap();
let arg = component.applicators[0].arguments.get_positioned(0);
match arg {
Some(Value::Map(map)) => {
assert_eq!(
map.get("default"),
Some(&Value::String("\"black\"".to_string()))
);
assert_eq!(
map.get("dark"),
Some(&Value::String("\"white\"".to_string()))
);
}
_ => panic!("Expected Map value, got: {:?}", arg),
}
}
#[test]
fn test_tw_applicator_simple() {
let input = r#"Text("Hello").tw("p-4 text-blue-500")"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse .tw() applicator: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.applicators.len(), 1);
assert_eq!(component.applicators[0].name, "tw");
let arg = component.applicators[0].arguments.get_positioned(0);
assert_eq!(
arg,
Some(&Value::String("\"p-4 text-blue-500\"".to_string()))
);
}
#[test]
fn test_tw_applicator_with_responsive_classes() {
let input = r#"Text("Hello").tw("p-2 md:p-4 lg:p-8 hover:bg-blue-500")"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse .tw() with responsive classes: {:?}",
result
);
let component = result.unwrap();
let arg = component.applicators[0].arguments.get_positioned(0);
assert_eq!(
arg,
Some(&Value::String(
"\"p-2 md:p-4 lg:p-8 hover:bg-blue-500\"".to_string()
))
);
}
#[test]
fn test_tw_applicator_combined_with_regular_applicators() {
let input = r#"Text("Hello").tw("p-4 rounded-lg").fontSize(18).onClick("@actions.click")"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse .tw() combined with applicators: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.applicators.len(), 3);
assert_eq!(component.applicators[0].name, "tw");
assert_eq!(component.applicators[1].name, "fontSize");
assert_eq!(component.applicators[2].name, "onClick");
}
#[test]
fn test_tw_and_breakpoint_maps_combined() {
let input = r#"Text("Hello").tw("rounded-lg shadow").fontSize({default: 16, md: 20})"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse .tw() with breakpoint maps: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.applicators.len(), 2);
assert_eq!(component.applicators[0].name, "tw");
assert_eq!(component.applicators[1].name, "fontSize");
assert!(matches!(
component.applicators[0].arguments.get_positioned(0),
Some(Value::String(_))
));
assert!(matches!(
component.applicators[1].arguments.get_positioned(0),
Some(Value::Map(_))
));
}
#[test]
fn test_single_quoted_strings() {
let input = r#"Text('hello "world"')"#;
let result = parse_component(input);
assert!(result.is_ok(), "Single-quoted strings should parse");
let comp = result.unwrap();
assert_eq!(comp.arguments.arguments.len(), 1);
match comp.arguments.get_positioned(0) {
Some(Value::String(s)) => assert_eq!(s, r#"'hello "world"'"#),
other => panic!("Expected string, got {:?}", other),
}
}
#[test]
fn test_escaped_quotes_in_strings() {
let input = r#"Text("hello \"world\"")"#;
let result = parse_component(input);
assert!(result.is_ok(), "Escaped quotes should parse");
let comp = result.unwrap();
assert_eq!(comp.arguments.arguments.len(), 1);
match comp.arguments.get_positioned(0) {
Some(Value::String(s)) => assert_eq!(s, r#""hello \"world\"""#),
other => panic!("Expected string, got {:?}", other),
}
}
#[test]
fn test_single_quotes_with_interpolation_and_nested_doubles() {
let input = r##"Row {
Text("hello")
}
.background('@{item.role === "user" ? "#007bff" : "#333"}')
"##;
let result = parse_component(input);
assert!(
result.is_ok(),
"Single-quoted string with embedded double quotes should parse"
);
let comp = result.unwrap();
assert_eq!(comp.applicators.len(), 1);
assert_eq!(comp.applicators[0].name, "background");
}
#[test]
fn test_unescaped_nested_quotes_still_fail() {
let input = r#"Text("hello "world"")"#;
let result = parse_component(input);
assert!(
result.is_err(),
"Unescaped nested double quotes should fail"
);
}
#[test]
fn test_complex_template_with_list_and_applicators() {
let input = r#"Column {
Text("wow really").color(black)
List(@state.messages) {
Row {
Text("@{item.content}")
}
.background("@{item.color}")
.padding(16)
.borderRadius(8)
.margin(8)
.alignItems("center")
.justifyContent("center")
.width("100%")
.height("100%")
}
Row {
Textarea(placeholder: "Enter message...")
.bind(@state.input)
.height(100)
.width("80%")
}
Button {
Text("Send")
.onClick("@actions.sendMessage", message: @state.input)
}
}.fillMaxHeight()"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Complex template with list, applicators, and bind should parse"
);
let comp = result.unwrap();
assert_eq!(comp.name, "Column");
assert_eq!(comp.children.len(), 4); assert_eq!(comp.applicators.len(), 1); assert_eq!(comp.applicators[0].name, "fillMaxHeight");
}
#[test]
fn test_data_source_reference_simple() {
let input = "ForEach(items: @spacetime.messages)";
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse data source reference: {:?}",
result
);
let component = result.unwrap();
let items = component.arguments.get_named("items");
assert!(matches!(items, Some(Value::Reference(s)) if s == "spacetime.messages"));
}
#[test]
fn test_data_source_reference_nested_path() {
let input = "Text(@firebase.user.profile.name)";
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse nested data source reference: {:?}",
result
);
let component = result.unwrap();
if let Some(Argument::Positioned { value, .. }) = component.arguments.arguments.first() {
assert!(matches!(value, Value::Reference(s) if s == "firebase.user.profile.name"));
} else {
panic!("Expected positional data source reference argument");
}
}
#[test]
fn test_data_source_reference_in_foreach() {
let input = r#"
ForEach(items: @spacetime.message, key: "id") {
Text("@{item.text}")
}
"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse ForEach with data source: {:?}",
result
);
let component = result.unwrap();
let items = component.arguments.get_named("items");
assert!(matches!(items, Some(Value::Reference(s)) if s == "spacetime.message"));
assert_eq!(component.children.len(), 1);
}
#[test]
fn test_data_source_reference_with_applicators() {
let input = r#"
Text(@spacetime.user.displayName)
.fontSize(18)
.color(blue)
"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse data source ref with applicators: {:?}",
result
);
let component = result.unwrap();
assert_eq!(component.applicators.len(), 2);
}
#[test]
fn test_multiple_data_source_references() {
let input = r#"Component(users: @spacetime.user, messages: @firebase.chat.messages)"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse multiple data source references: {:?}",
result
);
let component = result.unwrap();
let users = component.arguments.get_named("users");
let messages = component.arguments.get_named("messages");
assert!(matches!(users, Some(Value::Reference(s)) if s == "spacetime.user"));
assert!(matches!(messages, Some(Value::Reference(s)) if s == "firebase.chat.messages"));
}
#[test]
fn test_data_source_reference_mixed_with_state() {
let input = r#"Component(data: @spacetime.messages, count: @state.count)"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Should parse mixed data source and state refs: {:?}",
result
);
let component = result.unwrap();
let data = component.arguments.get_named("data");
let count = component.arguments.get_named("count");
assert!(matches!(data, Some(Value::Reference(_))));
assert!(matches!(count, Some(Value::Reference(_))));
}
#[test]
fn test_escaped_quote_not_unclosed_string() {
let input = r#"Text("hello \"world\"")"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Escaped quotes should not cause unclosed string detection: {:?}",
result
);
}
#[test]
fn test_single_quoted_escaped_not_unclosed() {
let input = r#"Text('it\'s working')"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Escaped single quotes should not cause unclosed string: {:?}",
result
);
}
#[test]
fn test_error_detection_with_escaped_quotes_in_unclosed() {
use crate::error::format_parse_errors;
let input = r#"Text("hello \"world)"#;
let result = parse_component(input);
assert!(
result.is_err(),
"Should fail - string is genuinely unclosed"
);
let errors = result.unwrap_err();
let formatted = format_parse_errors("test.hyp", input, &errors);
assert!(
formatted.contains("unclosed string"),
"Error should mention unclosed string, got: {}",
formatted
);
}
#[test]
fn test_nested_block_comment() {
let input = r#"
/* outer /* inner */ still comment */
Text("Hello")
"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Nested block comments should parse: {:?}",
result
);
let comp = result.unwrap();
assert_eq!(comp.name, "Text");
}
#[test]
fn test_deeply_nested_block_comment() {
let input = r#"
/* level 1 /* level 2 /* level 3 */ back to 2 */ back to 1 */
Text("Hello")
"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Deeply nested block comments should parse: {:?}",
result
);
}
#[test]
fn test_block_comment_with_url() {
let input = r#"
/* See: https://example.com/path */
Text("Hello")
"#;
let result = parse_component(input);
assert!(
result.is_ok(),
"Block comment with URL should parse: {:?}",
result
);
}
#[test]
fn test_very_large_number_rejected() {
let huge = format!("Text({})", "9".repeat(400));
let result = parse_component(&huge);
assert!(
result.is_err(),
"Very large number should produce a parse error, not silently become infinity"
);
}
#[test]
fn test_normal_large_number_accepted() {
let input = "Text(999999999999)";
let result = parse_component(input);
assert!(
result.is_ok(),
"Large but representable numbers should still work: {:?}",
result
);
}
#[test]
fn test_negative_large_number_rejected() {
let huge = format!("Text(-{})", "9".repeat(400));
let result = parse_component(&huge);
assert!(
result.is_err(),
"Very large negative number should produce a parse error"
);
}
#[test]
fn test_unclosed_double_quote_string() {
let input = r#"Text("unclosed)"#;
let result = parse_component(input);
assert!(
result.is_err(),
"Should fail - unclosed double-quoted string"
);
let errors = result.unwrap_err();
let err_str = format!("{:?}", errors);
assert!(
err_str.contains("closing quote"),
"Error should mention closing quote, got: {}",
err_str
);
}
#[test]
fn test_triple_quote_string_error() {
let input = r#"Text(""")"#;
let result = parse_component(input);
assert!(
result.is_err(),
"Should fail - malformed triple-quote string"
);
}
#[test]
fn test_escaped_closing_quote_unclosed() {
let input = "Text(\"hello\\\")";
let result = parse_component(input);
assert!(
result.is_err(),
"Should fail - escaped closing quote means string is unclosed"
);
let errors = result.unwrap_err();
let err_str = format!("{:?}", errors);
assert!(
err_str.contains("closing quote"),
"Error should mention closing quote, got: {}",
err_str
);
}
#[test]
fn test_unclosed_single_quote_string() {
let input = "Text('unclosed)";
let result = parse_component(input);
assert!(
result.is_err(),
"Should fail - unclosed single-quoted string"
);
let errors = result.unwrap_err();
let err_str = format!("{:?}", errors);
assert!(
err_str.contains("closing quote"),
"Error should mention closing quote, got: {}",
err_str
);
}