test: add comprehensive integration tests for JS backend
Add 10 integration tests that compile Lux to JavaScript and verify correct execution in Node.js: - test_js_factorial: Recursion and effects - test_js_fibonacci: Classic recursive algorithm - test_js_adt_and_pattern_matching: Custom ADTs with match - test_js_option_type: Built-in Option type handling - test_js_closures: Closure creation and variable capture - test_js_higher_order_functions: Functions as values - test_js_list_operations: List.map, List.foldl - test_js_pipe_operator: Pipe (|>) operator - test_js_records: Record literal and field access - test_js_string_concatenation: String operations Also fix List module operations being incorrectly treated as effects by adding special-case handling in EffectOp emission. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -508,6 +508,11 @@ impl JsBackend {
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
// Special case: List module operations (not an effect)
|
||||
if effect.name == "List" {
|
||||
return self.emit_list_operation(&operation.name, args);
|
||||
}
|
||||
|
||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||
let args_str = arg_strs?.join(", ");
|
||||
|
||||
@@ -1024,6 +1029,8 @@ impl Default for JsBackend {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::Parser;
|
||||
use std::process::Command;
|
||||
|
||||
#[test]
|
||||
fn test_literal_int() {
|
||||
@@ -1068,4 +1075,194 @@ mod tests {
|
||||
assert_eq!(backend.escape_js_keyword("class"), "class_");
|
||||
assert_eq!(backend.escape_js_keyword("foo"), "foo");
|
||||
}
|
||||
|
||||
/// Helper to compile Lux source to JS and run in Node.js
|
||||
fn compile_and_run(source: &str) -> Result<String, String> {
|
||||
let program = Parser::parse_source(source).map_err(|e| format!("Parse error: {}", e))?;
|
||||
|
||||
let mut backend = JsBackend::new();
|
||||
let js_code = backend
|
||||
.generate(&program)
|
||||
.map_err(|e| format!("Codegen error: {}", e))?;
|
||||
|
||||
let output = Command::new("node")
|
||||
.arg("-e")
|
||||
.arg(&js_code)
|
||||
.output()
|
||||
.map_err(|e| format!("Node.js error: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"Node.js execution failed:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_factorial() {
|
||||
let source = r#"
|
||||
fn factorial(n: Int): Int =
|
||||
if n <= 1 then 1
|
||||
else n * factorial(n - 1)
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("Result: " + toString(factorial(5)))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Result: 120");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_fibonacci() {
|
||||
let source = r#"
|
||||
fn fib(n: Int): Int =
|
||||
if n <= 1 then n
|
||||
else fib(n - 1) + fib(n - 2)
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("fib(10) = " + toString(fib(10)))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "fib(10) = 55");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_adt_and_pattern_matching() {
|
||||
let source = r#"
|
||||
type Tree =
|
||||
| Leaf(Int)
|
||||
| Node(Tree, Tree)
|
||||
|
||||
fn sumTree(tree: Tree): Int =
|
||||
match tree {
|
||||
Leaf(n) => n,
|
||||
Node(left, right) => sumTree(left) + sumTree(right)
|
||||
}
|
||||
|
||||
let tree = Node(Node(Leaf(1), Leaf(2)), Leaf(3))
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("Sum: " + toString(sumTree(tree)))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Sum: 6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_option_type() {
|
||||
let source = r#"
|
||||
fn safeDivide(a: Int, b: Int): Option<Int> =
|
||||
if b == 0 then None
|
||||
else Some(a / b)
|
||||
|
||||
fn showResult(opt: Option<Int>): String =
|
||||
match opt {
|
||||
Some(n) => "Got: " + toString(n),
|
||||
None => "None"
|
||||
}
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
Console.print(showResult(safeDivide(10, 2)))
|
||||
Console.print(showResult(safeDivide(10, 0)))
|
||||
}
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Got: 5\nNone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_closures() {
|
||||
let source = r#"
|
||||
fn makeAdder(x: Int): fn(Int): Int =
|
||||
fn(y: Int): Int => x + y
|
||||
|
||||
let add5 = makeAdder(5)
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("5 + 10 = " + toString(add5(10)))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "5 + 10 = 15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_higher_order_functions() {
|
||||
let source = r#"
|
||||
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
||||
fn double(x: Int): Int = x * 2
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("double(21) = " + toString(apply(double, 21)))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "double(21) = 42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_list_operations() {
|
||||
let source = r#"
|
||||
let nums = [1, 2, 3, 4, 5]
|
||||
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
||||
let sum = List.foldl(doubled, 0, fn(acc: Int, x: Int): Int => acc + x)
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("Sum of doubled: " + toString(sum))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Sum of doubled: 30");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_pipe_operator() {
|
||||
let source = r#"
|
||||
fn double(x: Int): Int = x * 2
|
||||
fn addOne(x: Int): Int = x + 1
|
||||
|
||||
let result = 5 |> double |> addOne
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("Result: " + toString(result))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Result: 11");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_records() {
|
||||
let source = r#"
|
||||
let point = { x: 10, y: 20 }
|
||||
let sum = point.x + point.y
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print("Sum: " + toString(sum))
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Sum: 30");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_js_string_concatenation() {
|
||||
let source = r#"
|
||||
let name = "World"
|
||||
let greeting = "Hello, " + name + "!"
|
||||
|
||||
fn main(): Unit with {Console} =
|
||||
Console.print(greeting)
|
||||
"#;
|
||||
|
||||
let output = compile_and_run(source).expect("Should compile and run");
|
||||
assert_eq!(output, "Hello, World!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user