1#[allow(
30 unused_imports,
31 dead_code,
32 clippy::all,
33 mismatched_lifetime_syntaxes,
34 non_snake_case,
35 non_camel_case_types,
36 missing_docs,
37 unsafe_op_in_unsafe_fn
38)]
39mod generated {
40 include!("generated/_combined_generated.rs");
41}
42
43pub mod errors;
44
45pub use generated::voce;
65
66#[cfg(test)]
67mod tests {
68 use super::voce::*;
69 use flatbuffers::FlatBufferBuilder;
70
71 #[test]
72 fn build_and_read_minimal_document() {
73 let mut builder = FlatBufferBuilder::new();
74
75 let content = builder.create_string("Hello, Voce");
76 let node_id = builder.create_string("heading");
77 let text_node = TextNode::create(
78 &mut builder,
79 &TextNodeArgs {
80 node_id: Some(node_id),
81 content: Some(content),
82 font_weight: FontWeight::Bold,
83 heading_level: 1,
84 line_height: 1.5,
85 opacity: 1.0,
86 ..Default::default()
87 },
88 );
89
90 let child = ChildNode::create(
91 &mut builder,
92 &ChildNodeArgs {
93 value_type: ChildUnion::TextNode,
94 value: Some(text_node.as_union_value()),
95 },
96 );
97 let children = builder.create_vector(&[child]);
98
99 let root_id = builder.create_string("root");
100 let lang = builder.create_string("en");
101 let root = ViewRoot::create(
102 &mut builder,
103 &ViewRootArgs {
104 node_id: Some(root_id),
105 children: Some(children),
106 document_language: Some(lang),
107 ..Default::default()
108 },
109 );
110
111 let doc = VoceDocument::create(
112 &mut builder,
113 &VoceDocumentArgs {
114 schema_version_major: 0,
115 schema_version_minor: 1,
116 root: Some(root),
117 ..Default::default()
118 },
119 );
120
121 builder.finish(doc, Some("VOCE"));
122 let buf = builder.finished_data();
123
124 let doc = flatbuffers::root::<VoceDocument>(buf).expect("valid FlatBuffer");
125 assert_eq!(doc.schema_version_major(), 0);
126 assert_eq!(doc.schema_version_minor(), 1);
127
128 let root = doc.root();
129 assert_eq!(root.node_id(), "root");
130 assert_eq!(root.document_language(), Some("en"));
131
132 let children = root.children().expect("has children");
133 assert_eq!(children.len(), 1);
134
135 let child = children.get(0);
136 assert_eq!(child.value_type(), ChildUnion::TextNode);
137
138 let text = child.value_as_text_node().expect("is TextNode");
139 assert_eq!(text.node_id(), "heading");
140 assert_eq!(text.content(), Some("Hello, Voce"));
141 assert_eq!(text.font_weight(), FontWeight::Bold);
142 assert_eq!(text.heading_level(), 1);
143 }
144
145 #[test]
146 fn verify_file_identifier() {
147 let mut builder = FlatBufferBuilder::new();
148
149 let root_id = builder.create_string("root");
150 let root = ViewRoot::create(
151 &mut builder,
152 &ViewRootArgs {
153 node_id: Some(root_id),
154 ..Default::default()
155 },
156 );
157 let doc = VoceDocument::create(
158 &mut builder,
159 &VoceDocumentArgs {
160 root: Some(root),
161 ..Default::default()
162 },
163 );
164
165 builder.finish(doc, Some("VOCE"));
166 let buf = builder.finished_data();
167
168 assert!(flatbuffers::buffer_has_identifier(buf, "VOCE", false));
169 }
170
171 #[test]
172 fn container_with_layout_properties() {
173 let mut builder = FlatBufferBuilder::new();
174
175 let node_id = builder.create_string("main");
176 let gap = Length::create(
177 &mut builder,
178 &LengthArgs {
179 value: 16.0,
180 unit: LengthUnit::Px,
181 },
182 );
183
184 let container = Container::create(
185 &mut builder,
186 &ContainerArgs {
187 node_id: Some(node_id),
188 layout: ContainerLayout::Flex,
189 direction: LayoutDirection::Row,
190 main_align: Alignment::SpaceBetween,
191 cross_align: Alignment::Center,
192 gap: Some(gap),
193 wrap: true,
194 opacity: 1.0,
195 ..Default::default()
196 },
197 );
198
199 let child = ChildNode::create(
200 &mut builder,
201 &ChildNodeArgs {
202 value_type: ChildUnion::Container,
203 value: Some(container.as_union_value()),
204 },
205 );
206 let children = builder.create_vector(&[child]);
207
208 let root_id = builder.create_string("root");
209 let root = ViewRoot::create(
210 &mut builder,
211 &ViewRootArgs {
212 node_id: Some(root_id),
213 children: Some(children),
214 ..Default::default()
215 },
216 );
217
218 let doc = VoceDocument::create(
219 &mut builder,
220 &VoceDocumentArgs {
221 root: Some(root),
222 ..Default::default()
223 },
224 );
225
226 builder.finish(doc, Some("VOCE"));
227 let buf = builder.finished_data();
228
229 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
230 let children = doc.root().children().unwrap();
231 let container = children.get(0).value_as_container().unwrap();
232
233 assert_eq!(container.node_id(), "main");
234 assert_eq!(container.layout(), ContainerLayout::Flex);
235 assert_eq!(container.direction(), LayoutDirection::Row);
236 assert_eq!(container.main_align(), Alignment::SpaceBetween);
237 assert_eq!(container.cross_align(), Alignment::Center);
238 assert!(container.wrap());
239
240 let gap = container.gap().unwrap();
241 assert_eq!(gap.value(), 16.0);
242 assert_eq!(gap.unit(), LengthUnit::Px);
243 }
244
245 #[test]
246 fn state_machine_creation() {
247 let mut builder = FlatBufferBuilder::new();
248
249 let idle = builder.create_string("idle");
250 let loading = builder.create_string("loading");
251 let loaded = builder.create_string("loaded");
252
253 let state_idle = State::create(
254 &mut builder,
255 &StateArgs {
256 name: Some(idle),
257 initial: true,
258 terminal: false,
259 },
260 );
261 let state_loading = State::create(
262 &mut builder,
263 &StateArgs {
264 name: Some(loading),
265 initial: false,
266 terminal: false,
267 },
268 );
269 let state_loaded = State::create(
270 &mut builder,
271 &StateArgs {
272 name: Some(loaded),
273 initial: false,
274 terminal: true,
275 },
276 );
277 let states = builder.create_vector(&[state_idle, state_loading, state_loaded]);
278
279 let ev_click = builder.create_string("click");
281 let ev_resolve = builder.create_string("resolve");
282 let from_idle = builder.create_string("idle");
283 let to_loading = builder.create_string("loading");
284 let from_loading = builder.create_string("loading");
285 let to_loaded = builder.create_string("loaded");
286
287 let t1 = Transition::create(
288 &mut builder,
289 &TransitionArgs {
290 event: Some(ev_click),
291 from: Some(from_idle),
292 to: Some(to_loading),
293 ..Default::default()
294 },
295 );
296 let t2 = Transition::create(
297 &mut builder,
298 &TransitionArgs {
299 event: Some(ev_resolve),
300 from: Some(from_loading),
301 to: Some(to_loaded),
302 ..Default::default()
303 },
304 );
305 let transitions = builder.create_vector(&[t1, t2]);
306
307 let sm_id = builder.create_string("fetch-machine");
308 let sm_name = builder.create_string("Fetch Data");
309 let sm = StateMachine::create(
310 &mut builder,
311 &StateMachineArgs {
312 node_id: Some(sm_id),
313 name: Some(sm_name),
314 states: Some(states),
315 transitions: Some(transitions),
316 },
317 );
318
319 let child = ChildNode::create(
320 &mut builder,
321 &ChildNodeArgs {
322 value_type: ChildUnion::StateMachine,
323 value: Some(sm.as_union_value()),
324 },
325 );
326 let children = builder.create_vector(&[child]);
327
328 let root_id = builder.create_string("root");
329 let root = ViewRoot::create(
330 &mut builder,
331 &ViewRootArgs {
332 node_id: Some(root_id),
333 children: Some(children),
334 ..Default::default()
335 },
336 );
337
338 let doc = VoceDocument::create(
339 &mut builder,
340 &VoceDocumentArgs {
341 root: Some(root),
342 ..Default::default()
343 },
344 );
345
346 builder.finish(doc, Some("VOCE"));
347 let buf = builder.finished_data();
348
349 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
350 let sm = doc
351 .root()
352 .children()
353 .unwrap()
354 .get(0)
355 .value_as_state_machine()
356 .unwrap();
357
358 assert_eq!(sm.node_id(), "fetch-machine");
359 assert_eq!(sm.name(), Some("Fetch Data"));
360 assert_eq!(sm.states().len(), 3);
362 assert_eq!(sm.transitions().len(), 2);
363
364 let first_state = sm.states().get(0);
365 assert_eq!(first_state.name(), "idle");
366 assert!(first_state.initial());
367
368 let first_transition = sm.transitions().get(0);
369 assert_eq!(first_transition.event(), "click");
370 assert_eq!(first_transition.from(), "idle");
371 assert_eq!(first_transition.to(), "loading");
372 }
373
374 #[test]
375 fn animation_transition_with_spring() {
376 let mut builder = FlatBufferBuilder::new();
377
378 let prop_str = builder.create_string("transform.translateY");
379 let from_str = builder.create_string("20px");
380 let to_str = builder.create_string("0px");
381 let prop = AnimatedProperty::create(
382 &mut builder,
383 &AnimatedPropertyArgs {
384 property: Some(prop_str),
385 from: Some(from_str),
386 to: Some(to_str),
387 },
388 );
389 let props = builder.create_vector(&[prop]);
390
391 let dur = Duration::create(&mut builder, &DurationArgs { ms: 300.0 });
392
393 let easing = Easing::create(
394 &mut builder,
395 &EasingArgs {
396 easing_type: EasingType::Spring,
397 stiffness: 300.0,
398 damping: 25.0,
399 mass: 1.0,
400 ..Default::default()
401 },
402 );
403
404 let rm = ReducedMotion::create(
405 &mut builder,
406 &ReducedMotionArgs {
407 strategy: ReducedMotionStrategy::Remove,
408 ..Default::default()
409 },
410 );
411
412 let target = builder.create_string("hero-text");
413 let anim_id = builder.create_string("hero-entrance");
414 let anim = AnimationTransition::create(
415 &mut builder,
416 &AnimationTransitionArgs {
417 node_id: Some(anim_id),
418 target_node_id: Some(target),
419 properties: Some(props),
420 duration: Some(dur),
421 easing: Some(easing),
422 reduced_motion: Some(rm),
423 ..Default::default()
424 },
425 );
426
427 let child = ChildNode::create(
428 &mut builder,
429 &ChildNodeArgs {
430 value_type: ChildUnion::AnimationTransition,
431 value: Some(anim.as_union_value()),
432 },
433 );
434 let children = builder.create_vector(&[child]);
435
436 let root_id = builder.create_string("root");
437 let root = ViewRoot::create(
438 &mut builder,
439 &ViewRootArgs {
440 node_id: Some(root_id),
441 children: Some(children),
442 ..Default::default()
443 },
444 );
445
446 let doc = VoceDocument::create(
447 &mut builder,
448 &VoceDocumentArgs {
449 root: Some(root),
450 ..Default::default()
451 },
452 );
453
454 builder.finish(doc, Some("VOCE"));
455 let buf = builder.finished_data();
456
457 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
458 let anim = doc
459 .root()
460 .children()
461 .unwrap()
462 .get(0)
463 .value_as_animation_transition()
464 .unwrap();
465
466 assert_eq!(anim.node_id(), "hero-entrance");
467 assert_eq!(anim.target_node_id(), "hero-text");
469
470 let easing = anim.easing().unwrap();
471 assert_eq!(easing.easing_type(), EasingType::Spring);
472 assert_eq!(easing.stiffness(), 300.0);
473 assert_eq!(easing.damping(), 25.0);
474
475 let rm = anim.reduced_motion().unwrap();
476 assert_eq!(rm.strategy(), ReducedMotionStrategy::Remove);
477
478 let props = anim.properties();
480 assert_eq!(props.len(), 1);
481 assert_eq!(props.get(0).property(), "transform.translateY");
482 }
483
484 #[test]
485 fn child_union_covers_all_types() {
486 assert_eq!(ChildUnion::Container.0, 1);
489 assert_eq!(ChildUnion::Surface.0, 2);
490 assert_eq!(ChildUnion::TextNode.0, 3);
491 assert_eq!(ChildUnion::MediaNode.0, 4);
492 assert_eq!(ChildUnion::StateMachine.0, 5);
494 assert_eq!(ChildUnion::DataNode.0, 6);
495 assert_eq!(ChildUnion::ComputeNode.0, 7);
496 assert_eq!(ChildUnion::EffectNode.0, 8);
497 assert_eq!(ChildUnion::ContextNode.0, 9);
498 assert_eq!(ChildUnion::AnimationTransition.0, 10);
500 assert_eq!(ChildUnion::Sequence.0, 11);
501 assert_eq!(ChildUnion::GestureHandler.0, 12);
502 assert_eq!(ChildUnion::ScrollBinding.0, 13);
503 assert_eq!(ChildUnion::PhysicsBody.0, 14);
504 assert_eq!(ChildUnion::RouteMap.0, 15);
506 assert_eq!(ChildUnion::SemanticNode.0, 16);
508 assert_eq!(ChildUnion::LiveRegion.0, 17);
509 assert_eq!(ChildUnion::FocusTrap.0, 18);
510 assert_eq!(ChildUnion::ThemeNode.0, 19);
512 assert_eq!(ChildUnion::PersonalizationSlot.0, 20);
513 assert_eq!(ChildUnion::ResponsiveRule.0, 21);
514 assert_eq!(ChildUnion::ActionNode.0, 22);
516 assert_eq!(ChildUnion::SubscriptionNode.0, 23);
517 assert_eq!(ChildUnion::AuthContextNode.0, 24);
518 assert_eq!(ChildUnion::ContentSlot.0, 25);
519 assert_eq!(ChildUnion::RichTextNode.0, 26);
520 assert_eq!(ChildUnion::FormNode.0, 27);
522 }
523
524 #[test]
525 fn semantic_node_with_button_role() {
526 let mut builder = FlatBufferBuilder::new();
527
528 let node_id = builder.create_string("sem-cta");
529 let role = builder.create_string("button");
530 let label = builder.create_string("Add to cart");
531
532 let sem = SemanticNode::create(
533 &mut builder,
534 &SemanticNodeArgs {
535 node_id: Some(node_id),
536 role: Some(role),
537 label: Some(label),
538 tab_index: 0,
539 aria_required: false,
540 ..Default::default()
541 },
542 );
543
544 let child = ChildNode::create(
545 &mut builder,
546 &ChildNodeArgs {
547 value_type: ChildUnion::SemanticNode,
548 value: Some(sem.as_union_value()),
549 },
550 );
551 let children = builder.create_vector(&[child]);
552
553 let root_id = builder.create_string("root");
554 let root = ViewRoot::create(
555 &mut builder,
556 &ViewRootArgs {
557 node_id: Some(root_id),
558 children: Some(children),
559 ..Default::default()
560 },
561 );
562
563 let doc = VoceDocument::create(
564 &mut builder,
565 &VoceDocumentArgs {
566 root: Some(root),
567 ..Default::default()
568 },
569 );
570
571 builder.finish(doc, Some("VOCE"));
572 let buf = builder.finished_data();
573
574 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
575 let sem = doc
576 .root()
577 .children()
578 .unwrap()
579 .get(0)
580 .value_as_semantic_node()
581 .unwrap();
582
583 assert_eq!(sem.node_id(), "sem-cta");
584 assert_eq!(sem.role(), "button");
585 assert_eq!(sem.label(), Some("Add to cart"));
586 assert_eq!(sem.tab_index(), 0);
587 }
588
589 #[test]
590 fn live_region_assertive() {
591 let mut builder = FlatBufferBuilder::new();
592
593 let node_id = builder.create_string("cart-updates");
594 let target = builder.create_string("cart-count");
595 let desc = builder.create_string("Shopping cart updates");
596
597 let lr = LiveRegion::create(
598 &mut builder,
599 &LiveRegionArgs {
600 node_id: Some(node_id),
601 target_node_id: Some(target),
602 politeness: LiveRegionPoliteness::Assertive,
603 atomic: true,
604 role_description: Some(desc),
605 ..Default::default()
606 },
607 );
608
609 let child = ChildNode::create(
610 &mut builder,
611 &ChildNodeArgs {
612 value_type: ChildUnion::LiveRegion,
613 value: Some(lr.as_union_value()),
614 },
615 );
616 let children = builder.create_vector(&[child]);
617
618 let root_id = builder.create_string("root");
619 let root = ViewRoot::create(
620 &mut builder,
621 &ViewRootArgs {
622 node_id: Some(root_id),
623 children: Some(children),
624 ..Default::default()
625 },
626 );
627
628 let doc = VoceDocument::create(
629 &mut builder,
630 &VoceDocumentArgs {
631 root: Some(root),
632 ..Default::default()
633 },
634 );
635
636 builder.finish(doc, Some("VOCE"));
637 let buf = builder.finished_data();
638
639 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
640 let lr = doc
641 .root()
642 .children()
643 .unwrap()
644 .get(0)
645 .value_as_live_region()
646 .unwrap();
647
648 assert_eq!(lr.node_id(), "cart-updates");
649 assert_eq!(lr.target_node_id(), "cart-count");
650 assert_eq!(lr.politeness(), LiveRegionPoliteness::Assertive);
651 assert!(lr.atomic());
652 assert_eq!(lr.role_description(), Some("Shopping cart updates"));
653 }
654
655 #[test]
656 fn theme_node_with_color_palette() {
657 let mut builder = FlatBufferBuilder::new();
658
659 let node_id = builder.create_string("theme-dark");
660 let name = builder.create_string("dark");
661
662 let colors = ColorPalette::create(
663 &mut builder,
664 &ColorPaletteArgs {
665 background: Some(&Color::new(12, 12, 14, 255)),
666 foreground: Some(&Color::new(232, 230, 225, 255)),
667 primary: Some(&Color::new(232, 89, 60, 255)),
668 surface: Some(&Color::new(20, 20, 23, 255)),
669 ..Default::default()
670 },
671 );
672
673 let theme = ThemeNode::create(
674 &mut builder,
675 &ThemeNodeArgs {
676 node_id: Some(node_id),
677 name: Some(name),
678 colors: Some(colors),
679 ..Default::default()
680 },
681 );
682
683 let child = ChildNode::create(
684 &mut builder,
685 &ChildNodeArgs {
686 value_type: ChildUnion::ThemeNode,
687 value: Some(theme.as_union_value()),
688 },
689 );
690 let children = builder.create_vector(&[child]);
691
692 let root_id = builder.create_string("root");
693 let root = ViewRoot::create(
694 &mut builder,
695 &ViewRootArgs {
696 node_id: Some(root_id),
697 children: Some(children),
698 ..Default::default()
699 },
700 );
701
702 let doc = VoceDocument::create(
703 &mut builder,
704 &VoceDocumentArgs {
705 root: Some(root),
706 theme: Some(theme),
707 ..Default::default()
708 },
709 );
710
711 builder.finish(doc, Some("VOCE"));
712 let buf = builder.finished_data();
713
714 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
715
716 let theme = doc.theme().unwrap();
718 assert_eq!(theme.name(), "dark");
719
720 let colors = theme.colors().unwrap();
721 let bg = colors.background().unwrap();
722 assert_eq!(bg.r(), 12);
723 assert_eq!(bg.g(), 12);
724 assert_eq!(bg.b(), 14);
725 assert_eq!(bg.a(), 255);
726
727 let primary = colors.primary().unwrap();
728 assert_eq!(primary.r(), 232);
729 assert_eq!(primary.g(), 89);
730 assert_eq!(primary.b(), 60);
731 }
732
733 #[test]
734 fn action_node_with_optimistic_update() {
735 let mut builder = FlatBufferBuilder::new();
736
737 let endpoint = builder.create_string("https://api.example.com/todos");
738 let resource = builder.create_string("todos");
739 let source = DataSource::create(
740 &mut builder,
741 &DataSourceArgs {
742 endpoint: Some(endpoint),
743 resource: Some(resource),
744 ..Default::default()
745 },
746 );
747
748 let target = builder.create_string("todo-list");
749 let optimistic = OptimisticConfig::create(
750 &mut builder,
751 &OptimisticConfigArgs {
752 strategy: OptimisticStrategy::MirrorInput,
753 target_data_node_id: Some(target),
754 ..Default::default()
755 },
756 );
757
758 let invalidate = builder.create_string("todo-list");
759 let invalidates = builder.create_vector(&[invalidate]);
760
761 let node_id = builder.create_string("create-todo");
762 let action = ActionNode::create(
763 &mut builder,
764 &ActionNodeArgs {
765 node_id: Some(node_id),
766 source: Some(source),
767 method: HttpMethod::POST,
768 optimistic: Some(optimistic),
769 invalidates: Some(invalidates),
770 csrf_protected: true,
771 ..Default::default()
772 },
773 );
774
775 let child = ChildNode::create(
776 &mut builder,
777 &ChildNodeArgs {
778 value_type: ChildUnion::ActionNode,
779 value: Some(action.as_union_value()),
780 },
781 );
782 let children = builder.create_vector(&[child]);
783
784 let root_id = builder.create_string("root");
785 let root = ViewRoot::create(
786 &mut builder,
787 &ViewRootArgs {
788 node_id: Some(root_id),
789 children: Some(children),
790 ..Default::default()
791 },
792 );
793
794 let doc = VoceDocument::create(
795 &mut builder,
796 &VoceDocumentArgs {
797 root: Some(root),
798 ..Default::default()
799 },
800 );
801
802 builder.finish(doc, Some("VOCE"));
803 let buf = builder.finished_data();
804
805 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
806 let action = doc
807 .root()
808 .children()
809 .unwrap()
810 .get(0)
811 .value_as_action_node()
812 .unwrap();
813
814 assert_eq!(action.node_id(), "create-todo");
815 assert_eq!(action.method(), HttpMethod::POST);
816 assert!(action.csrf_protected());
817
818 let opt = action.optimistic().unwrap();
819 assert_eq!(opt.strategy(), OptimisticStrategy::MirrorInput);
820 assert_eq!(opt.target_data_node_id(), Some("todo-list"));
821 }
822
823 #[test]
824 fn form_node_with_fields_and_validation() {
825 let mut builder = FlatBufferBuilder::new();
826
827 let field_name = builder.create_string("email");
829 let field_label = builder.create_string("Email address");
830 let placeholder = builder.create_string("you@example.com");
831
832 let req_msg = builder.create_string("Email is required");
833 let required = ValidationRule::create(
834 &mut builder,
835 &ValidationRuleArgs {
836 rule_type: ValidationType::Required,
837 message: Some(req_msg),
838 ..Default::default()
839 },
840 );
841
842 let email_msg = builder.create_string("Must be a valid email");
843 let email_rule = ValidationRule::create(
844 &mut builder,
845 &ValidationRuleArgs {
846 rule_type: ValidationType::Email,
847 message: Some(email_msg),
848 ..Default::default()
849 },
850 );
851
852 let validations = builder.create_vector(&[required, email_rule]);
853
854 let email_field = FormField::create(
855 &mut builder,
856 &FormFieldArgs {
857 name: Some(field_name),
858 field_type: FormFieldType::Email,
859 label: Some(field_label),
860 placeholder: Some(placeholder),
861 validations: Some(validations),
862 autocomplete: AutocompleteHint::Email,
863 ..Default::default()
864 },
865 );
866
867 let fields = builder.create_vector(&[email_field]);
868
869 let action_id = builder.create_string("submit-contact");
871 let submission = FormSubmission::create(
872 &mut builder,
873 &FormSubmissionArgs {
874 action_node_id: Some(action_id),
875 encoding: FormEncoding::Json,
876 progressive: true,
877 ..Default::default()
878 },
879 );
880
881 let node_id = builder.create_string("contact-form");
882 let form = FormNode::create(
883 &mut builder,
884 &FormNodeArgs {
885 node_id: Some(node_id),
886 fields: Some(fields),
887 validation_mode: ValidationMode::OnBlurThenChange,
888 submission: Some(submission),
889 ..Default::default()
890 },
891 );
892
893 let child = ChildNode::create(
894 &mut builder,
895 &ChildNodeArgs {
896 value_type: ChildUnion::FormNode,
897 value: Some(form.as_union_value()),
898 },
899 );
900 let children = builder.create_vector(&[child]);
901
902 let root_id = builder.create_string("root");
903 let root = ViewRoot::create(
904 &mut builder,
905 &ViewRootArgs {
906 node_id: Some(root_id),
907 children: Some(children),
908 ..Default::default()
909 },
910 );
911
912 let doc = VoceDocument::create(
913 &mut builder,
914 &VoceDocumentArgs {
915 root: Some(root),
916 ..Default::default()
917 },
918 );
919
920 builder.finish(doc, Some("VOCE"));
921 let buf = builder.finished_data();
922
923 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
924 let form = doc
925 .root()
926 .children()
927 .unwrap()
928 .get(0)
929 .value_as_form_node()
930 .unwrap();
931
932 assert_eq!(form.node_id(), "contact-form");
933 assert_eq!(form.validation_mode(), ValidationMode::OnBlurThenChange);
934
935 let fields = form.fields();
936 assert_eq!(fields.len(), 1);
937
938 let email = fields.get(0);
939 assert_eq!(email.name(), "email");
940 assert_eq!(email.field_type(), FormFieldType::Email);
941 assert_eq!(email.label(), "Email address");
942 assert_eq!(email.autocomplete(), AutocompleteHint::Email);
943
944 let validations = email.validations().unwrap();
945 assert_eq!(validations.len(), 2);
946 assert_eq!(validations.get(0).rule_type(), ValidationType::Required);
947 assert_eq!(validations.get(1).rule_type(), ValidationType::Email);
948
949 let sub = form.submission();
950 assert_eq!(sub.action_node_id(), "submit-contact");
951 assert!(sub.progressive());
952 }
953
954 #[test]
955 fn document_with_i18n_config() {
956 let mut builder = FlatBufferBuilder::new();
957
958 let default_locale = builder.create_string("en-US");
959 let fr = builder.create_string("fr-FR");
960 let ar = builder.create_string("ar-SA");
961 let locales = builder.create_vector(&[default_locale, fr, ar]);
962
963 let default_locale2 = builder.create_string("en-US");
965 let mode = builder.create_string("static");
966
967 let i18n = I18nConfig::create(
968 &mut builder,
969 &I18nConfigArgs {
970 default_locale: Some(default_locale2),
971 supported_locales: Some(locales),
972 mode: Some(mode),
973 },
974 );
975
976 let root_id = builder.create_string("root");
977 let root = ViewRoot::create(
978 &mut builder,
979 &ViewRootArgs {
980 node_id: Some(root_id),
981 ..Default::default()
982 },
983 );
984
985 let doc = VoceDocument::create(
986 &mut builder,
987 &VoceDocumentArgs {
988 root: Some(root),
989 i18n: Some(i18n),
990 ..Default::default()
991 },
992 );
993
994 builder.finish(doc, Some("VOCE"));
995 let buf = builder.finished_data();
996
997 let doc = flatbuffers::root::<VoceDocument>(buf).unwrap();
998 let i18n = doc.i18n().unwrap();
999 assert_eq!(i18n.default_locale(), "en-US");
1000 assert_eq!(i18n.supported_locales().len(), 3);
1001 assert_eq!(i18n.mode(), Some("static"));
1002 }
1003}