// Copyright (c) 2026 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 use jsg::Number; use jsg::ToJS; use jsg_macros::jsg_method; use jsg_macros::jsg_resource; use jsg_macros::jsg_struct; #[jsg_struct] struct Person { pub name: String, pub age: Number, } #[jsg_resource] struct ArrayResource; #[jsg_resource] impl ArrayResource { #[jsg_method] pub fn sum(&self, numbers: Vec) -> Number { Number::new(numbers.iter().map(jsg::Number::value).sum()) } #[jsg_method] pub fn sum_slice(&self, numbers: &[Number]) -> Number { Number::new(numbers.iter().map(jsg::Number::value).sum()) } #[jsg_method] pub fn join_strings(&self, strings: &[String]) -> String { strings.join("-") } #[jsg_method] pub fn double(&self, numbers: Vec) -> Vec { numbers .into_iter() .map(|n| Number::new(n.value() * 2.0)) .collect() } #[jsg_method] pub fn concat_strings(&self, strings: Vec) -> String { strings.join(", ") } #[jsg_method] pub fn split_string(&self, s: &str) -> Vec { s.split(',').map(|s| s.trim().to_owned()).collect() } #[jsg_method] pub fn filter_positive(&self, numbers: Vec) -> Vec { numbers.into_iter().filter(|n| n.value() > 0.0).collect() } #[jsg_method] pub fn reverse_bytes(&self, bytes: Vec) -> Vec { bytes.into_iter().rev().collect() } #[jsg_method] pub fn reverse_bytes_slice(&self, bytes: &[u8]) -> Vec { bytes.iter().copied().rev().collect() } #[jsg_method] pub fn sum_i32(&self, numbers: Vec) -> Number { Number::new(numbers.iter().map(|&n| f64::from(n)).sum()) } #[jsg_method] pub fn sum_i32_slice(&self, numbers: &[i32]) -> Number { Number::new(numbers.iter().map(|&n| f64::from(n)).sum()) } #[jsg_method] pub fn filter_adults(&self, people: Vec) -> Vec { people .into_iter() .filter(|p| p.age.value() >= 18.0) .collect() } // Float32Array methods #[jsg_method] pub fn sum_f32(&self, numbers: Vec) -> Number { Number::new(numbers.iter().map(|&n| f64::from(n)).sum()) } #[jsg_method] pub fn sum_f32_slice(&self, numbers: &[f32]) -> Number { Number::new(numbers.iter().map(|&n| f64::from(n)).sum()) } #[jsg_method] pub fn double_f32(&self, numbers: Vec) -> Vec { numbers.into_iter().map(|n| n * 2.0).collect() } // Float64Array methods #[jsg_method] pub fn sum_f64(&self, numbers: Vec) -> Number { Number::new(numbers.iter().sum()) } #[jsg_method] pub fn sum_f64_slice(&self, numbers: &[f64]) -> Number { Number::new(numbers.iter().sum()) } #[jsg_method] pub fn double_f64(&self, numbers: Vec) -> Vec { numbers.into_iter().map(|n| n * 2.0).collect() } // BigInt64Array methods #[jsg_method] pub fn sum_i64(&self, numbers: Vec) -> Number { #[expect(clippy::cast_precision_loss)] Number::new(numbers.iter().map(|&n| n as f64).sum()) } #[jsg_method] pub fn sum_i64_slice(&self, numbers: &[i64]) -> Number { #[expect(clippy::cast_precision_loss)] Number::new(numbers.iter().map(|&n| n as f64).sum()) } #[jsg_method] pub fn double_i64(&self, numbers: Vec) -> Vec { numbers.into_iter().map(|n| n * 2).collect() } // BigUint64Array methods #[jsg_method] pub fn sum_u64(&self, numbers: Vec) -> Number { #[expect(clippy::cast_precision_loss)] Number::new(numbers.iter().map(|&n| n as f64).sum()) } #[jsg_method] pub fn sum_u64_slice(&self, numbers: &[u64]) -> Number { #[expect(clippy::cast_precision_loss)] Number::new(numbers.iter().map(|&n| n as f64).sum()) } #[jsg_method] pub fn double_u64(&self, numbers: Vec) -> Vec { numbers.into_iter().map(|n| n * 2).collect() } } #[test] fn resource_accepts_array_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); let result: Number = ctx.eval(lock, "arr.sum([1, 2, 3, 4, 5])").unwrap(); assert!((result.value() - 15.0).abs() < f64::EPSILON); let result: String = ctx .eval(lock, "arr.concatStrings(['hello', 'world'])") .unwrap(); assert_eq!(result, "hello, world"); Ok(()) }); } #[test] fn resource_accepts_slice_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); let result: Number = ctx.eval(lock, "arr.sumSlice([1, 2, 3, 4, 5])").unwrap(); assert!((result.value() - 15.0).abs() < f64::EPSILON); let result: String = ctx.eval(lock, "arr.joinStrings(['a', 'b', 'c'])").unwrap(); assert_eq!(result, "a-b-c"); let result: Number = ctx.eval(lock, "arr.sumSlice([])").unwrap(); assert!((result.value() - 0.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn resource_returns_array() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); let result: Vec = ctx.eval(lock, "arr.double([1, 2, 3])").unwrap(); let values: Vec = result.iter().map(jsg::Number::value).collect(); assert_eq!(values, vec![2.0, 4.0, 6.0]); let result: Vec = ctx.eval(lock, "arr.splitString('a, b, c')").unwrap(); assert_eq!(result, vec!["a", "b", "c"]); let result: Vec = ctx .eval(lock, "arr.filterPositive([-1, 2, -3, 4, 0])") .unwrap(); let values: Vec = result.iter().map(jsg::Number::value).collect(); assert_eq!(values, vec![2.0, 4.0]); Ok(()) }); } #[test] fn resource_accepts_typed_array_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); let result: Vec = ctx .eval(lock, "arr.reverseBytes(new Uint8Array([1, 2, 3]))") .unwrap(); assert_eq!(result, vec![3, 2, 1]); let result: Number = ctx .eval(lock, "arr.sumI32(new Int32Array([-10, 20, -5]))") .unwrap(); assert!((result.value() - 5.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn resource_returns_typed_array() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); let is_u8: bool = ctx .eval( lock, "arr.reverseBytes(new Uint8Array([1, 2, 3])) instanceof Uint8Array", ) .unwrap(); assert!(is_u8); Ok(()) }); } #[test] fn resource_accepts_typed_array_slice_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test &[u8] accepts Uint8Array let result: Vec = ctx .eval(lock, "arr.reverseBytesSlice(new Uint8Array([1, 2, 3]))") .unwrap(); assert_eq!(result, vec![3, 2, 1]); // Test &[i32] accepts Int32Array let result: Number = ctx .eval(lock, "arr.sumI32Slice(new Int32Array([-10, 20, -5]))") .unwrap(); assert!((result.value() - 5.0).abs() < f64::EPSILON); // Test empty TypedArray let result: Vec = ctx .eval(lock, "arr.reverseBytesSlice(new Uint8Array([]))") .unwrap(); assert!(result.is_empty()); Ok(()) }); } #[test] fn typed_array_slice_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // &[u8] should reject Int8Array let result: Result, _> = ctx.eval(lock, "arr.reverseBytesSlice(new Int8Array([1, 2, 3]))"); assert!(result.is_err()); // &[u8] should reject regular Array let result: Result, _> = ctx.eval(lock, "arr.reverseBytesSlice([1, 2, 3])"); assert!(result.is_err()); // &[i32] should reject Uint32Array let result: Result = ctx.eval(lock, "arr.sumI32Slice(new Uint32Array([1, 2, 3]))"); assert!(result.is_err()); // &[i32] should reject regular Array let result: Result = ctx.eval(lock, "arr.sumI32Slice([1, 2, 3])"); assert!(result.is_err()); Ok(()) }); } #[test] fn resource_accepts_and_returns_struct_array() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); let result: Vec = ctx .eval( lock, "arr.filterAdults([{name: 'Alice', age: 25}, {name: 'Bob', age: 15}, {name: 'Charlie', age: 30}])", ) .unwrap(); assert_eq!(result.len(), 2); assert_eq!(result[0].name, "Alice"); assert_eq!(result[1].name, "Charlie"); Ok(()) }); } #[test] fn vec_to_js_creates_array() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let vec = vec!["hello".to_owned(), "world".to_owned()]; let js_val = vec.to_js(lock); ctx.set_global("arr", js_val); let is_array: bool = ctx.eval(lock, "Array.isArray(arr)").unwrap(); assert!(is_array); let length: Number = ctx.eval(lock, "arr.length").unwrap(); assert!((length.value() - 2.0).abs() < f64::EPSILON); let first: String = ctx.eval(lock, "arr[0]").unwrap(); assert_eq!(first, "hello"); let vec = vec![Number::new(1.5), Number::new(2.5), Number::new(3.5)]; let js_val = vec.to_js(lock); ctx.set_global("nums", js_val); let sum: Number = ctx.eval(lock, "nums[0] + nums[1] + nums[2]").unwrap(); assert!((sum.value() - 7.5).abs() < f64::EPSILON); let vec = vec![true, false, true]; let js_val = vec.to_js(lock); ctx.set_global("bools", js_val); let result: Vec = ctx.eval(lock, "bools").unwrap(); assert_eq!(result, vec![true, false, true]); Ok(()) }); } #[test] fn vec_from_js_parses_array() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let strings: Vec = ctx.eval(lock, "['a', 'b', 'c']").unwrap(); assert_eq!(strings, vec!["a", "b", "c"]); let numbers: Vec = ctx.eval(lock, "[1, 2, 3]").unwrap(); let values: Vec = numbers.iter().map(jsg::Number::value).collect(); assert_eq!(values, vec![1.0, 2.0, 3.0]); Ok(()) }); } #[test] fn vec_empty_roundtrip() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let vec: Vec = vec![]; let js_val = vec.to_js(lock); ctx.set_global("arr", js_val); let length: Number = ctx.eval(lock, "arr.length").unwrap(); assert!(length.value().abs() < f64::EPSILON); let result: Vec = ctx.eval(lock, "arr").unwrap(); assert!(result.is_empty()); Ok(()) }); } #[test] fn vec_nested_arrays() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let nested = vec![ vec![Number::new(1.0), Number::new(2.0)], vec![Number::new(3.0), Number::new(4.0)], ]; let js_val = nested.to_js(lock); ctx.set_global("matrix", js_val); let first_row_sum: Number = ctx.eval(lock, "matrix[0][0] + matrix[0][1]").unwrap(); assert!((first_row_sum.value() - 3.0).abs() < f64::EPSILON); let second_row_sum: Number = ctx.eval(lock, "matrix[1][0] + matrix[1][1]").unwrap(); assert!((second_row_sum.value() - 7.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn vec_from_non_array_returns_error() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let result: Result, _> = ctx.eval(lock, "'not an array'"); assert!(result.is_err()); Ok(()) }); } #[test] fn typed_array_to_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let vec: Vec = vec![1, 2, 255]; ctx.set_global("u8_arr", vec.to_js(lock)); let check: bool = ctx.eval(lock, "u8_arr instanceof Uint8Array").unwrap(); assert!(check); let val: Number = ctx.eval(lock, "u8_arr[2]").unwrap(); assert!((val.value() - 255.0).abs() < f64::EPSILON); let vec: Vec = vec![1, 2, 65535]; ctx.set_global("u16_arr", vec.to_js(lock)); let check: bool = ctx.eval(lock, "u16_arr instanceof Uint16Array").unwrap(); assert!(check); let val: Number = ctx.eval(lock, "u16_arr[2]").unwrap(); assert!((val.value() - 65535.0).abs() < f64::EPSILON); let vec: Vec = vec![1, 2, 4_294_967_295]; ctx.set_global("u32_arr", vec.to_js(lock)); let check: bool = ctx.eval(lock, "u32_arr instanceof Uint32Array").unwrap(); assert!(check); let val: Number = ctx.eval(lock, "u32_arr[2]").unwrap(); assert!((val.value() - 4_294_967_295.0).abs() < f64::EPSILON); let vec: Vec = vec![-128, 0, 127]; ctx.set_global("i8_arr", vec.to_js(lock)); let check: bool = ctx.eval(lock, "i8_arr instanceof Int8Array").unwrap(); assert!(check); let val: Number = ctx.eval(lock, "i8_arr[0]").unwrap(); assert!((val.value() - (-128.0)).abs() < f64::EPSILON); let vec: Vec = vec![-32768, 0, 32767]; ctx.set_global("i16_arr", vec.to_js(lock)); let check: bool = ctx.eval(lock, "i16_arr instanceof Int16Array").unwrap(); assert!(check); let val: Number = ctx.eval(lock, "i16_arr[0]").unwrap(); assert!((val.value() - (-32768.0)).abs() < f64::EPSILON); let vec: Vec = vec![-2_147_483_648, 0, 2_147_483_647]; ctx.set_global("i32_arr", vec.to_js(lock)); let check: bool = ctx.eval(lock, "i32_arr instanceof Int32Array").unwrap(); assert!(check); let val: Number = ctx.eval(lock, "i32_arr[0]").unwrap(); assert!((val.value() - (-2_147_483_648.0)).abs() < f64::EPSILON); let is_array: bool = ctx.eval(lock, "Array.isArray(u8_arr)").unwrap(); assert!(!is_array); Ok(()) }); } #[test] fn typed_array_from_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let u8_arr: Vec = ctx.eval(lock, "new Uint8Array([10, 20, 255])").unwrap(); assert_eq!(u8_arr, vec![10, 20, 255]); let u16_arr: Vec = ctx .eval(lock, "new Uint16Array([100, 200, 65535])") .unwrap(); assert_eq!(u16_arr, vec![100, 200, 65535]); let u32_arr: Vec = ctx .eval(lock, "new Uint32Array([1000, 2000, 4294967295])") .unwrap(); assert_eq!(u32_arr, vec![1000, 2000, 4_294_967_295]); let i8_arr: Vec = ctx.eval(lock, "new Int8Array([-128, 0, 127])").unwrap(); assert_eq!(i8_arr, vec![-128, 0, 127]); let i16_arr: Vec = ctx .eval(lock, "new Int16Array([-32768, 0, 32767])") .unwrap(); assert_eq!(i16_arr, vec![-32768, 0, 32767]); let i32_arr: Vec = ctx .eval(lock, "new Int32Array([-2147483648, 0, 2147483647])") .unwrap(); assert_eq!(i32_arr, vec![-2_147_483_648, 0, 2_147_483_647]); Ok(()) }); } #[test] fn typed_array_empty_roundtrip() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let vec: Vec = vec![]; ctx.set_global("arr", vec.to_js(lock)); let length: Number = ctx.eval(lock, "arr.length").unwrap(); assert!(length.value().abs() < f64::EPSILON); let result: Vec = ctx.eval(lock, "arr").unwrap(); assert!(result.is_empty()); Ok(()) }); } #[test] fn typed_array_type_mismatch_returns_error() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let result: Result, _> = ctx.eval(lock, "new Int8Array([1, 2, 3])"); assert!(result.is_err()); let result: Result, _> = ctx.eval(lock, "new Uint32Array([1, 2, 3])"); assert!(result.is_err()); let result: Result, _> = ctx.eval(lock, "[1, 2, 3]"); assert!(result.is_err()); let result: Result, _> = ctx.eval(lock, "'hello'"); assert!(result.is_err()); Ok(()) }); } #[test] fn large_typed_array_roundtrip() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let vec: Vec = (0_u32..65536).map(|i| (i % 256) as u8).collect(); ctx.set_global("arr", vec.clone().to_js(lock)); let length: Number = ctx.eval(lock, "arr.length").unwrap(); assert!((length.value() - 65536.0).abs() < f64::EPSILON); let result: Vec = ctx.eval(lock, "arr").unwrap(); assert_eq!(result, vec); Ok(()) }); } #[test] fn typed_array_iter_uint8() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![10, 20, 30]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate is valid and locked, js_val is a valid Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(typed.len(), 3); assert!(!typed.is_empty()); assert_eq!(typed.get(0), 10); assert_eq!(typed.get(1), 20); assert_eq!(typed.get(2), 30); let sum: u8 = typed.iter().fold(0u8, u8::wrapping_add); assert_eq!(sum, 60); let collected: Vec = typed.iter().collect(); assert_eq!(collected, vec![10, 20, 30]); Ok(()) }); } #[test] fn typed_array_into_iter_uint8() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![1, 2, 3, 4, 5]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate is valid and locked, js_val is a valid Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; let sum: u8 = typed.into_iter().sum(); assert_eq!(sum, 15); Ok(()) }); } #[test] fn typed_array_iter_int32() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![-100, 0, 100]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Int32Array> = // SAFETY: isolate is valid and locked, js_val is a valid Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(typed.len(), 3); assert_eq!(typed.get(0), -100); assert_eq!(typed.get(1), 0); assert_eq!(typed.get(2), 100); let sum: i32 = typed.iter().sum(); assert_eq!(sum, 0); Ok(()) }); } #[test] fn typed_array_iter_reverse() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![1, 2, 3, 4]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate is valid and locked, js_val is a valid Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; let reversed: Vec = typed.iter().rev().collect(); assert_eq!(reversed, vec![4, 3, 2, 1]); Ok(()) }); } #[test] fn typed_array_empty() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate is valid and locked, js_val is a valid Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(typed.len(), 0); assert!(typed.is_empty()); assert_eq!(typed.iter().count(), 0); Ok(()) }); } // ============================================================================= // Float32Array tests // ============================================================================= #[test] fn float32_array_parameter_and_return() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test Vec parameter let result: Number = ctx .eval(lock, "arr.sumF32(new Float32Array([1.5, 2.5, 3.0]))") .unwrap(); assert!((result.value() - 7.0).abs() < f64::EPSILON); // Test Vec return value let is_f32: bool = ctx .eval( lock, "arr.doubleF32(new Float32Array([1.0, 2.0])) instanceof Float32Array", ) .unwrap(); assert!(is_f32); let result: Vec = ctx .eval(lock, "arr.doubleF32(new Float32Array([1.0, 2.0, 3.0]))") .unwrap(); assert_eq!(result, vec![2.0, 4.0, 6.0]); Ok(()) }); } #[test] fn float32_array_slice_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test &[f32] parameter let result: Number = ctx .eval(lock, "arr.sumF32Slice(new Float32Array([1.5, 2.5, 3.0]))") .unwrap(); assert!((result.value() - 7.0).abs() < f64::EPSILON); // Test empty Float32Array let result: Number = ctx .eval(lock, "arr.sumF32Slice(new Float32Array([]))") .unwrap(); assert!((result.value() - 0.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn float32_array_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Float32Array should reject Float64Array let result: Result = ctx.eval(lock, "arr.sumF32Slice(new Float64Array([1.0, 2.0]))"); assert!(result.is_err()); // Float32Array should reject regular Array let result: Result = ctx.eval(lock, "arr.sumF32Slice([1.0, 2.0])"); assert!(result.is_err()); Ok(()) }); } #[test] fn float32_array_to_js_and_from_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { // Test ToJS: Vec -> Float32Array let data: Vec = vec![1.5, 2.5, 3.5, 4.5]; let js_value = data.to_js(lock); ctx.set_global("f32arr", js_value); let is_f32: bool = ctx.eval(lock, "f32arr instanceof Float32Array").unwrap(); assert!(is_f32); let len: Number = ctx.eval(lock, "f32arr.length").unwrap(); #[expect(clippy::cast_sign_loss)] let len_usize = len.value() as usize; assert_eq!(len_usize, 4); // Test FromJS: Float32Array -> Vec let result: Vec = ctx.eval(lock, "f32arr").unwrap(); assert_eq!(result.len(), 4); assert!((result[0] - 1.5).abs() < f32::EPSILON); assert!((result[1] - 2.5).abs() < f32::EPSILON); assert!((result[2] - 3.5).abs() < f32::EPSILON); assert!((result[3] - 4.5).abs() < f32::EPSILON); // Test roundtrip with special values let special: Vec = vec![f32::MIN, f32::MAX, 0.0, -0.0]; let js_special = special.to_js(lock); ctx.set_global("special", js_special); let roundtrip: Vec = ctx.eval(lock, "special").unwrap(); #[expect(clippy::float_cmp)] { assert_eq!(roundtrip[0], f32::MIN); assert_eq!(roundtrip[1], f32::MAX); } Ok(()) }); } // ============================================================================= // Float64Array tests // ============================================================================= #[test] fn float64_array_parameter_and_return() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test Vec parameter let result: Number = ctx .eval(lock, "arr.sumF64(new Float64Array([1.5, 2.5, 3.0]))") .unwrap(); assert!((result.value() - 7.0).abs() < f64::EPSILON); // Test Vec return value let is_f64: bool = ctx .eval( lock, "arr.doubleF64(new Float64Array([1.0, 2.0])) instanceof Float64Array", ) .unwrap(); assert!(is_f64); let result: Vec = ctx .eval(lock, "arr.doubleF64(new Float64Array([1.0, 2.0, 3.0]))") .unwrap(); assert_eq!(result, vec![2.0, 4.0, 6.0]); Ok(()) }); } #[test] fn float64_array_slice_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test &[f64] parameter let result: Number = ctx .eval(lock, "arr.sumF64Slice(new Float64Array([1.5, 2.5, 3.0]))") .unwrap(); assert!((result.value() - 7.0).abs() < f64::EPSILON); // Test empty Float64Array let result: Number = ctx .eval(lock, "arr.sumF64Slice(new Float64Array([]))") .unwrap(); assert!((result.value() - 0.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn float64_array_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Float64Array should reject Float32Array let result: Result = ctx.eval(lock, "arr.sumF64Slice(new Float32Array([1.0, 2.0]))"); assert!(result.is_err()); // Float64Array should reject regular Array let result: Result = ctx.eval(lock, "arr.sumF64Slice([1.0, 2.0])"); assert!(result.is_err()); Ok(()) }); } #[test] fn float64_array_to_js_and_from_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { // Test Vec to JavaScript let vec: Vec = vec![1.5, 2.5, -3.5]; ctx.set_global("f64_arr", vec.to_js(lock)); let is_f64: bool = ctx.eval(lock, "f64_arr instanceof Float64Array").unwrap(); assert!(is_f64); let val: Number = ctx.eval(lock, "f64_arr[0]").unwrap(); assert!((val.value() - 1.5).abs() < f64::EPSILON); let val: Number = ctx.eval(lock, "f64_arr[2]").unwrap(); assert!((val.value() - (-3.5)).abs() < f64::EPSILON); // Test Float64Array from JavaScript let result: Vec = ctx.eval(lock, "new Float64Array([1.1, 2.2, 3.3])").unwrap(); assert!((result[0] - 1.1).abs() < f64::EPSILON); assert!((result[1] - 2.2).abs() < f64::EPSILON); assert!((result[2] - 3.3).abs() < f64::EPSILON); Ok(()) }); } // ============================================================================= // BigInt64Array tests // ============================================================================= #[test] fn bigint64_array_parameter_and_return() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test Vec parameter let result: Number = ctx .eval(lock, "arr.sumI64(new BigInt64Array([1n, 2n, 3n]))") .unwrap(); assert!((result.value() - 6.0).abs() < f64::EPSILON); // Test Vec return value let is_i64: bool = ctx .eval( lock, "arr.doubleI64(new BigInt64Array([1n, 2n])) instanceof BigInt64Array", ) .unwrap(); assert!(is_i64); let result: Vec = ctx .eval(lock, "arr.doubleI64(new BigInt64Array([1n, 2n, 3n]))") .unwrap(); assert_eq!(result, vec![2, 4, 6]); // Test with negative values let result: Number = ctx .eval(lock, "arr.sumI64(new BigInt64Array([-10n, 20n, -5n]))") .unwrap(); assert!((result.value() - 5.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn bigint64_array_slice_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test &[i64] parameter let result: Number = ctx .eval(lock, "arr.sumI64Slice(new BigInt64Array([10n, 20n, 30n]))") .unwrap(); assert!((result.value() - 60.0).abs() < f64::EPSILON); // Test empty BigInt64Array let result: Number = ctx .eval(lock, "arr.sumI64Slice(new BigInt64Array([]))") .unwrap(); assert!((result.value() - 0.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn bigint64_array_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // BigInt64Array should reject BigUint64Array let result: Result = ctx.eval(lock, "arr.sumI64Slice(new BigUint64Array([1n, 2n]))"); assert!(result.is_err()); // BigInt64Array should reject Int32Array let result: Result = ctx.eval(lock, "arr.sumI64Slice(new Int32Array([1, 2]))"); assert!(result.is_err()); Ok(()) }); } #[test] fn bigint64_array_to_js_and_from_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { // Test Vec to JavaScript let vec: Vec = vec![100, -200, 300]; ctx.set_global("i64_arr", vec.to_js(lock)); let is_i64: bool = ctx.eval(lock, "i64_arr instanceof BigInt64Array").unwrap(); assert!(is_i64); // BigInt64Array returns BigInt values, verify via string let val: String = ctx.eval(lock, "String(i64_arr[0])").unwrap(); assert_eq!(val, "100"); let val: String = ctx.eval(lock, "String(i64_arr[1])").unwrap(); assert_eq!(val, "-200"); // Test BigInt64Array from JavaScript let result: Vec = ctx.eval(lock, "new BigInt64Array([1n, -2n, 3n])").unwrap(); assert_eq!(result, vec![1, -2, 3]); Ok(()) }); } // ============================================================================= // as_slice / as_mut_slice / byte_offset / data / FromJS for Local // ============================================================================= /// Helper resource whose methods accept `Local<'_, T>` directly via `FromJS`. #[jsg_resource] struct SliceResource; #[jsg_resource] impl SliceResource { /// Accepts a `Local` directly and sums its elements via `as_slice`. #[jsg_method] pub fn sum_u8_local(&self, arr: jsg::v8::Local) -> jsg::Number { jsg::Number::new(arr.as_slice().iter().map(|&x| f64::from(x)).sum()) } /// Accepts a `Local` directly and sums its elements via `as_slice`. #[jsg_method] pub fn sum_i32_local(&self, arr: jsg::v8::Local) -> jsg::Number { jsg::Number::new(arr.as_slice().iter().map(|&x| f64::from(x)).sum()) } /// Accepts a `Local` directly and sums its elements via `as_slice`. #[jsg_method] pub fn sum_f64_local(&self, arr: jsg::v8::Local) -> jsg::Number { jsg::Number::new(arr.as_slice().iter().copied().sum()) } /// Writes `0xFF` into every byte via `as_mut_slice` and returns the length. #[jsg_method] pub fn fill_ff( &self, lock: &mut jsg::Lock, mut arr: jsg::v8::Local, ) -> jsg::Number { // SAFETY: no other reference into this buffer is live during this call; // lock borrow prevents JS from running. unsafe { arr.as_mut_slice(lock) }.fill(0xFF); #[expect(clippy::cast_precision_loss)] jsg::Number::new(arr.len() as f64) } } #[test] fn typed_array_as_slice_zero_copy_sum() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let res = jsg::Rc::new(SliceResource); ctx.set_global("r", res.to_js(lock)); // Uint8Array let result: jsg::Number = ctx .eval(lock, "r.sumU8Local(new Uint8Array([10, 20, 30]))") .unwrap(); assert!((result.value() - 60.0).abs() < f64::EPSILON); // Int32Array let result: jsg::Number = ctx .eval(lock, "r.sumI32Local(new Int32Array([-1, 0, 1]))") .unwrap(); assert!((result.value() - 0.0).abs() < f64::EPSILON); // Float64Array let result: jsg::Number = ctx .eval(lock, "r.sumF64Local(new Float64Array([1.5, 2.5]))") .unwrap(); assert!((result.value() - 4.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn typed_array_as_slice_empty() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate valid, js_val is a Uint8Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(typed.as_slice(), &[] as &[u8]); Ok(()) }); } #[test] fn typed_array_as_mut_slice_mutations_visible_in_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let res = jsg::Rc::new(SliceResource); ctx.set_global("r", res.to_js(lock)); // fill_ff writes 0xFF into every element in-place. // The returned length tells us how many bytes were written. let len: jsg::Number = ctx .eval(lock, "r.fillFf(new Uint8Array([1, 2, 3]))") .unwrap(); assert!((len.value() - 3.0).abs() < f64::EPSILON); // Independently verify with a captured reference: create an array in JS, // pass it in, then read back the same object. let check: jsg::Number = ctx .eval( lock, r" const buf = new Uint8Array([0, 0, 0]); r.fillFf(buf); buf[0] === 255 && buf[1] === 255 && buf[2] === 255 ? 1 : 0 ", ) .unwrap(); assert!((check.value() - 1.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn typed_array_byte_offset_non_zero() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { // Create a view that starts 4 bytes into an 8-byte buffer. // byte_offset() must return 4, len() must return 1. let js_val = ctx .eval_raw( r" const buf = new ArrayBuffer(8); const view = new Uint32Array(buf, 4, 1); view ", ) .unwrap(); let typed: jsg::v8::Local<'_, jsg::v8::Uint32Array> = // SAFETY: isolate valid; js_val is a Uint32Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(typed.byte_offset(), 4); assert_eq!(typed.byte_length(), 4); // 1 element × 4 bytes/u32 assert_eq!(typed.len(), 1); Ok(()) }); } #[test] fn typed_array_as_slice_with_byte_offset() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { // Build a 12-byte buffer [0,1,2,...,11], create a Uint8Array view // starting at offset 4 covering 4 bytes: [4, 5, 6, 7]. let js_val = ctx .eval_raw( r" const buf = new ArrayBuffer(12); const all = new Uint8Array(buf); for (let i = 0; i < 12; i++) all[i] = i; new Uint8Array(buf, 4, 4) ", ) .unwrap(); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate valid; js_val is a Uint8Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(typed.byte_offset(), 4); assert_eq!(typed.len(), 4); assert_eq!(typed.as_slice(), &[4u8, 5, 6, 7]); Ok(()) }); } #[test] fn typed_array_data_pointer_and_byte_offset() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { let data: Vec = vec![10, 20, 30, 40]; let js_val = data.to_js(lock); let typed: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate valid; js_val is a Uint8Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; // A freshly created TypedArray has byte_offset == 0. assert_eq!(typed.byte_offset(), 0); // data() + byte_offset() must point at the first element. // as_slice() is derived from exactly this computation, so they must agree. let slice = typed.as_slice(); // SAFETY: as_slice() contracts guarantee the pointer + len are valid here. let from_ptr: &[u8] = unsafe { std::slice::from_raw_parts( typed.data().byte_add(typed.byte_offset()).cast_const(), typed.len(), ) }; assert_eq!(slice, from_ptr); assert_eq!(from_ptr, &[10u8, 20, 30, 40]); Ok(()) }); } #[test] fn typed_array_from_js_local_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let res = jsg::Rc::new(SliceResource); ctx.set_global("r", res.to_js(lock)); // sumU8Local expects Uint8Array — Int8Array must be rejected. let result: Result = ctx.eval(lock, "r.sumU8Local(new Int8Array([1, 2, 3]))"); assert!(result.is_err()); // Regular Array must also be rejected. let result: Result = ctx.eval(lock, "r.sumU8Local([1, 2, 3])"); assert!(result.is_err()); // sumI32Local expects Int32Array — Uint32Array must be rejected. let result: Result = ctx.eval(lock, "r.sumI32Local(new Uint32Array([1, 2, 3]))"); assert!(result.is_err()); Ok(()) }); } #[test] fn typed_array_element_size_and_is_integer_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, _ctx| { // u8 / Uint8Array: 1 byte per element, integer type. let js_val = vec![0u8, 0].to_js(lock); let arr: jsg::v8::Local<'_, jsg::v8::Uint8Array> = // SAFETY: isolate valid; js_val is a Uint8Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(arr.element_size(), 1); assert!(arr.is_integer_type()); // u32 / Uint32Array: 4 bytes per element, integer type. let js_val = vec![0u32, 0].to_js(lock); let arr: jsg::v8::Local<'_, jsg::v8::Uint32Array> = // SAFETY: isolate valid; js_val is a Uint32Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(arr.element_size(), 4); assert!(arr.is_integer_type()); // f32 / Float32Array: 4 bytes per element, NOT an integer type. let js_val = vec![0f32, 0.0].to_js(lock); let arr: jsg::v8::Local<'_, jsg::v8::Float32Array> = // SAFETY: isolate valid; js_val is a Float32Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(arr.element_size(), 4); assert!(!arr.is_integer_type()); // f64 / Float64Array: 8 bytes per element, NOT an integer type. let js_val = vec![0f64, 0.0].to_js(lock); let arr: jsg::v8::Local<'_, jsg::v8::Float64Array> = // SAFETY: isolate valid; js_val is a Float64Array Local. unsafe { jsg::v8::Local::from_ffi(lock.isolate(), js_val.into_ffi()) }; assert_eq!(arr.element_size(), 8); assert!(!arr.is_integer_type()); Ok(()) }); } // ============================================================================= // BigUint64Array tests // ============================================================================= #[test] fn biguint64_array_parameter_and_return() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test Vec parameter let result: Number = ctx .eval(lock, "arr.sumU64(new BigUint64Array([1n, 2n, 3n]))") .unwrap(); assert!((result.value() - 6.0).abs() < f64::EPSILON); // Test Vec return value let is_u64: bool = ctx .eval( lock, "arr.doubleU64(new BigUint64Array([1n, 2n])) instanceof BigUint64Array", ) .unwrap(); assert!(is_u64); let result: Vec = ctx .eval(lock, "arr.doubleU64(new BigUint64Array([1n, 2n, 3n]))") .unwrap(); assert_eq!(result, vec![2, 4, 6]); Ok(()) }); } #[test] fn biguint64_array_slice_parameter() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // Test &[u64] parameter let result: Number = ctx .eval(lock, "arr.sumU64Slice(new BigUint64Array([10n, 20n, 30n]))") .unwrap(); assert!((result.value() - 60.0).abs() < f64::EPSILON); // Test empty BigUint64Array let result: Number = ctx .eval(lock, "arr.sumU64Slice(new BigUint64Array([]))") .unwrap(); assert!((result.value() - 0.0).abs() < f64::EPSILON); Ok(()) }); } #[test] fn biguint64_array_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(ArrayResource); let wrapped = resource.to_js(lock); ctx.set_global("arr", wrapped); // BigUint64Array should reject BigInt64Array let result: Result = ctx.eval(lock, "arr.sumU64Slice(new BigInt64Array([1n, 2n]))"); assert!(result.is_err()); // BigUint64Array should reject Uint32Array let result: Result = ctx.eval(lock, "arr.sumU64Slice(new Uint32Array([1, 2]))"); assert!(result.is_err()); Ok(()) }); } #[test] fn biguint64_array_to_js_and_from_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { // Test Vec to JavaScript let vec: Vec = vec![100, 200, 300]; ctx.set_global("u64_arr", vec.to_js(lock)); let is_u64: bool = ctx.eval(lock, "u64_arr instanceof BigUint64Array").unwrap(); assert!(is_u64); // BigUint64Array returns BigInt values, verify via string let val: String = ctx.eval(lock, "String(u64_arr[0])").unwrap(); assert_eq!(val, "100"); let val: String = ctx.eval(lock, "String(u64_arr[2])").unwrap(); assert_eq!(val, "300"); // Test BigUint64Array from JavaScript let result: Vec = ctx.eval(lock, "new BigUint64Array([1n, 2n, 3n])").unwrap(); assert_eq!(result, vec![1, 2, 3]); Ok(()) }); } #[test] fn uint8clamped_array_from_js() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { use jsg::v8::Local; use jsg::v8::Uint8ClampedArray; let val = ctx .eval_raw("new Uint8ClampedArray([0, 128, 255])") .unwrap(); assert!(val.is_uint8clamped_array()); assert!(!val.is_uint8_array()); let arr: Local = // SAFETY: isolate valid; val is a Uint8ClampedArray Local. unsafe { Local::from_ffi(lock.isolate(), val.into_ffi()) }; assert_eq!(arr.len(), 3); assert_eq!(arr.get(0), 0u8); assert_eq!(arr.get(1), 128u8); assert_eq!(arr.get(2), 255u8); assert_eq!(arr.as_slice(), &[0u8, 128, 255]); assert!(arr.is_integer_type()); assert_eq!(arr.element_size(), 1); Ok(()) }); } #[test] fn uint8clamped_array_rejects_wrong_type() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { use jsg::FromJS; use jsg::v8::Local; use jsg::v8::Uint8ClampedArray; let val = ctx.eval_raw("new Uint8Array([1, 2, 3])").unwrap(); let result = Local::::from_js(lock, val); assert!(result.is_err()); Ok(()) }); } #[test] fn is_float16_array_check() { let harness = crate::Harness::new(); harness.run_in_context(|_lock, ctx| { let f16_val = ctx.eval_raw("new Float16Array([1.0, 2.0])").unwrap(); assert!(f16_val.is_float16_array()); assert!(!f16_val.is_float32_array()); let f32_val = ctx.eval_raw("new Float32Array([1.0])").unwrap(); assert!(!f32_val.is_float16_array()); Ok(()) }); }