Files
OxideJS-New/crates/oxide_vm/tests/call_spread_tests.rs
T

134 lines
4.1 KiB
Rust

//! 调用实参 spread 执行语义测试:`f(...args)`、`new F(...args)`、`super(...args)`。
//! 覆盖静态/混合实参顺序、迭代器协议、IteratorClose、native 目标、求值顺序。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&Arc::new(module))
}
fn to_str(vm: &Vm, val: JsValue) -> String {
if val.is_string() {
vm.lookup_str(val).unwrap_or_default()
} else {
format!("{}", val)
}
}
fn to_num(val: JsValue) -> f64 {
if val.is_int() {
val.as_int() as f64
} else {
val.as_double()
}
}
#[test]
fn spread_single_literal_preserves_all_args() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(...[1,2,3])").unwrap();
assert_eq!(result.as_int(), 3);
}
#[test]
fn spread_interleaved_static_and_spread_keeps_source_order() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(a,b,c){return [a,b,c].join('-')}; f('x', ...['y'], 'z')").unwrap();
assert_eq!(to_str(&vm, result), "x-y-z");
}
#[test]
fn spread_multiple_sources_append_in_order() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(...[1], 2, ...[3,4])").unwrap();
assert_eq!(result.as_int(), 4);
}
#[test]
fn spread_native_target() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Math.max(...[1,5,3])").unwrap();
assert_eq!(to_num(result), 5.0);
}
#[test]
fn spread_new_expression_bytecode_ctor() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new (function(a,b){this.sum=a+b})(...[2,3]).sum").unwrap();
assert_eq!(to_num(result), 5.0);
}
#[test]
fn spread_new_expression_native_ctor() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new Date(...[2020,1,1]).getFullYear()").unwrap();
assert_eq!(to_num(result), 2020.0);
}
#[test]
fn spread_super_call_passes_args() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"class A { constructor(a,b,c){ this.v=[a,b,c].join('') } }; \
class B extends A { constructor(){ super(...[1,2,3]) } }; new B().v",
)
.unwrap();
assert_eq!(to_str(&vm, result), "123");
}
#[test]
fn spread_non_iterable_throws_type_error() {
let mut vm = Vm::new();
let result = eval(&mut vm, "try { (function(){})(...null); 'no-throw' } catch(e) { e.constructor.name }").unwrap();
assert_eq!(to_str(&vm, result), "TypeError");
}
#[test]
fn spread_invokes_iterator_close_on_abrupt_completion() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var closed=0; try { (function(){}) (...{[Symbol.iterator]:function(){ return { \
next:function(){ throw new Error('boom') }, \
return:function(){ closed=1; return {} } } }}) } catch(e) {}; closed",
)
.unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn spread_does_not_close_iterator_on_normal_completion() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var closed=0; var i=0; (function(){}) (...{[Symbol.iterator]:function(){ return { \
next:function(){ return ++i<=2 ? {done:false,value:i} : {done:true} }, \
return:function(){ closed=1; return {} } } }}); closed",
)
.unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn spread_closure_capture_survives() {
let mut vm = Vm::new();
let result =
eval(&mut vm, "function outer(){var x=42; function g(a){return a+x}; return g(...[x])} outer()").unwrap();
assert_eq!(to_num(result), 84.0);
}
#[test]
fn spread_string_source() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(...'ab')").unwrap();
assert_eq!(result.as_int(), 2);
}