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

391 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 {
vm.lookup_str(val).unwrap_or_default()
}
// --- Symbol constructor ---
#[test]
fn symbol_creates_value() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol()").unwrap();
assert!(result.is_symbol());
}
#[test]
fn symbol_with_description() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol('hello') !== Symbol('hello')").unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_unique_identity() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var a = Symbol('x'); var b = Symbol('x'); a === b").unwrap();
assert!(!result.as_bool());
}
#[test]
fn symbol_same_reference_equals() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s = Symbol('x'); s === s").unwrap();
assert!(result.as_bool());
}
// --- typeof Symbol ---
#[test]
fn typeof_symbol_returns_symbol() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol()").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
#[test]
fn typeof_symbol_variable() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s = Symbol('test'); typeof s").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
// --- Symbol well-known properties ---
#[test]
fn symbol_match_exists() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol.match").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
#[test]
fn symbol_replace_exists() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol.replace").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
#[test]
fn symbol_search_exists() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol.search").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
#[test]
fn symbol_split_exists() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol.split").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
// --- Symbol coercion ---
#[test]
fn symbol_is_truthy() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol() ? true : false").unwrap();
assert!(result.as_bool());
}
// --- Symbol types ---
#[test]
fn symbol_not_equals_object() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol() === {}").unwrap();
assert!(!result.as_bool());
}
#[test]
fn symbol_not_equals_string() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol() === 'symbol'").unwrap();
assert!(!result.as_bool());
}
#[test]
fn symbol_iterator_exists() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol.iterator").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
#[test]
fn symbol_for_reuses_registered_symbol() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s1 = Symbol.for('shared'); var s2 = Symbol.for('shared'); s1 === s2").unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_for_is_distinct_from_symbol_constructor() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s1 = Symbol.for('shared'); var s2 = Symbol('shared'); s1 === s2").unwrap();
assert!(!result.as_bool());
}
#[test]
fn symbol_key_for_returns_registered_key() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol.keyFor(Symbol.for('shared'))").unwrap();
assert_eq!(to_str(&vm, result), "shared");
}
#[test]
fn symbol_key_for_unregistered_symbol_returns_undefined() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol.keyFor(Symbol('shared'))").unwrap();
assert!(result.is_undefined());
}
#[test]
fn symbol_key_for_non_symbol_throws_type_error() {
let mut vm = Vm::new();
let result = eval(&mut vm, "try { Symbol.keyFor(42) } catch (e) { e instanceof TypeError }").unwrap();
assert!(result.as_bool());
}
// --- Symbol.hasInstance ---
#[test]
fn symbol_has_instance_exists() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof Symbol.hasInstance").unwrap();
assert_eq!(to_str(&vm, result), "symbol");
}
#[test]
fn has_instance_overrides_instanceof() {
let mut vm = Vm::new();
let source = r#"
class C { static [Symbol.hasInstance](v) { return typeof v === 'string'; } }
'hello' instanceof C
"#;
let result = eval(&mut vm, source).unwrap();
assert!(result.as_bool());
}
#[test]
fn has_instance_non_callable_handler_throws() {
let mut vm = Vm::new();
// handler 在场但不可调用:Call(42) 抛 TypeError(旧行为静默回落
// prototype 分支,与规范步 4 不符)。
let source = r#"
class D {}
D[Symbol.hasInstance] = 42;
var threw = false;
try { ({} ) instanceof D; } catch (e) { threw = e instanceof TypeError; }
threw
"#;
let result = eval(&mut vm, source).unwrap();
assert!(result.as_bool());
}
#[test]
fn has_instance_null_handler_throws_type_error() {
let mut vm = Vm::new();
// null 非 undefined,规范仍须调用 handler,Call(null) 抛 TypeError。
let source = r#"
class D {}
D[Symbol.hasInstance] = null;
var threw = false;
try { ({} ) instanceof D; } catch (e) { threw = e instanceof TypeError; }
threw
"#;
let result = eval(&mut vm, source).unwrap();
assert!(result.as_bool());
}
#[test]
fn has_instance_coerces_return_to_boolean() {
let mut vm = Vm::new();
let source = r#"
class C { static [Symbol.hasInstance](v) { return 'truthy'; } }
'anything' instanceof C
"#;
let result = eval(&mut vm, source).unwrap();
assert!(result.as_bool());
}
#[test]
fn has_instance_preserves_ordinary_has_instance() {
let mut vm = Vm::new();
let result = eval(&mut vm, "[] instanceof Array").unwrap();
assert!(result.as_bool());
}
// --- Symbol 作属性键 ---
#[test]
fn symbol_keys_stay_distinct() {
let mut vm = Vm::new();
let result =
eval(&mut vm, "var s1=Symbol('a'), s2=Symbol('b'); var o={}; o[s1]=1; o[s2]=2; [o[s1],o[s2]]").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr.get_prop_at(0).as_int(), 1);
assert_eq!(arr.get_prop_at(1).as_int(), 2);
}
#[test]
fn symbol_keys_excluded_from_object_keys() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s=Symbol('x'); var o={a:1}; o[s]=2; Object.keys(o).length").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn symbol_keys_excluded_from_own_property_names() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=1; Object.getOwnPropertyNames(o).length").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn symbol_keys_excluded_from_json_stringify() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s=Symbol('x'); var o={a:1}; o[s]=2; JSON.stringify(o)").unwrap();
assert_eq!(to_str(&vm, result), "{\"a\":1}");
}
#[test]
fn symbol_keys_excluded_from_for_in() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=1; var n=0; for (var k in o) n++; n").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn get_own_property_symbols_roundtrip() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var s=Symbol('x'); var o={}; o[s]=7; var k=Object.getOwnPropertySymbols(o)[0]; o[k]",
)
.unwrap();
assert_eq!(result.as_int(), 7);
}
#[test]
fn get_own_property_symbols_length() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s=Symbol('x'); var o={}; o[s]=1; Object.getOwnPropertySymbols(o).length").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn computed_symbol_key_object_literal() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var o={[Symbol('k')]: 5, a: 1}; Object.keys(o).length").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn well_known_symbol_key_excluded_from_keys() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var o={}; o[Symbol.iterator]=1; Object.keys(o).length").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn symbol_key_read_via_get_own_property_symbols_index() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"var s1=Symbol('a'), s2=Symbol('b'); var o={}; o[s1]=1; o[s2]=2; var ks=Object.getOwnPropertySymbols(o); var t=0; for (var i=0;i<ks.length;i++) t+=o[ks[i]]; t === 3",
)
.unwrap();
assert!(result.as_bool());
}
// --- Symbol 原始值成员访问走 Symbol.prototype 链 ---
#[test]
fn symbol_primitive_to_string_via_symbol_proto() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol('66').toString()").unwrap();
assert_eq!(to_str(&vm, result), "Symbol(66)");
}
#[test]
fn symbol_primitive_value_of_via_symbol_proto() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var s = Symbol('x'); s.valueOf() === s").unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_primitive_chain_reaches_object_proto_method() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol('x').hasOwnProperty('description')").unwrap();
assert!(!result.as_bool());
}
#[test]
fn symbol_primitive_description_getter() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol('desc').description").unwrap();
assert_eq!(to_str(&vm, result), "desc");
}
#[test]
fn symbol_without_description_returns_undefined() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Symbol().description").unwrap();
assert!(result.is_undefined());
}
#[test]
fn symbol_wrapper_object_description_unboxes() {
let mut vm = Vm::new();
let result = eval(&mut vm, "Object(Symbol('w')).description").unwrap();
assert_eq!(to_str(&vm, result), "w");
}
#[test]
fn symbol_description_non_symbol_receiver_throws_type_error() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"try { Object.getOwnPropertyDescriptor(Symbol.prototype, 'description').get.call(42); false } catch (e) { e instanceof TypeError }",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_proto_direct_description_access_throws_type_error() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"try { Symbol.prototype.description; false } catch (e) { e instanceof TypeError }",
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn symbol_length_descriptor() {
// Symbol.length = 0,描述符 { writable:false, enumerable:false, configurable:true }。
let mut vm = Vm::new();
let r = eval(
&mut vm,
"var d = Object.getOwnPropertyDescriptor(Symbol, 'length'); \
d.value === 0 && d.writable === false && d.enumerable === false && d.configurable === true",
)
.unwrap();
assert!(r.as_bool());
}