#![cfg(feature = "compiler")]
use crate::compiler::compile_template;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::str::FromStr;
fn compile(input: &str) -> String {
compile_template(input, None, 0).unwrap().to_string()
}
#[test]
fn test_static_template_builds_source_string() {
let input = quote! { const value = 1; };
let s = compile(&input.to_string());
assert!(
s.contains("__stmts"),
"Expected __stmts for code generation. Got: {}",
s
);
}
#[test]
fn test_interpolation_expr_binding() {
let s = compile("const value = @{expr};");
assert!(
s.contains("to_ts_expr") || s.contains("ToTsExpr"),
"Expected ToTsExpr for expression interpolation. Got: {}",
s
);
}
#[test]
fn test_ident_block_binding() {
let input = TokenStream2::from_str("const foo@{bar} = 1;").unwrap();
let s = compile(&input.to_string());
assert!(
s.contains("push_str") || (s.contains("foo") && s.contains("bar")),
"Expected implicit concatenation of foo and bar. Got: {}",
s
);
}
#[test]
fn test_if_expression_in_statement() {
let s = compile("const status = {#if cond} \"a\" {:else} \"b\" {/if}");
assert!(
s.contains("if cond"),
"Expected Rust if for expression control. Got: {}",
s
);
}
#[test]
fn test_for_expression() {
let s = compile("const items = [{#for x in list} x.name {/for}]");
assert!(
s.contains("into_iter") && s.contains("map"),
"Expected iterator map pattern for for-expression. Got: {}",
s
);
}
#[test]
fn test_while_expression() {
let s = compile("const vals = {#while cond} get_next() {/while}");
assert!(
s.contains("from_fn"),
"Expected from_fn for while-expression. Got: {}",
s
);
}
#[test]
fn test_match_expression() {
let s = compile("const val = {#match x}{:case Some(v)} v {:case None} 0 {/match}");
assert!(
s.contains("match"),
"Expected Rust match for match-expression. Got: {}",
s
);
}
#[test]
fn test_nested_control_expressions() {
let s = compile("const x = {#if a} {#if b} 1 {:else} 2 {/if} {:else} 3 {/if}");
assert!(
s.contains("if a") || s.contains("if b"),
"Expected nested if expressions. Got: {}",
s
);
}
#[test]
fn test_if_expression_requires_else() {
use crate::compiler::compile_template;
let result = compile_template("const x = {#if cond} \"a\" {/if}", None, 0);
assert!(
result.is_err(),
"Expected error for if-expression without else branch"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("else") || err_msg.contains("MissingElseBranch"),
"Expected error message about missing else. Got: {}",
err_msg
);
}
#[test]
fn test_string_literal_interpolation() {
let s = compile("const msg = `Hello @{name}!`;");
assert!(
s.contains("Tpl") && s.contains("name"),
"Expected template literal with name placeholder. Got: {}",
s
);
}
#[test]
fn test_backtick_template_literal_syntax() {
let input =
TokenStream2::from_str("const html = \"'^<@{tag}>${content}</@{tag}>^'\";").unwrap();
let s = compile(&input.to_string());
assert!(
s.contains("__stmts"),
"Expected __stmts for code generation. Got: {}",
s
);
}
#[test]
#[ignore = "Doc comment handling removed in codegen simplification - to be reimplemented if needed"]
fn test_doc_attribute_comment_is_emitted() {
let input = quote! {
#[doc = "Generated field"]
const value = 1;
};
let s = compile(&input.to_string());
assert!(
s.contains("Generated field"),
"Expected doc comments to be preserved in generated output. Got: {}",
s
);
}
#[test]
fn test_block_comment_is_stripped() {
let tokens = TokenStream2::from_str("/* block comment */ const value = 1;").unwrap();
let raw = tokens.to_string();
assert!(
!raw.contains("block comment"),
"Expected block comments to be stripped from TokenStream"
);
}
#[test]
fn test_function_name_interpolation_is_ident() {
let input = TokenStream2::from_str("export function @{fn_name}() {}").unwrap();
let s = compile(&input.to_string());
assert!(
s.contains("to_ts_ident")
|| s.contains("ToTsIdent")
|| s.contains("to_ts_expr")
|| s.contains("ToTsExpr"),
"Expected identifier/expression handling for function name. Got: {}",
s
);
}
#[test]
fn test_dynamic_function_body() {
let input = TokenStream2::from_str("function test() { {#if true} console.log(\"hi\"); {/if} }")
.unwrap();
let input_str = input.to_string();
eprintln!("TokenStream string: {}", input_str);
let s = compile(&input_str);
assert!(
s.contains("if true"),
"Expected Rust if statement. Got: {}",
s
);
}
#[test]
fn test_debug_doc_comment_tokenstream() {
let input = quote! {
export function @{fn_name}(value: @{type_param}): string {
return @{body_expr};
}
};
let template_str = input.to_string();
eprintln!("Template string from TokenStream: {}", template_str);
assert!(
template_str.contains("doc =") || template_str.contains("Doc comment"),
"TokenStream should preserve doc comment somehow. Got: {}",
template_str
);
}
#[test]
fn test_function_with_doc_comment_uses_ident() {
let input = quote! {
export function @{fn_name}(value: @{type_param}): string {
return @{body_expr};
}
};
let s = compile(&input.to_string());
eprintln!("Generated code:\n{}", s);
assert!(
s.contains("to_ts_ident"),
"fn_name should use ToTsIdent for function name. Generated:\n{}",
s
);
assert!(
s.contains("to_ts_type"),
"type_param should use ToTsType for parameter type. Generated:\n{}",
s
);
assert!(
s.contains("to_ts_expr"),
"body_expr should use ToTsExpr for expression. Generated:\n{}",
s
);
}
#[test]
fn test_multiple_functions_with_doc_comments() {
let input = quote! {
export function @{fn_name1}(value: @{type1}): string {
return @{body1};
}
export function @{fn_name2}(value: @{type2}): Record<string, unknown> {
return @{body2};
}
};
let s = compile(&input.to_string());
eprintln!("Generated code for multiple functions:\n{}", s);
let ident_count = s.matches("to_ts_ident").count();
assert_eq!(
ident_count, 2,
"Expected 2 function names to use ToTsIdent, found {}. Generated:\n{}",
ident_count, s
);
let type_count = s.matches("to_ts_type").count();
assert_eq!(
type_count, 2,
"Expected 2 parameter types to use ToTsType, found {}. Generated:\n{}",
type_count, s
);
let expr_count = s.matches("to_ts_expr").count();
assert_eq!(
expr_count, 2,
"Expected 2 body expressions to use ToTsExpr, found {}. Generated:\n{}",
expr_count, s
);
}
#[test]
fn test_for_loop_field_interpolation_in_interface() {
let input = TokenStream2::from_str(
r#"export interface FieldControllers {
{#for field in fields}
readonly @{field.name}: FieldController<@{field.ts_type}>;
{/for}
}"#,
)
.unwrap();
let s = compile(&input.to_string());
eprintln!("Generated code for interface with for loop:\n{}", s);
assert!(
s.contains("field . name") || s.contains("field.name"),
"Expected field.name reference in generated code. Got:\n{}",
s
);
assert!(
s.contains("field . ts_type") || s.contains("field.ts_type"),
"Expected field.ts_type reference in generated code. Got:\n{}",
s
);
}
#[test]
fn test_for_loop_generates_runtime_iteration() {
let input = TokenStream2::from_str(
r#"{#for item in items}
const @{item.name} = @{item.value};
{/for}"#,
)
.unwrap();
let s = compile(&input.to_string());
eprintln!("Generated code for for loop:\n{}", s);
assert!(
s.contains("for item in items"),
"Expected Rust for loop in generated code. Got:\n{}",
s
);
}
#[test]
fn test_within_position_extracts_body_correctly() {
let wrapped = "class __MF_DUMMY__ { readonly foo: string; readonly bar: number; }";
let s = compile_template(wrapped, Some("Within"), 0)
.unwrap()
.to_string();
eprintln!("Generated code for Within body:\n{}", s);
assert!(
s.contains("__MF_DUMMY__") || s.contains("__stmts"),
"Expected statement building in generated code. Got:\n{}",
s
);
}
#[test]
fn test_interpolation_in_object_property_position() {
let input =
TokenStream2::from_str(r#"const obj = { @{field_name}: @{field_value} };"#).unwrap();
let s = compile(&input.to_string());
eprintln!("Generated code for object property interpolation:\n{}", s);
assert!(
s.contains("field_name"),
"Expected field_name in generated code. Got:\n{}",
s
);
assert!(
s.contains("field_value"),
"Expected field_value in generated code. Got:\n{}",
s
);
}
#[test]
fn test_multiple_interpolations_same_variable() {
let input = TokenStream2::from_str(
r#"const @{name}Obj = {};
let current = @{name}Obj;
obj.@{name} = @{name}Obj;"#,
)
.unwrap();
let s = compile(&input.to_string());
eprintln!(
"Generated code for multiple same-variable interpolations:\n{}",
s
);
let name_count = s.matches("name").count();
assert!(
name_count >= 4,
"Expected 'name' to appear at least 4 times (for each @{{name}}). Found {}. Got:\n{}",
name_count,
s
);
}
#[test]
fn test_conditional_in_interface_member() {
let input = TokenStream2::from_str(
r#"export interface Test {
{#if is_array}
readonly items: ArrayFieldController<@{element_type}>;
{:else}
readonly value: FieldController<@{value_type}>;
{/if}
}"#,
)
.unwrap();
let s = compile(&input.to_string());
eprintln!("Generated code for conditional in interface:\n{}", s);
assert!(
s.contains("if is_array"),
"Expected Rust if statement. Got:\n{}",
s
);
assert!(s.contains("else"), "Expected else branch. Got:\n{}", s);
}
#[test]
fn test_for_loop_with_tuple_pattern_in_function_body() {
let input = r#"export function compare(a: T, b: T): number {
if (a === b) return 0;
{#for (name, expr) in &steps}
const @{name.clone()} = @{expr.clone()};
if (@{name.clone()} !== 0) return @{name.clone()};
{/for}
return 0;
}"#;
eprintln!("=== INPUT ===\n{}\n=== END INPUT ===\n", input);
let s = compile(input);
eprintln!(
"Generated code for for loop with tuple in function body:\n{}",
s
);
assert!(
s.contains("for (name , expr) in & steps") || s.contains("for ( name , expr ) in"),
"Expected Rust for loop with tuple pattern. Got:\n{}",
s
);
assert!(
s.contains("name . clone"),
"Expected name.clone() inside loop. Got:\n{}",
s
);
}
#[test]
fn test_placeholder_followed_by_method_call() {
let input = r#"
if (@{pending_ref_expr}.is(result)) {
return { __pendingIdx: idx, __refId: result.id };
}
"#;
let s = compile(input);
eprintln!("Generated code for placeholder method call:\n{}", s);
assert!(
s.contains("is") && (s.contains("result") || s.contains("pending_ref_expr")),
"Expected placeholder.is(result) to be parsed as call. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_is() {
let input = r#"const result = obj.is(value);"#;
let s = compile(input);
assert!(
s.contains("is"),
"Expected .is() method call to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_as() {
let input = r#"const result = obj.as(Type);"#;
let s = compile(input);
assert!(
s.contains("as"),
"Expected .as() method call to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_type() {
let input = r#"const t = obj.type;"#;
let s = compile(input);
assert!(
s.contains("type"),
"Expected .type property access to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_default() {
let input = r#"const d = module.default;"#;
let s = compile(input);
assert!(
s.contains("default"),
"Expected .default property access to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_delete() {
let input = r#"await db.delete(id);"#;
let s = compile(input);
assert!(
s.contains("delete"),
"Expected .delete() method call to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_get_set() {
let input = r#"
const val = store.get(key);
store.set(key, value);
"#;
let s = compile(input);
assert!(
s.contains("get") && s.contains("set"),
"Expected .get() and .set() method calls to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_in_chain() {
let input = r#"const result = obj.type.is.default.get();"#;
let s = compile(input);
assert!(
s.contains("type") && s.contains("is") && s.contains("default") && s.contains("get"),
"Expected chained keyword property access to parse. Got:\n{}",
s
);
}
#[test]
fn test_placeholder_with_keyword_method_chain() {
let input = r#"const result = @{expr}.type.is(value).get();"#;
let s = compile(input);
assert!(
s.contains("type") && s.contains("is") && s.contains("get"),
"Expected placeholder with keyword method chain to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_new() {
let input = r#"const instance = factory.new(config);"#;
let s = compile(input);
assert!(
s.contains("new"),
"Expected .new() method call to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_return() {
let input = r#"const ret = response.return;"#;
let s = compile(input);
assert!(
s.contains("return"),
"Expected .return property access to parse. Got:\n{}",
s
);
}
#[test]
fn test_keyword_as_property_name_typeof() {
let input = r#"const t = schema.typeof(value);"#;
let s = compile(input);
assert!(
s.contains("typeof"),
"Expected .typeof() method call to parse. Got:\n{}",
s
);
}