1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10pub type ElementId = String;
12
13pub type CanvasId = String;
15
16fn default_text_format() -> String {
19 "plain".into()
20}
21
22fn default_field_type() -> String {
23 "text".into()
24}
25
26fn default_chart_type() -> String {
27 "bar".into()
28}
29
30fn default_true() -> bool {
31 true
32}
33
34#[non_exhaustive]
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42#[serde(tag = "type", rename_all = "snake_case")]
43pub enum CanvasElement {
44 Text {
46 content: String,
47 #[serde(default = "default_text_format")]
48 format: String,
49 },
50 Button {
52 label: String,
53 action: String,
54 #[serde(default)]
55 disabled: bool,
56 },
57 Input {
59 label: String,
60 #[serde(default)]
61 placeholder: String,
62 #[serde(default)]
63 value: String,
64 },
65 Image {
67 src: String,
68 #[serde(default)]
69 alt: String,
70 },
71 Code {
73 code: String,
74 #[serde(default)]
75 language: String,
76 },
77 Table {
79 headers: Vec<String>,
80 rows: Vec<Vec<String>>,
81 },
82 Form {
84 fields: Vec<FormField>,
85 submit_action: String,
86 },
87 Chart {
89 data: Vec<ChartDataPoint>,
90 #[serde(default = "default_chart_type")]
91 chart_type: String,
92 #[serde(default)]
93 title: Option<String>,
94 #[serde(default)]
95 colors: Option<Vec<String>>,
96 },
97 CodeEditor {
99 code: String,
100 #[serde(default)]
101 language: String,
102 #[serde(default)]
103 editable: bool,
104 #[serde(default = "default_true")]
105 line_numbers: bool,
106 },
107 FormAdvanced {
109 fields: Vec<AdvancedFormField>,
110 #[serde(default)]
111 submit_action: Option<String>,
112 },
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
117pub struct ChartDataPoint {
118 pub label: String,
120 pub value: f64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126pub struct FormField {
127 pub name: String,
129 pub label: String,
131 #[serde(default = "default_field_type")]
133 pub field_type: String,
134 #[serde(default)]
136 pub required: bool,
137 #[serde(default)]
139 pub placeholder: Option<String>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
144pub struct AdvancedFormField {
145 pub name: String,
147 #[serde(default = "default_field_type")]
149 pub field_type: String,
150 pub label: String,
152 #[serde(default)]
154 pub required: bool,
155 #[serde(default)]
157 pub options: Option<Vec<String>>,
158 #[serde(default)]
160 pub min: Option<f64>,
161 #[serde(default)]
163 pub max: Option<f64>,
164 #[serde(default)]
166 pub placeholder: Option<String>,
167}
168
169#[non_exhaustive]
176#[derive(Debug, Clone, Serialize, Deserialize)]
177#[serde(tag = "command", rename_all = "snake_case")]
178pub enum CanvasCommand {
179 Render {
181 id: ElementId,
182 element: CanvasElement,
183 #[serde(default)]
184 position: Option<u32>,
185 },
186 Update {
188 id: ElementId,
189 element: CanvasElement,
190 },
191 Remove { id: ElementId },
193 Reset,
195 Batch { commands: Vec<CanvasCommand> },
197}
198
199#[non_exhaustive]
206#[derive(Debug, Clone, Serialize, Deserialize)]
207#[serde(tag = "interaction", rename_all = "snake_case")]
208pub enum CanvasInteraction {
209 Click {
211 element_id: ElementId,
212 action: String,
213 },
214 InputSubmit {
216 element_id: ElementId,
217 value: String,
218 },
219 FormSubmit {
221 element_id: ElementId,
222 values: HashMap<String, String>,
223 },
224 CodeSubmit {
226 element_id: ElementId,
227 code: String,
228 language: String,
229 },
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
239 fn serialize_text_element() {
240 let elem = CanvasElement::Text {
241 content: "Hello, world!".into(),
242 format: "markdown".into(),
243 };
244 let json = serde_json::to_value(&elem).unwrap();
245 assert_eq!(json["type"], "text");
246 assert_eq!(json["content"], "Hello, world!");
247 assert_eq!(json["format"], "markdown");
248 }
249
250 #[test]
251 fn deserialize_text_element_with_default_format() {
252 let json = serde_json::json!({ "type": "text", "content": "hi" });
253 let elem: CanvasElement = serde_json::from_value(json).unwrap();
254 match elem {
255 CanvasElement::Text { content, format } => {
256 assert_eq!(content, "hi");
257 assert_eq!(format, "plain");
258 }
259 other => panic!("expected Text, got: {other:?}"),
260 }
261 }
262
263 #[test]
264 fn serialize_button_element() {
265 let elem = CanvasElement::Button {
266 label: "Click me".into(),
267 action: "do_thing".into(),
268 disabled: false,
269 };
270 let json = serde_json::to_value(&elem).unwrap();
271 assert_eq!(json["type"], "button");
272 assert_eq!(json["label"], "Click me");
273 assert_eq!(json["action"], "do_thing");
274 assert_eq!(json["disabled"], false);
275 }
276
277 #[test]
278 fn deserialize_button_element_default_disabled() {
279 let json = serde_json::json!({
280 "type": "button",
281 "label": "Go",
282 "action": "run"
283 });
284 let elem: CanvasElement = serde_json::from_value(json).unwrap();
285 match elem {
286 CanvasElement::Button { disabled, .. } => assert!(!disabled),
287 other => panic!("expected Button, got: {other:?}"),
288 }
289 }
290
291 #[test]
292 fn serialize_input_element() {
293 let elem = CanvasElement::Input {
294 label: "Name".into(),
295 placeholder: "Enter name".into(),
296 value: "".into(),
297 };
298 let json = serde_json::to_value(&elem).unwrap();
299 assert_eq!(json["type"], "input");
300 assert_eq!(json["label"], "Name");
301 assert_eq!(json["placeholder"], "Enter name");
302 }
303
304 #[test]
305 fn deserialize_input_element_defaults() {
306 let json = serde_json::json!({ "type": "input", "label": "Email" });
307 let elem: CanvasElement = serde_json::from_value(json).unwrap();
308 match elem {
309 CanvasElement::Input {
310 label,
311 placeholder,
312 value,
313 } => {
314 assert_eq!(label, "Email");
315 assert_eq!(placeholder, "");
316 assert_eq!(value, "");
317 }
318 other => panic!("expected Input, got: {other:?}"),
319 }
320 }
321
322 #[test]
323 fn serialize_image_element() {
324 let elem = CanvasElement::Image {
325 src: "https://example.com/img.png".into(),
326 alt: "Logo".into(),
327 };
328 let json = serde_json::to_value(&elem).unwrap();
329 assert_eq!(json["type"], "image");
330 assert_eq!(json["src"], "https://example.com/img.png");
331 assert_eq!(json["alt"], "Logo");
332 }
333
334 #[test]
335 fn deserialize_image_element_default_alt() {
336 let json = serde_json::json!({ "type": "image", "src": "a.png" });
337 let elem: CanvasElement = serde_json::from_value(json).unwrap();
338 match elem {
339 CanvasElement::Image { alt, .. } => assert_eq!(alt, ""),
340 other => panic!("expected Image, got: {other:?}"),
341 }
342 }
343
344 #[test]
345 fn serialize_code_element() {
346 let elem = CanvasElement::Code {
347 code: "fn main() {}".into(),
348 language: "rust".into(),
349 };
350 let json = serde_json::to_value(&elem).unwrap();
351 assert_eq!(json["type"], "code");
352 assert_eq!(json["code"], "fn main() {}");
353 assert_eq!(json["language"], "rust");
354 }
355
356 #[test]
357 fn deserialize_code_element_default_language() {
358 let json = serde_json::json!({ "type": "code", "code": "x = 1" });
359 let elem: CanvasElement = serde_json::from_value(json).unwrap();
360 match elem {
361 CanvasElement::Code { language, .. } => assert_eq!(language, ""),
362 other => panic!("expected Code, got: {other:?}"),
363 }
364 }
365
366 #[test]
367 fn serialize_table_element() {
368 let elem = CanvasElement::Table {
369 headers: vec!["Name".into(), "Age".into()],
370 rows: vec![vec!["Alice".into(), "30".into()]],
371 };
372 let json = serde_json::to_value(&elem).unwrap();
373 assert_eq!(json["type"], "table");
374 assert_eq!(json["headers"], serde_json::json!(["Name", "Age"]));
375 assert_eq!(json["rows"], serde_json::json!([["Alice", "30"]]));
376 }
377
378 #[test]
379 fn serialize_form_element() {
380 let elem = CanvasElement::Form {
381 fields: vec![FormField {
382 name: "username".into(),
383 label: "Username".into(),
384 field_type: "text".into(),
385 required: true,
386 placeholder: Some("Enter username".into()),
387 }],
388 submit_action: "create_user".into(),
389 };
390 let json = serde_json::to_value(&elem).unwrap();
391 assert_eq!(json["type"], "form");
392 assert_eq!(json["submit_action"], "create_user");
393 assert_eq!(json["fields"][0]["name"], "username");
394 assert_eq!(json["fields"][0]["required"], true);
395 }
396
397 #[test]
400 fn deserialize_form_field_defaults() {
401 let json = serde_json::json!({
402 "name": "email",
403 "label": "Email Address"
404 });
405 let field: FormField = serde_json::from_value(json).unwrap();
406 assert_eq!(field.name, "email");
407 assert_eq!(field.label, "Email Address");
408 assert_eq!(field.field_type, "text");
409 assert!(!field.required);
410 assert!(field.placeholder.is_none());
411 }
412
413 #[test]
414 fn form_field_roundtrip() {
415 let field = FormField {
416 name: "age".into(),
417 label: "Age".into(),
418 field_type: "number".into(),
419 required: false,
420 placeholder: Some("0".into()),
421 };
422 let json = serde_json::to_string(&field).unwrap();
423 let restored: FormField = serde_json::from_str(&json).unwrap();
424 assert_eq!(field, restored);
425 }
426
427 #[test]
430 fn serialize_chart_element() {
431 let elem = CanvasElement::Chart {
432 data: vec![
433 ChartDataPoint {
434 label: "Jan".into(),
435 value: 100.0,
436 },
437 ChartDataPoint {
438 label: "Feb".into(),
439 value: 200.0,
440 },
441 ],
442 chart_type: "bar".into(),
443 title: Some("Monthly Revenue".into()),
444 colors: Some(vec!["#6366f1".into(), "#22c55e".into()]),
445 };
446 let json = serde_json::to_value(&elem).unwrap();
447 assert_eq!(json["type"], "chart");
448 assert_eq!(json["chart_type"], "bar");
449 assert_eq!(json["title"], "Monthly Revenue");
450 assert_eq!(json["data"].as_array().unwrap().len(), 2);
451 assert_eq!(json["data"][0]["label"], "Jan");
452 assert_eq!(json["data"][0]["value"], 100.0);
453 }
454
455 #[test]
456 fn deserialize_chart_element_defaults() {
457 let json = serde_json::json!({
458 "type": "chart",
459 "data": [{"label": "A", "value": 10}]
460 });
461 let elem: CanvasElement = serde_json::from_value(json).unwrap();
462 match elem {
463 CanvasElement::Chart {
464 data,
465 chart_type,
466 title,
467 colors,
468 } => {
469 assert_eq!(data.len(), 1);
470 assert_eq!(data[0].label, "A");
471 assert_eq!(data[0].value, 10.0);
472 assert_eq!(chart_type, "bar");
473 assert!(title.is_none());
474 assert!(colors.is_none());
475 }
476 other => panic!("expected Chart, got: {other:?}"),
477 }
478 }
479
480 #[test]
481 fn chart_data_point_roundtrip() {
482 let point = ChartDataPoint {
483 label: "March".into(),
484 value: 42.5,
485 };
486 let json = serde_json::to_string(&point).unwrap();
487 let restored: ChartDataPoint = serde_json::from_str(&json).unwrap();
488 assert_eq!(point, restored);
489 }
490
491 #[test]
492 fn chart_element_pie_type() {
493 let elem = CanvasElement::Chart {
494 data: vec![
495 ChartDataPoint {
496 label: "Desktop".into(),
497 value: 60.0,
498 },
499 ChartDataPoint {
500 label: "Mobile".into(),
501 value: 40.0,
502 },
503 ],
504 chart_type: "pie".into(),
505 title: Some("Device Share".into()),
506 colors: None,
507 };
508 let json = serde_json::to_value(&elem).unwrap();
509 assert_eq!(json["type"], "chart");
510 assert_eq!(json["chart_type"], "pie");
511 let roundtripped: CanvasElement = serde_json::from_value(json).unwrap();
512 assert_eq!(elem, roundtripped);
513 }
514
515 #[test]
518 fn serialize_code_editor_element() {
519 let elem = CanvasElement::CodeEditor {
520 code: "console.log('hello')".into(),
521 language: "javascript".into(),
522 editable: true,
523 line_numbers: true,
524 };
525 let json = serde_json::to_value(&elem).unwrap();
526 assert_eq!(json["type"], "code_editor");
527 assert_eq!(json["code"], "console.log('hello')");
528 assert_eq!(json["language"], "javascript");
529 assert_eq!(json["editable"], true);
530 assert_eq!(json["line_numbers"], true);
531 }
532
533 #[test]
534 fn deserialize_code_editor_element_defaults() {
535 let json = serde_json::json!({
536 "type": "code_editor",
537 "code": "x = 1"
538 });
539 let elem: CanvasElement = serde_json::from_value(json).unwrap();
540 match elem {
541 CanvasElement::CodeEditor {
542 code,
543 language,
544 editable,
545 line_numbers,
546 } => {
547 assert_eq!(code, "x = 1");
548 assert_eq!(language, "");
549 assert!(!editable);
550 assert!(line_numbers); }
552 other => panic!("expected CodeEditor, got: {other:?}"),
553 }
554 }
555
556 #[test]
557 fn code_editor_roundtrip() {
558 let elem = CanvasElement::CodeEditor {
559 code: "fn main() {\n println!(\"Hello\");\n}".into(),
560 language: "rust".into(),
561 editable: false,
562 line_numbers: false,
563 };
564 let json = serde_json::to_string(&elem).unwrap();
565 let restored: CanvasElement = serde_json::from_str(&json).unwrap();
566 assert_eq!(elem, restored);
567 }
568
569 #[test]
572 fn serialize_form_advanced_element() {
573 let elem = CanvasElement::FormAdvanced {
574 fields: vec![
575 AdvancedFormField {
576 name: "name".into(),
577 field_type: "text".into(),
578 label: "Full Name".into(),
579 required: true,
580 options: None,
581 min: None,
582 max: None,
583 placeholder: Some("Enter your name".into()),
584 },
585 AdvancedFormField {
586 name: "age".into(),
587 field_type: "number".into(),
588 label: "Age".into(),
589 required: false,
590 options: None,
591 min: Some(0.0),
592 max: Some(150.0),
593 placeholder: None,
594 },
595 AdvancedFormField {
596 name: "role".into(),
597 field_type: "select".into(),
598 label: "Role".into(),
599 required: true,
600 options: Some(vec!["Admin".into(), "User".into(), "Guest".into()]),
601 min: None,
602 max: None,
603 placeholder: None,
604 },
605 ],
606 submit_action: Some("create_user".into()),
607 };
608 let json = serde_json::to_value(&elem).unwrap();
609 assert_eq!(json["type"], "form_advanced");
610 assert_eq!(json["submit_action"], "create_user");
611 assert_eq!(json["fields"].as_array().unwrap().len(), 3);
612 assert_eq!(json["fields"][0]["name"], "name");
613 assert_eq!(json["fields"][0]["required"], true);
614 assert_eq!(json["fields"][1]["min"], 0.0);
615 assert_eq!(json["fields"][2]["options"], serde_json::json!(["Admin", "User", "Guest"]));
616 }
617
618 #[test]
619 fn deserialize_advanced_form_field_defaults() {
620 let json = serde_json::json!({
621 "name": "notes",
622 "label": "Notes"
623 });
624 let field: AdvancedFormField = serde_json::from_value(json).unwrap();
625 assert_eq!(field.name, "notes");
626 assert_eq!(field.label, "Notes");
627 assert_eq!(field.field_type, "text");
628 assert!(!field.required);
629 assert!(field.options.is_none());
630 assert!(field.min.is_none());
631 assert!(field.max.is_none());
632 assert!(field.placeholder.is_none());
633 }
634
635 #[test]
636 fn advanced_form_field_roundtrip() {
637 let field = AdvancedFormField {
638 name: "priority".into(),
639 field_type: "select".into(),
640 label: "Priority".into(),
641 required: true,
642 options: Some(vec!["Low".into(), "Medium".into(), "High".into()]),
643 min: None,
644 max: None,
645 placeholder: Some("Select priority".into()),
646 };
647 let json = serde_json::to_string(&field).unwrap();
648 let restored: AdvancedFormField = serde_json::from_str(&json).unwrap();
649 assert_eq!(field, restored);
650 }
651
652 #[test]
655 fn serialize_code_submit_interaction() {
656 let interaction = CanvasInteraction::CodeSubmit {
657 element_id: "editor-1".into(),
658 code: "print('done')".into(),
659 language: "python".into(),
660 };
661 let json = serde_json::to_value(&interaction).unwrap();
662 assert_eq!(json["interaction"], "code_submit");
663 assert_eq!(json["element_id"], "editor-1");
664 assert_eq!(json["code"], "print('done')");
665 assert_eq!(json["language"], "python");
666 }
667
668 #[test]
669 fn deserialize_code_submit_interaction() {
670 let json = serde_json::json!({
671 "interaction": "code_submit",
672 "element_id": "ed-2",
673 "code": "x = 1",
674 "language": "python"
675 });
676 let interaction: CanvasInteraction = serde_json::from_value(json).unwrap();
677 match interaction {
678 CanvasInteraction::CodeSubmit {
679 element_id,
680 code,
681 language,
682 } => {
683 assert_eq!(element_id, "ed-2");
684 assert_eq!(code, "x = 1");
685 assert_eq!(language, "python");
686 }
687 other => panic!("expected CodeSubmit, got: {other:?}"),
688 }
689 }
690
691 #[test]
694 fn serialize_render_command() {
695 let cmd = CanvasCommand::Render {
696 id: "el-1".into(),
697 element: CanvasElement::Text {
698 content: "Hello".into(),
699 format: "plain".into(),
700 },
701 position: Some(0),
702 };
703 let json = serde_json::to_value(&cmd).unwrap();
704 assert_eq!(json["command"], "render");
705 assert_eq!(json["id"], "el-1");
706 assert_eq!(json["element"]["type"], "text");
707 assert_eq!(json["position"], 0);
708 }
709
710 #[test]
711 fn deserialize_render_command_no_position() {
712 let json = serde_json::json!({
713 "command": "render",
714 "id": "el-2",
715 "element": { "type": "button", "label": "Go", "action": "run" }
716 });
717 let cmd: CanvasCommand = serde_json::from_value(json).unwrap();
718 match cmd {
719 CanvasCommand::Render { id, position, .. } => {
720 assert_eq!(id, "el-2");
721 assert!(position.is_none());
722 }
723 other => panic!("expected Render, got: {other:?}"),
724 }
725 }
726
727 #[test]
728 fn serialize_update_command() {
729 let cmd = CanvasCommand::Update {
730 id: "el-1".into(),
731 element: CanvasElement::Text {
732 content: "Updated".into(),
733 format: "markdown".into(),
734 },
735 };
736 let json = serde_json::to_value(&cmd).unwrap();
737 assert_eq!(json["command"], "update");
738 assert_eq!(json["id"], "el-1");
739 }
740
741 #[test]
742 fn serialize_remove_command() {
743 let cmd = CanvasCommand::Remove {
744 id: "el-3".into(),
745 };
746 let json = serde_json::to_value(&cmd).unwrap();
747 assert_eq!(json["command"], "remove");
748 assert_eq!(json["id"], "el-3");
749 }
750
751 #[test]
752 fn serialize_reset_command() {
753 let cmd = CanvasCommand::Reset;
754 let json = serde_json::to_value(&cmd).unwrap();
755 assert_eq!(json["command"], "reset");
756 }
757
758 #[test]
759 fn serialize_batch_command() {
760 let cmd = CanvasCommand::Batch {
761 commands: vec![
762 CanvasCommand::Reset,
763 CanvasCommand::Render {
764 id: "el-1".into(),
765 element: CanvasElement::Text {
766 content: "Fresh".into(),
767 format: "plain".into(),
768 },
769 position: None,
770 },
771 ],
772 };
773 let json = serde_json::to_value(&cmd).unwrap();
774 assert_eq!(json["command"], "batch");
775 assert_eq!(json["commands"].as_array().unwrap().len(), 2);
776 }
777
778 #[test]
779 fn deserialize_reset_command() {
780 let json = serde_json::json!({ "command": "reset" });
781 let cmd: CanvasCommand = serde_json::from_value(json).unwrap();
782 assert!(matches!(cmd, CanvasCommand::Reset));
783 }
784
785 #[test]
788 fn serialize_click_interaction() {
789 let interaction = CanvasInteraction::Click {
790 element_id: "btn-1".into(),
791 action: "submit".into(),
792 };
793 let json = serde_json::to_value(&interaction).unwrap();
794 assert_eq!(json["interaction"], "click");
795 assert_eq!(json["element_id"], "btn-1");
796 assert_eq!(json["action"], "submit");
797 }
798
799 #[test]
800 fn serialize_input_submit_interaction() {
801 let interaction = CanvasInteraction::InputSubmit {
802 element_id: "input-1".into(),
803 value: "hello".into(),
804 };
805 let json = serde_json::to_value(&interaction).unwrap();
806 assert_eq!(json["interaction"], "input_submit");
807 assert_eq!(json["element_id"], "input-1");
808 assert_eq!(json["value"], "hello");
809 }
810
811 #[test]
812 fn serialize_form_submit_interaction() {
813 let mut values = HashMap::new();
814 values.insert("username".into(), "alice".into());
815 values.insert("email".into(), "alice@example.com".into());
816
817 let interaction = CanvasInteraction::FormSubmit {
818 element_id: "form-1".into(),
819 values,
820 };
821 let json = serde_json::to_value(&interaction).unwrap();
822 assert_eq!(json["interaction"], "form_submit");
823 assert_eq!(json["element_id"], "form-1");
824 assert_eq!(json["values"]["username"], "alice");
825 assert_eq!(json["values"]["email"], "alice@example.com");
826 }
827
828 #[test]
829 fn deserialize_click_interaction() {
830 let json = serde_json::json!({
831 "interaction": "click",
832 "element_id": "btn-x",
833 "action": "delete"
834 });
835 let interaction: CanvasInteraction = serde_json::from_value(json).unwrap();
836 match interaction {
837 CanvasInteraction::Click {
838 element_id,
839 action,
840 } => {
841 assert_eq!(element_id, "btn-x");
842 assert_eq!(action, "delete");
843 }
844 other => panic!("expected Click, got: {other:?}"),
845 }
846 }
847
848 #[test]
849 fn deserialize_form_submit_interaction() {
850 let json = serde_json::json!({
851 "interaction": "form_submit",
852 "element_id": "form-2",
853 "values": { "name": "Bob" }
854 });
855 let interaction: CanvasInteraction = serde_json::from_value(json).unwrap();
856 match interaction {
857 CanvasInteraction::FormSubmit {
858 element_id,
859 values,
860 } => {
861 assert_eq!(element_id, "form-2");
862 assert_eq!(values.get("name").unwrap(), "Bob");
863 }
864 other => panic!("expected FormSubmit, got: {other:?}"),
865 }
866 }
867
868 #[test]
871 fn canvas_element_roundtrip_all_variants() {
872 let elements = vec![
873 CanvasElement::Text {
874 content: "test".into(),
875 format: "markdown".into(),
876 },
877 CanvasElement::Button {
878 label: "OK".into(),
879 action: "confirm".into(),
880 disabled: true,
881 },
882 CanvasElement::Input {
883 label: "Query".into(),
884 placeholder: "Type here".into(),
885 value: "default".into(),
886 },
887 CanvasElement::Image {
888 src: "logo.png".into(),
889 alt: "Company Logo".into(),
890 },
891 CanvasElement::Code {
892 code: "print('hi')".into(),
893 language: "python".into(),
894 },
895 CanvasElement::Table {
896 headers: vec!["Col1".into()],
897 rows: vec![vec!["val".into()]],
898 },
899 CanvasElement::Form {
900 fields: vec![FormField {
901 name: "f".into(),
902 label: "Field".into(),
903 field_type: "text".into(),
904 required: false,
905 placeholder: None,
906 }],
907 submit_action: "go".into(),
908 },
909 CanvasElement::Chart {
910 data: vec![ChartDataPoint {
911 label: "Q1".into(),
912 value: 42.0,
913 }],
914 chart_type: "line".into(),
915 title: Some("Quarterly".into()),
916 colors: None,
917 },
918 CanvasElement::CodeEditor {
919 code: "let x = 1;".into(),
920 language: "typescript".into(),
921 editable: true,
922 line_numbers: true,
923 },
924 CanvasElement::FormAdvanced {
925 fields: vec![AdvancedFormField {
926 name: "email".into(),
927 field_type: "text".into(),
928 label: "Email".into(),
929 required: true,
930 options: None,
931 min: None,
932 max: None,
933 placeholder: Some("you@example.com".into()),
934 }],
935 submit_action: Some("register".into()),
936 },
937 ];
938
939 for elem in &elements {
940 let json = serde_json::to_string(elem).unwrap();
941 let restored: CanvasElement = serde_json::from_str(&json).unwrap();
942 assert_eq!(*elem, restored);
943 }
944 }
945
946 #[test]
949 fn render_chart_command() {
950 let cmd = CanvasCommand::Render {
951 id: "chart-1".into(),
952 element: CanvasElement::Chart {
953 data: vec![
954 ChartDataPoint { label: "A".into(), value: 10.0 },
955 ChartDataPoint { label: "B".into(), value: 20.0 },
956 ],
957 chart_type: "bar".into(),
958 title: None,
959 colors: None,
960 },
961 position: None,
962 };
963 let json = serde_json::to_value(&cmd).unwrap();
964 assert_eq!(json["command"], "render");
965 assert_eq!(json["element"]["type"], "chart");
966 assert_eq!(json["element"]["data"].as_array().unwrap().len(), 2);
967 }
968
969 #[test]
970 fn render_code_editor_command() {
971 let cmd = CanvasCommand::Render {
972 id: "editor-1".into(),
973 element: CanvasElement::CodeEditor {
974 code: "SELECT * FROM users;".into(),
975 language: "sql".into(),
976 editable: true,
977 line_numbers: true,
978 },
979 position: Some(0),
980 };
981 let json = serde_json::to_value(&cmd).unwrap();
982 assert_eq!(json["command"], "render");
983 assert_eq!(json["element"]["type"], "code_editor");
984 assert_eq!(json["element"]["editable"], true);
985 }
986}