1use super::config::WasmRunnerConfig;
6use serde::{Deserialize, Serialize};
7use std::time::Duration;
8
9#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
11pub struct DebugOutput {
12 pub console: bool,
14 pub metrics: bool,
16 pub network: bool,
18 pub memory: bool,
20}
21
22impl Default for DebugOutput {
23 fn default() -> Self {
24 Self {
25 console: true,
26 metrics: true,
27 network: false,
28 memory: false,
29 }
30 }
31}
32
33impl DebugOutput {
34 #[must_use]
36 pub fn none() -> Self {
37 Self {
38 console: false,
39 metrics: false,
40 network: false,
41 memory: false,
42 }
43 }
44
45 #[must_use]
47 pub fn all() -> Self {
48 Self {
49 console: true,
50 metrics: true,
51 network: true,
52 memory: true,
53 }
54 }
55
56 #[must_use]
58 pub fn with_console(mut self) -> Self {
59 self.console = true;
60 self
61 }
62
63 #[must_use]
65 pub fn with_metrics(mut self) -> Self {
66 self.metrics = true;
67 self
68 }
69
70 #[must_use]
72 pub fn with_network(mut self) -> Self {
73 self.network = true;
74 self
75 }
76
77 #[must_use]
79 pub fn with_memory(mut self) -> Self {
80 self.memory = true;
81 self
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub enum HotReloadEvent {
88 FileChanged {
90 path: String,
92 },
93 RebuildStarted,
95 Rebuild {
97 duration: Duration,
99 preserved: Vec<String>,
101 },
102 RebuildFailed {
104 errors: Vec<String>,
106 },
107 ClientConnected {
109 id: u32,
111 },
112 ClientDisconnected {
114 id: u32,
116 },
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ConsoleMessage {
122 pub level: ConsoleLevel,
124 pub text: String,
126 pub timestamp: std::time::SystemTime,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
132pub enum ConsoleLevel {
133 Debug,
135 Info,
137 Log,
139 Warn,
141 Error,
143}
144
145impl ConsoleLevel {
146 #[must_use]
148 pub fn prefix(&self) -> &'static str {
149 match self {
150 ConsoleLevel::Debug => "\x1b[34m[DEBUG]\x1b[0m",
151 ConsoleLevel::Info => "\x1b[36m[INFO]\x1b[0m",
152 ConsoleLevel::Log => "[LOG]",
153 ConsoleLevel::Warn => "\x1b[33m[WARN]\x1b[0m",
154 ConsoleLevel::Error => "\x1b[31m[ERROR]\x1b[0m",
155 }
156 }
157}
158
159#[derive(Debug)]
161pub struct WasmRunner {
162 config: WasmRunnerConfig,
163 debug_output: DebugOutput,
164 running: bool,
165 clients: Vec<u32>,
166 next_client_id: u32,
167}
168
169impl WasmRunner {
170 #[must_use]
172 pub fn new(config: WasmRunnerConfig) -> Self {
173 Self {
174 config,
175 debug_output: DebugOutput::default(),
176 running: false,
177 clients: Vec::new(),
178 next_client_id: 1,
179 }
180 }
181
182 #[must_use]
184 pub fn builder() -> WasmRunnerBuilder {
185 WasmRunnerBuilder::default()
186 }
187
188 #[must_use]
190 pub fn config(&self) -> &WasmRunnerConfig {
191 &self.config
192 }
193
194 #[must_use]
196 pub fn debug_output(&self) -> &DebugOutput {
197 &self.debug_output
198 }
199
200 pub fn set_debug_output(&mut self, output: DebugOutput) {
202 self.debug_output = output;
203 }
204
205 #[must_use]
207 pub fn is_running(&self) -> bool {
208 self.running
209 }
210
211 #[must_use]
213 pub fn client_count(&self) -> usize {
214 self.clients.len()
215 }
216
217 pub fn simulate_start(&mut self) {
219 self.running = true;
220 }
221
222 pub fn simulate_stop(&mut self) {
224 self.running = false;
225 self.clients.clear();
226 }
227
228 pub fn simulate_client_connect(&mut self) -> u32 {
230 let id = self.next_client_id;
231 self.next_client_id += 1;
232 self.clients.push(id);
233 id
234 }
235
236 pub fn simulate_client_disconnect(&mut self, id: u32) {
238 self.clients.retain(|&c| c != id);
239 }
240
241 #[must_use]
243 pub fn format_console_message(&self, msg: &ConsoleMessage) -> String {
244 #[cfg(not(target_arch = "wasm32"))]
246 {
247 use chrono::{DateTime, Local};
248 let timestamp: DateTime<Local> = msg.timestamp.into();
249 format!(
250 "[{}] {} {}",
251 timestamp.format("%H:%M:%S"),
252 msg.level.prefix(),
253 msg.text
254 )
255 }
256 #[cfg(target_arch = "wasm32")]
257 {
258 let secs = msg
260 .timestamp
261 .duration_since(std::time::UNIX_EPOCH)
262 .map(|d| d.as_secs())
263 .unwrap_or(0);
264 format!("[{}] {} {}", secs, msg.level.prefix(), msg.text)
265 }
266 }
267
268 #[must_use]
270 pub fn http_url(&self) -> String {
271 format!("http://localhost:{}", self.config.http_port)
272 }
273
274 #[must_use]
276 pub fn ws_url(&self) -> String {
277 format!("ws://localhost:{}", self.config.ws_port)
278 }
279}
280
281#[derive(Debug, Clone, Default)]
283pub struct WasmRunnerBuilder {
284 config: WasmRunnerConfig,
285 debug_output: DebugOutput,
286}
287
288impl WasmRunnerBuilder {
289 #[must_use]
291 pub fn new() -> Self {
292 Self::default()
293 }
294
295 #[must_use]
297 pub fn http_port(mut self, port: u16) -> Self {
298 self.config.http_port = port;
299 self
300 }
301
302 #[must_use]
304 pub fn ws_port(mut self, port: u16) -> Self {
305 self.config.ws_port = port;
306 self
307 }
308
309 #[must_use]
311 pub fn hot_reload(mut self, enabled: bool) -> Self {
312 self.config.hot_reload = enabled;
313 self
314 }
315
316 #[must_use]
318 pub fn preserve_state(mut self, enabled: bool) -> Self {
319 self.config.preserve_state = enabled;
320 self
321 }
322
323 #[must_use]
325 pub fn source_maps(mut self, enabled: bool) -> Self {
326 self.config.source_maps = enabled;
327 self
328 }
329
330 #[must_use]
332 pub fn debug_output(mut self, output: DebugOutput) -> Self {
333 self.debug_output = output;
334 self
335 }
336
337 #[must_use]
339 pub fn build(self) -> WasmRunner {
340 let mut runner = WasmRunner::new(self.config);
341 runner.debug_output = self.debug_output;
342 runner
343 }
344}
345
346#[cfg(test)]
347#[allow(clippy::unwrap_used, clippy::expect_used)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_debug_output_default() {
353 let output = DebugOutput::default();
354 assert!(output.console);
355 assert!(output.metrics);
356 assert!(!output.network);
357 assert!(!output.memory);
358 }
359
360 #[test]
361 fn test_debug_output_none() {
362 let output = DebugOutput::none();
363 assert!(!output.console);
364 assert!(!output.metrics);
365 assert!(!output.network);
366 assert!(!output.memory);
367 }
368
369 #[test]
370 fn test_debug_output_all() {
371 let output = DebugOutput::all();
372 assert!(output.console);
373 assert!(output.metrics);
374 assert!(output.network);
375 assert!(output.memory);
376 }
377
378 #[test]
379 fn test_debug_output_builders() {
380 let output = DebugOutput::none().with_console().with_network();
381
382 assert!(output.console);
383 assert!(!output.metrics);
384 assert!(output.network);
385 assert!(!output.memory);
386 }
387
388 #[test]
389 fn test_console_level_prefix() {
390 assert!(ConsoleLevel::Error.prefix().contains("[ERROR]"));
391 assert!(ConsoleLevel::Warn.prefix().contains("[WARN]"));
392 assert!(ConsoleLevel::Info.prefix().contains("[INFO]"));
393 }
394
395 #[test]
396 fn test_wasm_runner_new() {
397 let config = WasmRunnerConfig::default();
398 let runner = WasmRunner::new(config);
399 assert!(!runner.is_running());
400 assert_eq!(runner.client_count(), 0);
401 }
402
403 #[test]
404 fn test_wasm_runner_builder() {
405 let runner = WasmRunnerBuilder::new()
406 .http_port(9000)
407 .ws_port(9001)
408 .hot_reload(false)
409 .build();
410
411 assert_eq!(runner.config().http_port, 9000);
412 assert_eq!(runner.config().ws_port, 9001);
413 assert!(!runner.config().hot_reload);
414 }
415
416 #[test]
417 fn test_wasm_runner_urls() {
418 let runner = WasmRunnerBuilder::new()
419 .http_port(8080)
420 .ws_port(8081)
421 .build();
422
423 assert_eq!(runner.http_url(), "http://localhost:8080");
424 assert_eq!(runner.ws_url(), "ws://localhost:8081");
425 }
426
427 #[test]
428 fn test_wasm_runner_simulate() {
429 let mut runner = WasmRunner::new(WasmRunnerConfig::default());
430
431 runner.simulate_start();
432 assert!(runner.is_running());
433
434 let id1 = runner.simulate_client_connect();
435 let id2 = runner.simulate_client_connect();
436 assert_eq!(runner.client_count(), 2);
437
438 runner.simulate_client_disconnect(id1);
439 assert_eq!(runner.client_count(), 1);
440
441 runner.simulate_stop();
442 assert!(!runner.is_running());
443 assert_eq!(runner.client_count(), 0);
444
445 assert_ne!(id1, id2);
447 }
448
449 #[test]
450 fn test_hot_reload_event_variants() {
451 let event = HotReloadEvent::FileChanged {
452 path: "src/main.rs".to_string(),
453 };
454 assert!(matches!(event, HotReloadEvent::FileChanged { .. }));
455
456 let event = HotReloadEvent::Rebuild {
457 duration: Duration::from_millis(500),
458 preserved: vec!["State".to_string()],
459 };
460 assert!(matches!(event, HotReloadEvent::Rebuild { .. }));
461 }
462
463 #[test]
464 fn test_console_message() {
465 let msg = ConsoleMessage {
466 level: ConsoleLevel::Info,
467 text: "Test message".to_string(),
468 timestamp: std::time::SystemTime::now(),
469 };
470
471 let runner = WasmRunner::new(WasmRunnerConfig::default());
472 let formatted = runner.format_console_message(&msg);
473 assert!(formatted.contains("[INFO]"));
474 assert!(formatted.contains("Test message"));
475 }
476
477 #[test]
478 fn test_debug_output_with_metrics() {
479 let output = DebugOutput::none().with_metrics();
480 assert!(!output.console);
481 assert!(output.metrics);
482 assert!(!output.network);
483 assert!(!output.memory);
484 }
485
486 #[test]
487 fn test_debug_output_with_memory() {
488 let output = DebugOutput::none().with_memory();
489 assert!(!output.console);
490 assert!(!output.metrics);
491 assert!(!output.network);
492 assert!(output.memory);
493 }
494
495 #[test]
496 fn test_debug_output_chaining_all() {
497 let output = DebugOutput::none()
498 .with_console()
499 .with_metrics()
500 .with_network()
501 .with_memory();
502
503 assert!(output.console);
504 assert!(output.metrics);
505 assert!(output.network);
506 assert!(output.memory);
507 }
508
509 #[test]
510 fn test_console_level_debug_prefix() {
511 let prefix = ConsoleLevel::Debug.prefix();
512 assert!(prefix.contains("[DEBUG]"));
513 }
514
515 #[test]
516 fn test_console_level_log_prefix() {
517 let prefix = ConsoleLevel::Log.prefix();
518 assert!(prefix.contains("[LOG]"));
519 }
520
521 #[test]
522 fn test_console_level_all_prefixes() {
523 assert!(ConsoleLevel::Debug.prefix().contains("[DEBUG]"));
525 assert!(ConsoleLevel::Info.prefix().contains("[INFO]"));
526 assert!(ConsoleLevel::Log.prefix().contains("[LOG]"));
527 assert!(ConsoleLevel::Warn.prefix().contains("[WARN]"));
528 assert!(ConsoleLevel::Error.prefix().contains("[ERROR]"));
529 }
530
531 #[test]
532 fn test_wasm_runner_debug_output_accessor() {
533 let runner = WasmRunner::new(WasmRunnerConfig::default());
534 let debug_output = runner.debug_output();
535
536 assert!(debug_output.console);
538 assert!(debug_output.metrics);
539 assert!(!debug_output.network);
540 assert!(!debug_output.memory);
541 }
542
543 #[test]
544 fn test_wasm_runner_set_debug_output() {
545 let mut runner = WasmRunner::new(WasmRunnerConfig::default());
546
547 runner.set_debug_output(DebugOutput::all());
549 assert!(runner.debug_output().console);
550 assert!(runner.debug_output().metrics);
551 assert!(runner.debug_output().network);
552 assert!(runner.debug_output().memory);
553
554 runner.set_debug_output(DebugOutput::none());
556 assert!(!runner.debug_output().console);
557 assert!(!runner.debug_output().metrics);
558 assert!(!runner.debug_output().network);
559 assert!(!runner.debug_output().memory);
560 }
561
562 #[test]
563 fn test_wasm_runner_builder_preserve_state() {
564 let runner = WasmRunnerBuilder::new().preserve_state(false).build();
565 assert!(!runner.config().preserve_state);
566
567 let runner2 = WasmRunnerBuilder::new().preserve_state(true).build();
568 assert!(runner2.config().preserve_state);
569 }
570
571 #[test]
572 fn test_wasm_runner_builder_source_maps() {
573 let runner = WasmRunnerBuilder::new().source_maps(false).build();
574 assert!(!runner.config().source_maps);
575
576 let runner2 = WasmRunnerBuilder::new().source_maps(true).build();
577 assert!(runner2.config().source_maps);
578 }
579
580 #[test]
581 fn test_wasm_runner_builder_debug_output() {
582 let runner = WasmRunnerBuilder::new()
583 .debug_output(DebugOutput::all())
584 .build();
585
586 assert!(runner.debug_output().console);
587 assert!(runner.debug_output().metrics);
588 assert!(runner.debug_output().network);
589 assert!(runner.debug_output().memory);
590 }
591
592 #[test]
593 fn test_hot_reload_event_rebuild_started() {
594 let event = HotReloadEvent::RebuildStarted;
595 assert!(matches!(event, HotReloadEvent::RebuildStarted));
596 }
597
598 #[test]
599 fn test_hot_reload_event_rebuild_failed() {
600 let event = HotReloadEvent::RebuildFailed {
601 errors: vec![
602 "error[E0599]: no method named `foo`".to_string(),
603 "error: aborting due to previous error".to_string(),
604 ],
605 };
606 if let HotReloadEvent::RebuildFailed { errors } = event {
607 assert_eq!(errors.len(), 2);
608 assert!(errors[0].contains("E0599"));
609 } else {
610 panic!("Expected HotReloadEvent::RebuildFailed");
611 }
612 }
613
614 #[test]
615 fn test_hot_reload_event_client_connected() {
616 let event = HotReloadEvent::ClientConnected { id: 42 };
617 if let HotReloadEvent::ClientConnected { id } = event {
618 assert_eq!(id, 42);
619 } else {
620 panic!("Expected HotReloadEvent::ClientConnected");
621 }
622 }
623
624 #[test]
625 fn test_hot_reload_event_client_disconnected() {
626 let event = HotReloadEvent::ClientDisconnected { id: 99 };
627 if let HotReloadEvent::ClientDisconnected { id } = event {
628 assert_eq!(id, 99);
629 } else {
630 panic!("Expected HotReloadEvent::ClientDisconnected");
631 }
632 }
633
634 #[test]
635 fn test_wasm_runner_builder_new_vs_default() {
636 let builder1 = WasmRunnerBuilder::new();
637 let builder2 = WasmRunnerBuilder::default();
638
639 let runner1 = builder1.build();
640 let runner2 = builder2.build();
641
642 assert_eq!(runner1.config().http_port, runner2.config().http_port);
643 assert_eq!(runner1.config().ws_port, runner2.config().ws_port);
644 }
645
646 #[test]
647 fn test_wasm_runner_config_accessor() {
648 let runner = WasmRunnerBuilder::new()
649 .http_port(5000)
650 .ws_port(5001)
651 .hot_reload(true)
652 .build();
653
654 let config = runner.config();
655 assert_eq!(config.http_port, 5000);
656 assert_eq!(config.ws_port, 5001);
657 assert!(config.hot_reload);
658 }
659
660 #[test]
661 fn test_wasm_runner_simulate_multiple_clients() {
662 let mut runner = WasmRunner::new(WasmRunnerConfig::default());
663 runner.simulate_start();
664
665 let id1 = runner.simulate_client_connect();
667 let id2 = runner.simulate_client_connect();
668 let id3 = runner.simulate_client_connect();
669
670 assert_eq!(runner.client_count(), 3);
671
672 assert_eq!(id1, 1);
674 assert_eq!(id2, 2);
675 assert_eq!(id3, 3);
676
677 runner.simulate_client_disconnect(id2);
679 assert_eq!(runner.client_count(), 2);
680
681 runner.simulate_client_disconnect(999);
683 assert_eq!(runner.client_count(), 2);
684 }
685
686 #[test]
687 fn test_console_message_all_levels() {
688 let runner = WasmRunner::new(WasmRunnerConfig::default());
689
690 for level in [
691 ConsoleLevel::Debug,
692 ConsoleLevel::Info,
693 ConsoleLevel::Log,
694 ConsoleLevel::Warn,
695 ConsoleLevel::Error,
696 ] {
697 let msg = ConsoleMessage {
698 level,
699 text: format!("Message at {:?} level", level),
700 timestamp: std::time::SystemTime::now(),
701 };
702 let formatted = runner.format_console_message(&msg);
703 assert!(formatted.contains(&msg.text));
704 }
705 }
706
707 #[test]
708 fn test_hot_reload_event_serialization() {
709 let event = HotReloadEvent::Rebuild {
710 duration: Duration::from_millis(1500),
711 preserved: vec!["GameState".to_string(), "PlayerData".to_string()],
712 };
713
714 let json = serde_json::to_string(&event).unwrap();
715 assert!(json.contains("GameState"));
716 assert!(json.contains("PlayerData"));
717 }
718
719 #[test]
720 fn test_console_message_serialization() {
721 let msg = ConsoleMessage {
722 level: ConsoleLevel::Warn,
723 text: "Warning message".to_string(),
724 timestamp: std::time::SystemTime::now(),
725 };
726
727 let json = serde_json::to_string(&msg).unwrap();
728 let deserialized: ConsoleMessage = serde_json::from_str(&json).unwrap();
729
730 assert_eq!(deserialized.level, ConsoleLevel::Warn);
731 assert_eq!(deserialized.text, "Warning message");
732 }
733
734 #[test]
735 fn test_debug_output_serialization() {
736 let output = DebugOutput::all();
737 let json = serde_json::to_string(&output).unwrap();
738 let deserialized: DebugOutput = serde_json::from_str(&json).unwrap();
739
740 assert!(deserialized.console);
741 assert!(deserialized.metrics);
742 assert!(deserialized.network);
743 assert!(deserialized.memory);
744 }
745
746 #[test]
747 fn test_wasm_runner_builder_all_options() {
748 let runner = WasmRunnerBuilder::new()
749 .http_port(7000)
750 .ws_port(7001)
751 .hot_reload(false)
752 .preserve_state(false)
753 .source_maps(false)
754 .debug_output(DebugOutput::none())
755 .build();
756
757 assert_eq!(runner.config().http_port, 7000);
758 assert_eq!(runner.config().ws_port, 7001);
759 assert!(!runner.config().hot_reload);
760 assert!(!runner.config().preserve_state);
761 assert!(!runner.config().source_maps);
762 assert!(!runner.debug_output().console);
763 }
764
765 #[test]
766 fn test_console_level_equality() {
767 assert_eq!(ConsoleLevel::Debug, ConsoleLevel::Debug);
768 assert_eq!(ConsoleLevel::Info, ConsoleLevel::Info);
769 assert_eq!(ConsoleLevel::Log, ConsoleLevel::Log);
770 assert_eq!(ConsoleLevel::Warn, ConsoleLevel::Warn);
771 assert_eq!(ConsoleLevel::Error, ConsoleLevel::Error);
772
773 assert_ne!(ConsoleLevel::Debug, ConsoleLevel::Error);
774 assert_ne!(ConsoleLevel::Info, ConsoleLevel::Warn);
775 }
776
777 #[test]
778 fn test_wasm_runner_initial_state() {
779 let runner = WasmRunner::new(WasmRunnerConfig::default());
780
781 assert!(!runner.is_running());
782 assert_eq!(runner.client_count(), 0);
783 }
784
785 #[test]
786 fn test_wasm_runner_url_formats() {
787 let runner = WasmRunnerBuilder::new()
788 .http_port(3000)
789 .ws_port(3001)
790 .build();
791
792 assert!(runner.http_url().starts_with("http://"));
793 assert!(runner.http_url().contains("localhost"));
794 assert!(runner.http_url().contains("3000"));
795
796 assert!(runner.ws_url().starts_with("ws://"));
797 assert!(runner.ws_url().contains("localhost"));
798 assert!(runner.ws_url().contains("3001"));
799 }
800
801 #[test]
802 fn test_hot_reload_event_file_changed() {
803 let event = HotReloadEvent::FileChanged {
804 path: "src/game.rs".to_string(),
805 };
806 if let HotReloadEvent::FileChanged { path } = event {
807 assert_eq!(path, "src/game.rs");
808 } else {
809 panic!("Expected HotReloadEvent::FileChanged");
810 }
811 }
812
813 #[test]
814 fn test_hot_reload_event_rebuild_details() {
815 let event = HotReloadEvent::Rebuild {
816 duration: Duration::from_secs(2),
817 preserved: vec!["AppState".to_string()],
818 };
819 if let HotReloadEvent::Rebuild {
820 duration,
821 preserved,
822 } = event
823 {
824 assert_eq!(duration, Duration::from_secs(2));
825 assert_eq!(preserved.len(), 1);
826 assert_eq!(preserved[0], "AppState");
827 } else {
828 panic!("Expected HotReloadEvent::Rebuild");
829 }
830 }
831
832 #[test]
833 fn test_wasm_runner_stop_clears_clients() {
834 let mut runner = WasmRunner::new(WasmRunnerConfig::default());
835 runner.simulate_start();
836
837 runner.simulate_client_connect();
838 runner.simulate_client_connect();
839 assert_eq!(runner.client_count(), 2);
840
841 runner.simulate_stop();
842 assert!(!runner.is_running());
843 assert_eq!(runner.client_count(), 0);
844 }
845
846 #[test]
847 fn test_wasm_runner_builder_default_debug_output() {
848 let runner = WasmRunnerBuilder::default().build();
849
850 assert!(runner.debug_output().console);
852 assert!(runner.debug_output().metrics);
853 assert!(!runner.debug_output().network);
854 assert!(!runner.debug_output().memory);
855 }
856
857 #[test]
858 fn test_console_level_serialization_all_variants() {
859 for level in [
860 ConsoleLevel::Debug,
861 ConsoleLevel::Info,
862 ConsoleLevel::Log,
863 ConsoleLevel::Warn,
864 ConsoleLevel::Error,
865 ] {
866 let json = serde_json::to_string(&level).unwrap();
867 let deserialized: ConsoleLevel = serde_json::from_str(&json).unwrap();
868 assert_eq!(level, deserialized);
869 }
870 }
871
872 #[test]
873 fn test_hot_reload_event_all_variants_serialization() {
874 let event1 = HotReloadEvent::FileChanged {
876 path: "test.rs".to_string(),
877 };
878 let json1 = serde_json::to_string(&event1).unwrap();
879 assert!(json1.contains("test.rs"));
880
881 let event2 = HotReloadEvent::RebuildStarted;
883 let json2 = serde_json::to_string(&event2).unwrap();
884 assert!(json2.contains("RebuildStarted"));
885
886 let event3 = HotReloadEvent::RebuildFailed {
888 errors: vec!["error".to_string()],
889 };
890 let json3 = serde_json::to_string(&event3).unwrap();
891 assert!(json3.contains("error"));
892
893 let event4 = HotReloadEvent::ClientConnected { id: 1 };
895 let json4 = serde_json::to_string(&event4).unwrap();
896 assert!(json4.contains('1'));
897
898 let event5 = HotReloadEvent::ClientDisconnected { id: 2 };
900 let json5 = serde_json::to_string(&event5).unwrap();
901 assert!(json5.contains('2'));
902 }
903}