diff --git a/changelog.d/9872-mock-timers-validation.md b/changelog.d/9872-mock-timers-validation.md new file mode 100644 index 0000000000..d068f1dac0 --- /dev/null +++ b/changelog.d/9872-mock-timers-validation.md @@ -0,0 +1,4 @@ +### Fixed + +- Match Node 26 mock-timer validation by accepting default primitive options + and non-negative infinite clock advances. diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index 8b3b032340..cb70c0b994 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -228,7 +228,7 @@ extern "C" fn mock_timers_tick(_closure: *const ClosureHeader, ms: f64) -> f64 { let delay = if is_undefined_value(ms) { 1.0 } else { - validate_mock_timer_number("time", ms) + validate_mock_timer_number("time", ms, false) }; crate::timer::js_mock_timers_tick(delay); undefined_value() @@ -240,7 +240,7 @@ extern "C" fn mock_timers_run_all(_closure: *const ClosureHeader) -> f64 { } extern "C" fn mock_timers_set_time(_closure: *const ClosureHeader, ms: f64) -> f64 { - let time = validate_mock_timer_number("time", ms); + let time = validate_mock_timer_number("time", ms, false); crate::timer::js_mock_timers_set_time(time); undefined_value() } @@ -250,15 +250,15 @@ extern "C" fn mock_timers_reset(_closure: *const ClosureHeader) -> f64 { undefined_value() } -fn validate_mock_timer_number(arg: &str, value: f64) -> f64 { +fn validate_mock_timer_number(arg: &str, value: f64, reject_nan: bool) -> f64 { let js = JSValue::from_bits(value.to_bits()); if !crate::fs::validate::is_numeric(js) { throw_invalid_arg_type(arg, "number", value); } let n = crate::builtins::js_number_coerce(value); - if !n.is_finite() || n < 0.0 { + if n < 0.0 || (reject_nan && n.is_nan()) { let message = format!( - "The \"{}\" argument must be a non-negative finite number. Received {}", + "The \"{}\" argument must be a non-negative number. Received {}", arg, crate::fs::validate::describe_received(value) ); @@ -271,16 +271,13 @@ fn parse_mock_timer_options(options: f64) -> (u32, f64) { let mut apis_value = options; let mut now = 0.0; let js = JSValue::from_bits(options.to_bits()); - if js.is_undefined() { + if js.is_undefined() || js.is_null() || !js.is_pointer() { return (crate::timer::MOCK_TIMERS_ALL_APIS, now); } if !is_array_value(options) { - if js.is_null() || !js.is_pointer() { - throw_invalid_arg_type("options", "object", options); - } apis_value = object_property(options, b"apis").unwrap_or(undefined_value()); if let Some(now_value) = object_property(options, b"now") { - now = validate_mock_timer_number("options.now", now_value); + now = validate_mock_timer_number("options.now", now_value, true); } } if JSValue::from_bits(apis_value.to_bits()).is_undefined() { diff --git a/crates/perry-runtime/src/node_submodules/test_unit_tests.rs b/crates/perry-runtime/src/node_submodules/test_unit_tests.rs index a54218a18c..c27a4bc471 100644 --- a/crates/perry-runtime/src/node_submodules/test_unit_tests.rs +++ b/crates/perry-runtime/src/node_submodules/test_unit_tests.rs @@ -175,3 +175,21 @@ fn mock_timers_exposes_dispose_as_reset() { assert!(is_callable_value(symbol_method)); assert_ne!(symbol_method.to_bits(), reset.to_bits()); } + +#[test] +fn mock_timers_accepts_null_and_primitives_as_default_options() { + for options in [f64::from_bits(crate::value::TAG_NULL), 1.0] { + let (apis, now) = parse_mock_timer_options(options); + + assert_eq!(apis, crate::timer::MOCK_TIMERS_ALL_APIS); + assert_eq!(now, 0.0); + } +} + +#[test] +fn mock_timer_clock_values_accept_positive_infinity() { + assert_eq!( + validate_mock_timer_number("time", f64::INFINITY, false), + f64::INFINITY + ); +} diff --git a/test-parity/node-suite/test/mock-timers/validation.ts b/test-parity/node-suite/test/mock-timers/validation.ts index 6f69fbc0fb..d33f949fc9 100644 --- a/test-parity/node-suite/test/mock-timers/validation.ts +++ b/test-parity/node-suite/test/mock-timers/validation.ts @@ -12,7 +12,10 @@ function codeOf(fn: () => void): string { console.log("runAll disabled:", codeOf(() => mock.timers.runAll())); console.log("tick disabled:", codeOf(() => mock.timers.tick())); console.log("setTime disabled:", codeOf(() => mock.timers.setTime(1))); -console.log("bad options:", codeOf(() => mock.timers.enable(null as any))); +console.log("null options:", codeOf(() => mock.timers.enable(null as any))); +mock.timers.reset(); +console.log("number options:", codeOf(() => mock.timers.enable(1 as any))); +mock.timers.reset(); console.log("bad api type:", codeOf(() => mock.timers.enable({ apis: [1 as any] }))); console.log("bad now:", codeOf(() => mock.timers.enable({ now: -1 }))); mock.timers.enable({ apis: ["Date"], now: 0 });