feat(tera_factory): Implement hot reload example for Tera templates with Rhai
This commit adds a comprehensive hot reload example that demonstrates how to use the rhai_system for dynamic template rendering with Tera. Key improvements include: - Refactor the example to use external script files instead of hardcoded Rhai code - Implement proper module imports using the BasePathModuleResolver approach - Fix template rendering by using keyword arguments in Tera function calls - Add support for hot reloading both main and utility scripts - Remove unnecessary output file generation to keep the example clean - Fix compatibility issues with Rhai functions (avoiding to_string with parameters) This example showcases how changes to Rhai scripts are automatically detected and applied to rendered templates without restarting the application, providing a smooth development experience.
This commit is contained in:
338
tera_factory/tests/function_adapter_tests.rs
Normal file
338
tera_factory/tests/function_adapter_tests.rs
Normal file
@@ -0,0 +1,338 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use rhai::{Engine, AST};
|
||||
use tera::{Value, Function};
|
||||
|
||||
use tera_factory::function_adapter::RhaiFunctionAdapter;
|
||||
|
||||
// Helper function to create a test AST with a simple function
|
||||
fn create_test_ast(fn_name: &str, fn_body: &str) -> AST {
|
||||
let engine = Engine::new();
|
||||
|
||||
// Create the full script with the function
|
||||
let script = format!("fn {}() {{ {} }}", fn_name, fn_body);
|
||||
|
||||
println!("Creating test AST with script: {}", script);
|
||||
engine.compile(script).expect("Failed to compile test script")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tera_to_rhai_conversion() {
|
||||
println!("Running test_tera_to_rhai_conversion");
|
||||
|
||||
// Create a test function that echoes its input
|
||||
let ast = create_test_ast("echo", "params[0]");
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a function adapter
|
||||
let adapter = RhaiFunctionAdapter {
|
||||
fn_name: "echo".to_string(),
|
||||
hot_ast: hot_ast.clone(),
|
||||
};
|
||||
|
||||
// Test with different Tera value types
|
||||
let test_cases = vec![
|
||||
// String
|
||||
("string_value", Value::String("hello".to_string()), "hello"),
|
||||
// Integer
|
||||
("int_value", Value::Number(42.into()), "42"),
|
||||
// Float
|
||||
("float_value", Value::Number(serde_json::Number::from_f64(3.14).unwrap()), "3.14"),
|
||||
// Boolean
|
||||
("bool_value", Value::Bool(true), "true"),
|
||||
// Array
|
||||
("array_value", Value::Array(vec![
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
]), "[1, 2, 3]"),
|
||||
// Object
|
||||
("object_value", {
|
||||
let mut obj = serde_json::Map::new();
|
||||
obj.insert("name".to_string(), Value::String("John".to_string()));
|
||||
obj.insert("age".to_string(), Value::Number(30.into()));
|
||||
Value::Object(obj)
|
||||
}, "{\"age\": 30, \"name\": \"John\"}"),
|
||||
];
|
||||
|
||||
for (name, tera_value, expected_output) in test_cases {
|
||||
println!("Testing conversion of {}: {:?}", name, tera_value);
|
||||
|
||||
// Create args with the test value
|
||||
let mut args = HashMap::new();
|
||||
args.insert("value".to_string(), tera_value);
|
||||
|
||||
// Call the function adapter
|
||||
let result = adapter.call(&args);
|
||||
|
||||
// Verify the result
|
||||
assert!(result.is_ok(), "Failed to call function with {}", name);
|
||||
let result_value = result.unwrap();
|
||||
|
||||
// For direct value comparison instead of string comparison
|
||||
println!("Result for {}: {:?}", name, result_value);
|
||||
|
||||
// Compare based on the expected type
|
||||
match (name, &result_value) {
|
||||
("string_value", Value::String(s)) => {
|
||||
assert_eq!(s, "hello", "Incorrect string result for {}", name);
|
||||
},
|
||||
("int_value", Value::Number(n)) => {
|
||||
assert!(n.is_i64(), "Expected integer for {}", name);
|
||||
assert_eq!(n.as_i64().unwrap(), 42, "Incorrect integer result for {}", name);
|
||||
},
|
||||
("float_value", Value::Number(n)) => {
|
||||
assert!(n.is_f64(), "Expected float for {}", name);
|
||||
assert!((n.as_f64().unwrap() - 3.14).abs() < 0.001, "Incorrect float result for {}", name);
|
||||
},
|
||||
("bool_value", Value::Bool(b)) => {
|
||||
assert_eq!(*b, true, "Incorrect boolean result for {}", name);
|
||||
},
|
||||
("array_value", Value::Array(arr)) => {
|
||||
assert_eq!(arr.len(), 3, "Incorrect array length for {}", name);
|
||||
if let (Some(Value::Number(n1)), Some(Value::Number(n2)), Some(Value::Number(n3))) =
|
||||
(arr.get(0), arr.get(1), arr.get(2)) {
|
||||
assert_eq!(n1.as_i64().unwrap(), 1, "Incorrect first array element for {}", name);
|
||||
assert_eq!(n2.as_i64().unwrap(), 2, "Incorrect second array element for {}", name);
|
||||
assert_eq!(n3.as_i64().unwrap(), 3, "Incorrect third array element for {}", name);
|
||||
} else {
|
||||
panic!("Array elements have incorrect types for {}", name);
|
||||
}
|
||||
},
|
||||
("object_value", Value::Object(obj)) => {
|
||||
assert_eq!(obj.len(), 2, "Incorrect object size for {}", name);
|
||||
assert!(obj.contains_key("name"), "Object missing 'name' key for {}", name);
|
||||
assert!(obj.contains_key("age"), "Object missing 'age' key for {}", name);
|
||||
|
||||
if let Some(Value::String(name_val)) = obj.get("name") {
|
||||
assert_eq!(name_val, "John", "Incorrect name value for {}", name);
|
||||
} else {
|
||||
panic!("Name has incorrect type for {}", name);
|
||||
}
|
||||
|
||||
if let Some(Value::Number(age_val)) = obj.get("age") {
|
||||
assert_eq!(age_val.as_i64().unwrap(), 30, "Incorrect age value for {}", name);
|
||||
} else {
|
||||
panic!("Age has incorrect type for {}", name);
|
||||
}
|
||||
},
|
||||
_ => panic!("Unexpected result type for {}: {:?}", name, result_value),
|
||||
}
|
||||
}
|
||||
|
||||
println!("test_tera_to_rhai_conversion passed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_to_tera_conversion() {
|
||||
println!("Running test_rhai_to_tera_conversion");
|
||||
|
||||
// Create test functions that return different types
|
||||
let test_cases = vec![
|
||||
// Integer
|
||||
("return_int", "42", Value::Number(42.into())),
|
||||
// Float
|
||||
("return_float", "3.14", Value::Number(serde_json::Number::from_f64(3.14).unwrap())),
|
||||
// String
|
||||
("return_string", "\"hello\"", Value::String("hello".to_string())),
|
||||
// Boolean
|
||||
("return_bool", "true", Value::Bool(true)),
|
||||
// Array
|
||||
("return_array", "[1, 2, 3]", Value::Array(vec![
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
])),
|
||||
// Object/Map
|
||||
("return_map", "#{\"name\": \"John\", \"age\": 30}", {
|
||||
let mut obj = serde_json::Map::new();
|
||||
obj.insert("name".to_string(), Value::String("John".to_string()));
|
||||
obj.insert("age".to_string(), Value::Number(30.into()));
|
||||
Value::Object(obj)
|
||||
}),
|
||||
];
|
||||
|
||||
for (fn_name, fn_body, expected_value) in test_cases {
|
||||
println!("Testing conversion of Rhai {} to Tera value", fn_name);
|
||||
|
||||
// Create the AST with the test function
|
||||
let ast = create_test_ast(fn_name, fn_body);
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a function adapter
|
||||
let adapter = RhaiFunctionAdapter {
|
||||
fn_name: fn_name.to_string(),
|
||||
hot_ast: hot_ast.clone(),
|
||||
};
|
||||
|
||||
// Call the function adapter with empty args
|
||||
let args = HashMap::new();
|
||||
let result = adapter.call(&args);
|
||||
|
||||
// Verify the result
|
||||
assert!(result.is_ok(), "Failed to call function {}", fn_name);
|
||||
let result_value = result.unwrap();
|
||||
|
||||
println!("Result for {}: {:?}", fn_name, result_value);
|
||||
|
||||
// Compare with expected value
|
||||
match (&result_value, &expected_value) {
|
||||
(Value::Number(n1), Value::Number(n2)) => {
|
||||
// Special handling for numbers to handle float comparison
|
||||
if n1.is_f64() || n2.is_f64() {
|
||||
let f1 = n1.as_f64().unwrap();
|
||||
let f2 = n2.as_f64().unwrap();
|
||||
assert!((f1 - f2).abs() < 0.0001, "Incorrect float result for {}", fn_name);
|
||||
} else {
|
||||
assert_eq!(n1, n2, "Incorrect number result for {}", fn_name);
|
||||
}
|
||||
},
|
||||
(Value::Array(a1), Value::Array(a2)) => {
|
||||
assert_eq!(a1.len(), a2.len(), "Array length mismatch for {}", fn_name);
|
||||
// Compare each element
|
||||
for (i, (v1, v2)) in a1.iter().zip(a2.iter()).enumerate() {
|
||||
assert_eq!(v1, v2, "Array element {} mismatch for {}", i, fn_name);
|
||||
}
|
||||
},
|
||||
(Value::Object(o1), Value::Object(o2)) => {
|
||||
assert_eq!(o1.len(), o2.len(), "Object key count mismatch for {}", fn_name);
|
||||
// Compare each key-value pair
|
||||
for (k, v1) in o1 {
|
||||
assert!(o2.contains_key(k), "Missing key {} in object for {}", k, fn_name);
|
||||
assert_eq!(v1, o2.get(k).unwrap(), "Value mismatch for key {} in {}", k, fn_name);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
assert_eq!(result_value, expected_value, "Incorrect result for {}", fn_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("test_rhai_to_tera_conversion passed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_handling_in_function_adapter() {
|
||||
println!("Running test_error_handling_in_function_adapter");
|
||||
|
||||
// Create a test function that throws an error
|
||||
let ast = create_test_ast("throw_error", "throw \"This is a test error\";");
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a function adapter
|
||||
let adapter = RhaiFunctionAdapter {
|
||||
fn_name: "throw_error".to_string(),
|
||||
hot_ast: hot_ast.clone(),
|
||||
};
|
||||
|
||||
// Call the function adapter
|
||||
let args = HashMap::new();
|
||||
let result = adapter.call(&args);
|
||||
|
||||
// Verify that the call returns an error
|
||||
assert!(result.is_err(), "Function should return an error");
|
||||
let error = result.err().unwrap();
|
||||
println!("Error (expected): {:?}", error);
|
||||
|
||||
// Create a test function that doesn't exist
|
||||
let adapter_non_existent = RhaiFunctionAdapter {
|
||||
fn_name: "non_existent_function".to_string(),
|
||||
hot_ast: hot_ast.clone(),
|
||||
};
|
||||
|
||||
// Call the function adapter
|
||||
let result = adapter_non_existent.call(&args);
|
||||
|
||||
// Verify that the call returns an error
|
||||
assert!(result.is_err(), "Call to non-existent function should return an error");
|
||||
let error = result.err().unwrap();
|
||||
println!("Error for non-existent function (expected): {:?}", error);
|
||||
|
||||
println!("test_error_handling_in_function_adapter passed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_with_parameters() {
|
||||
println!("Running test_function_with_parameters");
|
||||
|
||||
// Create a test function that takes parameters
|
||||
let ast = create_test_ast("add", "params[0] + params[1]");
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a function adapter
|
||||
let adapter = RhaiFunctionAdapter {
|
||||
fn_name: "add".to_string(),
|
||||
hot_ast: hot_ast.clone(),
|
||||
};
|
||||
|
||||
// Create args with the test values
|
||||
let mut args = HashMap::new();
|
||||
args.insert("a".to_string(), Value::Number(5.into()));
|
||||
args.insert("b".to_string(), Value::Number(7.into()));
|
||||
|
||||
// Call the function adapter
|
||||
let result = adapter.call(&args);
|
||||
|
||||
// Verify the result
|
||||
assert!(result.is_ok(), "Failed to call function with parameters");
|
||||
let result_value = result.unwrap();
|
||||
|
||||
println!("Result for add(5, 7): {:?}", result_value);
|
||||
assert_eq!(result_value, Value::Number(12.into()), "Incorrect result for add function");
|
||||
|
||||
println!("test_function_with_parameters passed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concurrent_access() {
|
||||
use std::thread;
|
||||
|
||||
println!("Running test_concurrent_access");
|
||||
|
||||
// Create a test function
|
||||
let ast = create_test_ast("increment", "params[0] + 1");
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a function adapter
|
||||
let _adapter = RhaiFunctionAdapter {
|
||||
fn_name: "increment".to_string(),
|
||||
hot_ast: hot_ast.clone(),
|
||||
};
|
||||
|
||||
// Create multiple threads to call the function concurrently
|
||||
let mut handles = vec![];
|
||||
for i in 0..10 {
|
||||
// Create a new adapter for this thread with the same function name and AST
|
||||
let adapter_clone = RhaiFunctionAdapter {
|
||||
fn_name: "increment".to_string(),
|
||||
hot_ast: Arc::clone(&hot_ast),
|
||||
};
|
||||
let handle = thread::spawn(move || {
|
||||
// Create args with the test value
|
||||
let mut args = HashMap::new();
|
||||
args.insert("value".to_string(), Value::Number(i.into()));
|
||||
|
||||
// Call the function adapter
|
||||
let result = adapter_clone.call(&args);
|
||||
|
||||
// Verify the result
|
||||
assert!(result.is_ok(), "Failed to call function in thread {}", i);
|
||||
let result_value = result.unwrap();
|
||||
|
||||
println!("Thread {} result: {:?}", i, result_value);
|
||||
assert_eq!(result_value, Value::Number((i + 1).into()),
|
||||
"Incorrect result for increment function in thread {}", i);
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
for (i, handle) in handles.into_iter().enumerate() {
|
||||
handle.join().unwrap();
|
||||
println!("Thread {} completed successfully", i);
|
||||
}
|
||||
|
||||
println!("test_concurrent_access passed");
|
||||
}
|
383
tera_factory/tests/tera_factory_tests.rs
Normal file
383
tera_factory/tests/tera_factory_tests.rs
Normal file
@@ -0,0 +1,383 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use rhai::Engine;
|
||||
use tempfile::TempDir;
|
||||
use tera::Context;
|
||||
|
||||
use rhai_factory::RhaiFactory;
|
||||
use tera_factory::TeraFactory;
|
||||
|
||||
// Helper function to create a temporary directory with test files
|
||||
fn setup_test_environment() -> (TempDir, PathBuf, PathBuf) {
|
||||
// Create a temporary directory for our test files
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp directory");
|
||||
let temp_path = temp_dir.path();
|
||||
|
||||
// Create a templates directory
|
||||
let templates_dir = temp_path.join("templates");
|
||||
std::fs::create_dir(&templates_dir).expect("Failed to create templates directory");
|
||||
|
||||
// Create a scripts directory
|
||||
let scripts_dir = temp_path.join("scripts");
|
||||
std::fs::create_dir(&scripts_dir).expect("Failed to create scripts directory");
|
||||
|
||||
// Create a simple Rhai script with test functions
|
||||
let script_path = scripts_dir.join("test_functions.rhai");
|
||||
let mut script_file = File::create(&script_path).expect("Failed to create script file");
|
||||
script_file.write_all(b"
|
||||
// Test function to add two numbers
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
// Test function to format a string
|
||||
fn format_greeting(name) {
|
||||
`Hello, ${name}!`
|
||||
}
|
||||
").expect("Failed to write to script file");
|
||||
|
||||
// Create a simple template file with .html.tera extension
|
||||
let template_path = templates_dir.join("test.html.tera");
|
||||
let mut template_file = File::create(&template_path).expect("Failed to create template file");
|
||||
template_file.write_all(b"
|
||||
<html>
|
||||
<body>
|
||||
<h1>{{ format_greeting(name=name) }}</h1>
|
||||
<p>The sum of {{ a }} and {{ b }} is: {{ add(a=a, b=b) }}</p>
|
||||
</body>
|
||||
</html>
|
||||
").expect("Failed to write to template file");
|
||||
|
||||
println!("Test environment set up with:");
|
||||
println!(" Templates directory: {:?}", templates_dir);
|
||||
println!(" Scripts directory: {:?}", scripts_dir);
|
||||
|
||||
(temp_dir, templates_dir, scripts_dir)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_tera_engine() {
|
||||
println!("Running test_create_tera_engine");
|
||||
|
||||
// Set up the test environment
|
||||
let (temp_dir, templates_dir, _) = setup_test_environment();
|
||||
|
||||
// Create a TeraFactory
|
||||
let factory = TeraFactory::new();
|
||||
|
||||
// Create a Tera engine
|
||||
let tera = factory.create_tera_engine(&[&templates_dir]).expect("Failed to create Tera engine");
|
||||
|
||||
// Verify that the template was loaded
|
||||
assert!(tera.get_template_names().any(|name| name == "test.html.tera"),
|
||||
"Template 'test.html.tera' was not loaded");
|
||||
|
||||
println!("test_create_tera_engine passed");
|
||||
|
||||
// Keep temp_dir in scope until the end of the test
|
||||
drop(temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_tera_with_rhai() {
|
||||
println!("Running test_create_tera_with_rhai");
|
||||
|
||||
// Set up the test environment
|
||||
let (temp_dir, templates_dir, scripts_dir) = setup_test_environment();
|
||||
|
||||
// Create factories
|
||||
let rhai_factory = RhaiFactory::with_caching();
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Compile the Rhai script
|
||||
let script_path = scripts_dir.join("test_functions.rhai");
|
||||
println!("Compiling script: {:?}", script_path);
|
||||
|
||||
let ast = rhai_factory.compile_modules(&[&script_path], None)
|
||||
.expect("Failed to compile Rhai script");
|
||||
|
||||
// Verify that the functions were compiled
|
||||
let _engine = Engine::new();
|
||||
let fn_names: Vec<String> = ast.iter_functions().map(|f| f.name.to_string()).collect();
|
||||
println!("Compiled functions: {:?}", fn_names);
|
||||
assert!(fn_names.contains(&"add".to_string()), "Function 'add' was not compiled");
|
||||
assert!(fn_names.contains(&"format_greeting".to_string()), "Function 'format_greeting' was not compiled");
|
||||
|
||||
// Create a hot reloadable AST
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
println!("Creating Tera engine with Rhai integration");
|
||||
let tera = tera_factory.create_tera_with_rhai(&[&templates_dir], hot_ast.clone())
|
||||
.expect("Failed to create Tera engine with Rhai");
|
||||
|
||||
// Render the template
|
||||
let mut context = Context::new();
|
||||
context.insert("name", "World");
|
||||
context.insert("a", &10);
|
||||
context.insert("b", &5);
|
||||
|
||||
println!("Rendering template with context: {:?}", context);
|
||||
let rendered = tera.render("test.html.tera", &context).expect("Failed to render template");
|
||||
|
||||
// Verify the rendered output
|
||||
println!("Rendered output: {}", rendered);
|
||||
assert!(rendered.contains("Hello, World!"), "Rendered output does not contain greeting");
|
||||
assert!(rendered.contains("The sum of 10 and 5 is: 15"), "Rendered output does not contain correct sum");
|
||||
|
||||
println!("test_create_tera_with_rhai passed");
|
||||
|
||||
// Keep temp_dir in scope until the end of the test
|
||||
drop(temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hot_reload() {
|
||||
println!("Running test_hot_reload");
|
||||
|
||||
// Set up the test environment
|
||||
let (temp_dir, templates_dir, scripts_dir) = setup_test_environment();
|
||||
|
||||
// Create factories
|
||||
let rhai_factory = RhaiFactory::with_hot_reload().expect("Failed to create RhaiFactory with hot reload");
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Compile the Rhai script
|
||||
let script_path = scripts_dir.join("test_functions.rhai");
|
||||
println!("Compiling script: {:?}", script_path);
|
||||
|
||||
let ast = rhai_factory.compile_modules(&[&script_path], None)
|
||||
.expect("Failed to compile Rhai script");
|
||||
|
||||
// Create a hot reloadable AST
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
println!("Enabling hot reloading");
|
||||
let _handle = rhai_factory.enable_hot_reload(
|
||||
hot_ast.clone(),
|
||||
&[&script_path],
|
||||
None,
|
||||
Some(Box::new(|| println!("Script reloaded!")))
|
||||
).expect("Failed to enable hot reloading");
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
println!("Creating Tera engine with Rhai integration");
|
||||
let tera = tera_factory.create_tera_with_rhai(&[&templates_dir], hot_ast.clone())
|
||||
.expect("Failed to create Tera engine with Rhai");
|
||||
|
||||
// Render the template before modification
|
||||
let mut context = Context::new();
|
||||
context.insert("name", "World");
|
||||
context.insert("a", &10);
|
||||
context.insert("b", &5);
|
||||
|
||||
println!("Rendering template before script modification");
|
||||
let rendered_before = tera.render("test.html.tera", &context).expect("Failed to render template");
|
||||
|
||||
// Verify the rendered output before modification
|
||||
println!("Rendered output before: {}", rendered_before);
|
||||
assert!(rendered_before.contains("Hello, World!"), "Rendered output does not contain greeting");
|
||||
assert!(rendered_before.contains("The sum of 10 and 5 is: 15"), "Rendered output does not contain correct sum");
|
||||
|
||||
// Modify the script file
|
||||
println!("Modifying script file");
|
||||
let mut script_file = File::create(&script_path).expect("Failed to create script file");
|
||||
script_file.write_all(b"
|
||||
// Modified test function to add two numbers
|
||||
fn add(a, b) {
|
||||
a + b + 1 // Changed to add 1 to the result
|
||||
}
|
||||
|
||||
// Modified test function to format a string
|
||||
fn format_greeting(name) {
|
||||
`Greetings, ${name}!` // Changed greeting format
|
||||
}
|
||||
").expect("Failed to write to script file");
|
||||
|
||||
// Check for changes
|
||||
println!("Checking for script changes");
|
||||
let changes_detected = rhai_factory.check_for_changes().expect("Failed to check for changes");
|
||||
assert!(changes_detected, "Changes were not detected");
|
||||
|
||||
// Render the template after modification
|
||||
println!("Rendering template after script modification");
|
||||
let rendered_after = tera.render("test.html.tera", &context).expect("Failed to render template");
|
||||
|
||||
// Verify the rendered output after modification
|
||||
println!("Rendered output after: {}", rendered_after);
|
||||
assert!(rendered_after.contains("Greetings, World!"), "Rendered output does not contain modified greeting");
|
||||
assert!(rendered_after.contains("The sum of 10 and 5 is: 16"), "Rendered output does not contain modified sum");
|
||||
|
||||
println!("test_hot_reload passed");
|
||||
|
||||
// Keep temp_dir in scope until the end of the test
|
||||
drop(temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_handling() {
|
||||
println!("Running test_error_handling");
|
||||
|
||||
// Set up the test environment
|
||||
let (temp_dir, templates_dir, scripts_dir) = setup_test_environment();
|
||||
|
||||
// Create factories
|
||||
let rhai_factory = RhaiFactory::with_caching();
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Create a script with syntax errors
|
||||
let invalid_script_path = scripts_dir.join("invalid.rhai");
|
||||
let mut invalid_script_file = File::create(&invalid_script_path).expect("Failed to create invalid script file");
|
||||
invalid_script_file.write_all(b"
|
||||
// This script has a syntax error
|
||||
fn broken_function(a, b { // Missing closing parenthesis
|
||||
a + b
|
||||
}
|
||||
").expect("Failed to write to invalid script file");
|
||||
|
||||
// Try to compile the invalid script
|
||||
println!("Attempting to compile invalid script");
|
||||
let compile_result = rhai_factory.compile_modules(&[&invalid_script_path], None);
|
||||
|
||||
// Verify that compilation fails with an error
|
||||
assert!(compile_result.is_err(), "Compilation of invalid script should fail");
|
||||
println!("Compilation error (expected): {:?}", compile_result.err());
|
||||
|
||||
// Create a template with invalid syntax
|
||||
let invalid_template_path = templates_dir.join("invalid.html.tera");
|
||||
let mut invalid_template_file = File::create(&invalid_template_path).expect("Failed to create invalid template file");
|
||||
invalid_template_file.write_all(b"
|
||||
<html>
|
||||
<body>
|
||||
<h1>{{ unclosed tag </h1>
|
||||
</body>
|
||||
</html>
|
||||
").expect("Failed to write to invalid template file");
|
||||
|
||||
// Try to create a Tera engine with the invalid template
|
||||
println!("Attempting to create Tera engine with invalid template");
|
||||
let tera_result = tera_factory.create_tera_engine(&[&templates_dir]);
|
||||
|
||||
// Verify that Tera creation still succeeds (Tera loads valid templates and logs errors for invalid ones)
|
||||
assert!(tera_result.is_ok(), "Tera engine creation should succeed even with invalid templates");
|
||||
|
||||
println!("test_error_handling passed");
|
||||
|
||||
// Keep temp_dir in scope until the end of the test
|
||||
drop(temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_adapter() {
|
||||
println!("Running test_function_adapter");
|
||||
|
||||
// Set up the test environment
|
||||
let (temp_dir, templates_dir, scripts_dir) = setup_test_environment();
|
||||
|
||||
// Create factories
|
||||
let rhai_factory = RhaiFactory::with_caching();
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Create a script with functions that use different parameter types
|
||||
let types_script_path = scripts_dir.join("types.rhai");
|
||||
let mut types_script_file = File::create(&types_script_path).expect("Failed to create types script file");
|
||||
types_script_file.write_all(b"
|
||||
// Function that returns an integer
|
||||
fn return_int() {
|
||||
42
|
||||
}
|
||||
|
||||
// Function that returns a float
|
||||
fn return_float() {
|
||||
3.14159
|
||||
}
|
||||
|
||||
// Function that returns a string
|
||||
fn return_string() {
|
||||
\"Hello, World!\"
|
||||
}
|
||||
|
||||
// Function that returns a boolean
|
||||
fn return_bool() {
|
||||
true
|
||||
}
|
||||
|
||||
// Function that returns an array
|
||||
fn return_array() {
|
||||
[1, 2, 3, 4, 5]
|
||||
}
|
||||
|
||||
// Function that returns a map
|
||||
fn return_map() {
|
||||
#{
|
||||
\"name\": \"John\",
|
||||
\"age\": 30,
|
||||
\"city\": \"New York\"
|
||||
}
|
||||
}
|
||||
").expect("Failed to write to types script file");
|
||||
|
||||
// Create a template that uses these functions with .html.tera extension
|
||||
let types_template_path = templates_dir.join("types.html.tera");
|
||||
let mut types_template_file = File::create(&types_template_path).expect("Failed to create types template file");
|
||||
println!("Created template file at: {:?}", types_template_path);
|
||||
types_template_file.write_all(b"
|
||||
<html>
|
||||
<body>
|
||||
<p>Integer: {{ return_int() }}</p>
|
||||
<p>Float: {{ return_float() }}</p>
|
||||
<p>String: {{ return_string() }}</p>
|
||||
<p>Boolean: {{ return_bool() }}</p>
|
||||
<p>Array: {{ return_array() }}</p>
|
||||
<p>Map name: {{ return_map().name }}</p>
|
||||
<p>Map age: {{ return_map().age }}</p>
|
||||
</body>
|
||||
</html>
|
||||
").expect("Failed to write to types template file");
|
||||
|
||||
// Compile the script
|
||||
println!("Compiling types script");
|
||||
let ast = rhai_factory.compile_modules(&[&types_script_path], None)
|
||||
.expect("Failed to compile types script");
|
||||
|
||||
// Create a hot reloadable AST
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
println!("Creating Tera engine with Rhai integration for types test");
|
||||
println!("Template directory exists: {:?}", std::fs::metadata(&templates_dir).is_ok());
|
||||
println!("Template directory contents:");
|
||||
if let Ok(entries) = std::fs::read_dir(&templates_dir) {
|
||||
for entry in entries.filter_map(Result::ok) {
|
||||
println!(" - {:?}", entry.path());
|
||||
}
|
||||
}
|
||||
let tera = tera_factory.create_tera_with_rhai(&[&templates_dir], hot_ast.clone())
|
||||
.expect("Failed to create Tera engine with Rhai");
|
||||
|
||||
// Print available template names for debugging
|
||||
println!("Available templates: {:?}", tera.get_template_names().collect::<Vec<_>>());
|
||||
|
||||
// Render the template
|
||||
println!("Rendering types template");
|
||||
let rendered = tera.render("types.html.tera", &Context::new()).expect("Failed to render types template");
|
||||
|
||||
// Verify the rendered output for each type
|
||||
println!("Rendered output: {}", rendered);
|
||||
assert!(rendered.contains("Integer: 42"), "Integer not correctly rendered");
|
||||
assert!(rendered.contains("Float: 3.14159"), "Float not correctly rendered");
|
||||
assert!(rendered.contains("String: Hello, World!"), "String not correctly rendered");
|
||||
assert!(rendered.contains("Boolean: true"), "Boolean not correctly rendered");
|
||||
assert!(rendered.contains("Array: [1, 2, 3, 4, 5]"), "Array not correctly rendered");
|
||||
assert!(rendered.contains("Map name: John"), "Map name not correctly rendered");
|
||||
assert!(rendered.contains("Map age: 30"), "Map age not correctly rendered");
|
||||
|
||||
println!("test_function_adapter passed");
|
||||
|
||||
// Keep temp_dir in scope until the end of the test
|
||||
drop(temp_dir);
|
||||
}
|
Reference in New Issue
Block a user