use super::*;
use crate::engine::test_support::assert_script_true;
use crate::engine::test_support::test_context;
#[test]
fn global_cell_execution_observes_updates_and_property_fallback_once() {
let (_runtime, mut context) = test_context();
assert_eq!(
context
.eval("var changingCell=1; function readChanging(){return changingCell} readChanging()")
.unwrap(),
Value::Int(1)
);
assert_eq!(
context.eval("changingCell=2; readChanging()").unwrap(),
Value::Int(2)
);
assert_eq!(
context
.eval("changingCell={x:3}; readChanging().x")
.unwrap(),
Value::Int(3)
);
assert_eq!(context.eval("var reads=0; Object.defineProperty(globalThis,'propertyOnly',{configurable:true,get:function(){reads++;return 17}}); function readProperty(){return propertyOnly} readProperty()+reads").unwrap(), Value::Int(18));
assert_eq!(
context
.eval("delete globalThis.propertyOnly; typeof propertyOnly")
.unwrap(),
Value::String(JsString::from_static("undefined"))
);
assert_eq!(context.eval("Object.defineProperty(Object.getPrototypeOf(globalThis),'propertyOnly',{configurable:true,get:function(){reads++;return 23}}); readProperty()+reads").unwrap(), Value::Int(25));
assert_script_true(
&mut context,
"delete Object.getPrototypeOf(globalThis).propertyOnly; try { readProperty(); } catch(e) { e instanceof ReferenceError }",
);
assert_script_true(
&mut context,
"try { eval('typeof tdzCell; let tdzCell=1'); } catch(e) { e instanceof ReferenceError }",
);
}
#[test]
fn detached_vm_rejects_runtime_global_execution_explicitly() {
let error = compile_script("answer").unwrap_err();
assert!(error.message().contains("global-environment"));
let error = compile_script("delete answer").unwrap_err();
assert!(error.message().contains("global-environment"));
}
#[test]
fn global_put_matches_strict_sloppy_readonly_and_setter_semantics() {
let (runtime, context) = test_context();
let global = context.global_object().unwrap();
let descriptor = |value, writable| OrdinaryPropertyDescriptor {
value: DescriptorField::Present(value),
writable: DescriptorField::Present(writable),
enumerable: DescriptorField::Present(true),
configurable: DescriptorField::Present(true),
..OrdinaryPropertyDescriptor::new()
};
assert_eq!(context.eval("created = 7").unwrap(), Value::Int(7));
assert_eq!(context.eval("created").unwrap(), Value::Int(7));
let readonly = runtime.intern_property_key("readonly").unwrap();
assert!(
context
.define_own_property(&global, &readonly, &descriptor(Value::Int(1), false))
.unwrap()
);
assert_eq!(context.eval("readonly = 2").unwrap(), Value::Int(2));
assert_eq!(context.eval("readonly").unwrap(), Value::Int(1));
let Err(RuntimeError::Exception(Value::Object(exception))) =
context.eval("'use strict'; readonly = 2")
else {
panic!("strict read-only global assignment did not throw an object");
};
let message = runtime.intern_property_key("message").unwrap();
assert_eq!(
context.get_property(&exception, &message).unwrap(),
Value::String(JsString::from_static("'readonly' is read-only"))
);
assert_eq!(context.eval("readonly += 2").unwrap(), Value::Int(3));
assert_eq!(context.eval("readonly").unwrap(), Value::Int(1));
assert_eq!(
context.eval("'use strict'; readonly ||= 9").unwrap(),
Value::Int(1)
);
assert!(matches!(
context.eval("'use strict'; readonly += 2"),
Err(RuntimeError::Exception(_))
));
assert!(matches!(
context.eval("'use strict'; readonly &&= 2"),
Err(RuntimeError::Exception(_))
));
let inherited = runtime.intern_property_key("inheritedReadOnly").unwrap();
assert!(
context
.define_own_property(
&context.object_prototype().unwrap(),
&inherited,
&descriptor(Value::Int(5), false),
)
.unwrap()
);
assert_eq!(
context.eval("inheritedReadOnly = 6").unwrap(),
Value::Int(6)
);
assert_eq!(context.eval("inheritedReadOnly").unwrap(), Value::Int(5));
let Err(RuntimeError::Exception(Value::Object(exception))) =
context.eval("'use strict'; inheritedReadOnly = 6")
else {
panic!("strict inherited read-only assignment did not throw an object");
};
assert_eq!(
context.get_property(&exception, &message).unwrap(),
Value::String(JsString::from_static("'inheritedReadOnly' is read-only"))
);
let no_setter = runtime.intern_property_key("noSetter").unwrap();
assert!(
context
.define_own_property(
&global,
&no_setter,
&OrdinaryPropertyDescriptor {
get: DescriptorField::Present(AccessorValue::Undefined),
set: DescriptorField::Present(AccessorValue::Undefined),
enumerable: DescriptorField::Present(true),
configurable: DescriptorField::Present(true),
..OrdinaryPropertyDescriptor::new()
},
)
.unwrap()
);
assert_eq!(context.eval("noSetter = 8").unwrap(), Value::Int(8));
let Err(RuntimeError::Exception(Value::Object(exception))) =
context.eval("'use strict'; noSetter = 8")
else {
panic!("strict setter-less assignment did not throw an object");
};
assert_eq!(
context.get_property(&exception, &message).unwrap(),
Value::String(JsString::from_static("no setter for property"))
);
assert_eq!(context.eval("noSetter ||= 8").unwrap(), Value::Int(8));
assert!(matches!(
context.eval("'use strict'; noSetter ||= 8"),
Err(RuntimeError::Exception(_))
));
assert_eq!(
context.eval("'use strict'; noSetter &&= 8").unwrap(),
Value::Undefined
);
assert!(matches!(
context.eval("'use strict'; trulyMissing = 1"),
Err(RuntimeError::Exception(_))
));
let truly_missing = runtime.intern_property_key("trulyMissing").unwrap();
assert!(!runtime.has_own_property(&global, &truly_missing).unwrap());
let sink = runtime.intern_property_key("sink").unwrap();
assert!(
context
.define_own_property(&global, &sink, &descriptor(Value::Int(0), true))
.unwrap()
);
let Value::Object(setter) = context
.eval("(function(v) { sink = v; return 99; })")
.unwrap()
else {
panic!("setter source did not produce a function");
};
let setter = runtime.as_callable(&setter).unwrap().unwrap();
let target = runtime.intern_property_key("setterTarget").unwrap();
assert!(
context
.define_own_property(
&global,
&target,
&OrdinaryPropertyDescriptor {
get: DescriptorField::Present(AccessorValue::Undefined),
set: DescriptorField::Present(AccessorValue::Callable(setter)),
enumerable: DescriptorField::Present(true),
configurable: DescriptorField::Present(true),
..OrdinaryPropertyDescriptor::new()
},
)
.unwrap()
);
assert_eq!(context.eval("setterTarget = 42").unwrap(), Value::Int(42));
assert_eq!(context.eval("sink").unwrap(), Value::Int(42));
assert_eq!(context.eval("setterTarget ||= 17").unwrap(), Value::Int(17));
assert_eq!(context.eval("sink").unwrap(), Value::Int(17));
let Value::Object(getter) = context.eval("(function() { return this; })").unwrap() else {
panic!("getter source did not produce a function");
};
let getter = runtime.as_callable(&getter).unwrap().unwrap();
let getter_target = runtime.intern_property_key("getterTarget").unwrap();
assert!(
context
.define_own_property(
&global,
&getter_target,
&OrdinaryPropertyDescriptor {
get: DescriptorField::Present(AccessorValue::Callable(getter)),
set: DescriptorField::Present(AccessorValue::Undefined),
enumerable: DescriptorField::Present(true),
configurable: DescriptorField::Present(true),
..OrdinaryPropertyDescriptor::new()
},
)
.unwrap()
);
assert_eq!(
context.eval("getterTarget").unwrap(),
Value::Object(global.try_clone().expect("duplicate root"))
);
assert_eq!(
context.eval("typeof getterTarget").unwrap(),
Value::String(JsString::from_static("object"))
);
let Value::Object(throwing_getter) = context.eval("(function() { throw 17; })").unwrap() else {
panic!("throwing getter source did not produce a function");
};
let throwing_getter = runtime.as_callable(&throwing_getter).unwrap().unwrap();
let throwing_target = runtime.intern_property_key("throwingGetter").unwrap();
assert!(
context
.define_own_property(
&global,
&throwing_target,
&OrdinaryPropertyDescriptor {
get: DescriptorField::Present(AccessorValue::Callable(throwing_getter)),
set: DescriptorField::Present(AccessorValue::Undefined),
enumerable: DescriptorField::Present(true),
configurable: DescriptorField::Present(true),
..OrdinaryPropertyDescriptor::new()
},
)
.unwrap()
);
assert_eq!(
context.eval("typeof throwingGetter").err(),
Some(RuntimeError::Exception(Value::Int(17)))
);
assert_eq!(context.eval("compoundSide = 0").unwrap(), Value::Int(0));
assert!(matches!(
context.eval("missingCompound += (compoundSide = 1)"),
Err(RuntimeError::Exception(_))
));
assert_eq!(context.eval("compoundSide").unwrap(), Value::Int(0));
assert!(matches!(
context.eval("missingLogical ||= (compoundSide = 2)"),
Err(RuntimeError::Exception(_))
));
assert_eq!(context.eval("compoundSide").unwrap(), Value::Int(0));
}
#[test]
fn unresolved_name_compiles_to_one_global_then_parent_global_relays() {
let script = compile_unlinked_script(
"(function() { return function() { return function() { return relayName; }; }; })",
)
.unwrap();
let mut function = &script;
for depth in 0..4 {
let descriptor = function
.closure_variables()
.first()
.expect("every function on the unresolved-name path needs a closure slot");
assert_eq!(
descriptor.source,
if depth == 0 {
ClosureSource::Global
} else {
ClosureSource::ParentGlobal(0)
}
);
let ClosureVariableName::Constant(name_index) = descriptor.name else {
panic!("unlinked global relay did not retain a name constant");
};
assert!(matches!(
function.constants()[name_index as usize].as_primitive(),
Some(crate::engine::value::PrimitiveValue::String(name)) if name == &JsString::from_static("relayName")
));
if depth == 3 {
assert!(matches!(
function.code(),
[Instruction::GetVar(0), Instruction::Return, ..]
));
break;
}
function = function
.constants()
.iter()
.find_map(|constant| constant.as_child())
.expect("global relay path lost its nested child");
}
}
#[test]
fn late_global_property_delete_reconnect_and_cross_realm_use_the_defining_realm() {
let runtime = Runtime::new();
let defining = runtime.new_context().expect("create context");
let caller = runtime.new_context().expect("create context");
let Value::Object(reader) = defining
.eval("(function() { return lateRealmValue; })")
.unwrap()
else {
panic!("late global reader source did not produce a function");
};
let reader = runtime.as_callable(&reader).unwrap().unwrap();
let key = runtime.intern_property_key("lateRealmValue").unwrap();
let descriptor = |value| OrdinaryPropertyDescriptor {
value: DescriptorField::Present(Value::Int(value)),
writable: DescriptorField::Present(true),
enumerable: DescriptorField::Present(true),
configurable: DescriptorField::Present(true),
..OrdinaryPropertyDescriptor::new()
};
assert!(
caller
.define_own_property(&caller.global_object().unwrap(), &key, &descriptor(9),)
.unwrap()
);
let defining_global = defining.global_object().unwrap();
assert!(
defining
.define_own_property(&defining_global, &key, &descriptor(1))
.unwrap()
);
runtime.run_gc().unwrap();
assert_eq!(
caller.call(&reader, Value::Undefined, &[]).unwrap(),
Value::Int(1)
);
assert!(runtime.delete_property(&defining_global, &key).unwrap());
runtime.run_gc().unwrap();
let Err(RuntimeError::Exception(Value::Object(exception))) =
caller.call(&reader, Value::Undefined, &[])
else {
panic!("missing defining-realm global did not throw an object");
};
let reference_error = runtime.intern_property_key("ReferenceError").unwrap();
let prototype = runtime.intern_property_key("prototype").unwrap();
let Value::Object(reference_error) = defining
.get_property(&defining_global, &reference_error)
.unwrap()
else {
panic!("defining realm ReferenceError was not an object");
};
let Value::Object(reference_error_prototype) =
defining.get_property(&reference_error, &prototype).unwrap()
else {
panic!("defining realm ReferenceError.prototype was not an object");
};
assert_eq!(
runtime.get_prototype_of(&exception).unwrap(),
Some(reference_error_prototype)
);
assert!(
defining
.define_own_property(&defining_global, &key, &descriptor(2))
.unwrap()
);
runtime.run_gc().unwrap();
assert_eq!(
caller.call(&reader, Value::Undefined, &[]).unwrap(),
Value::Int(2)
);
}