Skip to main content

batuta/viz/
tree.rs

1//! Visualization Frameworks Tree
2//!
3//! Hierarchical view of Python visualization frameworks and their PAIML replacements.
4
5use serde::{Deserialize, Serialize};
6
7// ============================================================================
8// Core Types
9// ============================================================================
10
11/// Visualization framework identifier
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum Framework {
14    Gradio,
15    Streamlit,
16    Panel,
17    Dash,
18}
19
20impl Framework {
21    /// Get framework display name
22    pub fn name(&self) -> &'static str {
23        match self {
24            Framework::Gradio => "Gradio",
25            Framework::Streamlit => "Streamlit",
26            Framework::Panel => "Panel",
27            Framework::Dash => "Dash",
28        }
29    }
30
31    /// Get PAIML replacement
32    pub fn replacement(&self) -> &'static str {
33        match self {
34            Framework::Gradio => "Presentar",
35            Framework::Streamlit => "Presentar",
36            Framework::Panel => "Trueno-Viz",
37            Framework::Dash => "Presentar + Trueno-Viz",
38        }
39    }
40
41    /// Get all frameworks
42    pub fn all() -> Vec<Self> {
43        vec![Framework::Gradio, Framework::Streamlit, Framework::Panel, Framework::Dash]
44    }
45}
46
47impl std::fmt::Display for Framework {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "{}", self.name())
50    }
51}
52
53/// Integration type for component mappings
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
55pub enum IntegrationType {
56    /// PAIML fully replaces Python component
57    Replaces,
58    /// Depyler transpiles Python to Rust
59    Transpiles,
60    /// Can consume output format (PNG, SVG, etc.)
61    Compatible,
62}
63
64impl IntegrationType {
65    /// Get short code for display
66    pub fn code(&self) -> &'static str {
67        match self {
68            IntegrationType::Replaces => "REP",
69            IntegrationType::Transpiles => "TRN",
70            IntegrationType::Compatible => "CMP",
71        }
72    }
73}
74
75impl std::fmt::Display for IntegrationType {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        write!(f, "{}", self.code())
78    }
79}
80
81// ============================================================================
82// Tree Structures
83// ============================================================================
84
85/// A component within a framework category
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct FrameworkComponent {
88    pub name: String,
89    pub description: String,
90    pub replacement: String,
91    pub sub_components: Vec<String>,
92}
93
94impl FrameworkComponent {
95    pub fn new(
96        name: impl Into<String>,
97        description: impl Into<String>,
98        replacement: impl Into<String>,
99    ) -> Self {
100        Self {
101            name: name.into(),
102            description: description.into(),
103            replacement: replacement.into(),
104            sub_components: Vec::new(),
105        }
106    }
107
108    pub fn with_sub(mut self, sub: impl Into<String>) -> Self {
109        self.sub_components.push(sub.into());
110        self
111    }
112}
113
114/// A category within a framework
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct FrameworkCategory {
117    pub name: String,
118    pub components: Vec<FrameworkComponent>,
119}
120
121impl FrameworkCategory {
122    pub fn new(name: impl Into<String>) -> Self {
123        Self { name: name.into(), components: Vec::new() }
124    }
125
126    pub fn with_component(mut self, component: FrameworkComponent) -> Self {
127        self.components.push(component);
128        self
129    }
130}
131
132/// Complete framework tree
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct VizTree {
135    pub framework: Framework,
136    pub replacement: String,
137    pub categories: Vec<FrameworkCategory>,
138}
139
140impl VizTree {
141    pub fn new(framework: Framework) -> Self {
142        Self { replacement: framework.replacement().to_string(), framework, categories: Vec::new() }
143    }
144
145    pub fn add_category(mut self, category: FrameworkCategory) -> Self {
146        self.categories.push(category);
147        self
148    }
149}
150
151/// Integration mapping between Python and PAIML
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct IntegrationMapping {
154    pub python_component: String,
155    pub paiml_component: String,
156    pub integration_type: IntegrationType,
157    pub category: String,
158}
159
160impl IntegrationMapping {
161    pub fn new(
162        python: impl Into<String>,
163        paiml: impl Into<String>,
164        int_type: IntegrationType,
165        category: impl Into<String>,
166    ) -> Self {
167        Self {
168            python_component: python.into(),
169            paiml_component: paiml.into(),
170            integration_type: int_type,
171            category: category.into(),
172        }
173    }
174}
175
176// ============================================================================
177// Tree Builders
178// ============================================================================
179
180/// Build Gradio framework tree
181pub fn build_gradio_tree() -> VizTree {
182    VizTree::new(Framework::Gradio)
183        .add_category(
184            FrameworkCategory::new("Interface").with_component(
185                FrameworkComponent::new("Interface", "Quick demo builder", "Presentar::QuickApp")
186                    .with_sub("Inputs")
187                    .with_sub("Outputs")
188                    .with_sub("Examples"),
189            ),
190        )
191        .add_category(
192            FrameworkCategory::new("Blocks").with_component(
193                FrameworkComponent::new("Blocks", "Custom layouts", "Presentar::Layout")
194                    .with_sub("Layout")
195                    .with_sub("Events")
196                    .with_sub("State"),
197            ),
198        )
199        .add_category(
200            FrameworkCategory::new("Components")
201                .with_component(FrameworkComponent::new(
202                    "Image",
203                    "Image display/upload",
204                    "Trueno-Viz::ImageView",
205                ))
206                .with_component(FrameworkComponent::new(
207                    "Audio",
208                    "Audio player/recorder",
209                    "Presentar::AudioPlayer",
210                ))
211                .with_component(FrameworkComponent::new(
212                    "Video",
213                    "Video player",
214                    "Presentar::VideoPlayer",
215                ))
216                .with_component(FrameworkComponent::new(
217                    "Chatbot",
218                    "Chat interface",
219                    "Realizar + Presentar",
220                ))
221                .with_component(FrameworkComponent::new(
222                    "DataFrame",
223                    "Data table",
224                    "Trueno-Viz::DataGrid",
225                ))
226                .with_component(FrameworkComponent::new(
227                    "Plot",
228                    "Chart display",
229                    "Trueno-Viz::Chart",
230                )),
231        )
232        .add_category(
233            FrameworkCategory::new("Deployment").with_component(
234                FrameworkComponent::new("Deployment", "Hosting options", "Batuta deploy")
235                    .with_sub("HuggingFace Spaces")
236                    .with_sub("Gradio Cloud")
237                    .with_sub("Self-hosted"),
238            ),
239        )
240}
241
242/// Build Streamlit framework tree
243pub fn build_streamlit_tree() -> VizTree {
244    VizTree::new(Framework::Streamlit)
245        .add_category(
246            FrameworkCategory::new("Widgets")
247                .with_component(
248                    FrameworkComponent::new("Input", "User input widgets", "Presentar::Widgets")
249                        .with_sub("text_input")
250                        .with_sub("number_input")
251                        .with_sub("slider")
252                        .with_sub("selectbox"),
253                )
254                .with_component(
255                    FrameworkComponent::new("Display", "Output widgets", "Presentar + Trueno-Viz")
256                        .with_sub("write")
257                        .with_sub("dataframe")
258                        .with_sub("chart"),
259                ),
260        )
261        .add_category(
262            FrameworkCategory::new("Layout").with_component(
263                FrameworkComponent::new("Layout", "Page structure", "Presentar::Layout")
264                    .with_sub("columns")
265                    .with_sub("tabs")
266                    .with_sub("sidebar")
267                    .with_sub("expander"),
268            ),
269        )
270        .add_category(
271            FrameworkCategory::new("Caching")
272                .with_component(FrameworkComponent::new(
273                    "@st.cache_data",
274                    "Data caching",
275                    "Trueno::TensorCache",
276                ))
277                .with_component(FrameworkComponent::new(
278                    "@st.cache_resource",
279                    "Resource caching",
280                    "Presentar::ResourceCache",
281                ))
282                .with_component(FrameworkComponent::new(
283                    "session_state",
284                    "Session state",
285                    "Presentar::State",
286                )),
287        )
288        .add_category(
289            FrameworkCategory::new("Deployment").with_component(
290                FrameworkComponent::new("Deployment", "Hosting options", "Batuta deploy")
291                    .with_sub("Streamlit Cloud")
292                    .with_sub("Community Cloud")
293                    .with_sub("Self-hosted"),
294            ),
295        )
296}
297
298/// Build Panel/HoloViz framework tree
299pub fn build_panel_tree() -> VizTree {
300    VizTree::new(Framework::Panel)
301        .add_category(
302            FrameworkCategory::new("Panes").with_component(
303                FrameworkComponent::new("Panes", "Visualization containers", "Trueno-Viz::Chart")
304                    .with_sub("Matplotlib")
305                    .with_sub("Plotly")
306                    .with_sub("HoloViews")
307                    .with_sub("Bokeh"),
308            ),
309        )
310        .add_category(
311            FrameworkCategory::new("HoloViz Stack")
312                .with_component(FrameworkComponent::new(
313                    "HoloViews",
314                    "Declarative viz",
315                    "Trueno-Viz::ReactiveChart",
316                ))
317                .with_component(FrameworkComponent::new(
318                    "Datashader",
319                    "Big data raster",
320                    "Trueno-Viz::GPURaster",
321                ))
322                .with_component(FrameworkComponent::new(
323                    "hvPlot",
324                    "High-level plotting",
325                    "Trueno-Viz::Plot",
326                ))
327                .with_component(FrameworkComponent::new(
328                    "Param",
329                    "Parameters",
330                    "Presentar::Params",
331                )),
332        )
333        .add_category(
334            FrameworkCategory::new("Layout").with_component(
335                FrameworkComponent::new("Layout", "Dashboard structure", "Presentar::Layout")
336                    .with_sub("Row")
337                    .with_sub("Column")
338                    .with_sub("Tabs")
339                    .with_sub("GridSpec"),
340            ),
341        )
342}
343
344/// Build Dash framework tree
345pub fn build_dash_tree() -> VizTree {
346    VizTree::new(Framework::Dash)
347        .add_category(
348            FrameworkCategory::new("Core")
349                .with_component(FrameworkComponent::new(
350                    "dash.Dash",
351                    "App container",
352                    "Presentar::App",
353                ))
354                .with_component(FrameworkComponent::new(
355                    "html.*",
356                    "HTML components",
357                    "Presentar::Html",
358                ))
359                .with_component(FrameworkComponent::new(
360                    "@callback",
361                    "Event handlers",
362                    "Presentar::on_event",
363                ))
364                .with_component(FrameworkComponent::new(
365                    "State",
366                    "State management",
367                    "Presentar::State",
368                )),
369        )
370        .add_category(
371            FrameworkCategory::new("Components")
372                .with_component(FrameworkComponent::new(
373                    "dcc.Graph",
374                    "Plotly charts",
375                    "Trueno-Viz::Chart",
376                ))
377                .with_component(FrameworkComponent::new(
378                    "dcc.Input",
379                    "Text input",
380                    "Presentar::TextInput",
381                ))
382                .with_component(FrameworkComponent::new(
383                    "dash_table",
384                    "Data tables",
385                    "Trueno-Viz::DataGrid",
386                ))
387                .with_component(FrameworkComponent::new(
388                    "dcc.Store",
389                    "Client storage",
390                    "Presentar::Store",
391                )),
392        )
393        .add_category(
394            FrameworkCategory::new("Plotly")
395                .with_component(
396                    FrameworkComponent::new("plotly.express", "Quick charts", "Trueno-Viz::Charts")
397                        .with_sub("line")
398                        .with_sub("scatter")
399                        .with_sub("bar")
400                        .with_sub("histogram"),
401                )
402                .with_component(FrameworkComponent::new(
403                    "plotly.graph_objects",
404                    "Custom charts",
405                    "Trueno-Viz::Figure",
406                )),
407        )
408        .add_category(
409            FrameworkCategory::new("Enterprise").with_component(
410                FrameworkComponent::new("Enterprise", "Dash Enterprise", "Batuta deploy")
411                    .with_sub("Auth")
412                    .with_sub("Deployment")
413                    .with_sub("Snapshots"),
414            ),
415        )
416}
417
418// ============================================================================
419// Integration Mappings
420// ============================================================================
421
422/// Build complete integration mappings
423pub fn build_integration_mappings() -> Vec<IntegrationMapping> {
424    vec![
425        // UI Frameworks
426        IntegrationMapping::new(
427            "gr.Interface",
428            "Presentar::QuickApp",
429            IntegrationType::Replaces,
430            "UI FRAMEWORKS",
431        ),
432        IntegrationMapping::new(
433            "gr.Blocks",
434            "Presentar::Layout",
435            IntegrationType::Replaces,
436            "UI FRAMEWORKS",
437        ),
438        IntegrationMapping::new(
439            "dash.Dash",
440            "Presentar::App",
441            IntegrationType::Replaces,
442            "UI FRAMEWORKS",
443        ),
444        IntegrationMapping::new(
445            "st.columns/sidebar",
446            "Presentar::Layout",
447            IntegrationType::Replaces,
448            "UI FRAMEWORKS",
449        ),
450        // Visualization
451        IntegrationMapping::new(
452            "dcc.Graph",
453            "Trueno-Viz::Chart",
454            IntegrationType::Replaces,
455            "VISUALIZATION",
456        ),
457        IntegrationMapping::new(
458            "st.plotly_chart",
459            "Trueno-Viz::Chart",
460            IntegrationType::Replaces,
461            "VISUALIZATION",
462        ),
463        IntegrationMapping::new(
464            "st.dataframe",
465            "Trueno-Viz::DataGrid",
466            IntegrationType::Replaces,
467            "VISUALIZATION",
468        ),
469        IntegrationMapping::new(
470            "dash_table",
471            "Trueno-Viz::DataGrid",
472            IntegrationType::Replaces,
473            "VISUALIZATION",
474        ),
475        IntegrationMapping::new(
476            "datashader",
477            "Trueno-Viz::GPURaster",
478            IntegrationType::Replaces,
479            "VISUALIZATION",
480        ),
481        IntegrationMapping::new(
482            "matplotlib/plotly/bokeh",
483            "Trueno-Viz::Plot",
484            IntegrationType::Replaces,
485            "VISUALIZATION",
486        ),
487        // Components
488        IntegrationMapping::new(
489            "st.text_input",
490            "Presentar::TextInput",
491            IntegrationType::Replaces,
492            "COMPONENTS",
493        ),
494        IntegrationMapping::new(
495            "st.slider",
496            "Presentar::Slider",
497            IntegrationType::Replaces,
498            "COMPONENTS",
499        ),
500        IntegrationMapping::new(
501            "st.selectbox",
502            "Presentar::Select",
503            IntegrationType::Replaces,
504            "COMPONENTS",
505        ),
506        IntegrationMapping::new(
507            "st.button",
508            "Presentar::Button",
509            IntegrationType::Replaces,
510            "COMPONENTS",
511        ),
512        IntegrationMapping::new(
513            "gr.Image",
514            "Trueno-Viz::ImageView",
515            IntegrationType::Replaces,
516            "COMPONENTS",
517        ),
518        // State & Caching
519        IntegrationMapping::new(
520            "st.session_state",
521            "Presentar::State",
522            IntegrationType::Replaces,
523            "STATE & CACHING",
524        ),
525        IntegrationMapping::new(
526            "@st.cache_data",
527            "Trueno::TensorCache",
528            IntegrationType::Replaces,
529            "STATE & CACHING",
530        ),
531        IntegrationMapping::new(
532            "@callback",
533            "Presentar::on_event",
534            IntegrationType::Replaces,
535            "STATE & CACHING",
536        ),
537        // Deployment
538        IntegrationMapping::new(
539            "HuggingFace Spaces",
540            "Batuta deploy",
541            IntegrationType::Replaces,
542            "DEPLOYMENT",
543        ),
544        IntegrationMapping::new(
545            "Streamlit Cloud",
546            "Batuta deploy",
547            IntegrationType::Replaces,
548            "DEPLOYMENT",
549        ),
550        IntegrationMapping::new(
551            "Dash Enterprise",
552            "Batuta deploy",
553            IntegrationType::Replaces,
554            "DEPLOYMENT",
555        ),
556    ]
557}
558
559// ============================================================================
560// Formatters
561// ============================================================================
562
563/// Format a single framework tree as ASCII
564pub fn format_framework_tree(tree: &VizTree) -> String {
565    let mut output = String::new();
566    output.push_str(&format!(
567        "{} (Python) → {} (Rust)\n",
568        tree.framework.name().to_uppercase(),
569        tree.replacement
570    ));
571
572    let cat_count = tree.categories.len();
573    for (i, category) in tree.categories.iter().enumerate() {
574        let is_last_cat = i == cat_count - 1;
575        let cat_prefix = if is_last_cat { "└──" } else { "├──" };
576        let cat_cont = if is_last_cat { "    " } else { "│   " };
577
578        output.push_str(&format!("{} {}\n", cat_prefix, category.name));
579
580        let comp_count = category.components.len();
581        for (j, component) in category.components.iter().enumerate() {
582            let is_last_comp = j == comp_count - 1;
583            let comp_prefix = if is_last_comp { "└──" } else { "├──" };
584            let comp_cont = if is_last_comp { "    " } else { "│   " };
585
586            output.push_str(&format!(
587                "{}{} {} → {}\n",
588                cat_cont, comp_prefix, component.name, component.replacement
589            ));
590
591            // Sub-components
592            let sub_count = component.sub_components.len();
593            for (k, sub) in component.sub_components.iter().enumerate() {
594                let is_last_sub = k == sub_count - 1;
595                let sub_prefix = if is_last_sub { "└──" } else { "├──" };
596                output.push_str(&format!("{}{}{} {}\n", cat_cont, comp_cont, sub_prefix, sub));
597            }
598        }
599    }
600
601    output
602}
603
604/// Format all frameworks as ASCII tree
605pub fn format_all_frameworks() -> String {
606    let mut output = String::new();
607    output.push_str("VISUALIZATION FRAMEWORKS ECOSYSTEM\n");
608    output.push_str("==================================\n\n");
609
610    let trees =
611        vec![build_gradio_tree(), build_streamlit_tree(), build_panel_tree(), build_dash_tree()];
612
613    for tree in &trees {
614        output.push_str(&format_framework_tree(tree));
615        output.push('\n');
616    }
617
618    output.push_str(&format!(
619        "Summary: {} Python frameworks replaced by 2 Rust libraries (Presentar, Trueno-Viz)\n",
620        trees.len()
621    ));
622
623    output
624}
625
626/// Format integration mappings as ASCII
627pub fn format_integration_mappings() -> String {
628    let mut output = String::new();
629    output.push_str("PAIML REPLACEMENTS FOR PYTHON VIZ\n");
630    output.push_str("=================================\n\n");
631
632    let mappings = build_integration_mappings();
633    let mut current_category = String::new();
634
635    for mapping in &mappings {
636        if mapping.category != current_category {
637            if !current_category.is_empty() {
638                output.push('\n');
639            }
640            output.push_str(&format!("{}\n", mapping.category));
641            current_category = mapping.category.clone();
642        }
643
644        output.push_str(&format!(
645            "├── [{}] {} ← {}\n",
646            mapping.integration_type.code(),
647            mapping.paiml_component,
648            mapping.python_component
649        ));
650    }
651
652    output.push_str("\nLegend: [REP]=Replaces (Python eliminated)\n");
653    output.push_str("\nSummary: ");
654
655    let rep_count =
656        mappings.iter().filter(|m| m.integration_type == IntegrationType::Replaces).count();
657
658    output.push_str(&format!(
659        "{} Python components replaced by sovereign Rust alternatives\n",
660        rep_count
661    ));
662    output.push_str("         Zero Python dependencies in production\n");
663
664    output
665}
666
667// ============================================================================
668// Tests
669// ============================================================================
670
671#[cfg(test)]
672#[allow(non_snake_case)]
673mod tests {
674    use super::*;
675
676    // ========================================================================
677    // VIZ-TREE-001: Framework Tests
678    // ========================================================================
679
680    #[test]
681    fn test_VIZ_TREE_001_framework_names() {
682        assert_eq!(Framework::Gradio.name(), "Gradio");
683        assert_eq!(Framework::Streamlit.name(), "Streamlit");
684        assert_eq!(Framework::Panel.name(), "Panel");
685        assert_eq!(Framework::Dash.name(), "Dash");
686    }
687
688    #[test]
689    fn test_VIZ_TREE_001_framework_replacements() {
690        assert_eq!(Framework::Gradio.replacement(), "Presentar");
691        assert_eq!(Framework::Streamlit.replacement(), "Presentar");
692        assert_eq!(Framework::Panel.replacement(), "Trueno-Viz");
693        assert_eq!(Framework::Dash.replacement(), "Presentar + Trueno-Viz");
694    }
695
696    #[test]
697    fn test_VIZ_TREE_001_framework_all() {
698        let all = Framework::all();
699        assert_eq!(all.len(), 4);
700    }
701
702    #[test]
703    fn test_VIZ_TREE_001_framework_display() {
704        assert_eq!(format!("{}", Framework::Gradio), "Gradio");
705    }
706
707    // ========================================================================
708    // VIZ-TREE-002: Integration Type Tests
709    // ========================================================================
710
711    #[test]
712    fn test_VIZ_TREE_002_integration_codes() {
713        assert_eq!(IntegrationType::Replaces.code(), "REP");
714        assert_eq!(IntegrationType::Transpiles.code(), "TRN");
715        assert_eq!(IntegrationType::Compatible.code(), "CMP");
716    }
717
718    #[test]
719    fn test_VIZ_TREE_002_integration_display() {
720        assert_eq!(format!("{}", IntegrationType::Replaces), "REP");
721    }
722
723    // ========================================================================
724    // VIZ-TREE-003: Tree Builder Tests
725    // ========================================================================
726
727    #[test]
728    fn test_VIZ_TREE_003_gradio_tree() {
729        let tree = build_gradio_tree();
730        assert_eq!(tree.framework, Framework::Gradio);
731        assert_eq!(tree.replacement, "Presentar");
732        assert!(!tree.categories.is_empty());
733    }
734
735    #[test]
736    fn test_VIZ_TREE_003_streamlit_tree() {
737        let tree = build_streamlit_tree();
738        assert_eq!(tree.framework, Framework::Streamlit);
739        assert_eq!(tree.replacement, "Presentar");
740        assert!(!tree.categories.is_empty());
741    }
742
743    #[test]
744    fn test_VIZ_TREE_003_panel_tree() {
745        let tree = build_panel_tree();
746        assert_eq!(tree.framework, Framework::Panel);
747        assert_eq!(tree.replacement, "Trueno-Viz");
748        assert!(!tree.categories.is_empty());
749    }
750
751    #[test]
752    fn test_VIZ_TREE_003_dash_tree() {
753        let tree = build_dash_tree();
754        assert_eq!(tree.framework, Framework::Dash);
755        assert_eq!(tree.replacement, "Presentar + Trueno-Viz");
756        assert!(!tree.categories.is_empty());
757    }
758
759    // ========================================================================
760    // VIZ-TREE-004: Integration Mapping Tests
761    // ========================================================================
762
763    #[test]
764    fn test_VIZ_TREE_004_mappings_exist() {
765        let mappings = build_integration_mappings();
766        assert!(!mappings.is_empty());
767        assert!(mappings.len() >= 20);
768    }
769
770    #[test]
771    fn test_VIZ_TREE_004_all_replaces() {
772        let mappings = build_integration_mappings();
773        // All mappings should be Replaces type (no Python allowed)
774        for mapping in &mappings {
775            assert_eq!(
776                mapping.integration_type,
777                IntegrationType::Replaces,
778                "Mapping {} should be Replaces",
779                mapping.python_component
780            );
781        }
782    }
783
784    #[test]
785    fn test_VIZ_TREE_004_mapping_categories() {
786        let mappings = build_integration_mappings();
787        let categories: std::collections::HashSet<_> =
788            mappings.iter().map(|m| m.category.as_str()).collect();
789        assert!(categories.contains("UI FRAMEWORKS"));
790        assert!(categories.contains("VISUALIZATION"));
791        assert!(categories.contains("COMPONENTS"));
792        assert!(categories.contains("DEPLOYMENT"));
793    }
794
795    // ========================================================================
796    // VIZ-TREE-005: Formatter Tests
797    // ========================================================================
798
799    #[test]
800    fn test_VIZ_TREE_005_format_framework_tree() {
801        let tree = build_gradio_tree();
802        let output = format_framework_tree(&tree);
803        assert!(output.contains("GRADIO"));
804        assert!(output.contains("Presentar"));
805        assert!(output.contains("Interface"));
806    }
807
808    #[test]
809    fn test_VIZ_TREE_005_format_all_frameworks() {
810        let output = format_all_frameworks();
811        assert!(output.contains("VISUALIZATION FRAMEWORKS ECOSYSTEM"));
812        assert!(output.contains("GRADIO"));
813        assert!(output.contains("STREAMLIT"));
814        assert!(output.contains("PANEL"));
815        assert!(output.contains("DASH"));
816        assert!(output.contains("Summary:"));
817    }
818
819    #[test]
820    fn test_VIZ_TREE_005_format_integration_mappings() {
821        let output = format_integration_mappings();
822        assert!(output.contains("PAIML REPLACEMENTS"));
823        assert!(output.contains("[REP]"));
824        assert!(output.contains("Presentar"));
825        assert!(output.contains("Trueno-Viz"));
826        assert!(output.contains("Legend:"));
827    }
828
829    // ========================================================================
830    // VIZ-TREE-006: Component Tests
831    // ========================================================================
832
833    #[test]
834    fn test_VIZ_TREE_006_framework_component() {
835        let component = FrameworkComponent::new("Test", "Description", "Replacement")
836            .with_sub("Sub1")
837            .with_sub("Sub2");
838        assert_eq!(component.name, "Test");
839        assert_eq!(component.sub_components.len(), 2);
840    }
841
842    #[test]
843    fn test_VIZ_TREE_006_framework_category() {
844        let category = FrameworkCategory::new("Test Category")
845            .with_component(FrameworkComponent::new("Comp1", "Desc1", "Rep1"));
846        assert_eq!(category.name, "Test Category");
847        assert_eq!(category.components.len(), 1);
848    }
849
850    #[test]
851    fn test_VIZ_TREE_006_integration_mapping() {
852        let mapping =
853            IntegrationMapping::new("Python", "Rust", IntegrationType::Replaces, "Category");
854        assert_eq!(mapping.python_component, "Python");
855        assert_eq!(mapping.paiml_component, "Rust");
856    }
857
858    // ========================================================================
859    // VIZ-TREE-007: Additional Coverage Tests
860    // ========================================================================
861
862    #[test]
863    fn test_VIZ_TREE_007_framework_equality() {
864        assert_eq!(Framework::Gradio, Framework::Gradio);
865        assert_ne!(Framework::Gradio, Framework::Streamlit);
866    }
867
868    #[test]
869    fn test_VIZ_TREE_007_framework_clone() {
870        let f1 = Framework::Panel;
871        let f2 = f1;
872        assert_eq!(f1, f2);
873    }
874
875    #[test]
876    fn test_VIZ_TREE_007_integration_type_equality() {
877        assert_eq!(IntegrationType::Replaces, IntegrationType::Replaces);
878        assert_ne!(IntegrationType::Replaces, IntegrationType::Transpiles);
879    }
880
881    #[test]
882    fn test_VIZ_TREE_007_integration_type_clone() {
883        let t1 = IntegrationType::Compatible;
884        let t2 = t1;
885        assert_eq!(t1, t2);
886    }
887
888    #[test]
889    fn test_VIZ_TREE_007_framework_serialization() {
890        let framework = Framework::Dash;
891        let json = serde_json::to_string(&framework).expect("json serialize failed");
892        let parsed: Framework = serde_json::from_str(&json).expect("json deserialize failed");
893        assert_eq!(framework, parsed);
894    }
895
896    #[test]
897    fn test_VIZ_TREE_007_integration_type_serialization() {
898        let integration = IntegrationType::Transpiles;
899        let json = serde_json::to_string(&integration).expect("json serialize failed");
900        let parsed: IntegrationType = serde_json::from_str(&json).expect("json deserialize failed");
901        assert_eq!(integration, parsed);
902    }
903
904    #[test]
905    fn test_VIZ_TREE_007_framework_component_serialization() {
906        let component = FrameworkComponent::new("Test", "Desc", "Rep");
907        let json = serde_json::to_string(&component).expect("json serialize failed");
908        let parsed: FrameworkComponent =
909            serde_json::from_str(&json).expect("json deserialize failed");
910        assert_eq!(component.name, parsed.name);
911    }
912
913    #[test]
914    fn test_VIZ_TREE_007_framework_tree_categories() {
915        let tree = build_gradio_tree();
916        // Gradio should have multiple categories
917        assert!(tree.categories.len() > 1);
918        // Each category should have components
919        for category in &tree.categories {
920            assert!(!category.components.is_empty());
921        }
922    }
923
924    #[test]
925    fn test_VIZ_TREE_007_framework_debug() {
926        let f = Framework::Gradio;
927        let debug = format!("{:?}", f);
928        assert!(debug.contains("Gradio"));
929    }
930
931    #[test]
932    fn test_VIZ_TREE_007_integration_type_debug() {
933        let t = IntegrationType::Replaces;
934        let debug = format!("{:?}", t);
935        assert!(debug.contains("Replaces"));
936    }
937}