1use std::fmt;
25
26#[derive(Debug, Clone, Copy)]
37pub struct PerformanceTiming;
38
39impl PerformanceTiming {
40 #[cfg(target_arch = "wasm32")]
44 #[must_use]
45 pub fn now() -> f64 {
46 web_sys::window()
47 .and_then(|w| w.performance())
48 .map(|p| p.now())
49 .unwrap_or(0.0)
50 }
51
52 #[cfg(not(target_arch = "wasm32"))]
54 #[must_use]
55 pub fn now() -> f64 {
56 use std::time::{SystemTime, UNIX_EPOCH};
57 SystemTime::now()
58 .duration_since(UNIX_EPOCH)
59 .map(|d| d.as_secs_f64() * 1000.0)
60 .unwrap_or(0.0)
61 }
62
63 #[must_use]
65 pub fn measure<F, T>(f: F) -> (T, f64)
66 where
67 F: FnOnce() -> T,
68 {
69 let start = Self::now();
70 let result = f();
71 let duration = Self::now() - start;
72 (result, duration)
73 }
74}
75
76#[derive(Debug, Clone)]
82pub enum EventDetail {
83 None,
85 String(String),
87 Number(f64),
89 Bool(bool),
91 Json(String),
93}
94
95impl EventDetail {
96 #[must_use]
98 pub fn string(s: impl Into<String>) -> Self {
99 Self::String(s.into())
100 }
101
102 #[must_use]
104 pub fn number(n: f64) -> Self {
105 Self::Number(n)
106 }
107
108 #[must_use]
110 pub fn json<T: serde::Serialize>(value: &T) -> Self {
111 match serde_json::to_string(value) {
112 Ok(s) => Self::Json(s),
113 Err(_) => Self::None,
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
128pub struct CustomEventDispatcher {
129 #[allow(dead_code)] event_name: String,
131}
132
133impl CustomEventDispatcher {
134 #[must_use]
136 pub fn new(event_name: impl Into<String>) -> Self {
137 Self {
138 event_name: event_name.into(),
139 }
140 }
141
142 #[cfg(target_arch = "wasm32")]
144 pub fn dispatch(&self) -> Result<bool, WebSysError> {
145 use wasm_bindgen::JsCast;
146
147 let window = web_sys::window().ok_or(WebSysError::NoWindow)?;
148
149 let event = web_sys::CustomEvent::new(&self.event_name)
150 .map_err(|_| WebSysError::EventCreationFailed)?;
151
152 window
153 .dispatch_event(&event)
154 .map_err(|_| WebSysError::DispatchFailed)
155 }
156
157 #[cfg(target_arch = "wasm32")]
159 pub fn dispatch_with_detail(&self, detail: EventDetail) -> Result<bool, WebSysError> {
160 use wasm_bindgen::JsValue;
161
162 let window = web_sys::window().ok_or(WebSysError::NoWindow)?;
163
164 let init = web_sys::CustomEventInit::new();
165
166 let js_detail: JsValue = match detail {
167 EventDetail::None => JsValue::NULL,
168 EventDetail::String(s) => JsValue::from_str(&s),
169 EventDetail::Number(n) => JsValue::from_f64(n),
170 EventDetail::Bool(b) => JsValue::from_bool(b),
171 EventDetail::Json(json) => js_sys::JSON::parse(&json).unwrap_or(JsValue::NULL),
172 };
173
174 init.set_detail(&js_detail);
175
176 let event = web_sys::CustomEvent::new_with_event_init_dict(&self.event_name, &init)
177 .map_err(|_| WebSysError::EventCreationFailed)?;
178
179 window
180 .dispatch_event(&event)
181 .map_err(|_| WebSysError::DispatchFailed)
182 }
183
184 #[cfg(not(target_arch = "wasm32"))]
186 pub fn dispatch(&self) -> Result<bool, WebSysError> {
187 Ok(true)
188 }
189
190 #[cfg(not(target_arch = "wasm32"))]
192 pub fn dispatch_with_detail(&self, _detail: EventDetail) -> Result<bool, WebSysError> {
193 Ok(true)
194 }
195}
196
197#[derive(Debug)]
203pub struct FetchResult {
204 pub status: u16,
206 pub body: Vec<u8>,
208}
209
210#[derive(Debug, Clone, Default)]
214pub struct FetchClient;
215
216impl FetchClient {
217 #[must_use]
219 pub fn new() -> Self {
220 Self
221 }
222
223 #[cfg(target_arch = "wasm32")]
226 pub async fn fetch_bytes(&self, url: &str) -> Result<Vec<u8>, WebSysError> {
227 use wasm_bindgen::JsCast;
228 use wasm_bindgen_futures::JsFuture;
229
230 let global = js_sys::global();
232 let fetch_fn = js_sys::Reflect::get(&global, &wasm_bindgen::JsValue::from_str("fetch"))
233 .map_err(|_| WebSysError::NoWindow)?;
234 let fetch_fn: js_sys::Function = fetch_fn.dyn_into().map_err(|_| WebSysError::NoWindow)?;
235
236 let promise = fetch_fn
237 .call1(
238 &wasm_bindgen::JsValue::UNDEFINED,
239 &wasm_bindgen::JsValue::from_str(url),
240 )
241 .map_err(|_| WebSysError::FetchFailed)?;
242
243 let response = JsFuture::from(js_sys::Promise::from(promise))
244 .await
245 .map_err(|_| WebSysError::FetchFailed)?;
246
247 let response: web_sys::Response =
248 response.dyn_into().map_err(|_| WebSysError::FetchFailed)?;
249
250 let array_buffer = JsFuture::from(
251 response
252 .array_buffer()
253 .map_err(|_| WebSysError::FetchFailed)?,
254 )
255 .await
256 .map_err(|_| WebSysError::FetchFailed)?;
257
258 let uint8_array = js_sys::Uint8Array::new(&array_buffer);
259 Ok(uint8_array.to_vec())
260 }
261
262 #[cfg(not(target_arch = "wasm32"))]
264 #[allow(unknown_lints, clippy::unused_async, clippy::unused_async_trait_impl)] pub async fn fetch_bytes(&self, _url: &str) -> Result<Vec<u8>, WebSysError> {
271 Err(WebSysError::NotInBrowser)
272 }
273}
274
275#[derive(Debug, Clone)]
289pub struct BlobUrl;
290
291impl BlobUrl {
292 #[cfg(target_arch = "wasm32")]
294 pub fn from_js_code(code: &str) -> Result<String, WebSysError> {
295 use wasm_bindgen::JsValue;
296
297 let options = web_sys::BlobPropertyBag::new();
298 options.set_type("application/javascript");
299
300 let js_string = JsValue::from_str(code);
301 let blob_parts = js_sys::Array::new();
302 blob_parts.push(&js_string);
303
304 let blob = web_sys::Blob::new_with_blob_sequence_and_options(&blob_parts, &options)
305 .map_err(|_| WebSysError::BlobCreationFailed)?;
306
307 web_sys::Url::create_object_url_with_blob(&blob).map_err(|_| WebSysError::UrlCreationFailed)
308 }
309
310 #[cfg(target_arch = "wasm32")]
312 pub fn revoke(url: &str) -> Result<(), WebSysError> {
313 web_sys::Url::revoke_object_url(url).map_err(|_| WebSysError::UrlRevokeFailed)
314 }
315
316 #[cfg(not(target_arch = "wasm32"))]
318 pub fn from_js_code(_code: &str) -> Result<String, WebSysError> {
319 Err(WebSysError::NotInBrowser)
320 }
321
322 #[cfg(not(target_arch = "wasm32"))]
324 pub fn revoke(_url: &str) -> Result<(), WebSysError> {
325 Ok(())
326 }
327}
328
329#[cfg(target_arch = "wasm32")]
335#[must_use]
336pub fn get_base_url() -> Option<String> {
337 web_sys::window()
338 .and_then(|w| w.location().href().ok())
339 .and_then(|href| {
340 href.rsplit_once('/').map(|(base, _)| format!("{}/", base))
342 })
343}
344
345#[cfg(not(target_arch = "wasm32"))]
347#[must_use]
348pub fn get_base_url() -> Option<String> {
349 Some("http://localhost/".to_string())
350}
351
352#[cfg(target_arch = "wasm32")]
358pub struct GeneratedWorker {
359 inner: web_sys::Worker,
360 _on_message: wasm_bindgen::closure::Closure<dyn Fn(web_sys::MessageEvent)>,
361}
362
363#[cfg(target_arch = "wasm32")]
364impl GeneratedWorker {
365 pub fn from_code<F>(code: &str, on_message: F) -> Result<Self, WebSysError>
367 where
368 F: Fn(web_sys::MessageEvent) + 'static,
369 {
370 use wasm_bindgen::closure::Closure;
371 use wasm_bindgen::JsCast;
372
373 let worker_url = BlobUrl::from_js_code(code)?;
374
375 let worker_options = web_sys::WorkerOptions::new();
376 worker_options.set_type(web_sys::WorkerType::Module);
377
378 let worker = web_sys::Worker::new_with_options(&worker_url, &worker_options)
379 .map_err(|_| WebSysError::WorkerCreationFailed)?;
380
381 let _ = BlobUrl::revoke(&worker_url);
383
384 let on_message_closure =
386 Closure::wrap(Box::new(on_message) as Box<dyn Fn(web_sys::MessageEvent)>);
387
388 worker.set_onmessage(Some(on_message_closure.as_ref().unchecked_ref()));
389
390 Ok(Self {
391 inner: worker,
392 _on_message: on_message_closure,
393 })
394 }
395
396 pub fn post_message(&self, message: &wasm_bindgen::JsValue) -> Result<(), WebSysError> {
398 self.inner
399 .post_message(message)
400 .map_err(|_| WebSysError::PostMessageFailed)
401 }
402
403 pub fn terminate(&self) {
405 self.inner.terminate();
406 }
407}
408
409#[cfg(target_arch = "wasm32")]
410impl fmt::Debug for GeneratedWorker {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 f.debug_struct("GeneratedWorker").finish()
413 }
414}
415
416#[derive(Debug, Clone)]
422pub enum WebSysError {
423 NoWindow,
425 EventCreationFailed,
427 DispatchFailed,
429 FetchFailed,
431 NotInBrowser,
433 BlobCreationFailed,
435 UrlCreationFailed,
437 UrlRevokeFailed,
439 WorkerCreationFailed,
441 PostMessageFailed,
443}
444
445impl fmt::Display for WebSysError {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 match self {
448 Self::NoWindow => write!(f, "No window object available"),
449 Self::EventCreationFailed => write!(f, "Failed to create custom event"),
450 Self::DispatchFailed => write!(f, "Failed to dispatch event"),
451 Self::FetchFailed => write!(f, "Fetch operation failed"),
452 Self::NotInBrowser => write!(f, "Not running in browser environment"),
453 Self::BlobCreationFailed => write!(f, "Failed to create blob"),
454 Self::UrlCreationFailed => write!(f, "Failed to create object URL"),
455 Self::UrlRevokeFailed => write!(f, "Failed to revoke object URL"),
456 Self::WorkerCreationFailed => write!(f, "Failed to create web worker"),
457 Self::PostMessageFailed => write!(f, "Failed to post message to worker"),
458 }
459 }
460}
461
462impl std::error::Error for WebSysError {}
463
464pub trait GeneratedWebSys {
472 fn source_brick() -> &'static str;
474
475 fn generated_at() -> &'static str;
477}
478
479#[derive(Debug, Clone)]
481pub struct GenerationMetadata {
482 pub spec: &'static str,
484 pub ticket: &'static str,
486 pub method: &'static str,
488}
489
490pub const GENERATION_METADATA: GenerationMetadata = GenerationMetadata {
492 spec: "PROBAR-SPEC-009-P7",
493 ticket: "PROBAR-WEBSYS-001",
494 method: "probar::brick::web_sys_gen",
495};
496
497#[cfg(test)]
502mod tests {
503 use super::*;
504
505 #[test]
506 fn test_performance_timing_native() {
507 let t1 = PerformanceTiming::now();
508 std::thread::sleep(std::time::Duration::from_millis(10));
509 let t2 = PerformanceTiming::now();
510
511 assert!(t2 > t1);
512 }
513
514 #[test]
515 fn test_performance_measure() {
516 let (result, duration) = PerformanceTiming::measure(|| {
517 std::thread::sleep(std::time::Duration::from_millis(5));
518 42
519 });
520
521 assert_eq!(result, 42);
522 assert!(duration >= 4.0); }
524
525 #[test]
526 fn test_custom_event_dispatcher() {
527 let dispatcher = CustomEventDispatcher::new("test-event");
528 assert!(dispatcher.dispatch().is_ok());
530 }
531
532 #[test]
533 fn test_event_detail_variants() {
534 let string = EventDetail::string("hello");
535 assert!(matches!(string, EventDetail::String(_)));
536
537 let number = EventDetail::number(42.0);
538 assert!(matches!(number, EventDetail::Number(_)));
539
540 let json = EventDetail::json(&vec![1, 2, 3]);
541 assert!(matches!(json, EventDetail::Json(_)));
542 }
543
544 #[test]
545 fn test_fetch_client_native_fallback() {
546 let client = FetchClient::new();
547 let _ = client;
549 }
550
551 #[test]
552 fn test_blob_url_native_fallback() {
553 let result = BlobUrl::from_js_code("console.log('test')");
554 assert!(matches!(result, Err(WebSysError::NotInBrowser)));
555
556 assert!(BlobUrl::revoke("blob:test").is_ok());
558 }
559
560 #[test]
561 fn test_get_base_url_native() {
562 let url = get_base_url();
563 assert!(url.is_some());
564 assert!(url.unwrap().starts_with("http"));
565 }
566
567 #[test]
568 fn test_web_sys_error_display() {
569 let err = WebSysError::NoWindow;
570 assert_eq!(format!("{}", err), "No window object available");
571 }
572
573 #[test]
574 fn test_generation_metadata() {
575 assert_eq!(GENERATION_METADATA.spec, "PROBAR-SPEC-009-P7");
576 assert_eq!(GENERATION_METADATA.ticket, "PROBAR-WEBSYS-001");
577 }
578
579 #[test]
584 fn test_web_sys_error_all_variants_display() {
585 let errors = [
587 (WebSysError::NoWindow, "No window object available"),
588 (
589 WebSysError::EventCreationFailed,
590 "Failed to create custom event",
591 ),
592 (WebSysError::DispatchFailed, "Failed to dispatch event"),
593 (WebSysError::FetchFailed, "Fetch operation failed"),
594 (
595 WebSysError::NotInBrowser,
596 "Not running in browser environment",
597 ),
598 (WebSysError::BlobCreationFailed, "Failed to create blob"),
599 (
600 WebSysError::UrlCreationFailed,
601 "Failed to create object URL",
602 ),
603 (WebSysError::UrlRevokeFailed, "Failed to revoke object URL"),
604 (
605 WebSysError::WorkerCreationFailed,
606 "Failed to create web worker",
607 ),
608 (
609 WebSysError::PostMessageFailed,
610 "Failed to post message to worker",
611 ),
612 ];
613
614 for (error, expected_msg) in errors {
615 assert_eq!(format!("{}", error), expected_msg);
616 }
617 }
618
619 #[test]
620 fn test_web_sys_error_debug() {
621 let err = WebSysError::NoWindow;
622 let debug_str = format!("{:?}", err);
623 assert!(debug_str.contains("NoWindow"));
624 }
625
626 #[test]
627 fn test_web_sys_error_clone() {
628 let err = WebSysError::FetchFailed;
629 let cloned = err;
630 assert!(matches!(cloned, WebSysError::FetchFailed));
631 }
632
633 #[test]
634 fn test_web_sys_error_std_error_trait() {
635 let err: Box<dyn std::error::Error> = Box::new(WebSysError::NoWindow);
636 let _ = err.to_string();
638 }
639
640 #[test]
641 fn test_dispatch_with_detail_native_fallback() {
642 let dispatcher = CustomEventDispatcher::new("test-event");
643
644 assert!(dispatcher.dispatch_with_detail(EventDetail::None).is_ok());
646 assert!(dispatcher
647 .dispatch_with_detail(EventDetail::String("hello".to_string()))
648 .is_ok());
649 assert!(dispatcher
650 .dispatch_with_detail(EventDetail::Number(42.0))
651 .is_ok());
652 assert!(dispatcher
653 .dispatch_with_detail(EventDetail::Bool(true))
654 .is_ok());
655 assert!(dispatcher
656 .dispatch_with_detail(EventDetail::Json(r#"{"key":"value"}"#.to_string()))
657 .is_ok());
658 }
659
660 #[test]
661 fn test_event_detail_none() {
662 let detail = EventDetail::None;
663 assert!(matches!(detail, EventDetail::None));
664 }
665
666 #[test]
667 fn test_event_detail_bool() {
668 let detail_true = EventDetail::Bool(true);
669 let detail_false = EventDetail::Bool(false);
670 assert!(matches!(detail_true, EventDetail::Bool(true)));
671 assert!(matches!(detail_false, EventDetail::Bool(false)));
672 }
673
674 #[test]
675 fn test_event_detail_debug() {
676 let detail = EventDetail::String("test".to_string());
677 let debug_str = format!("{:?}", detail);
678 assert!(debug_str.contains("String"));
679 }
680
681 #[test]
682 fn test_event_detail_clone() {
683 let detail = EventDetail::Number(42.0);
684 let cloned = detail;
685 assert!(matches!(cloned, EventDetail::Number(n) if (n - 42.0).abs() < f64::EPSILON));
686 }
687
688 #[test]
689 fn test_event_detail_json_serialization_failure() {
690 struct FailsToSerialize;
693
694 impl serde::Serialize for FailsToSerialize {
695 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
696 where
697 S: serde::Serializer,
698 {
699 Err(serde::ser::Error::custom("intentional failure"))
700 }
701 }
702
703 let json = EventDetail::json(&FailsToSerialize);
704 assert!(matches!(json, EventDetail::None));
706
707 let json_ok = EventDetail::json(&"simple string");
709 assert!(matches!(json_ok, EventDetail::Json(_)));
710 }
711
712 #[tokio::test]
713 async fn test_fetch_bytes_native_fallback() {
714 let client = FetchClient::new();
716
717 let result = client.fetch_bytes("http://example.com").await;
718
719 assert!(matches!(result, Err(WebSysError::NotInBrowser)));
721 }
722
723 #[test]
724 fn test_fetch_client_default() {
725 let client = FetchClient;
726 let debug_str = format!("{:?}", client);
727 assert!(debug_str.contains("FetchClient"));
728 }
729
730 #[test]
731 fn test_fetch_client_clone() {
732 let client = FetchClient::new();
733 let cloned = client;
734 let _ = format!("{:?}", cloned);
735 }
736
737 #[test]
738 fn test_fetch_result_struct() {
739 let result = FetchResult {
740 status: 200,
741 body: vec![1, 2, 3, 4],
742 };
743 assert_eq!(result.status, 200);
744 assert_eq!(result.body.len(), 4);
745
746 let debug_str = format!("{:?}", result);
748 assert!(debug_str.contains("FetchResult"));
749 assert!(debug_str.contains("200"));
750 }
751
752 #[test]
753 fn test_custom_event_dispatcher_debug() {
754 let dispatcher = CustomEventDispatcher::new("my-event");
755 let debug_str = format!("{:?}", dispatcher);
756 assert!(debug_str.contains("CustomEventDispatcher"));
757 }
758
759 #[test]
760 fn test_custom_event_dispatcher_clone() {
761 let dispatcher = CustomEventDispatcher::new("clone-test");
762 let cloned = dispatcher;
763 assert!(cloned.dispatch().is_ok());
764 }
765
766 #[test]
767 fn test_performance_timing_debug() {
768 let timing = PerformanceTiming;
769 let debug_str = format!("{:?}", timing);
770 assert!(debug_str.contains("PerformanceTiming"));
771 }
772
773 #[test]
774 fn test_performance_timing_clone_copy() {
775 let timing1 = PerformanceTiming;
776 let timing2 = timing1; let timing3 = timing1; let _ = format!("{:?}", timing2);
779 let _ = format!("{:?}", timing3);
780 }
781
782 #[test]
783 fn test_blob_url_debug() {
784 let blob = BlobUrl;
785 let debug_str = format!("{:?}", blob);
786 assert!(debug_str.contains("BlobUrl"));
787 }
788
789 #[test]
790 fn test_blob_url_clone() {
791 let blob1 = BlobUrl;
792 let blob2 = blob1;
793 let _ = format!("{:?}", blob2);
794 }
795
796 #[test]
797 fn test_generation_metadata_debug() {
798 let debug_str = format!("{:?}", GENERATION_METADATA);
799 assert!(debug_str.contains("GenerationMetadata"));
800 assert!(debug_str.contains("PROBAR-SPEC-009-P7"));
801 }
802
803 #[test]
804 fn test_generation_metadata_clone() {
805 let cloned = GENERATION_METADATA.clone();
806 assert_eq!(cloned.spec, GENERATION_METADATA.spec);
807 assert_eq!(cloned.ticket, GENERATION_METADATA.ticket);
808 assert_eq!(cloned.method, GENERATION_METADATA.method);
809 }
810
811 #[test]
812 fn test_generation_metadata_method_field() {
813 assert_eq!(GENERATION_METADATA.method, "probar::brick::web_sys_gen");
814 }
815
816 #[test]
817 fn test_get_base_url_native_format() {
818 let url = get_base_url().expect("should return Some in native mode");
819 assert_eq!(url, "http://localhost/");
820 }
821
822 #[test]
823 fn test_performance_timing_now_returns_positive() {
824 let now = PerformanceTiming::now();
825 assert!(now > 0.0, "Timestamp should be positive");
826 }
827
828 #[test]
829 fn test_performance_timing_monotonic() {
830 let t1 = PerformanceTiming::now();
831 let t2 = PerformanceTiming::now();
832 let t3 = PerformanceTiming::now();
833 assert!(t2 >= t1);
834 assert!(t3 >= t2);
835 }
836
837 #[test]
838 fn test_event_detail_string_with_into() {
839 let s1 = EventDetail::string("literal str");
841 let s2 = EventDetail::string(String::from("String type"));
842
843 match s1 {
844 EventDetail::String(s) => assert_eq!(s, "literal str"),
845 _ => panic!("Expected String variant"),
846 }
847
848 match s2 {
849 EventDetail::String(s) => assert_eq!(s, "String type"),
850 _ => panic!("Expected String variant"),
851 }
852 }
853
854 #[test]
855 fn test_event_detail_number_special_values() {
856 let nan = EventDetail::number(f64::NAN);
858 let inf = EventDetail::number(f64::INFINITY);
859 let neg_inf = EventDetail::number(f64::NEG_INFINITY);
860 let zero = EventDetail::number(0.0);
861 let neg_zero = EventDetail::number(-0.0);
862
863 assert!(matches!(nan, EventDetail::Number(_)));
864 assert!(matches!(inf, EventDetail::Number(_)));
865 assert!(matches!(neg_inf, EventDetail::Number(_)));
866 assert!(matches!(zero, EventDetail::Number(_)));
867 assert!(matches!(neg_zero, EventDetail::Number(_)));
868 }
869
870 #[test]
871 fn test_event_detail_json_complex_structures() {
872 use std::collections::HashMap;
873
874 let mut map: HashMap<&str, Vec<i32>> = HashMap::new();
876 map.insert("numbers", vec![1, 2, 3]);
877
878 let json = EventDetail::json(&map);
879 match json {
880 EventDetail::Json(s) => {
881 assert!(s.contains("numbers"));
882 assert!(s.contains("[1,2,3]"));
883 }
884 _ => panic!("Expected Json variant"),
885 }
886 }
887
888 #[test]
889 fn test_custom_event_dispatcher_new_with_string() {
890 let dispatcher1 = CustomEventDispatcher::new("event-name");
891 let dispatcher2 = CustomEventDispatcher::new(String::from("event-name-string"));
892 assert!(dispatcher1.dispatch().is_ok());
893 assert!(dispatcher2.dispatch().is_ok());
894 }
895
896 #[test]
897 fn test_blob_url_revoke_empty_string() {
898 assert!(BlobUrl::revoke("").is_ok());
900 }
901
902 #[test]
903 fn test_blob_url_from_js_code_empty() {
904 let result = BlobUrl::from_js_code("");
906 assert!(matches!(result, Err(WebSysError::NotInBrowser)));
907 }
908
909 #[test]
910 fn test_performance_measure_with_panic_safe() {
911 let (result, duration) = PerformanceTiming::measure(|| {
913 let mut sum = 0u64;
914 for i in 0..1000 {
915 sum = sum.wrapping_add(i);
916 }
917 sum
918 });
919
920 assert!(result > 0);
921 assert!(duration >= 0.0);
922 }
923
924 #[test]
925 fn test_fetch_result_empty_body() {
926 let result = FetchResult {
927 status: 204,
928 body: vec![],
929 };
930 assert_eq!(result.status, 204);
931 assert!(result.body.is_empty());
932 }
933
934 #[test]
935 fn test_fetch_result_large_body() {
936 let result = FetchResult {
937 status: 200,
938 body: vec![0u8; 10000],
939 };
940 assert_eq!(result.body.len(), 10000);
941 }
942
943 #[test]
944 fn test_web_sys_error_is_send_sync() {
945 fn assert_send<T: Send>() {}
946 fn assert_sync<T: Sync>() {}
947
948 assert_send::<WebSysError>();
949 assert_sync::<WebSysError>();
950 }
951
952 struct TestGeneratedCode;
954
955 impl GeneratedWebSys for TestGeneratedCode {
956 fn source_brick() -> &'static str {
957 "test-brick"
958 }
959
960 fn generated_at() -> &'static str {
961 "2024-01-01T00:00:00Z"
962 }
963 }
964
965 #[test]
966 fn test_generated_websys_trait() {
967 assert_eq!(TestGeneratedCode::source_brick(), "test-brick");
968 assert_eq!(TestGeneratedCode::generated_at(), "2024-01-01T00:00:00Z");
969 }
970
971 #[test]
972 fn test_all_event_detail_variants_in_match() {
973 let variants = vec![
974 EventDetail::None,
975 EventDetail::String("test".to_string()),
976 EventDetail::Number(1.5),
977 EventDetail::Bool(false),
978 EventDetail::Json("{}".to_string()),
979 ];
980
981 for variant in variants {
982 let _ = match &variant {
983 EventDetail::None => "none",
984 EventDetail::String(_) => "string",
985 EventDetail::Number(_) => "number",
986 EventDetail::Bool(_) => "bool",
987 EventDetail::Json(_) => "json",
988 };
989 }
990 }
991}