File
Blob: src/rust/jsg-test/tests/local_cast.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 `impl_local_cast!` conversions added for Float/BigInt typed arrays, |
| 6 | //! and upcasts to Object (Function -> Object, Array -> Object, `TypedArray` -> Object). |
| 7 | |
| 8 | use jsg::ToJS; |
| 9 | use jsg::v8; |
| 10 | |
| 11 | // ============================================================================= |
| 12 | // Float/BigInt typed arrays -> Value |
| 13 | // ============================================================================= |
| 14 | |
| 15 | /// `Local<Float32Array>` → `Local<Value>` via `Into`, preserving the type tag. |
| 16 | #[test] |
| 17 | fn float32_array_into_value() { |
| 18 | let harness = crate::Harness::new(); |
| 19 | harness.run_in_context(|lock, _ctx| { |
| 20 | let data: Vec<f32> = vec![1.0, 2.0, 3.0]; |
| 21 | let js_val = data.to_js(lock); |
| 22 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 23 | let typed: v8::Local<'_, v8::Float32Array> = |
| 24 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 25 | assert_eq!(typed.len(), 3); |
| 26 | let value: v8::Local<v8::Value> = typed.into(); |
| 27 | assert!(value.is_float32_array()); |
| 28 | assert!(value.is_object()); |
| 29 | assert!(!value.is_float64_array()); |
| 30 | Ok(()) |
| 31 | }); |
| 32 | } |
| 33 | |
| 34 | /// `Local<Float64Array>` → `Local<Value>` via `Into`, preserving the type tag. |
| 35 | #[test] |
| 36 | fn float64_array_into_value() { |
| 37 | let harness = crate::Harness::new(); |
| 38 | harness.run_in_context(|lock, _ctx| { |
| 39 | let data: Vec<f64> = vec![1.0, 2.0, 3.0]; |
| 40 | let js_val = data.to_js(lock); |
| 41 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 42 | let typed: v8::Local<'_, v8::Float64Array> = |
| 43 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 44 | assert_eq!(typed.len(), 3); |
| 45 | let value: v8::Local<v8::Value> = typed.into(); |
| 46 | assert!(value.is_float64_array()); |
| 47 | assert!(value.is_object()); |
| 48 | assert!(!value.is_float32_array()); |
| 49 | Ok(()) |
| 50 | }); |
| 51 | } |
| 52 | |
| 53 | /// `Local<BigInt64Array>` → `Local<Value>` via `Into`, preserving the type tag. |
| 54 | #[test] |
| 55 | fn bigint64_array_into_value() { |
| 56 | let harness = crate::Harness::new(); |
| 57 | harness.run_in_context(|lock, _ctx| { |
| 58 | let data: Vec<i64> = vec![1, 2, 3]; |
| 59 | let js_val = data.to_js(lock); |
| 60 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 61 | let typed: v8::Local<'_, v8::BigInt64Array> = |
| 62 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 63 | assert_eq!(typed.len(), 3); |
| 64 | let value: v8::Local<v8::Value> = typed.into(); |
| 65 | assert!(value.is_bigint64_array()); |
| 66 | assert!(value.is_object()); |
| 67 | assert!(!value.is_biguint64_array()); |
| 68 | Ok(()) |
| 69 | }); |
| 70 | } |
| 71 | |
| 72 | /// `Local<BigUint64Array>` → `Local<Value>` via `Into`, preserving the type tag. |
| 73 | #[test] |
| 74 | fn biguint64_array_into_value() { |
| 75 | let harness = crate::Harness::new(); |
| 76 | harness.run_in_context(|lock, _ctx| { |
| 77 | let data: Vec<u64> = vec![1, 2, 3]; |
| 78 | let js_val = data.to_js(lock); |
| 79 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 80 | let typed: v8::Local<'_, v8::BigUint64Array> = |
| 81 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 82 | assert_eq!(typed.len(), 3); |
| 83 | let value: v8::Local<v8::Value> = typed.into(); |
| 84 | assert!(value.is_biguint64_array()); |
| 85 | assert!(value.is_object()); |
| 86 | assert!(!value.is_bigint64_array()); |
| 87 | Ok(()) |
| 88 | }); |
| 89 | } |
| 90 | |
| 91 | // ============================================================================= |
| 92 | // Float/BigInt typed arrays -> TypedArray |
| 93 | // ============================================================================= |
| 94 | |
| 95 | /// `Local<Float32Array>` → `Local<TypedArray>` preserves length. |
| 96 | #[test] |
| 97 | fn float32_array_into_typed_array() { |
| 98 | let harness = crate::Harness::new(); |
| 99 | harness.run_in_context(|lock, _ctx| { |
| 100 | let data: Vec<f32> = vec![1.5, 2.5]; |
| 101 | let js_val = data.to_js(lock); |
| 102 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 103 | let typed: v8::Local<'_, v8::Float32Array> = |
| 104 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 105 | assert_eq!(typed.len(), 2); |
| 106 | let ta: v8::Local<v8::TypedArray> = typed.into(); |
| 107 | assert_eq!(ta.len(), 2); |
| 108 | assert!(!ta.is_empty()); |
| 109 | // Round-trip back and verify elements |
| 110 | let back: v8::Local<v8::Float32Array> = ta.into(); |
| 111 | assert!((back.get(0) - 1.5).abs() < f32::EPSILON); |
| 112 | assert!((back.get(1) - 2.5).abs() < f32::EPSILON); |
| 113 | Ok(()) |
| 114 | }); |
| 115 | } |
| 116 | |
| 117 | /// `Local<Float64Array>` → `Local<TypedArray>` preserves length. |
| 118 | #[test] |
| 119 | fn float64_array_into_typed_array() { |
| 120 | let harness = crate::Harness::new(); |
| 121 | harness.run_in_context(|lock, _ctx| { |
| 122 | let data: Vec<f64> = vec![1.5, 2.5]; |
| 123 | let js_val = data.to_js(lock); |
| 124 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 125 | let typed: v8::Local<'_, v8::Float64Array> = |
| 126 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 127 | assert_eq!(typed.len(), 2); |
| 128 | let ta: v8::Local<v8::TypedArray> = typed.into(); |
| 129 | assert_eq!(ta.len(), 2); |
| 130 | assert!(!ta.is_empty()); |
| 131 | // Round-trip back and verify elements |
| 132 | let back: v8::Local<v8::Float64Array> = ta.into(); |
| 133 | assert!((back.get(0) - 1.5).abs() < f64::EPSILON); |
| 134 | assert!((back.get(1) - 2.5).abs() < f64::EPSILON); |
| 135 | Ok(()) |
| 136 | }); |
| 137 | } |
| 138 | |
| 139 | /// `Local<BigInt64Array>` → `Local<TypedArray>` preserves length. |
| 140 | #[test] |
| 141 | fn bigint64_array_into_typed_array() { |
| 142 | let harness = crate::Harness::new(); |
| 143 | harness.run_in_context(|lock, _ctx| { |
| 144 | let data: Vec<i64> = vec![10, -20]; |
| 145 | let js_val = data.to_js(lock); |
| 146 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 147 | let typed: v8::Local<'_, v8::BigInt64Array> = |
| 148 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 149 | assert_eq!(typed.len(), 2); |
| 150 | let ta: v8::Local<v8::TypedArray> = typed.into(); |
| 151 | assert_eq!(ta.len(), 2); |
| 152 | assert!(!ta.is_empty()); |
| 153 | // Round-trip back and verify elements |
| 154 | let back: v8::Local<v8::BigInt64Array> = ta.into(); |
| 155 | assert_eq!(back.get(0), 10); |
| 156 | assert_eq!(back.get(1), -20); |
| 157 | Ok(()) |
| 158 | }); |
| 159 | } |
| 160 | |
| 161 | /// `Local<BigUint64Array>` → `Local<TypedArray>` preserves length. |
| 162 | #[test] |
| 163 | fn biguint64_array_into_typed_array() { |
| 164 | let harness = crate::Harness::new(); |
| 165 | harness.run_in_context(|lock, _ctx| { |
| 166 | let data: Vec<u64> = vec![10, 20]; |
| 167 | let js_val = data.to_js(lock); |
| 168 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 169 | let typed: v8::Local<'_, v8::BigUint64Array> = |
| 170 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 171 | assert_eq!(typed.len(), 2); |
| 172 | let ta: v8::Local<v8::TypedArray> = typed.into(); |
| 173 | assert_eq!(ta.len(), 2); |
| 174 | assert!(!ta.is_empty()); |
| 175 | // Round-trip back and verify elements |
| 176 | let back: v8::Local<v8::BigUint64Array> = ta.into(); |
| 177 | assert_eq!(back.get(0), 10); |
| 178 | assert_eq!(back.get(1), 20); |
| 179 | Ok(()) |
| 180 | }); |
| 181 | } |
| 182 | |
| 183 | // ============================================================================= |
| 184 | // Upcasts to Object |
| 185 | // ============================================================================= |
| 186 | |
| 187 | /// `Local<Function>` → `Local<Object>` via `Into`; verify `has()` sees `name` property. |
| 188 | #[test] |
| 189 | fn function_into_object() { |
| 190 | let harness = crate::Harness::new(); |
| 191 | harness.run_in_context(|lock, ctx| { |
| 192 | let func_val = ctx.eval_raw("(function myFunc() {})").unwrap(); |
| 193 | assert!(func_val.is_function()); |
| 194 | let func = func_val.try_as::<v8::Function>().unwrap(); |
| 195 | let obj: v8::Local<v8::Object> = func.into(); |
| 196 | // Functions are objects — the `name` property should be accessible. |
| 197 | assert!(obj.has(lock, "name")); |
| 198 | let prop = obj.get(lock, "name").unwrap(); |
| 199 | let name: String = <String as jsg::FromJS>::from_js(lock, prop)?; |
| 200 | assert_eq!(name, "myFunc"); |
| 201 | Ok(()) |
| 202 | }); |
| 203 | } |
| 204 | |
| 205 | /// `Local<Array>` → `Local<Object>` via `Into`; verify `length` property preserved. |
| 206 | #[test] |
| 207 | fn array_into_object() { |
| 208 | let harness = crate::Harness::new(); |
| 209 | harness.run_in_context(|lock, ctx| { |
| 210 | let arr_val = ctx.eval_raw("[1, 2, 3]").unwrap(); |
| 211 | assert!(arr_val.is_array()); |
| 212 | let arr = arr_val.try_as::<v8::Array>().unwrap(); |
| 213 | assert_eq!(arr.len(), 3); |
| 214 | let obj: v8::Local<v8::Object> = arr.into(); |
| 215 | assert!(obj.has(lock, "length")); |
| 216 | let prop = obj.get(lock, "length").unwrap(); |
| 217 | let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?; |
| 218 | assert!((len.value() - 3.0).abs() < f64::EPSILON); |
| 219 | Ok(()) |
| 220 | }); |
| 221 | } |
| 222 | |
| 223 | /// `Local<TypedArray>` → `Local<Object>` via `Into`; verify `byteLength` property. |
| 224 | #[test] |
| 225 | fn typed_array_into_object() { |
| 226 | let harness = crate::Harness::new(); |
| 227 | harness.run_in_context(|lock, _ctx| { |
| 228 | let data: Vec<u8> = vec![1, 2, 3]; |
| 229 | let js_val = data.to_js(lock); |
| 230 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 231 | let typed: v8::Local<'_, v8::Uint8Array> = |
| 232 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 233 | assert_eq!(typed.len(), 3); |
| 234 | // Uint8Array -> TypedArray -> Object |
| 235 | let ta: v8::Local<v8::TypedArray> = typed.into(); |
| 236 | assert_eq!(ta.len(), 3); |
| 237 | let obj: v8::Local<v8::Object> = ta.into(); |
| 238 | assert!(obj.has(lock, "byteLength")); |
| 239 | let prop = obj.get(lock, "byteLength").unwrap(); |
| 240 | let byte_len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?; |
| 241 | assert!((byte_len.value() - 3.0).abs() < f64::EPSILON); |
| 242 | Ok(()) |
| 243 | }); |
| 244 | } |
| 245 | |
| 246 | // ============================================================================= |
| 247 | // Property access on Object obtained from upcast |
| 248 | // ============================================================================= |
| 249 | |
| 250 | /// Upcast `Function` to `Object`, then read a custom property via `.get()`. |
| 251 | /// Functions are objects in JS, so custom properties can be set on them. |
| 252 | #[test] |
| 253 | fn function_as_object_property_access() { |
| 254 | let harness = crate::Harness::new(); |
| 255 | harness.run_in_context(|lock, ctx| { |
| 256 | let func_val = ctx |
| 257 | .eval_raw("var f = () => {}; f.custom = 'hello'; f") |
| 258 | .unwrap(); |
| 259 | let func = func_val.try_as::<v8::Function>().unwrap(); |
| 260 | let obj: v8::Local<v8::Object> = func.into(); |
| 261 | |
| 262 | assert!(obj.has(lock, "custom")); |
| 263 | let prop = obj.get(lock, "custom").unwrap(); |
| 264 | let val: String = <String as jsg::FromJS>::from_js(lock, prop)?; |
| 265 | assert_eq!(val, "hello"); |
| 266 | Ok(()) |
| 267 | }); |
| 268 | } |
| 269 | |
| 270 | /// Upcast `Array` to `Object`, then read `length` via `.get()`. |
| 271 | #[test] |
| 272 | fn array_as_object_property_access() { |
| 273 | let harness = crate::Harness::new(); |
| 274 | harness.run_in_context(|lock, ctx| { |
| 275 | let arr_val = ctx.eval_raw("[10, 20, 30]").unwrap(); |
| 276 | let arr = arr_val.try_as::<v8::Array>().unwrap(); |
| 277 | let obj: v8::Local<v8::Object> = arr.into(); |
| 278 | |
| 279 | assert!(obj.has(lock, "length")); |
| 280 | let prop = obj.get(lock, "length").unwrap(); |
| 281 | let val: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?; |
| 282 | assert!((val.value() - 3.0).abs() < f64::EPSILON); |
| 283 | Ok(()) |
| 284 | }); |
| 285 | } |
| 286 | |
| 287 | /// Upcast `TypedArray` (via `Uint8Array`) to `Object`, then read `byteLength`. |
| 288 | #[test] |
| 289 | fn typed_array_as_object_property_access() { |
| 290 | let harness = crate::Harness::new(); |
| 291 | harness.run_in_context(|lock, _ctx| { |
| 292 | let data: Vec<u8> = vec![1, 2, 3, 4, 5]; |
| 293 | let js_val = data.to_js(lock); |
| 294 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 295 | let typed: v8::Local<'_, v8::Uint8Array> = |
| 296 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 297 | let ta: v8::Local<v8::TypedArray> = typed.into(); |
| 298 | let obj: v8::Local<v8::Object> = ta.into(); |
| 299 | |
| 300 | assert!(obj.has(lock, "byteLength")); |
| 301 | assert!(obj.has(lock, "length")); |
| 302 | let prop = obj.get(lock, "byteLength").unwrap(); |
| 303 | let byte_len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?; |
| 304 | assert!((byte_len.value() - 5.0).abs() < f64::EPSILON); |
| 305 | let prop = obj.get(lock, "length").unwrap(); |
| 306 | let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?; |
| 307 | assert!((len.value() - 5.0).abs() < f64::EPSILON); |
| 308 | Ok(()) |
| 309 | }); |
| 310 | } |
| 311 | |
| 312 | // ============================================================================= |
| 313 | // Bidirectional casts (downcast from Value back to concrete type) |
| 314 | // ============================================================================= |
| 315 | |
| 316 | /// Round-trip: `Float32Array` → `Value` → `Float32Array`; verify elements preserved. |
| 317 | #[test] |
| 318 | fn float32_array_value_roundtrip() { |
| 319 | let harness = crate::Harness::new(); |
| 320 | harness.run_in_context(|lock, _ctx| { |
| 321 | let data: Vec<f32> = vec![1.5, 2.5, 3.5]; |
| 322 | let js_val = data.to_js(lock); |
| 323 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 324 | let typed: v8::Local<'_, v8::Float32Array> = |
| 325 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 326 | assert_eq!(typed.len(), 3); |
| 327 | let value: v8::Local<v8::Value> = typed.into(); |
| 328 | assert!(value.is_float32_array()); |
| 329 | let back: v8::Local<v8::Float32Array> = value.into(); |
| 330 | assert_eq!(back.len(), 3); |
| 331 | assert!((back.get(0) - 1.5).abs() < f32::EPSILON); |
| 332 | assert!((back.get(1) - 2.5).abs() < f32::EPSILON); |
| 333 | assert!((back.get(2) - 3.5).abs() < f32::EPSILON); |
| 334 | Ok(()) |
| 335 | }); |
| 336 | } |
| 337 | |
| 338 | /// Round-trip: `Float64Array` → `Value` → `Float64Array`; verify elements preserved. |
| 339 | #[test] |
| 340 | fn float64_array_value_roundtrip() { |
| 341 | let harness = crate::Harness::new(); |
| 342 | harness.run_in_context(|lock, _ctx| { |
| 343 | let data: Vec<f64> = vec![1.1, 2.2, 3.3]; |
| 344 | let js_val = data.to_js(lock); |
| 345 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 346 | let typed: v8::Local<'_, v8::Float64Array> = |
| 347 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 348 | assert_eq!(typed.len(), 3); |
| 349 | let value: v8::Local<v8::Value> = typed.into(); |
| 350 | assert!(value.is_float64_array()); |
| 351 | let back: v8::Local<v8::Float64Array> = value.into(); |
| 352 | assert_eq!(back.len(), 3); |
| 353 | assert!((back.get(0) - 1.1).abs() < f64::EPSILON); |
| 354 | assert!((back.get(1) - 2.2).abs() < f64::EPSILON); |
| 355 | assert!((back.get(2) - 3.3).abs() < f64::EPSILON); |
| 356 | Ok(()) |
| 357 | }); |
| 358 | } |
| 359 | |
| 360 | /// Round-trip: `BigInt64Array` → `Value` → `BigInt64Array`; verify elements preserved. |
| 361 | #[test] |
| 362 | fn bigint64_array_value_roundtrip() { |
| 363 | let harness = crate::Harness::new(); |
| 364 | harness.run_in_context(|lock, _ctx| { |
| 365 | let data: Vec<i64> = vec![-100, 0, 100]; |
| 366 | let js_val = data.to_js(lock); |
| 367 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 368 | let typed: v8::Local<'_, v8::BigInt64Array> = |
| 369 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 370 | assert_eq!(typed.len(), 3); |
| 371 | let value: v8::Local<v8::Value> = typed.into(); |
| 372 | assert!(value.is_bigint64_array()); |
| 373 | let back: v8::Local<v8::BigInt64Array> = value.into(); |
| 374 | assert_eq!(back.len(), 3); |
| 375 | assert_eq!(back.get(0), -100); |
| 376 | assert_eq!(back.get(1), 0); |
| 377 | assert_eq!(back.get(2), 100); |
| 378 | Ok(()) |
| 379 | }); |
| 380 | } |
| 381 | |
| 382 | /// Round-trip: `BigUint64Array` → `Value` → `BigUint64Array`; verify elements preserved. |
| 383 | #[test] |
| 384 | fn biguint64_array_value_roundtrip() { |
| 385 | let harness = crate::Harness::new(); |
| 386 | harness.run_in_context(|lock, _ctx| { |
| 387 | let data: Vec<u64> = vec![0, 42, 999]; |
| 388 | let js_val = data.to_js(lock); |
| 389 | // SAFETY: The isolate is locked and the FFI handle is from a valid eval result. |
| 390 | let typed: v8::Local<'_, v8::BigUint64Array> = |
| 391 | unsafe { v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; |
| 392 | assert_eq!(typed.len(), 3); |
| 393 | let value: v8::Local<v8::Value> = typed.into(); |
| 394 | assert!(value.is_biguint64_array()); |
| 395 | let back: v8::Local<v8::BigUint64Array> = value.into(); |
| 396 | assert_eq!(back.len(), 3); |
| 397 | assert_eq!(back.get(0), 0); |
| 398 | assert_eq!(back.get(1), 42); |
| 399 | assert_eq!(back.get(2), 999); |
| 400 | Ok(()) |
| 401 | }); |
| 402 | } |
| 403 | |
| 404 | /// Round-trip: `Function` → `Object` → `Function`; verify callable after roundtrip. |
| 405 | #[test] |
| 406 | fn function_object_roundtrip() { |
| 407 | let harness = crate::Harness::new(); |
| 408 | harness.run_in_context(|lock, ctx| { |
| 409 | let func_val = ctx.eval_raw("(function namedFn() { return 42; })").unwrap(); |
| 410 | assert!(func_val.is_function()); |
| 411 | let func = func_val.try_as::<v8::Function>().unwrap(); |
| 412 | let obj: v8::Local<v8::Object> = func.into(); |
| 413 | // Verify function-specific property is readable as Object |
| 414 | assert!(obj.has(lock, "name")); |
| 415 | let prop = obj.get(lock, "name").unwrap(); |
| 416 | let name: String = <String as jsg::FromJS>::from_js(lock, prop)?; |
| 417 | assert_eq!(name, "namedFn"); |
| 418 | // Roundtrip back and verify it's still callable |
| 419 | let back: v8::Local<v8::Function> = obj.into(); |
| 420 | let result = back.call::<jsg::Number, v8::Local<v8::Value>>(lock, None, &[])?; |
| 421 | assert!((result.value() - 42.0).abs() < f64::EPSILON); |
| 422 | Ok(()) |
| 423 | }); |
| 424 | } |
| 425 | |
| 426 | /// Round-trip: `Array` → `Object` → `Array`; verify length preserved. |
| 427 | #[test] |
| 428 | fn array_object_roundtrip() { |
| 429 | let harness = crate::Harness::new(); |
| 430 | harness.run_in_context(|lock, ctx| { |
| 431 | let arr_val = ctx.eval_raw("[10, 20, 30]").unwrap(); |
| 432 | assert!(arr_val.is_array()); |
| 433 | let arr = arr_val.try_as::<v8::Array>().unwrap(); |
| 434 | assert_eq!(arr.len(), 3); |
| 435 | let obj: v8::Local<v8::Object> = arr.into(); |
| 436 | // Verify length via Object property access |
| 437 | let prop = obj.get(lock, "length").unwrap(); |
| 438 | let len: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, prop)?; |
| 439 | assert!((len.value() - 3.0).abs() < f64::EPSILON); |
| 440 | // Roundtrip back and verify Array-specific methods work |
| 441 | let back: v8::Local<v8::Array> = obj.into(); |
| 442 | assert_eq!(back.len(), 3); |
| 443 | assert!(!back.is_empty()); |
| 444 | Ok(()) |
| 445 | }); |
| 446 | } |