Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.d/7102-node-test-mock-symbol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added `node:test` mock method support for Symbol-keyed object methods, including
Node-compatible call tracking and restore behavior.
68 changes: 63 additions & 5 deletions crates/perry-runtime/src/node_submodules/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ fn parse_mock_timer_options(options: f64) -> (u32, f64) {
#[derive(Clone)]
enum MockRestoreTarget {
None,
// Node 26.5 accepts Symbol method names, but its restore path only
// recognizes string names and falls through to the bare-function case.
ObjectSymbolMethod,
ObjectProperty {
target: f64,
property: String,
Expand Down Expand Up @@ -743,7 +746,11 @@ fn restore_mock_state(id: i64) {
let Some(state) = states.iter_mut().find(|state| state.id == id) else {
return None;
};
state.implementation = state.original;
state.implementation = if matches!(state.restore, MockRestoreTarget::ObjectSymbolMethod) {
undefined_value()
} else {
state.original
};
state.once.clear();
reset_mock_state_calls(state);
Some(state.restore.clone())
Expand Down Expand Up @@ -824,20 +831,42 @@ mod metadata_tests;
extern "C" fn mock_function_invoke(closure: *const ClosureHeader, rest: f64) -> f64 {
let id = closure_id(closure);
let args = array_values(rest).unwrap_or_default();
let implementation = MOCK_STATES.with(|states| {
let (implementation, is_symbol_method) = MOCK_STATES.with(|states| {
let mut states = states.borrow_mut();
let Some(state) = states.iter_mut().find(|state| state.id == id) else {
return undefined_value();
return (undefined_value(), false);
};
if !state.once.is_empty() {
let implementation = if !state.once.is_empty() {
state.once.remove(0)
} else {
state.implementation
}
};
(
implementation,
matches!(state.restore, MockRestoreTarget::ObjectSymbolMethod),
)
});

let this_value = crate::object::js_implicit_this_get();
if JSValue::from_bits(implementation.to_bits()).is_undefined() {
if is_symbol_method {
let scope = crate::gc::RuntimeHandleScope::new();
let rest_handle = scope.root_nanbox_f64(rest);
let this_handle = scope.root_nanbox_f64(this_value);
let message_handle = scope.root_nanbox_f64(string_value("undefined is not a function"));
let error = crate::error::js_typeerror_new(raw_ptr_from_value(
message_handle.get_nanbox_f64(),
) as *mut crate::StringHeader);
let error_handle = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(error as i64));
record_mock_call(
id,
rest_handle.get_nanbox_f64(),
this_handle.get_nanbox_f64(),
undefined_value(),
error_handle.get_nanbox_f64(),
);
crate::exception::js_throw(error_handle.get_nanbox_f64());
}
record_mock_call(id, rest, this_value, undefined_value(), undefined_value());
return undefined_value();
}
Expand Down Expand Up @@ -995,6 +1024,35 @@ extern "C" fn mock_method_thunk(
implementation.get_nanbox_f64(),
);
}
if unsafe { crate::symbol::js_is_symbol(property.get_nanbox_f64()) } != 0 {
object_target_addr(target.get_nanbox_f64());
let original = unsafe {
crate::symbol::js_object_get_symbol_property(
target.get_nanbox_f64(),
property.get_nanbox_f64(),
)
};
let implementation = if is_undefined_value(implementation.get_nanbox_f64()) {
original
} else {
implementation.get_nanbox_f64()
};
assert_callable_arg("implementation", implementation);
let function = scope.root_nanbox_f64(create_mock_function(
original,
implementation,
MockRestoreTarget::ObjectSymbolMethod,
));
unsafe {
crate::symbol::js_object_set_symbol_property(
target.get_nanbox_f64(),
property.get_nanbox_f64(),
function.get_nanbox_f64(),
);
}
return function.get_nanbox_f64();
}

let property_name = property_name(property.get_nanbox_f64());
let original = get_property_value(target.get_nanbox_f64(), &property_name);
let implementation = if is_undefined_value(implementation.get_nanbox_f64()) {
Expand Down
Loading