Skip to content
File

Blob: src/rust/jsg-test/tests/function.rs

rust196 lines
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 
7use jsg::Number;
8use jsg::ToJS;
9use jsg::v8;
10 
11/// `try_as::<Function>()` succeeds for a JS function value.
12#[test]
13fn 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]
24fn 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]
35fn 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]
46fn 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]
57fn 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]
68fn 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]
79fn 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]
92fn 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]
108fn 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]
123fn 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]
139fn 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]
159fn 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]
173fn 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]
186fn 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}