Skip to content
File

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

rust210 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 
5use jsg::Error;
6use jsg::ExceptionType;
7use jsg::ToJS;
8use jsg::v8;
9use jsg::v8::ToLocalValue;
10use jsg_macros::jsg_struct;
11 
12#[jsg_struct]
13struct TestStruct {
14 pub str: String,
15}
16 
17#[jsg_struct]
18struct MultiPropertyStruct {
19 pub name: String,
20 pub age: u32,
21 pub active: String,
22}
23 
24#[jsg_struct]
25struct NestedStruct {
26 pub inner: String,
27}
28 
29#[test]
30fn objects_can_be_wrapped_and_unwrapped() {
31 let harness = crate::Harness::new();
32 harness.run_in_context(|lock, _ctx| {
33 let instance = TestStruct {
34 str: "test".to_owned(),
35 };
36 let wrapped = instance.to_js(lock);
37 let mut obj: v8::Local<'_, v8::Object> = wrapped.into();
38 assert!(obj.has(lock, "str"));
39 let str_value = obj.get(lock, "str");
40 assert!(str_value.unwrap().is_string());
41 assert!(!obj.has(lock, "test"));
42 let value = "value".to_local(lock);
43 assert!(value.is_string());
44 obj.set(lock, "test", value);
45 assert!(obj.has(lock, "test"));
46 Ok(())
47 });
48}
49 
50#[test]
51fn struct_with_multiple_properties() {
52 let harness = crate::Harness::new();
53 harness.run_in_context(|lock, _ctx| {
54 let instance = MultiPropertyStruct {
55 name: "Alice".to_owned(),
56 age: 30,
57 active: "true".to_owned(),
58 };
59 let wrapped = instance.to_js(lock);
60 let obj: v8::Local<'_, v8::Object> = wrapped.into();
61 
62 assert!(obj.has(lock, "name"));
63 assert!(obj.has(lock, "age"));
64 assert!(obj.has(lock, "active"));
65 
66 let name_value = obj.get(lock, "name");
67 assert!(name_value.is_some());
68 assert!(name_value.unwrap().is_string());
69 
70 let age_value = obj.get(lock, "age");
71 assert!(age_value.is_some());
72 
73 let active_value = obj.get(lock, "active");
74 assert!(active_value.is_some());
75 assert!(active_value.unwrap().is_string());
76 Ok(())
77 });
78}
79 
80#[test]
81fn number_type_conversions() {
82 let harness = crate::Harness::new();
83 harness.run_in_context(|lock, _ctx| {
84 let byte_val: u8 = 42;
85 let byte_local = byte_val.to_local(lock);
86 assert!(byte_local.has_value());
87 
88 let int_val: u32 = 12345;
89 let int_local = int_val.to_local(lock);
90 assert!(int_local.has_value());
91 Ok(())
92 });
93}
94 
95#[test]
96fn empty_object_and_property_setting() {
97 let harness = crate::Harness::new();
98 harness.run_in_context(|lock, _ctx| {
99 let mut obj = lock.new_object();
100 
101 assert!(!obj.has(lock, "nonexistent"));
102 assert!(obj.get(lock, "nonexistent").is_none());
103 
104 let str_value = "hello".to_local(lock);
105 obj.set(lock, "key1", str_value);
106 assert!(obj.has(lock, "key1"));
107 
108 let num_value = 100u32.to_local(lock);
109 obj.set(lock, "key2", num_value);
110 assert!(obj.has(lock, "key2"));
111 
112 let val1 = obj.get(lock, "key1");
113 assert!(val1.is_some());
114 assert!(val1.unwrap().is_string());
115 
116 let val2 = obj.get(lock, "key2");
117 assert!(val2.is_some());
118 Ok(())
119 });
120}
121 
122#[test]
123fn global_handle_conversion() {
124 let harness = crate::Harness::new();
125 harness.run_in_context(|lock, _ctx| {
126 let local_str = "global test".to_local(lock);
127 assert!(local_str.has_value());
128 
129 let global_str = local_str.to_global(lock);
130 let local_again = global_str.as_local(lock);
131 assert!(local_again.has_value());
132 Ok(())
133 });
134}
135 
136#[test]
137fn nested_object_properties() {
138 let harness = crate::Harness::new();
139 harness.run_in_context(|lock, _ctx| {
140 let mut outer = lock.new_object();
141 
142 let inner_instance = NestedStruct {
143 inner: "nested value".to_owned(),
144 };
145 let inner_wrapped = inner_instance.to_js(lock);
146 outer.set(lock, "nested", inner_wrapped);
147 
148 assert!(outer.has(lock, "nested"));
149 let nested_val = outer.get(lock, "nested");
150 assert!(nested_val.is_some());
151 
152 let nested_obj: v8::Local<'_, v8::Object> = nested_val.unwrap().into();
153 assert!(nested_obj.has(lock, "inner"));
154 
155 let inner_val = nested_obj.get(lock, "inner");
156 assert!(inner_val.is_some());
157 assert!(inner_val.unwrap().is_string());
158 Ok(())
159 });
160}
161 
162#[test]
163fn error_creation_and_display() {
164 let type_error = Error::new_type_error("Invalid type");
165 assert_eq!(type_error.name, ExceptionType::TypeError);
166 assert_eq!(type_error.message, "Invalid type");
167 
168 // Test Display for all exception types
169 assert_eq!(format!("{}", ExceptionType::RangeError), "RangeError");
170 assert_eq!(
171 format!("{}", ExceptionType::ReferenceError),
172 "ReferenceError"
173 );
174 assert_eq!(format!("{}", ExceptionType::SyntaxError), "SyntaxError");
175}
176 
177#[test]
178fn error_from_parse_int_error() {
179 let parse_result: Result<i32, _> = "not_a_number".parse();
180 let error: Error = parse_result.unwrap_err().into();
181 assert_eq!(error.name, ExceptionType::RangeError);
182 assert!(error.message.contains("Failed to parse integer"));
183}
184 
185#[test]
186fn type_of_returns_correct_js_types() {
187 let harness = crate::Harness::new();
188 harness.run_in_context(|lock, _ctx| {
189 let str_val = "hello".to_local(lock);
190 assert_eq!(str_val.type_of(), "string");
191 assert!(str_val.is_string());
192 assert!(!str_val.is_null_or_undefined());
193 
194 let num_val = 42u32.to_local(lock);
195 assert_eq!(num_val.type_of(), "number");
196 assert!(num_val.is_number());
197 assert!(!num_val.is_null_or_undefined());
198 
199 let bool_val = true.to_local(lock);
200 assert_eq!(bool_val.type_of(), "boolean");
201 assert!(bool_val.is_boolean());
202 assert!(!bool_val.is_null_or_undefined());
203 
204 let obj_val = lock.new_object();
205 assert_eq!(obj_val.type_of(), "object");
206 assert!(!obj_val.is_null_or_undefined());
207 Ok(())
208 });
209}