use super::{eval, run};
use tsrun::value::JsString;
use tsrun::{Interpreter, JsValue, StepResult};
#[test]
fn test_function_name_property() {
assert_eq!(
eval("function foo() {} foo.name"),
JsValue::String(JsString::from("foo"))
);
assert_eq!(
eval("const f = function bar() {}; f.name"),
JsValue::String(JsString::from("bar"))
);
assert_eq!(
eval("[].push.name"),
JsValue::String(JsString::from("push"))
);
}
#[test]
fn test_function_name_from_assignment() {
assert_eq!(
eval("const myFunc = function() {}; myFunc.name"),
JsValue::String(JsString::from("myFunc"))
);
assert_eq!(
eval("const arrow = () => {}; arrow.name"),
JsValue::String(JsString::from("arrow"))
);
}
#[test]
fn test_function() {
assert_eq!(
eval("function add(a: number, b: number): number { return a + b; } add(2, 3)"),
JsValue::Number(5.0)
);
}
#[test]
fn test_this_binding() {
assert_eq!(
eval(
"let obj = {x: 42 as number, getX: function(): number { return this.x; }}; obj.getX()"
),
JsValue::Number(42.0)
);
}
#[test]
fn test_trailing_comma_in_function_call() {
assert_eq!(
eval("function add(a: number, b: number): number { return a + b; } add(2, 3,)"),
JsValue::Number(5.0)
);
}
#[test]
fn test_trailing_comma_in_function_call_multiline() {
assert_eq!(
eval(
"function foo(a: string, b: string): string { return a + b; } foo(\n'hello',\n'world',\n)"
),
JsValue::from("helloworld")
);
assert_eq!(
eval(
r#"
export const processor = {
elementHeader: function (element: any): any {
return element;
},
};
function test(a: string, b: string): string {
return foo(
a,
b,
);
}
function foo(a: string, b: string): string { return a + b; }
test("hello", "world");
"#
),
JsValue::from("helloworld")
);
}
#[test]
fn test_function_call() {
assert_eq!(
eval(
"function greet(): string { return 'Hello ' + this.name; } greet.call({name: 'World'})"
),
JsValue::from("Hello World")
);
assert_eq!(
eval("function add(a: number, b: number): number { return a + b; } add.call(null, 2, 3)"),
JsValue::Number(5.0)
);
}
#[test]
fn test_function_apply() {
assert_eq!(
eval(
"function greet(): string { return 'Hello ' + this.name; } greet.apply({name: 'World'})"
),
JsValue::from("Hello World")
);
assert_eq!(
eval(
"function add(a: number, b: number): number { return a + b; } add.apply(null, [2, 3])"
),
JsValue::Number(5.0)
);
}
#[test]
fn test_function_bind() {
assert_eq!(
eval(
"function greet(): string { return 'Hello ' + this.name; } const boundGreet: Function = greet.bind({name: 'World'}); boundGreet()"
),
JsValue::from("Hello World")
);
assert_eq!(
eval(
"function add(a: number, b: number): number { return a + b; } const add5: Function = add.bind(null, 5); add5(3)"
),
JsValue::Number(8.0)
);
}
#[test]
fn test_arrow_function() {
assert_eq!(
eval("const add: (a: number, b: number) => number = (a, b) => a + b; add(2, 3)"),
JsValue::Number(5.0)
);
}
#[test]
fn test_arguments_length() {
assert_eq!(
eval("function f(): number { return arguments.length; } f(1, 2, 3)"),
JsValue::Number(3.0)
);
assert_eq!(
eval("function f(): number { return arguments.length; } f()"),
JsValue::Number(0.0)
);
}
#[test]
fn test_arguments_access() {
assert_eq!(
eval("function f(): number { return arguments[0]; } f(42)"),
JsValue::Number(42.0)
);
assert_eq!(
eval("function f(): number { return arguments[1]; } f(1, 2, 3)"),
JsValue::Number(2.0)
);
assert_eq!(
eval("function f(): string { return arguments[2]; } f('a', 'b', 'c')"),
JsValue::from("c")
);
}
#[test]
fn test_arguments_out_of_bounds() {
assert_eq!(
eval("function f(): any { return arguments[5]; } f(1, 2)"),
JsValue::Undefined
);
}
#[test]
fn test_arguments_with_named_params() {
assert_eq!(
eval("function f(a: number, b: number): number { return arguments.length; } f(1, 2, 3, 4)"),
JsValue::Number(4.0)
);
assert_eq!(
eval("function f(a: number, b: number): number { return arguments[2]; } f(1, 2, 3)"),
JsValue::Number(3.0)
);
}
#[test]
fn test_arguments_in_nested_function() {
assert_eq!(
eval(
"function outer(): number { function inner(): number { return arguments[0]; } return inner(42); } outer(99)"
),
JsValue::Number(42.0)
);
}
#[test]
fn test_destructuring_object_param() {
assert_eq!(
eval(
"function f({ x, y }: { x: number; y: number }): number { return x + y; } f({ x: 1, y: 2 })"
),
JsValue::Number(3.0)
);
}
#[test]
fn test_destructuring_object_param_with_default() {
assert_eq!(
eval(
"function f({ x, y = 10 }: { x: number; y?: number }): number { return x + y; } f({ x: 1 })"
),
JsValue::Number(11.0)
);
}
#[test]
fn test_destructuring_array_param() {
assert_eq!(
eval("function f([a, b]: number[]): number { return a + b; } f([3, 4])"),
JsValue::Number(7.0)
);
}
#[test]
fn test_destructuring_array_param_with_rest() {
assert_eq!(
eval(
"function f([first, ...rest]: number[]): number { return rest.length; } f([1, 2, 3, 4])"
),
JsValue::Number(3.0)
);
}
#[test]
fn test_destructuring_nested_param() {
assert_eq!(
eval(
"function f({ person: { name } }: { person: { name: string } }): string { return name; } f({ person: { name: 'John' } })"
),
JsValue::from("John")
);
}
#[test]
fn test_destructuring_object_param_with_rest() {
assert_eq!(
eval(
r#"
function f({ id, ...rest }: { id: number; name: string; age: number }): string {
return id + "-" + rest.name + "-" + rest.age;
}
f({ id: 1, name: "Bob", age: 30 })
"#
),
JsValue::from("1-Bob-30")
);
}
#[test]
fn test_arrow_destructuring_param_with_rest() {
assert_eq!(
eval(
r#"
const extract = ({ type, ...data }: { type: string; x: number; y: number }) => data.x + data.y;
extract({ type: "point", x: 10, y: 20 })
"#
),
JsValue::Number(30.0)
);
}
#[test]
fn test_arrow_destructuring_param() {
assert_eq!(
eval("const f: (obj: { x: number }) => number = ({ x }) => x * 2; f({ x: 5 })"),
JsValue::Number(10.0)
);
}
#[test]
fn test_arrow_with_return_type_annotation() {
assert_eq!(
eval(
r#"
const filterByCategory = (items: any[], category: string): any[] =>
items.filter(p => p.category === category);
const products = [
{ id: 1, category: "X" },
{ id: 2, category: "Y" },
];
filterByCategory(products, "X").length
"#
),
JsValue::Number(1.0)
);
}
#[test]
fn test_arrow_array_destructuring_param() {
assert_eq!(
eval(
r#"
const arr = [[1, 2], [3, 4]];
arr.map(([a, b]) => a + b).reduce((sum, x) => sum + x, 0)
"#
),
JsValue::Number(10.0)
);
}
#[test]
fn test_recursive_fibonacci() {
let result = eval(
r#"
function fibRecursive(n: number): number {
if (n <= 1) return n;
return fibRecursive(n - 1) + fibRecursive(n - 2);
}
fibRecursive(10)
"#,
);
assert_eq!(result, JsValue::Number(55.0));
}
#[test]
fn test_memoized_closure() {
let result = eval(
r#"
function createMemoizedFib(): (n: number) => number {
const cache: { [key: number]: number } = {};
return function fib(n: number): number {
if (n in cache) return cache[n];
if (n <= 1) return n;
const result = fib(n - 1) + fib(n - 2);
cache[n] = result;
return result;
};
}
const fib = createMemoizedFib();
fib(10)
"#,
);
assert_eq!(result, JsValue::Number(55.0));
}
#[test]
fn test_function_returning_array() {
let result = eval(
r#"
function getNumbers(): number[] {
const arr: number[] = [];
for (let i = 0; i < 5; i++) {
arr.push(i);
}
return arr;
}
const nums = getNumbers();
nums.map(x => x * 2).join(",")
"#,
);
assert_eq!(result, JsValue::String("0,2,4,6,8".into()));
}
#[test]
fn test_fibonacci_iterative() {
let result = eval(
r#"
function fibIterative(n: number): number {
if (n <= 1) return n;
let prev = 0;
let curr = 1;
for (let i = 2; i <= n; i++) {
const next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
fibIterative(10)
"#,
);
assert_eq!(result, JsValue::Number(55.0));
}
#[test]
fn test_array_map_callback() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
[1, 2, 3].map(function(x) { return x * 2; })
"#,
None,
);
assert!(result.is_ok(), "Simple map should work: {:?}", result);
}
#[test]
fn test_nested_array_callbacks() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
const arr = [[1, 2], [3, 4]];
arr.map(function(inner) {
return inner.map(function(x) { return x * 2; });
})
"#,
None,
);
assert!(result.is_ok(), "Nested map should work: {:?}", result);
}
#[test]
fn test_array_callback_with_recursion() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
let sum = 0;
function countDown(n) {
if (n <= 0) return 0;
return 1 + countDown(n - 1);
}
[1, 2, 3].forEach(function(x) {
sum += countDown(3);
});
sum
"#,
None,
);
assert!(
result.is_ok(),
"forEach with recursive callback should work: {:?}",
result
);
let result_step = result.unwrap();
let result_value = if let StepResult::Complete(rv) = result_step {
rv
} else {
panic!("Expected Complete, got {:?}", result_step);
};
assert_eq!(result_value, JsValue::Number(9.0));
}
#[test]
fn test_function_constructor_no_args() {
assert_eq!(
eval("const f = new Function('return 42'); f()"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_one_arg() {
assert_eq!(
eval("const f = new Function('x', 'return x * 2'); f(21)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_two_args() {
assert_eq!(
eval("const f = new Function('a', 'b', 'return a + b'); f(10, 32)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_three_args() {
assert_eq!(
eval("const f = new Function('a', 'b', 'c', 'return a + b + c'); f(10, 20, 12)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_comma_separated_params() {
assert_eq!(
eval("const f = new Function('a, b', 'return a + b'); f(10, 32)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_mixed_params() {
assert_eq!(
eval("const f = new Function('a', 'b, c', 'return a + b + c'); f(10, 20, 12)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_no_body() {
assert_eq!(eval("const f = new Function(''); f()"), JsValue::Undefined);
}
#[test]
fn test_function_constructor_empty_no_args() {
assert_eq!(eval("const f = new Function(); f()"), JsValue::Undefined);
}
#[test]
fn test_function_constructor_returns_string() {
assert_eq!(
eval("const f = new Function('return \"hello\"'); f()"),
JsValue::from("hello")
);
}
#[test]
fn test_function_constructor_multiple_statements() {
assert_eq!(
eval("const f = new Function('x', 'let y = x * 2; return y + 1'); f(20)"),
JsValue::Number(41.0)
);
}
#[test]
fn test_function_constructor_global_scope() {
assert_eq!(
eval(
r#"
const outer = 100;
function test(): string {
const inner = 50;
const f = new Function('try { return inner; } catch(e) { return "not accessible"; }');
return f();
}
test()
"#
),
JsValue::from("not accessible")
);
}
#[test]
fn test_function_constructor_access_global() {
assert_eq!(
eval(
r#"
var globalVar = 42;
const f = new Function('return globalVar');
f()
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_this_binding() {
assert_eq!(
eval(
r#"
const f = new Function('return this.value');
const obj = { value: 42 };
f.call(obj)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_call_without_new() {
assert_eq!(
eval("const f = Function('x', 'return x + 1'); f(41)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_with_array_operations() {
assert_eq!(
eval(
r#"
const mapper = new Function('arr', 'return arr.map(x => x * 2)');
const result = mapper([1, 2, 3]);
result.join(',')
"#
),
JsValue::from("2,4,6")
);
}
#[test]
fn test_function_constructor_with_object() {
assert_eq!(
eval(
r#"
const makeObj = new Function('x', 'y', 'return { sum: x + y, product: x * y }');
const obj = makeObj(3, 4);
obj.sum + obj.product
"#
),
JsValue::Number(19.0) );
}
#[test]
fn test_function_constructor_recursive() {
assert_eq!(
eval(
r#"
var factorial = new Function('n', 'return n <= 1 ? 1 : n * factorial(n - 1)');
factorial(5)
"#
),
JsValue::Number(120.0)
);
}
#[test]
fn test_function_constructor_closure_in_body() {
assert_eq!(
eval(
r#"
const makeAdder = new Function('x', 'return function(y) { return x + y; }');
const add10 = makeAdder(10);
add10(32)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_with_default_params() {
assert_eq!(
eval("const f = new Function('x = 10', 'return x * 2'); f()"),
JsValue::Number(20.0)
);
assert_eq!(
eval("const f = new Function('x = 10', 'return x * 2'); f(5)"),
JsValue::Number(10.0)
);
}
#[test]
fn test_function_constructor_rest_params() {
assert_eq!(
eval(
r#"
const f = new Function('...args', 'return args.length');
f(1, 2, 3, 4, 5)
"#
),
JsValue::Number(5.0)
);
}
#[test]
fn test_function_constructor_rest_params_sum() {
assert_eq!(
eval(
r#"
const sum = new Function('...nums', 'return nums.reduce((a, b) => a + b, 0)');
sum(1, 2, 3, 4, 5)
"#
),
JsValue::Number(15.0)
);
}
#[test]
fn test_function_constructor_destructuring_param() {
assert_eq!(
eval(
r#"
const f = new Function('{x, y}', 'return x + y');
f({x: 10, y: 32})
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_array_destructuring() {
assert_eq!(
eval(
r#"
const f = new Function('[a, b]', 'return a * b');
f([6, 7])
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_length_property() {
assert_eq!(
eval("const f = new Function('a', 'b', 'c', 'return 0'); f.length"),
JsValue::Number(3.0)
);
}
#[test]
fn test_function_constructor_name_property() {
assert_eq!(
eval("const f = new Function('return 0'); f.name"),
JsValue::from("anonymous")
);
}
#[test]
fn test_function_constructor_is_callable() {
assert_eq!(
eval("typeof new Function('return 1')"),
JsValue::from("function")
);
}
#[test]
fn test_function_constructor_bind() {
assert_eq!(
eval(
r#"
const f = new Function('a', 'b', 'return a + b');
const bound = f.bind(null, 10);
bound(32)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_apply() {
assert_eq!(
eval(
r#"
const f = new Function('a', 'b', 'return a + b');
f.apply(null, [10, 32])
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_call() {
assert_eq!(
eval(
r#"
const f = new Function('a', 'b', 'return a + b');
f.call(null, 10, 32)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_constructor_syntax_error() {
use super::throws_error;
assert!(throws_error("const f = new Function('{');", "SyntaxError"));
}
#[test]
fn test_function_constructor_whitespace_in_params() {
assert_eq!(
eval("const f = new Function(' x ', ' y ', 'return x + y'); f(10, 32)"),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_prototype_constructor() {
assert_eq!(
eval("Function.prototype.constructor === Function"),
JsValue::Boolean(true)
);
}
#[test]
fn test_function_has_call_method() {
assert_eq!(
eval(
r#"
let fn = function() { return this.value; };
typeof fn.call
"#
),
JsValue::from("function")
);
}
#[test]
fn test_proxied_function_has_call_method() {
assert_eq!(
eval(
r#"
let fn = function() { return this.value; };
let p = new Proxy(fn, {});
typeof p.call
"#
),
JsValue::from("function")
);
}
#[test]
fn test_proxied_function_call_method_stored() {
assert_eq!(
eval(
r#"
let fn = function() { return this.value; };
let p = new Proxy(fn, {});
let callMethod = p.call;
let obj = { value: 42 };
callMethod.call(fn, obj)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_function_call_on_function_value() {
assert_eq!(
eval(
r#"
let fn = function() { return this.value; };
let p = new Proxy(fn, {});
let obj = { value: 42 };
Function.prototype.call.call(p, obj)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_proxied_function_call_method() {
assert_eq!(
eval(
r#"
let fn = function() { return this.value; };
let p = new Proxy(fn, {});
let obj = { value: 42 };
p.call(obj)
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_proxied_function_apply_method() {
assert_eq!(
eval(
r#"
let fn = function(a: number, b: number) { return a + b; };
let p = new Proxy(fn, {});
p.apply(null, [10, 32])
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_proxied_function_bind_method() {
assert_eq!(
eval(
r#"
let fn = function() { return this.value; };
let p = new Proxy(fn, {});
let bound = p.bind({ value: 42 });
bound()
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_spread_in_new_basic() {
assert_eq!(
eval(
r#"
function Point(x: number, y: number) {
this.x = x;
this.y = y;
}
let args: number[] = [10, 20];
let p = new Point(...args);
p.x + p.y
"#
),
JsValue::Number(30.0)
);
}
#[test]
fn test_spread_in_new_with_regular_args() {
assert_eq!(
eval(
r#"
function Triple(a: number, b: number, c: number) {
this.sum = a + b + c;
}
let rest: number[] = [2, 3];
let t = new Triple(1, ...rest);
t.sum
"#
),
JsValue::Number(6.0)
);
}
#[test]
fn test_spread_in_new_multiple_spreads() {
assert_eq!(
eval(
r#"
function Sum(a: number, b: number, c: number, d: number) {
this.total = a + b + c + d;
}
let arr1: number[] = [1, 2];
let arr2: number[] = [3, 4];
let s = new Sum(...arr1, ...arr2);
s.total
"#
),
JsValue::Number(10.0)
);
}
#[test]
fn test_function_declaration_name() {
assert_eq!(eval(r#"function foo() {} foo.name"#), JsValue::from("foo"));
}
#[test]
fn test_function_declaration_length() {
assert_eq!(
eval(r#"function foo(a: number, b: number, c: number) {} foo.length"#),
JsValue::Number(3.0)
);
}
#[test]
fn test_function_declaration_length_no_params() {
assert_eq!(
eval(r#"function foo() {} foo.length"#),
JsValue::Number(0.0)
);
}
#[test]
fn test_function_expression_name() {
assert_eq!(
eval(r#"const f = function bar() {}; f.name"#),
JsValue::from("bar")
);
}
#[test]
fn test_arrow_function_length() {
assert_eq!(
eval(r#"const f = (a: number, b: number) => a + b; f.length"#),
JsValue::Number(2.0)
);
}
#[test]
fn test_class_constructor_name() {
assert_eq!(
eval(r#"class MyClass {} MyClass.name"#),
JsValue::from("MyClass")
);
}
#[test]
fn test_class_expression_name() {
assert_eq!(
eval(r#"const C = class MyClass {}; C.name"#),
JsValue::from("MyClass")
);
}
#[test]
fn test_call_this_null_strict() {
assert_eq!(
eval(r#"function f() { return this; } f.call(null)"#),
JsValue::Null
);
}
#[test]
fn test_call_this_undefined_strict() {
assert_eq!(
eval(r#"function f() { return this; } f.call(undefined)"#),
JsValue::Undefined
);
}
#[test]
fn test_call_this_primitive_number() {
assert_eq!(
eval(r#"function f() { return this; } f.call(42)"#),
JsValue::Number(42.0)
);
}
#[test]
fn test_call_this_primitive_string() {
assert_eq!(
eval(r#"function f() { return this; } f.call("hello")"#),
JsValue::from("hello")
);
}
#[test]
fn test_call_this_primitive_boolean() {
assert_eq!(
eval(r#"function f() { return this; } f.call(true)"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_apply_this_null_strict() {
assert_eq!(
eval(r#"function f() { return this; } f.apply(null)"#),
JsValue::Null
);
}
#[test]
fn test_apply_this_primitive_number() {
assert_eq!(
eval(r#"function f() { return this; } f.apply(42)"#),
JsValue::Number(42.0)
);
}
#[test]
fn test_call_with_args_this_preserved() {
assert_eq!(
eval(r#"function f(a: number, b: number) { return this + a + b; } f.call(100, 20, 3)"#),
JsValue::Number(123.0)
);
}
#[test]
fn test_apply_with_args_this_preserved() {
assert_eq!(
eval(r#"function f(a: number, b: number) { return this + a + b; } f.apply(100, [20, 3])"#),
JsValue::Number(123.0)
);
}
#[test]
fn test_call_args_evaluated_before_callable_check() {
assert_eq!(
eval(
r#"
var fooCalled = false;
function foo() { fooCalled = true; }
var o = {};
try {
o.bar(foo());
} catch (e) {}
fooCalled
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_call_args_side_effects_on_undefined() {
assert_eq!(
eval(
r#"
var count = 0;
function increment() { count++; return count; }
try {
undefined(increment(), increment());
} catch (e) {}
count
"#
),
JsValue::Number(2.0)
);
}
#[test]
fn test_builtin_function_has_length() {
assert_eq!(
eval(r#"JSON.parse.hasOwnProperty("length")"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_builtin_function_length_value() {
assert_eq!(eval(r#"JSON.parse.length"#), JsValue::Number(2.0));
}
#[test]
fn test_builtin_function_has_name() {
assert_eq!(
eval(r#"JSON.parse.hasOwnProperty("name")"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_builtin_function_name_value() {
assert_eq!(eval(r#"JSON.parse.name"#), JsValue::from("parse"));
}
#[test]
fn test_array_push_has_length() {
assert_eq!(
eval(r#"[].push.hasOwnProperty("length")"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_array_push_length_value() {
assert_eq!(eval(r#"[].push.length"#), JsValue::Number(1.0));
}
#[test]
fn test_function_prototype_call_has_length() {
assert_eq!(
eval(r#"Function.prototype.call.hasOwnProperty("length")"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_function_prototype_call_length() {
assert_eq!(
eval(r#"Function.prototype.call.length"#),
JsValue::Number(1.0)
);
}
#[test]
fn test_call_args_not_evaluated_when_callee_throws() {
assert_eq!(
eval(
r#"
var fooCalled = false;
function foo() { fooCalled = true; }
var o = {};
try {
o.bar.gar(foo()); // o.bar is undefined, .gar throws
} catch (e) {}
fooCalled
"#
),
JsValue::Boolean(false)
);
}
#[test]
fn test_function_prototype_is_function() {
assert_eq!(
eval(r#"Object.prototype.toString.call(Function.prototype)"#),
JsValue::from("[object Function]")
);
}
#[test]
fn test_function_prototype_typeof() {
assert_eq!(
eval(r#"typeof Function.prototype"#),
JsValue::from("function")
);
}
#[test]
fn test_function_prototype_callable() {
assert_eq!(eval(r#"Function.prototype()"#), JsValue::Undefined);
}
#[test]
fn test_function_prototype_callable_with_args() {
assert_eq!(
eval(r#"Function.prototype(1, 2, 3, "test", {})"#),
JsValue::Undefined
);
}
#[test]
fn test_function_prototype_callable_with_this() {
assert_eq!(
eval(r#"Function.prototype.call({value: 42})"#),
JsValue::Undefined
);
}
#[test]
fn test_function_prototype_length() {
assert_eq!(eval(r#"Function.prototype.length"#), JsValue::Number(0.0));
}
#[test]
fn test_function_prototype_name() {
assert_eq!(eval(r#"Function.prototype.name"#), JsValue::from(""));
}
#[test]
fn test_function_prototype_has_call() {
assert_eq!(
eval(r#"typeof Function.prototype.call"#),
JsValue::from("function")
);
}
#[test]
fn test_function_prototype_has_apply() {
assert_eq!(
eval(r#"typeof Function.prototype.apply"#),
JsValue::from("function")
);
}
#[test]
fn test_function_prototype_has_bind() {
assert_eq!(
eval(r#"typeof Function.prototype.bind"#),
JsValue::from("function")
);
}
#[test]
fn test_function_inherits_from_function_prototype() {
assert_eq!(
eval(r#"function foo() {} Object.getPrototypeOf(foo) === Function.prototype"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_arrow_inherits_from_function_prototype() {
assert_eq!(
eval(r#"const f = () => {}; Object.getPrototypeOf(f) === Function.prototype"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_function_prototype_inherits_from_object_prototype() {
assert_eq!(
eval(r#"Object.getPrototypeOf(Function.prototype) === Object.prototype"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_function_has_symbol_hasinstance() {
assert_eq!(
eval(r#"typeof Function.prototype[Symbol.hasInstance]"#),
JsValue::from("function")
);
}
#[test]
fn test_instanceof_uses_symbol_hasinstance() {
assert_eq!(
eval(
r#"
function Foo() {}
const obj = new Foo();
obj instanceof Foo
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_symbol_hasinstance_direct_call() {
assert_eq!(
eval(
r#"
function Foo() {}
const obj = new Foo();
Foo[Symbol.hasInstance](obj)
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_symbol_hasinstance_non_instance() {
assert_eq!(
eval(
r#"
function Foo() {}
function Bar() {}
const obj = new Bar();
Foo[Symbol.hasInstance](obj)
"#
),
JsValue::Boolean(false)
);
}
#[test]
fn test_symbol_hasinstance_primitive() {
assert_eq!(
eval(r#"Function.prototype[Symbol.hasInstance].call(Object, 42)"#),
JsValue::Boolean(false)
);
}
#[test]
fn test_function_name_in_destructuring_default_array() {
assert_eq!(
eval(
r#"
let [arrow = () => {}] = [];
arrow.name
"#
),
JsValue::String(JsString::from("arrow"))
);
}
#[test]
fn test_function_name_in_destructuring_default_object() {
assert_eq!(
eval(
r#"
function test({fn = () => {}}) {
return fn.name;
}
test({})
"#
),
JsValue::String(JsString::from("fn"))
);
}
#[test]
fn test_function_name_in_destructuring_default_function_expr() {
assert_eq!(
eval(
r#"
function test([func = function() {}]) {
return func.name;
}
test([])
"#
),
JsValue::String(JsString::from("func"))
);
}
#[test]
fn test_function_name_descriptor() {
assert_eq!(
eval(
r#"
const f = function foo(a: number, b: number): number { return a + b; };
const desc = Object.getOwnPropertyDescriptor(f, 'name');
desc.writable
"#
),
JsValue::Boolean(false)
);
assert_eq!(
eval(
r#"
const f = function foo(a: number, b: number): number { return a + b; };
const desc = Object.getOwnPropertyDescriptor(f, 'name');
desc.enumerable
"#
),
JsValue::Boolean(false)
);
assert_eq!(
eval(
r#"
const f = function foo(a: number, b: number): number { return a + b; };
const desc = Object.getOwnPropertyDescriptor(f, 'name');
desc.configurable
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_function_length_descriptor() {
assert_eq!(
eval(
r#"
const f = function foo(a: number, b: number): number { return a + b; };
const desc = Object.getOwnPropertyDescriptor(f, 'length');
desc.writable
"#
),
JsValue::Boolean(false)
);
assert_eq!(
eval(
r#"
const f = function foo(a: number, b: number): number { return a + b; };
const desc = Object.getOwnPropertyDescriptor(f, 'length');
desc.enumerable
"#
),
JsValue::Boolean(false)
);
assert_eq!(
eval(
r#"
const f = function foo(a: number, b: number): number { return a + b; };
const desc = Object.getOwnPropertyDescriptor(f, 'length');
desc.configurable
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_builtin_function_name_descriptor() {
assert_eq!(
eval(
r#"
const desc = Object.getOwnPropertyDescriptor([].push, 'name');
[desc.writable, desc.enumerable, desc.configurable].join(',')
"#
),
JsValue::from("false,false,true")
);
}
#[test]
fn test_builtin_function_length_descriptor() {
assert_eq!(
eval(
r#"
const desc = Object.getOwnPropertyDescriptor([].push, 'length');
[desc.writable, desc.enumerable, desc.configurable].join(',')
"#
),
JsValue::from("false,false,true")
);
}
#[test]
fn test_arrow_function_name_descriptor() {
assert_eq!(
eval(
r#"
const f = (a: number) => a * 2;
const desc = Object.getOwnPropertyDescriptor(f, 'name');
[desc.writable, desc.enumerable, desc.configurable].join(',')
"#
),
JsValue::from("false,false,true")
);
}
#[test]
fn test_arrow_function_length_descriptor() {
assert_eq!(
eval(
r#"
const f = (a: number, b: number) => a + b;
const desc = Object.getOwnPropertyDescriptor(f, 'length');
[desc.writable, desc.enumerable, desc.configurable].join(',')
"#
),
JsValue::from("false,false,true")
);
}
#[test]
fn test_member_chain_simple() {
assert_eq!(eval("typeof Array.prototype"), JsValue::from("object"));
}
#[test]
fn test_member_chain_deep() {
assert_eq!(
eval("typeof Array.prototype.push"),
JsValue::from("function")
);
}
#[test]
fn test_member_chain_in_call_arg() {
assert_eq!(
eval("(function(f) { return typeof f; })(Array.prototype.push)"),
JsValue::from("function")
);
}
#[test]
fn test_function_prototype_call() {
assert_eq!(
eval("typeof Function.prototype.call"),
JsValue::from("function")
);
}
#[test]
fn test_function_prototype_call_bind() {
assert_eq!(
eval("typeof Function.prototype.call.bind"),
JsValue::from("function")
);
}
#[test]
fn test_member_chain_four_deep() {
assert_eq!(
eval("typeof Function.prototype.call.bind"),
JsValue::from("function")
);
}
#[test]
fn test_call_bind_result() {
assert_eq!(
eval("typeof Function.prototype.call.bind(Array.prototype.push)"),
JsValue::from("function")
);
}
#[test]
fn test_call_bind_assign_and_use() {
assert_eq!(
eval(
r#"
var __push = Function.prototype.call.bind(Array.prototype.push);
typeof __push
"#
),
JsValue::from("function")
);
}
#[test]
fn test_call_bind_invoke_simple() {
assert_eq!(
eval(
r#"
var arr = [1, 2];
var bound = Array.prototype.push.bind(arr);
bound(3);
arr.length
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_call_bind_debug_chain() {
assert_eq!(eval("typeof Function.prototype"), JsValue::from("function"));
assert_eq!(
eval("typeof Function.prototype.call"),
JsValue::from("function")
);
assert_eq!(
eval("typeof Array.prototype.push"),
JsValue::from("function")
);
assert_eq!(
eval("typeof Function.prototype.call.bind"),
JsValue::from("function")
);
assert_eq!(
eval(
r#"
var push = Array.prototype.push;
var bound = push.bind([]);
typeof bound
"#
),
JsValue::from("function")
);
assert_eq!(
eval("var f = function(){}; typeof f.bind"),
JsValue::from("function")
);
assert_eq!(
eval("var f = function(){}; typeof f.bind({})"),
JsValue::from("function")
);
assert_eq!(
eval(
r#"
var f = function(){};
var bound = f.bind({});
typeof bound
"#
),
JsValue::from("function")
);
assert_eq!(
eval("typeof Array.prototype.push"),
JsValue::from("function")
);
assert_eq!(
eval("(function(x) { return typeof x; })(Array.prototype.push)"),
JsValue::from("function")
);
assert_eq!(
eval("var x = (function(){}).bind(Array.prototype.push); typeof x"),
JsValue::from("function")
);
assert_eq!(
eval("var call = Function.prototype.call; typeof call"),
JsValue::from("function")
);
assert_eq!(
eval("var bind = Function.prototype.call.bind; typeof bind"),
JsValue::from("function")
);
assert_eq!(
eval("var x = Function.prototype.call.bind(function(){}); typeof x"),
JsValue::from("function")
);
assert_eq!(
eval("var x = Function.prototype.call.bind(Array.prototype.push); typeof x"),
JsValue::from("function")
);
assert_eq!(
eval(
r#"
var __push = Function.prototype.call.bind(Array.prototype.push);
typeof __push
"#
),
JsValue::from("function")
);
}
#[test]
fn test_call_bind_basic() {
assert_eq!(
eval(
r#"
var __push = Function.prototype.call.bind(Array.prototype.push);
var arr = [1, 2];
__push(arr, 3);
arr.length
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_call_bind_with_array() {
assert_eq!(
eval(
r#"
var __push = Function.prototype.call.bind(Array.prototype.push);
var arr = [1, 2];
__push(arr, 3);
arr[2]
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_call_bind_hasownproperty() {
assert_eq!(
eval(
r#"
var __hasOwnProperty = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
var obj = { foo: 1 };
__hasOwnProperty(obj, "foo")
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_call_bind_hasownproperty_false() {
assert_eq!(
eval(
r#"
var __hasOwnProperty = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
var obj = { foo: 1 };
__hasOwnProperty(obj, "bar")
"#
),
JsValue::Boolean(false)
);
}
#[test]
fn test_call_bind_join() {
assert_eq!(
eval(
r#"
var __join = Function.prototype.call.bind(Array.prototype.join);
var arr = [1, 2, 3];
__join(arr, "-")
"#
),
JsValue::from("1-2-3")
);
}
#[test]
fn test_call_bind_tostring() {
assert_eq!(
eval(
r#"
var __toString = Function.prototype.call.bind(Object.prototype.toString);
__toString({})
"#
),
JsValue::from("[object Object]")
);
}
#[test]
fn test_call_bind_get_own_property_descriptor() {
assert_eq!(
eval(
r#"
var __getOwnPropertyDescriptor = Function.prototype.call.bind(Object.getOwnPropertyDescriptor);
var obj = { foo: 1 };
var desc = __getOwnPropertyDescriptor(Object, obj, "foo");
desc.value
"#
),
JsValue::Number(1.0)
);
}
#[test]
fn test_tco_basic_tail_recursion() {
assert_eq!(
eval(
r#"
function sum(n, acc = 0) {
if (n <= 0) return acc;
return sum(n - 1, acc + n);
}
sum(100)
"#
),
JsValue::Number(5050.0)
);
}
#[test]
fn test_tco_factorial() {
assert_eq!(
eval(
r#"
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
factorial(10)
"#
),
JsValue::Number(3628800.0)
);
}
#[test]
fn test_tco_mutual_recursion() {
assert_eq!(
eval(
r#"
function isEven(n) {
if (n === 0) return true;
return isOdd(n - 1);
}
function isOdd(n) {
if (n === 0) return false;
return isEven(n - 1);
}
isEven(100)
"#
),
JsValue::Boolean(true)
);
assert_eq!(
eval(
r#"
function isEven(n) {
if (n === 0) return true;
return isOdd(n - 1);
}
function isOdd(n) {
if (n === 0) return false;
return isEven(n - 1);
}
isOdd(99)
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_tco_deep_recursion() {
assert_eq!(
eval(
r#"
function countdown(n) {
if (n <= 0) return "done";
return countdown(n - 1);
}
countdown(10000)
"#
),
JsValue::from("done")
);
}
#[test]
fn test_non_tail_call_not_optimized() {
assert_eq!(
eval(
r#"
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // NOT tail call - multiplication after
}
factorial(10)
"#
),
JsValue::Number(3628800.0)
);
}
#[test]
fn test_tco_with_closures() {
assert_eq!(
eval(
r#"
function makeCounter() {
let count = 0;
function loop(n) {
if (n <= 0) return count;
count += 1;
return loop(n - 1);
}
return loop;
}
const counter = makeCounter();
counter(100)
"#
),
JsValue::Number(100.0)
);
}
#[test]
fn test_tco_returns_correct_value() {
assert_eq!(
eval(
r#"
function a() { return b(); }
function b() { return c(); }
function c() { return 42; }
a()
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_tco_with_arguments() {
assert_eq!(
eval(
r#"
function sum3(a, b, c, acc = 0, n = 10) {
if (n <= 0) return acc;
return sum3(a, b, c, acc + a + b + c, n - 1);
}
sum3(1, 2, 3)
"#
),
JsValue::Number(60.0) );
}
#[test]
fn test_tco_native_function_fallback() {
assert_eq!(
eval(
r#"
function test() {
return Math.abs(-42);
}
test()
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_async_tco_basic_recursion() {
assert_eq!(
eval(
r#"
let result = 0;
async function sum(n: number, acc: number = 0): Promise<number> {
if (n <= 0) return acc;
return await sum(n - 1, acc + n);
}
sum(100).then(x => { result = x; });
result
"#
),
JsValue::Number(5050.0)
);
}
#[test]
fn test_async_tco_deep_recursion() {
assert_eq!(
eval(
r#"
let result = "";
async function countdown(n: number): Promise<string> {
if (n <= 0) return "done";
return await countdown(n - 1);
}
countdown(10000).then(x => { result = x; });
result
"#
),
JsValue::from("done")
);
}
#[test]
fn test_async_tco_mutual_recursion() {
assert_eq!(
eval(
r#"
let result = false;
async function isEven(n: number): Promise<boolean> {
if (n === 0) return true;
return await isOdd(n - 1);
}
async function isOdd(n: number): Promise<boolean> {
if (n === 0) return false;
return await isEven(n - 1);
}
isEven(1000).then(x => { result = x; });
result
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_async_tco_returns_correct_value() {
assert_eq!(
eval(
r#"
let result = 0;
async function a(): Promise<number> {
return await b();
}
async function b(): Promise<number> {
return await c();
}
async function c(): Promise<number> {
return 42;
}
a().then(x => { result = x; });
result
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_async_tco_with_arguments() {
assert_eq!(
eval(
r#"
let result = 0;
async function process(a: number, b: number, c: number): Promise<number> {
if (a <= 0) return b + c;
return await process(a - 1, b + 1, c + 2);
}
process(10, 0, 0).then(x => { result = x; });
result
"#
),
JsValue::Number(30.0) );
}
#[test]
fn test_async_tco_calling_sync_function() {
assert_eq!(
eval(
r#"
let result = 0;
function sync(n: number): number {
return n * 2;
}
async function foo(): Promise<number> {
return await sync(21);
}
foo().then(x => { result = x; });
result
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_async_tco_factorial() {
assert_eq!(
eval(
r#"
let result = 0;
async function factorial(n: number, acc: number = 1): Promise<number> {
if (n <= 1) return acc;
return await factorial(n - 1, n * acc);
}
factorial(10).then(x => { result = x; });
result
"#
),
JsValue::Number(3628800.0)
);
}
#[test]
fn test_wasm_playground_functions_example() {
run(
&mut super::create_test_runtime(),
r#"
// Regular function declaration
function greet(name: string): string {
return "Hello, " + name + "!";
}
// Arrow function
const add = (a: number, b: number): number => a + b;
// Function with default parameters
function power(base: number, exponent: number = 2): number {
let result = 1;
for (let i = 0; i < exponent; i++) {
result *= base;
}
return result;
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(greet("TypeScript"));
console.log("2 + 3 =", add(2, 3));
console.log("5^3 =", power(5, 3));
console.log("5^2 =", power(5));
console.log("sum(1,2,3,4,5) =", sum(1, 2, 3, 4, 5));
"#,
None,
)
.expect("Functions example should run without error");
}
#[test]
fn test_release_mode_minimal_function() {
run(
&mut super::create_test_runtime(),
"function f(x: string): string { return x; } f('test')",
None,
)
.expect("Basic function should work");
}
#[test]
fn test_release_mode_minimal_arrow() {
run(
&mut super::create_test_runtime(),
"const f = (a: number, b: number): number => a + b; f(1, 2)",
None,
)
.expect("Arrow function should work");
}
#[test]
fn test_release_mode_minimal_default_param() {
run(
&mut super::create_test_runtime(),
"function f(x: number = 2): number { return x; } f()",
None,
)
.expect("Default param should work");
}
#[test]
fn test_release_mode_minimal_rest_param() {
run(
&mut super::create_test_runtime(),
"function f(...args: number[]): number { return args.length; } f(1, 2, 3)",
None,
)
.expect("Rest param should work");
}
#[test]
fn test_wasm_playground_closures_example() {
run(
&mut super::create_test_runtime(),
r#"
// Closures capture variables from their enclosing scope
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = makeCounter();
const counter2 = makeCounter();
console.log("counter1:", counter1()); // 1
console.log("counter1:", counter1()); // 2
console.log("counter1:", counter1()); // 3
console.log("counter2:", counter2()); // 1 (separate instance)
console.log("counter1:", counter1()); // 4
// Closure with parameters
function multiplier(factor: number) {
return (x: number) => x * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
console.log("double(5):", double(5));
console.log("triple(5):", triple(5));
"#,
None,
)
.expect("Closures example should run without error");
}
#[test]
fn test_wasm_playground_arrays_example() {
run(
&mut super::create_test_runtime(),
r#"
// Array creation and access
const numbers = [1, 2, 3, 4, 5];
const mixed = [1, "two", true, null];
console.log("numbers:", numbers);
console.log("first:", numbers[0]);
console.log("length:", numbers.length);
// Array methods
console.log("\n--- Array Methods ---");
// map - transform each element
const doubled = numbers.map(x => x * 2);
console.log("doubled:", doubled);
// filter - keep elements matching condition
const evens = numbers.filter(x => x % 2 === 0);
console.log("evens:", evens);
// reduce - accumulate to single value
const sum = numbers.reduce((acc, x) => acc + x, 0);
console.log("sum:", sum);
// find - first matching element
const firstBig = numbers.find(x => x > 3);
console.log("first > 3:", firstBig);
// some/every - test conditions
console.log("some > 3:", numbers.some(x => x > 3));
console.log("every > 0:", numbers.every(x => x > 0));
// forEach
console.log("\nforEach:");
numbers.forEach((x, i) => console.log(" [" + i + "] =", x));
// Spread operator
const more = [...numbers, 6, 7, 8];
console.log("\nspread:", more);
// Array destructuring
const [first, second, ...rest] = numbers;
console.log("first:", first, "second:", second, "rest:", rest);
"#,
None,
)
.expect("Arrays example should run without error");
}
#[test]
fn test_wasm_playground_classes_example() {
run(
&mut super::create_test_runtime(),
r#"
// Class declaration
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): string {
return this.name + " makes a sound";
}
}
// Inheritance
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
speak(): string {
return this.name + " barks!";
}
fetch(): string {
return this.name + " fetches the ball";
}
}
// Static members
class MathUtils {
static PI = 3.14159;
static circleArea(radius: number): number {
return MathUtils.PI * radius * radius;
}
}
// Usage
const animal = new Animal("Generic Animal");
console.log(animal.speak());
const dog = new Dog("Rex", "German Shepherd");
console.log(dog.speak());
console.log(dog.fetch());
console.log("breed:", dog.breed);
console.log("\n--- Static Members ---");
console.log("PI:", MathUtils.PI);
console.log("Circle area (r=5):", MathUtils.circleArea(5));
"#,
None,
)
.expect("Classes example should run without error");
}
#[test]
fn test_wasm_playground_typescript_types_example() {
run(
&mut super::create_test_runtime(),
r#"
// Type annotations (parsed but not enforced)
let num: number = 42;
let str: string = "hello";
let bool: boolean = true;
let arr: number[] = [1, 2, 3];
// Interface declaration
interface User {
id: number;
name: string;
email?: string; // Optional property
}
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
console.log("User:", user);
// Type alias
type StringOrNumber = string | number;
type Point = { x: number; y: number };
const value: StringOrNumber = 42;
const point: Point = { x: 10, y: 20 };
console.log("value:", value);
console.log("point:", point);
// Generic function
function identity<T>(x: T): T {
return x;
}
console.log("identity(42):", identity(42));
console.log("identity('hello'):", identity("hello"));
// Enum
enum Color {
Red,
Green,
Blue
}
enum Status {
Pending = "pending",
Active = "active",
Completed = "completed"
}
console.log("\n--- Enums ---");
console.log("Color.Red:", Color.Red);
console.log("Color.Green:", Color.Green);
console.log("Status.Active:", Status.Active);
"#,
None,
)
.expect("TypeScript Types example should run without error");
}