File
Blob: src/rust/jsg-test/tests/function.rs
| 1 | // Copyright (c) 2026 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | //! Tests for `Local<Function>::call()`, `As<T>` trait, and `impl_local_cast!` conversions. |
| 6 | |
| 7 | use jsg::Number; |
| 8 | use jsg::ToJS; |
| 9 | use jsg::v8; |
| 10 | |
| 11 | /// `try_as::<Function>()` succeeds for a JS function value. |
| 12 | #[test] |
| 13 | fn try_as_function_succeeds() { |
| 14 | let harness = crate::Harness::new(); |
| 15 | harness.run_in_context(|_lock, ctx| { |
| 16 | let value = ctx.eval_raw("(x => x * 2)").unwrap(); |
| 17 | assert!(value.try_as::<v8::Function>().is_some()); |
| 18 | Ok(()) |
| 19 | }); |
| 20 | } |
| 21 | |
| 22 | /// `try_as::<Function>()` returns `None` for a non-function value. |
| 23 | #[test] |
| 24 | fn try_as_function_fails_for_number() { |
| 25 | let harness = crate::Harness::new(); |
| 26 | harness.run_in_context(|_lock, ctx| { |
| 27 | let value = ctx.eval_raw("42").unwrap(); |
| 28 | assert!(value.try_as::<v8::Function>().is_none()); |
| 29 | Ok(()) |
| 30 | }); |
| 31 | } |
| 32 | |
| 33 | /// `try_as::<Object>()` succeeds for a plain object. |
| 34 | #[test] |
| 35 | fn try_as_object_succeeds() { |
| 36 | let harness = crate::Harness::new(); |
| 37 | harness.run_in_context(|_lock, ctx| { |
| 38 | let value = ctx.eval_raw("({a: 1})").unwrap(); |
| 39 | assert!(value.try_as::<v8::Object>().is_some()); |
| 40 | Ok(()) |
| 41 | }); |
| 42 | } |
| 43 | |
| 44 | /// `try_as::<Object>()` returns `None` for a primitive. |
| 45 | #[test] |
| 46 | fn try_as_object_fails_for_string() { |
| 47 | let harness = crate::Harness::new(); |
| 48 | harness.run_in_context(|_lock, ctx| { |
| 49 | let value = ctx.eval_raw("'hello'").unwrap(); |
| 50 | assert!(value.try_as::<v8::Object>().is_none()); |
| 51 | Ok(()) |
| 52 | }); |
| 53 | } |
| 54 | |
| 55 | /// `try_as::<Array>()` succeeds for an array. |
| 56 | #[test] |
| 57 | fn try_as_array_succeeds() { |
| 58 | let harness = crate::Harness::new(); |
| 59 | harness.run_in_context(|_lock, ctx| { |
| 60 | let value = ctx.eval_raw("[1, 2, 3]").unwrap(); |
| 61 | assert!(value.try_as::<v8::Array>().is_some()); |
| 62 | Ok(()) |
| 63 | }); |
| 64 | } |
| 65 | |
| 66 | /// `try_as::<Array>()` returns `None` for a non-array object. |
| 67 | #[test] |
| 68 | fn try_as_array_fails_for_object() { |
| 69 | let harness = crate::Harness::new(); |
| 70 | harness.run_in_context(|_lock, ctx| { |
| 71 | let value = ctx.eval_raw("({length: 3})").unwrap(); |
| 72 | assert!(value.try_as::<v8::Array>().is_none()); |
| 73 | Ok(()) |
| 74 | }); |
| 75 | } |
| 76 | |
| 77 | /// `Local<Function>::call` with zero args returns the correct value via `FromJS`. |
| 78 | #[test] |
| 79 | fn call_zero_args() { |
| 80 | let harness = crate::Harness::new(); |
| 81 | harness.run_in_context(|lock, ctx| { |
| 82 | let value = ctx.eval_raw("(() => 42)").unwrap(); |
| 83 | let func = value.try_as::<v8::Function>().unwrap(); |
| 84 | let result = func.call::<Number, _>(lock, None::<v8::Local<v8::Value>>, &[])?; |
| 85 | assert!((result.value() - 42.0).abs() < f64::EPSILON); |
| 86 | Ok(()) |
| 87 | }); |
| 88 | } |
| 89 | |
| 90 | /// `Local<Function>::call` with arguments. |
| 91 | #[test] |
| 92 | fn call_with_args() { |
| 93 | let harness = crate::Harness::new(); |
| 94 | harness.run_in_context(|lock, ctx| { |
| 95 | let value = ctx.eval_raw("((a, b) => a + b)").unwrap(); |
| 96 | let func = value.try_as::<v8::Function>().unwrap(); |
| 97 | |
| 98 | let a = Number::new(10.0).to_js(lock); |
| 99 | let b = Number::new(32.0).to_js(lock); |
| 100 | let result = func.call::<Number, _>(lock, None::<v8::Local<v8::Value>>, &[a, b])?; |
| 101 | assert!((result.value() - 42.0).abs() < f64::EPSILON); |
| 102 | Ok(()) |
| 103 | }); |
| 104 | } |
| 105 | |
| 106 | /// `Local<Function>::call` returning a string via `FromJS`. |
| 107 | #[test] |
| 108 | fn call_returns_string() { |
| 109 | let harness = crate::Harness::new(); |
| 110 | harness.run_in_context(|lock, ctx| { |
| 111 | let value = ctx.eval_raw("(s => s.toUpperCase())").unwrap(); |
| 112 | let func = value.try_as::<v8::Function>().unwrap(); |
| 113 | |
| 114 | let arg = String::from("hello").to_js(lock); |
| 115 | let result = func.call::<String, _>(lock, None::<v8::Local<v8::Value>>, &[arg])?; |
| 116 | assert_eq!(result, "HELLO"); |
| 117 | Ok(()) |
| 118 | }); |
| 119 | } |
| 120 | |
| 121 | /// `Local<Function>::call` with an object receiver (`this`). |
| 122 | #[test] |
| 123 | fn call_with_receiver() { |
| 124 | let harness = crate::Harness::new(); |
| 125 | harness.run_in_context(|lock, ctx| { |
| 126 | // Create an object with a property and a function that reads `this.x` |
| 127 | let obj = ctx.eval_raw("({x: 100})").unwrap(); |
| 128 | let func_val = ctx.eval_raw("(function() { return this.x; })").unwrap(); |
| 129 | let func = func_val.try_as::<v8::Function>().unwrap(); |
| 130 | |
| 131 | let result = func.call::<Number, _>(lock, Some(obj), &[])?; |
| 132 | assert!((result.value() - 100.0).abs() < f64::EPSILON); |
| 133 | Ok(()) |
| 134 | }); |
| 135 | } |
| 136 | |
| 137 | /// `Local<Function>::call` with a `Local<Object>` receiver via `.into()`. |
| 138 | #[test] |
| 139 | fn call_with_object_receiver_via_into() { |
| 140 | let harness = crate::Harness::new(); |
| 141 | harness.run_in_context(|lock, ctx| { |
| 142 | let obj_val = ctx.eval_raw("({name: 'world'})").unwrap(); |
| 143 | let obj = obj_val.try_as::<v8::Object>().unwrap(); |
| 144 | |
| 145 | let func_val = ctx |
| 146 | .eval_raw("(function() { return 'hello ' + this.name; })") |
| 147 | .unwrap(); |
| 148 | let func = func_val.try_as::<v8::Function>().unwrap(); |
| 149 | |
| 150 | // Pass Local<Object> as receiver — the Into<Local<Value>> bound handles conversion. |
| 151 | let result = func.call::<String, v8::Local<v8::Object>>(lock, Some(obj), &[])?; |
| 152 | assert_eq!(result, "hello world"); |
| 153 | Ok(()) |
| 154 | }); |
| 155 | } |
| 156 | |
| 157 | /// `impl_local_cast!` conversions: `Local<Object>` → `Local<Value>` via `Into`. |
| 158 | #[test] |
| 159 | fn local_object_into_value() { |
| 160 | let harness = crate::Harness::new(); |
| 161 | harness.run_in_context(|_lock, ctx| { |
| 162 | let obj_val = ctx.eval_raw("({})").unwrap(); |
| 163 | let obj = obj_val.try_as::<v8::Object>().unwrap(); |
| 164 | // Into<Local<Value>> should compile and not panic in debug. |
| 165 | let value: v8::Local<v8::Value> = obj.into(); |
| 166 | assert!(value.is_object()); |
| 167 | Ok(()) |
| 168 | }); |
| 169 | } |
| 170 | |
| 171 | /// `impl_local_cast!` conversions: `Local<Function>` → `Local<Value>` via `Into`. |
| 172 | #[test] |
| 173 | fn local_function_into_value() { |
| 174 | let harness = crate::Harness::new(); |
| 175 | harness.run_in_context(|_lock, ctx| { |
| 176 | let func_val = ctx.eval_raw("(() => {})").unwrap(); |
| 177 | let func = func_val.try_as::<v8::Function>().unwrap(); |
| 178 | let value: v8::Local<v8::Value> = func.into(); |
| 179 | assert!(value.is_object()); |
| 180 | Ok(()) |
| 181 | }); |
| 182 | } |
| 183 | |
| 184 | /// `impl_local_cast!` conversions: `Local<Array>` → `Local<Value>` via `Into`. |
| 185 | #[test] |
| 186 | fn local_array_into_value() { |
| 187 | let harness = crate::Harness::new(); |
| 188 | harness.run_in_context(|_lock, ctx| { |
| 189 | let arr_val = ctx.eval_raw("[1, 2]").unwrap(); |
| 190 | let arr = arr_val.try_as::<v8::Array>().unwrap(); |
| 191 | let value: v8::Local<v8::Value> = arr.into(); |
| 192 | assert!(value.is_array()); |
| 193 | Ok(()) |
| 194 | }); |
| 195 | } |