Skip to content
File

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

rust691 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 `ArrayBuffer`, `ArrayBufferView`, and `BackingStore`.
6 
7use jsg::ToJS;
8use jsg::v8;
9 
10// =============================================================================
11// ArrayBuffer::new / byte_length / is_empty
12// =============================================================================
13 
14#[test]
15fn array_buffer_new_copies_data() {
16 let harness = crate::Harness::new();
17 harness.run_in_context(|lock, ctx| {
18 // Create from a mutable source, then mutate the source afterwards.
19 // The ArrayBuffer must retain the original values (copy semantics).
20 let mut src = [10u8, 20, 30, 40];
21 let buf = v8::ArrayBuffer::new(lock, &src);
22 
23 // Mutate the source after creation — must not affect the ArrayBuffer.
24 src.fill(0xFF);
25 
26 ctx.set_global("buf", buf.into());
27 
28 let is_ab: bool = ctx.eval(lock, "buf instanceof ArrayBuffer").unwrap();
29 assert!(is_ab);
30 
31 let len: jsg::Number = ctx.eval(lock, "buf.byteLength").unwrap();
32 assert!((len.value() - 4.0).abs() < f64::EPSILON);
33 
34 // Values must be the originals, not 0xFF.
35 let v0: jsg::Number = ctx.eval(lock, "new Uint8Array(buf)[0]").unwrap();
36 assert!((v0.value() - 10.0).abs() < f64::EPSILON);
37 
38 let v3: jsg::Number = ctx.eval(lock, "new Uint8Array(buf)[3]").unwrap();
39 assert!((v3.value() - 40.0).abs() < f64::EPSILON);
40 
41 Ok(())
42 });
43}
44 
45#[test]
46fn array_buffer_new_zeroed_is_zeroed() {
47 let harness = crate::Harness::new();
48 harness.run_in_context(|lock, ctx| {
49 let buf = v8::ArrayBuffer::new_zeroed(lock, 8);
50 ctx.set_global("buf", buf.into());
51 
52 let len: jsg::Number = ctx.eval(lock, "buf.byteLength").unwrap();
53 assert!((len.value() - 8.0).abs() < f64::EPSILON);
54 
55 // V8 zero-initializes new backing stores
56 let all_zero: bool = ctx
57 .eval(lock, "new Uint8Array(buf).every((i) => i === 0)")
58 .unwrap();
59 assert!(all_zero);
60 
61 Ok(())
62 });
63}
64 
65#[test]
66fn array_buffer_is_empty() {
67 let harness = crate::Harness::new();
68 harness.run_in_context(|lock, _ctx| {
69 let buf = v8::ArrayBuffer::new_zeroed(lock, 0);
70 assert!(buf.is_empty());
71 assert_eq!(buf.byte_length(), 0);
72 Ok(())
73 });
74}
75 
76#[test]
77fn array_buffer_byte_length() {
78 let harness = crate::Harness::new();
79 harness.run_in_context(|lock, _ctx| {
80 let buf = v8::ArrayBuffer::new(lock, &[1u8, 2, 3]);
81 assert_eq!(buf.byte_length(), 3);
82 assert!(!buf.is_empty());
83 Ok(())
84 });
85}
86 
87// =============================================================================
88// ArrayBuffer as_slice / as_mut_slice / to_vec
89// =============================================================================
90 
91#[test]
92fn array_buffer_as_slice_reads_data() {
93 let harness = crate::Harness::new();
94 harness.run_in_context(|lock, _ctx| {
95 let buf = v8::ArrayBuffer::new(lock, &[1u8, 2, 3, 4]);
96 assert_eq!(buf.as_slice(), &[1u8, 2, 3, 4]);
97 Ok(())
98 });
99}
100 
101#[test]
102fn array_buffer_as_slice_empty() {
103 let harness = crate::Harness::new();
104 harness.run_in_context(|lock, _ctx| {
105 let buf = v8::ArrayBuffer::new_zeroed(lock, 0);
106 assert_eq!(buf.as_slice(), &[] as &[u8]);
107 Ok(())
108 });
109}
110 
111#[test]
112fn array_buffer_as_mut_slice_mutations_visible_in_js() {
113 let harness = crate::Harness::new();
114 harness.run_in_context(|lock, ctx| {
115 let mut buf = v8::ArrayBuffer::new(lock, &[0u8, 0, 0]);
116 // SAFETY: no other reference into this buffer is live during this call.
117 unsafe { buf.as_mut_slice(lock) }.copy_from_slice(&[10, 20, 30]);
118 ctx.set_global("buf", buf.into());
119 
120 let v1: jsg::Number = ctx.eval(lock, "new Uint8Array(buf)[1]").unwrap();
121 assert!((v1.value() - 20.0).abs() < f64::EPSILON);
122 
123 Ok(())
124 });
125}
126 
127#[test]
128fn array_buffer_to_vec() {
129 let harness = crate::Harness::new();
130 harness.run_in_context(|lock, _ctx| {
131 let buf = v8::ArrayBuffer::new(lock, &[5u8, 6, 7]);
132 assert_eq!(buf.to_vec(), vec![5u8, 6, 7]);
133 Ok(())
134 });
135}
136 
137// =============================================================================
138// ArrayBuffer detach / is_detachable / was_detached / is_shared
139// =============================================================================
140 
141#[test]
142fn array_buffer_detach() {
143 let harness = crate::Harness::new();
144 harness.run_in_context(|lock, _ctx| {
145 let mut buf = v8::ArrayBuffer::new(lock, &[1u8, 2, 3]);
146 assert!(buf.is_detachable());
147 assert!(!buf.was_detached());
148 assert_eq!(buf.byte_length(), 3);
149 
150 buf.detach();
151 assert!(buf.was_detached());
152 assert_eq!(buf.byte_length(), 0);
153 
154 Ok(())
155 });
156}
157 
158#[test]
159fn array_buffer_is_shared() {
160 let harness = crate::Harness::new();
161 harness.run_in_context(|lock, _ctx| {
162 let buf = v8::ArrayBuffer::new(lock, &[1u8]);
163 assert!(!buf.is_shared());
164 Ok(())
165 });
166}
167 
168// =============================================================================
169// ArrayBuffer cast: Value <-> ArrayBuffer, Object <-> ArrayBuffer
170// =============================================================================
171 
172#[test]
173fn array_buffer_into_value_and_back() {
174 let harness = crate::Harness::new();
175 harness.run_in_context(|lock, _ctx| {
176 let buf = v8::ArrayBuffer::new(lock, &[1u8, 2]);
177 let val: v8::Local<v8::Value> = buf.into();
178 assert!(val.is_array_buffer());
179 assert!(!val.is_array_buffer_view());
180 let back: v8::Local<v8::ArrayBuffer> = val.into();
181 assert_eq!(back.byte_length(), 2);
182 assert_eq!(back.as_slice(), &[1u8, 2]);
183 Ok(())
184 });
185}
186 
187#[test]
188fn array_buffer_try_as_from_value() {
189 let harness = crate::Harness::new();
190 harness.run_in_context(|_lock, ctx| {
191 let val = ctx.eval_raw("new ArrayBuffer(4)").unwrap();
192 let buf = val
193 .try_as::<v8::ArrayBuffer>()
194 .expect("should cast to ArrayBuffer");
195 assert_eq!(buf.byte_length(), 4);
196 Ok(())
197 });
198}
199 
200#[test]
201fn array_buffer_try_as_fails_for_typed_array() {
202 let harness = crate::Harness::new();
203 harness.run_in_context(|_lock, ctx| {
204 let val = ctx.eval_raw("new Uint8Array([1,2,3])").unwrap();
205 let result = val.try_as::<v8::ArrayBuffer>();
206 assert!(result.is_none());
207 Ok(())
208 });
209}
210 
211#[test]
212fn array_buffer_into_object() {
213 let harness = crate::Harness::new();
214 harness.run_in_context(|lock, _ctx| {
215 let buf = v8::ArrayBuffer::new(lock, &[42u8]);
216 let obj: v8::Local<v8::Object> = buf.into();
217 // ArrayBuffer is an Object — verify we can read byteLength as a property
218 let bl = obj.get(lock, "byteLength").unwrap();
219 let n: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, bl)?;
220 assert!((n.value() - 1.0).abs() < f64::EPSILON);
221 Ok(())
222 });
223}
224 
225// =============================================================================
226// FromJS for Local<ArrayBuffer>
227// =============================================================================
228 
229#[test]
230fn array_buffer_from_js_rejects_typed_array() {
231 use jsg_macros::jsg_method;
232 use jsg_macros::jsg_resource;
233 
234 #[jsg_resource]
235 struct AbResource;
236 #[jsg_resource]
237 impl AbResource {
238 #[jsg_method]
239 pub fn byte_len(&self, buf: jsg::v8::Local<jsg::v8::ArrayBuffer>) -> jsg::Number {
240 #[expect(clippy::cast_precision_loss)]
241 jsg::Number::new(buf.byte_length() as f64)
242 }
243 }
244 
245 let harness = crate::Harness::new();
246 harness.run_in_context(|lock, ctx| {
247 let res = jsg::Rc::new(AbResource);
248 ctx.set_global("r", res.to_js(lock));
249 
250 let ok: jsg::Number = ctx.eval(lock, "r.byteLen(new ArrayBuffer(5))").unwrap();
251 assert!((ok.value() - 5.0).abs() < f64::EPSILON);
252 
253 let err: Result<jsg::Number, _> = ctx.eval(lock, "r.byteLen(new Uint8Array([1,2,3]))");
254 assert!(err.is_err());
255 
256 Ok(())
257 });
258}
259 
260// =============================================================================
261// BackingStore
262// =============================================================================
263 
264#[test]
265fn backing_store_data_and_byte_length() {
266 let harness = crate::Harness::new();
267 harness.run_in_context(|lock, _ctx| {
268 let buf = v8::ArrayBuffer::new(lock, &[11u8, 22, 33]);
269 let bs = buf.backing_store();
270 assert_eq!(bs.byte_length(), 3);
271 assert!(!bs.is_empty());
272 // SAFETY: non-shared backing store; no concurrent access.
273 assert_eq!(unsafe { bs.as_slice(lock) }, &[11u8, 22, 33]);
274 Ok(())
275 });
276}
277 
278#[test]
279fn backing_store_max_byte_length_equals_byte_length_for_fixed() {
280 let harness = crate::Harness::new();
281 harness.run_in_context(|lock, _ctx| {
282 let buf = v8::ArrayBuffer::new(lock, &[0u8; 16]);
283 let bs = buf.backing_store();
284 assert_eq!(bs.max_byte_length(), bs.byte_length());
285 Ok(())
286 });
287}
288 
289#[test]
290fn backing_store_is_not_resizable_for_regular_buffer() {
291 let harness = crate::Harness::new();
292 harness.run_in_context(|lock, _ctx| {
293 let buf = v8::ArrayBuffer::new(lock, &[1u8, 2]);
294 let bs = buf.backing_store();
295 assert!(!bs.is_resizable_by_user_javascript());
296 Ok(())
297 });
298}
299 
300#[test]
301fn backing_store_empty() {
302 let harness = crate::Harness::new();
303 harness.run_in_context(|lock, _ctx| {
304 let buf = v8::ArrayBuffer::new_zeroed(lock, 0);
305 let bs = buf.backing_store();
306 assert_eq!(bs.byte_length(), 0);
307 assert!(bs.is_empty());
308 // SAFETY: non-shared backing store; no concurrent access.
309 assert_eq!(unsafe { bs.as_slice(lock) }, &[] as &[u8]);
310 Ok(())
311 });
312}
313 
314#[test]
315fn backing_store_as_mut_slice_mutations_visible_via_local() {
316 let harness = crate::Harness::new();
317 harness.run_in_context(|lock, ctx| {
318 let buf = v8::ArrayBuffer::new(lock, &[0u8, 0, 0]);
319 let mut bs = buf.backing_store();
320 // SAFETY: single-threaded test; no concurrent access to this non-shared backing store.
321 unsafe { bs.as_mut_slice(lock) }.copy_from_slice(&[7, 8, 9]);
322 // The Local<ArrayBuffer> shares the same backing store
323 ctx.set_global("buf", buf.into());
324 let v: jsg::Number = ctx.eval(lock, "new Uint8Array(buf)[0]").unwrap();
325 assert!((v.value() - 7.0).abs() < f64::EPSILON);
326 Ok(())
327 });
328}
329 
330#[test]
331fn backing_store_outlives_local() {
332 let harness = crate::Harness::new();
333 harness.run_in_context(|lock, _ctx| {
334 // BackingStore keeps backing memory alive even after the Local is dropped
335 let bs = {
336 let buf = v8::ArrayBuffer::new(lock, &[42u8, 43, 44]);
337 buf.backing_store()
338 // buf (Local<ArrayBuffer>) dropped here
339 };
340 // The shared_ptr in BackingStore keeps the memory alive
341 assert_eq!(bs.byte_length(), 3);
342 // SAFETY: single-threaded test; the backing store outlives this borrow.
343 // SAFETY: non-shared backing store; no concurrent access.
344 assert_eq!(unsafe { bs.as_slice(lock) }, &[42u8, 43, 44]);
345 Ok(())
346 });
347}
348 
349// =============================================================================
350// ArrayBufferView
351// =============================================================================
352 
353#[test]
354fn array_buffer_view_from_typed_array() {
355 let harness = crate::Harness::new();
356 harness.run_in_context(|_lock, ctx| {
357 let val = ctx.eval_raw("new Uint8Array([10, 20, 30])").unwrap();
358 let view: v8::Local<v8::ArrayBufferView> = val
359 .try_as::<v8::ArrayBufferView>()
360 .expect("Uint8Array is ArrayBufferView");
361 assert_eq!(view.byte_length(), 3);
362 assert_eq!(view.byte_offset(), 0);
363 assert_eq!(view.as_slice(), &[10u8, 20, 30]);
364 Ok(())
365 });
366}
367 
368#[test]
369fn array_buffer_view_with_byte_offset() {
370 let harness = crate::Harness::new();
371 harness.run_in_context(|_lock, ctx| {
372 let val = ctx
373 .eval_raw(
374 r"
375 const buf = new ArrayBuffer(12);
376 const all = new Uint8Array(buf);
377 for (let i = 0; i < 12; i++) all[i] = i;
378 new Uint8Array(buf, 4, 4)
379 ",
380 )
381 .unwrap();
382 let view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
383 assert_eq!(view.byte_offset(), 4);
384 assert_eq!(view.byte_length(), 4);
385 assert_eq!(view.as_slice(), &[4u8, 5, 6, 7]);
386 Ok(())
387 });
388}
389 
390#[test]
391fn array_buffer_view_as_mut_slice() {
392 let harness = crate::Harness::new();
393 harness.run_in_context(|lock, ctx| {
394 let val = ctx.eval_raw("new Uint8Array([1, 2, 3])").unwrap();
395 let mut view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
396 // SAFETY: no other reference into this buffer is live during this call.
397 unsafe { view.as_mut_slice(lock) }.fill(0xFF);
398 ctx.set_global("arr", view.into());
399 let v: jsg::Number = ctx.eval(lock, "arr[0]").unwrap();
400 assert!((v.value() - 255.0).abs() < f64::EPSILON);
401 Ok(())
402 });
403}
404 
405#[test]
406fn array_buffer_view_buffer() {
407 let harness = crate::Harness::new();
408 harness.run_in_context(|_lock, ctx| {
409 let val = ctx.eval_raw("new Uint16Array([100, 200])").unwrap();
410 let view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
411 let backing = view.buffer();
412 // Uint16Array([100,200]) → 4 bytes
413 assert_eq!(backing.byte_length(), 4);
414 Ok(())
415 });
416}
417 
418#[test]
419fn array_buffer_view_to_vec() {
420 let harness = crate::Harness::new();
421 harness.run_in_context(|_lock, ctx| {
422 let val = ctx.eval_raw("new Uint8Array([7, 8, 9])").unwrap();
423 let view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
424 assert_eq!(view.to_vec(), vec![7u8, 8, 9]);
425 Ok(())
426 });
427}
428 
429#[test]
430fn array_buffer_view_is_empty() {
431 let harness = crate::Harness::new();
432 harness.run_in_context(|_lock, ctx| {
433 let val = ctx.eval_raw("new Uint8Array([])").unwrap();
434 let view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
435 assert!(view.is_empty());
436 assert_eq!(view.byte_length(), 0);
437 Ok(())
438 });
439}
440 
441#[test]
442fn array_buffer_view_into_value_and_back() {
443 let harness = crate::Harness::new();
444 harness.run_in_context(|_lock, ctx| {
445 let val = ctx.eval_raw("new Int32Array([1, 2, 3])").unwrap();
446 let view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
447 let val2: v8::Local<v8::Value> = view.into();
448 assert!(val2.is_array_buffer_view());
449 Ok(())
450 });
451}
452 
453#[test]
454fn array_buffer_view_into_object() {
455 let harness = crate::Harness::new();
456 harness.run_in_context(|lock, ctx| {
457 let val = ctx.eval_raw("new Uint8Array([1])").unwrap();
458 let view: v8::Local<v8::ArrayBufferView> = val.try_as::<v8::ArrayBufferView>().unwrap();
459 let obj: v8::Local<v8::Object> = view.into();
460 let bl = obj.get(lock, "byteLength").unwrap();
461 let n: jsg::Number = <jsg::Number as jsg::FromJS>::from_js(lock, bl)?;
462 assert!((n.value() - 1.0).abs() < f64::EPSILON);
463 Ok(())
464 });
465}
466 
467#[test]
468fn array_buffer_view_try_as_fails_for_array_buffer() {
469 let harness = crate::Harness::new();
470 harness.run_in_context(|_lock, ctx| {
471 let val = ctx.eval_raw("new ArrayBuffer(4)").unwrap();
472 assert!(val.try_as::<v8::ArrayBufferView>().is_none());
473 Ok(())
474 });
475}
476 
477#[test]
478fn array_buffer_view_from_js_rejects_array_buffer() {
479 use jsg_macros::jsg_method;
480 use jsg_macros::jsg_resource;
481 
482 #[jsg_resource]
483 struct ViewResource;
484 #[jsg_resource]
485 impl ViewResource {
486 #[jsg_method]
487 pub fn byte_len(&self, view: jsg::v8::Local<jsg::v8::ArrayBufferView>) -> jsg::Number {
488 #[expect(clippy::cast_precision_loss)]
489 jsg::Number::new(view.byte_length() as f64)
490 }
491 }
492 
493 let harness = crate::Harness::new();
494 harness.run_in_context(|lock, ctx| {
495 let res = jsg::Rc::new(ViewResource);
496 ctx.set_global("r", res.to_js(lock));
497 
498 let ok: jsg::Number = ctx
499 .eval(lock, "r.byteLen(new Uint8Array([1,2,3]))")
500 .unwrap();
501 assert!((ok.value() - 3.0).abs() < f64::EPSILON);
502 
503 // Plain ArrayBuffer is not an ArrayBufferView
504 let err: Result<jsg::Number, _> = ctx.eval(lock, "r.byteLen(new ArrayBuffer(3))");
505 assert!(err.is_err());
506 
507 Ok(())
508 });
509}
510 
511// =============================================================================
512// ArrayBufferView element_size
513// =============================================================================
514 
515#[test]
516fn array_buffer_view_element_size_uint8() {
517 let harness = crate::Harness::new();
518 harness.run_in_context(|_lock, ctx| {
519 let val = ctx.eval_raw("new Uint8Array([1, 2])").unwrap();
520 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
521 assert_eq!(view.element_size(), 1);
522 Ok(())
523 });
524}
525 
526#[test]
527fn array_buffer_view_element_size_uint16() {
528 let harness = crate::Harness::new();
529 harness.run_in_context(|_lock, ctx| {
530 let val = ctx.eval_raw("new Uint16Array([1])").unwrap();
531 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
532 assert_eq!(view.element_size(), 2);
533 Ok(())
534 });
535}
536 
537#[test]
538fn array_buffer_view_element_size_int32() {
539 let harness = crate::Harness::new();
540 harness.run_in_context(|_lock, ctx| {
541 let val = ctx.eval_raw("new Int32Array([1])").unwrap();
542 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
543 assert_eq!(view.element_size(), 4);
544 Ok(())
545 });
546}
547 
548#[test]
549fn array_buffer_view_element_size_float32() {
550 let harness = crate::Harness::new();
551 harness.run_in_context(|_lock, ctx| {
552 let val = ctx.eval_raw("new Float32Array([1.0])").unwrap();
553 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
554 assert_eq!(view.element_size(), 4);
555 Ok(())
556 });
557}
558 
559#[test]
560fn array_buffer_view_element_size_float64() {
561 let harness = crate::Harness::new();
562 harness.run_in_context(|_lock, ctx| {
563 let val = ctx.eval_raw("new Float64Array([1.0])").unwrap();
564 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
565 assert_eq!(view.element_size(), 8);
566 Ok(())
567 });
568}
569 
570#[test]
571fn array_buffer_view_element_size_bigint64() {
572 let harness = crate::Harness::new();
573 harness.run_in_context(|_lock, ctx| {
574 let val = ctx.eval_raw("new BigInt64Array([1n])").unwrap();
575 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
576 assert_eq!(view.element_size(), 8);
577 Ok(())
578 });
579}
580 
581#[test]
582fn array_buffer_view_element_size_data_view() {
583 let harness = crate::Harness::new();
584 harness.run_in_context(|_lock, ctx| {
585 let val = ctx.eval_raw("new DataView(new ArrayBuffer(8))").unwrap();
586 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
587 assert_eq!(view.element_size(), 0); // DataView has no fixed element size
588 Ok(())
589 });
590}
591 
592// =============================================================================
593// ArrayBuffer detach effects
594// =============================================================================
595 
596#[test]
597fn array_buffer_detach_zeroes_slices() {
598 let harness = crate::Harness::new();
599 harness.run_in_context(|lock, _ctx| {
600 let mut buf = v8::ArrayBuffer::new(lock, &[1u8, 2, 3]);
601 assert_eq!(buf.as_slice(), &[1, 2, 3]);
602 buf.detach();
603 assert!(buf.as_slice().is_empty());
604 assert!(buf.to_vec().is_empty());
605 Ok(())
606 });
607}
608 
609// =============================================================================
610// ArrayBufferView to_vec with multi-byte typed arrays
611// =============================================================================
612 
613#[test]
614fn array_buffer_view_to_vec_uint32() {
615 let harness = crate::Harness::new();
616 harness.run_in_context(|_lock, ctx| {
617 // Uint32Array([1]) is 4 bytes: 0x01 0x00 0x00 0x00 on little-endian
618 let val = ctx.eval_raw("new Uint32Array([1])").unwrap();
619 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
620 let bytes = view.to_vec();
621 assert_eq!(bytes.len(), 4);
622 // Verify it's the raw bytes (little-endian u32 value 1)
623 assert_eq!(
624 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
625 1
626 );
627 Ok(())
628 });
629}
630 
631#[test]
632fn array_buffer_view_to_vec_float64() {
633 let harness = crate::Harness::new();
634 harness.run_in_context(|_lock, ctx| {
635 let val = ctx.eval_raw("new Float64Array([1.5])").unwrap();
636 let view = val.try_as::<v8::ArrayBufferView>().unwrap();
637 let bytes = view.to_vec();
638 assert_eq!(bytes.len(), 8);
639 let reconstructed = f64::from_le_bytes(bytes.try_into().unwrap());
640 assert!((reconstructed - 1.5).abs() < f64::EPSILON);
641 Ok(())
642 });
643}
644 
645// =============================================================================
646// Typed array -> ArrayBufferView casts
647// =============================================================================
648 
649#[test]
650fn typed_arrays_cast_to_array_buffer_view() {
651 let harness = crate::Harness::new();
652 harness.run_in_context(|lock, _ctx| {
653 // Uint8Array -> ArrayBufferView
654 let u8_data: Vec<u8> = vec![1, 2, 3];
655 let js = u8_data.to_js(lock);
656 let typed: v8::Local<v8::Uint8Array> =
657 // SAFETY: js was just created by to_js within the current HandleScope.
658 unsafe { v8::Local::from_ffi(lock.isolate(), js.into_ffi()) };
659 let view: v8::Local<v8::ArrayBufferView> = typed.into();
660 assert_eq!(view.byte_length(), 3);
661 
662 // Float64Array -> ArrayBufferView
663 let f64_data: Vec<f64> = vec![1.0, 2.0];
664 let js2 = f64_data.to_js(lock);
665 let typed2: v8::Local<v8::Float64Array> =
666 // SAFETY: js2 was just created by to_js within the current HandleScope.
667 unsafe { v8::Local::from_ffi(lock.isolate(), js2.into_ffi()) };
668 let view2: v8::Local<v8::ArrayBufferView> = typed2.into();
669 assert_eq!(view2.byte_length(), 16); // 2 × 8 bytes
670 
671 Ok(())
672 });
673}
674 
675#[test]
676fn array_buffer_view_back_to_typed_array() {
677 let harness = crate::Harness::new();
678 harness.run_in_context(|lock, _ctx| {
679 let data: Vec<u8> = vec![10, 20, 30];
680 let js = data.to_js(lock);
681 let typed: v8::Local<v8::Uint8Array> =
682 // SAFETY: js was just created by to_js within the current HandleScope.
683 unsafe { v8::Local::from_ffi(lock.isolate(), js.into_ffi()) };
684 let view: v8::Local<v8::ArrayBufferView> = typed.into();
685 // Cast back to Uint8Array
686 let back: v8::Local<v8::Uint8Array> = view.into();
687 assert_eq!(back.as_slice(), &[10u8, 20, 30]);
688 Ok(())
689 });
690}