// 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::ExceptionType; use jsg::NonCoercible; use jsg::Number; use jsg::ToJS; use jsg_macros::jsg_method; use jsg_macros::jsg_resource; #[jsg_resource] struct MyResource; #[jsg_resource] #[expect(clippy::unnecessary_wraps)] impl MyResource { #[jsg_method] pub fn string(&self, nc: NonCoercible) -> Result { Ok(nc.as_ref().clone()) } #[jsg_method] pub fn boolean(&self, nc: NonCoercible) -> Result { Ok(*nc.as_ref()) } #[jsg_method] pub fn number(&self, nc: NonCoercible) -> Result { Ok(*nc.as_ref()) } } #[test] fn non_coercible_string_new_and_as_ref() { let nc: NonCoercible = NonCoercible::new("hello".to_owned()); assert_eq!(nc.as_ref(), "hello"); } #[test] fn non_coercible_bool_new_and_as_ref() { let nc_true: NonCoercible = NonCoercible::new(true); let nc_false: NonCoercible = NonCoercible::new(false); assert!(*nc_true.as_ref()); assert!(!*nc_false.as_ref()); } #[test] fn non_coercible_number_new_and_as_ref() { let nc: NonCoercible = NonCoercible::new(Number::new(2.5)); assert!((nc.as_ref().value() - 2.5).abs() < f64::EPSILON); } #[test] fn non_coercible_from_trait() { let nc_string: NonCoercible = "test".to_owned().into(); assert_eq!(nc_string.as_ref(), "test"); let nc_bool: NonCoercible = true.into(); assert!(*nc_bool.as_ref()); let nc_num: NonCoercible = Number::new(42.0).into(); assert!((nc_num.as_ref().value() - 42.0).abs() < f64::EPSILON); } #[test] fn non_coercible_as_ref_trait() { let nc: NonCoercible = NonCoercible::new("as_ref test".to_owned()); let s: &String = nc.as_ref(); assert_eq!(s, "as_ref test"); let nc_bool: NonCoercible = NonCoercible::new(true); let b: &bool = nc_bool.as_ref(); assert!(*b); } #[test] fn non_coercible_deref_trait() { let nc: NonCoercible = NonCoercible::new("deref test".to_owned()); // Deref allows us to call String methods directly assert_eq!(nc.len(), 10); assert!(nc.starts_with("deref")); let nc_bool: NonCoercible = NonCoercible::new(true); // Deref to bool assert!(*nc_bool); let nc_num: NonCoercible = NonCoercible::new(Number::new(2.5)); // Deref to Number, then get value assert!(nc_num.value() > 2.0); } #[test] fn non_coercible_clone() { let nc1: NonCoercible = NonCoercible::new("clone test".to_owned()); let nc2 = nc1.clone(); assert_eq!(nc1.as_ref(), nc2.as_ref()); assert_eq!(*nc1, *nc2); } #[test] fn non_coercible_partial_eq() { let nc1: NonCoercible = NonCoercible::new("equal".to_owned()); let nc2: NonCoercible = NonCoercible::new("equal".to_owned()); let nc3: NonCoercible = NonCoercible::new("different".to_owned()); assert_eq!(nc1, nc2); assert_ne!(nc1, nc3); let nc_bool1: NonCoercible = NonCoercible::new(true); let nc_bool2: NonCoercible = NonCoercible::new(true); let nc_bool3: NonCoercible = NonCoercible::new(false); assert_eq!(nc_bool1, nc_bool2); assert_ne!(nc_bool1, nc_bool3); } #[test] fn non_coercible_debug() { let nc: NonCoercible = NonCoercible::new("debug test".to_owned()); let debug_str = format!("{nc:?}"); assert!(debug_str.contains("NonCoercible")); assert!(debug_str.contains("debug test")); } #[test] fn non_coercible_methods_accept_correct_types_and_reject_incorrect_types() { let harness = crate::Harness::new(); harness.run_in_context(|lock, ctx| { let resource = jsg::Rc::new(MyResource); let wrapped = resource.to_js(lock); ctx.set_global("resource", wrapped); // String method accepts string let result: String = ctx.eval(lock, "resource.string('hello')").unwrap(); assert_eq!(result, "hello"); // String method rejects number let err = ctx .eval::(lock, "resource.string(123)") .unwrap_err() .unwrap_jsg_err(lock); assert_eq!(err.name, ExceptionType::TypeError); assert!(err.message.contains("string")); // Boolean method accepts boolean let result: bool = ctx.eval(lock, "resource.boolean(true)").unwrap(); assert!(result); // Boolean method rejects string let err = ctx .eval::(lock, "resource.boolean('true')") .unwrap_err() .unwrap_jsg_err(lock); assert_eq!(err.name, ExceptionType::TypeError); assert!(err.message.contains("bool")); // Number method accepts number let result: Number = ctx.eval(lock, "resource.number(42.5)").unwrap(); assert!((result.value() - 42.5).abs() < f64::EPSILON); // Number method rejects string let err = ctx .eval::(lock, "resource.number('42')") .unwrap_err() .unwrap_jsg_err(lock); assert_eq!(err.name, ExceptionType::TypeError); assert!(err.message.contains("number")); Ok(()) }); }