more tests
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 11 Apr 2025 00:25:13 +0000 (17:25 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 11 Apr 2025 00:25:13 +0000 (17:25 -0700)
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/output/pivot/test.rs
rust/pspp/src/output/text.rs

index 5cbc71912610bf8e24e81d73dba76e2438cccb92..64e806b8ce98d005bdd009cc8cd5001f04d02729 100644 (file)
@@ -739,6 +739,17 @@ pub struct Look {
     pub n_orphan_lines: usize,
 }
 
+impl Look {
+    pub fn with_omit_empty(mut self, omit_empty: bool) -> Self {
+        self.omit_empty = omit_empty;
+        self
+    }
+    pub fn with_row_label_position(mut self, row_label_position: LabelPosition) -> Self {
+        self.row_label_position = row_label_position;
+        self
+    }
+}
+
 impl Default for Look {
     fn default() -> Self {
         Self {
@@ -1378,6 +1389,47 @@ pub struct PivotTable {
     pub cells: HashMap<usize, Value>,
 }
 
+impl PivotTable {
+    fn with_title(mut self, title: Value) -> Self {
+        self.title = Some(Box::new(title));
+        self.show_title = true;
+        self
+    }
+
+    fn with_caption(mut self, caption: Value) -> Self {
+        self.caption = Some(Box::new(caption));
+        self.show_caption = true;
+        self
+    }
+
+    fn with_show_title(mut self, show_title: bool) -> Self {
+        self.show_title = show_title;
+        self
+    }
+
+    fn with_show_caption(mut self, show_caption: bool) -> Self {
+        self.show_caption = show_caption;
+        self
+    }
+
+    fn with_layer(mut self, layer: &[usize]) -> Self {
+        debug_assert_eq!(layer.len(), self.current_layer.len());
+        if self.look.print_all_layers {
+            Arc::make_mut(&mut self.look).print_all_layers = false;
+        }
+        self.current_layer.clear();
+        self.current_layer.extend_from_slice(layer);
+        self
+    }
+
+    fn with_all_layers(mut self) -> Self {
+        if !self.look.print_all_layers {
+            Arc::make_mut(&mut self.look).print_all_layers = true;
+        }
+        self
+    }
+}
+
 impl Default for PivotTable {
     fn default() -> Self {
         Self {
@@ -1474,7 +1526,9 @@ impl PivotTable {
     ///
     /// - Otherwise, the iterator will just visit `self.current_layer`.
     pub fn layers(&self, print: bool) -> Box<dyn Iterator<Item = SmallVec<[usize; 4]>> + '_> {
+        dbg!(self.look.print_all_layers);
         if print && self.look.print_all_layers {
+            dbg!();
             Box::new(self.axis_values(Axis3::Z))
         } else {
             Box::new(once(SmallVec::from_slice(&self.current_layer)))
index 4a131c408f18625dc3a9246c322a31f0cb336fec..92318a2b74b6d9a53723acfa38470af7ebf5ce8d 100644 (file)
@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use crate::output::pivot::{Area, Color, Look, PivotTable};
+use crate::output::pivot::{Area, Color, LabelPosition, Look, PivotTable};
 
 use super::{Axis3, DimensionBuilder, GroupBuilder, PivotTableBuilder, Value};
 
@@ -27,7 +27,7 @@ fn d1(title: &str, axis: Axis3) -> PivotTable {
     for i in 0..3 {
         pt.insert(&[i], Value::new_integer(Some(i as f64)));
     }
-    pt.with_look(test_look()).build()
+    pt.with_look(Arc::new(test_look())).build()
 }
 
 #[test]
@@ -64,21 +64,21 @@ Rows
     );
 }
 
-fn test_look() -> Arc<Look> {
+fn test_look() -> Look {
     let mut look = Look::default();
     look.areas[Area::Title].cell_style.horz_align = Some(super::HorzAlign::Left);
     look.areas[Area::Title].font_style.bold = false;
-    Arc::new(look)
+    look
 }
 
-fn d2(title: &str, axes: [Axis3; 2], dimension_labels: bool) -> PivotTable {
-    let mut a = GroupBuilder::new(Value::new_text("a")).with_show_label(dimension_labels);
+fn d2(title: &str, axes: [Axis3; 2], dimension_labels: Option<LabelPosition>) -> PivotTable {
+    let mut a = GroupBuilder::new(Value::new_text("a")).with_show_label(dimension_labels.is_some());
     for name in ["a1", "a2", "a3"] {
         a.push(Value::new_text(name));
     }
     let d1 = DimensionBuilder::new(axes[0], a);
 
-    let mut b = GroupBuilder::new(Value::new_text("b")).with_show_label(dimension_labels);
+    let mut b = GroupBuilder::new(Value::new_text("b")).with_show_label(dimension_labels.is_some());
     for name in ["b1", "b2", "b3"] {
         b.push(Value::new_text(name));
     }
@@ -92,21 +92,33 @@ fn d2(title: &str, axes: [Axis3; 2], dimension_labels: bool) -> PivotTable {
             i += 1;
         }
     }
-    pt.with_look(test_look()).build()
+    let look = match dimension_labels {
+        Some(position) => test_look().with_row_label_position(position),
+        None => test_look(),
+    };
+    pt.with_look(Arc::new(look)).build()
 }
 
 #[track_caller]
-fn assert_rendering(pivot_table: PivotTable, expected: &str) {
+fn assert_rendering(pivot_table: &PivotTable, expected: &str) {
     let actual = pivot_table.to_string();
     if actual != expected {
-        panic!("Unexpected pivot table rendering.\nActual:\n{actual}\nExpected:\n{expected}");
+        eprintln!("Unexpected pivot table rendering:\n--- expected\n+++ actual");
+        for result in diff::lines(expected, &actual) {
+            match result {
+                diff::Result::Left(line) => eprintln!("-{line:?}"),
+                diff::Result::Both(line, _) => eprintln!(" {line:?}"),
+                diff::Result::Right(line) => eprintln!("+{line:?}"),
+            }
+        }
+        panic!();
     }
 }
 
 #[test]
 fn d2_cc() {
     assert_rendering(
-        d2("Columns", [Axis3::X, Axis3::X], false),
+        &d2("Columns", [Axis3::X, Axis3::X], None),
         "\
 Columns
 ╭────────┬────────┬────────╮
@@ -123,7 +135,7 @@ Columns
 #[test]
 fn d2_cc_with_dim_labels() {
     assert_rendering(
-        d2("Columns", [Axis3::X, Axis3::X], true),
+        &d2("Columns", [Axis3::X, Axis3::X], Some(LabelPosition::Corner)),
         "\
 Columns
 ╭──────────────────────────╮
@@ -144,7 +156,7 @@ Columns
 #[test]
 fn d2_rr() {
     assert_rendering(
-        d2("Rows", [Axis3::Y, Axis3::Y], false),
+        &d2("Rows", [Axis3::Y, Axis3::Y], None),
         "\
 Rows
 ╭─────┬─╮
@@ -165,9 +177,13 @@ Rows
 }
 
 #[test]
-fn d2_rr_with_dim_labels() {
+fn d2_rr_with_corner_dim_labels() {
     assert_rendering(
-        d2("Rows - Corner", [Axis3::Y, Axis3::Y], true),
+        &d2(
+            "Rows - Corner",
+            [Axis3::Y, Axis3::Y],
+            Some(LabelPosition::Corner),
+        ),
         "\
 Rows - Corner
 ╭─────┬─╮
@@ -189,10 +205,37 @@ Rows - Corner
     );
 }
 
+#[test]
+fn d2_rr_with_nested_dim_labels() {
+    assert_rendering(
+        &d2(
+            "Rows - Nested",
+            [Axis3::Y, Axis3::Y],
+            Some(LabelPosition::Nested),
+        ),
+        "\
+Rows - Nested
+╭─────────┬─╮
+│b b1 a a1│0│
+│       a2│1│
+│       a3│2│
+│ ╶───────┼─┤
+│  b2 a a1│3│
+│       a2│4│
+│       a3│5│
+│ ╶───────┼─┤
+│  b3 a a1│6│
+│       a2│7│
+│       a3│8│
+╰─────────┴─╯
+",
+    );
+}
+
 #[test]
 fn d2_cr() {
     assert_rendering(
-        d2("Column x Row", [Axis3::X, Axis3::Y], false),
+        &d2("Column x Row", [Axis3::X, Axis3::Y], None),
         "\
 Column x Row
 ╭──┬──┬──┬──╮
@@ -206,10 +249,56 @@ Column x Row
     );
 }
 
+#[test]
+fn d2_cr_with_corner_dim_labels() {
+    assert_rendering(
+        &d2(
+            "Column x Row - Corner",
+            [Axis3::X, Axis3::Y],
+            Some(LabelPosition::Corner),
+        ),
+        "\
+Column x Row - Corner
+╭──┬────────╮
+│  │    a   │
+│  ├──┬──┬──┤
+│b │a1│a2│a3│
+├──┼──┼──┼──┤
+│b1│ 0│ 1│ 2│
+│b2│ 3│ 4│ 5│
+│b3│ 6│ 7│ 8│
+╰──┴──┴──┴──╯
+",
+    );
+}
+
+#[test]
+fn d2_cr_with_nested_dim_labels() {
+    assert_rendering(
+        &d2(
+            "Column x Row - Nested",
+            [Axis3::X, Axis3::Y],
+            Some(LabelPosition::Nested),
+        ),
+        "\
+Column x Row - Nested
+╭────┬────────╮
+│    │    a   │
+│    ├──┬──┬──┤
+│    │a1│a2│a3│
+├────┼──┼──┼──┤
+│b b1│ 0│ 1│ 2│
+│  b2│ 3│ 4│ 5│
+│  b3│ 6│ 7│ 8│
+╰────┴──┴──┴──╯
+",
+    );
+}
+
 #[test]
 fn d2_rc() {
     assert_rendering(
-        d2("Row x Column", [Axis3::Y, Axis3::X], false),
+        &d2("Row x Column", [Axis3::Y, Axis3::X], None),
         "\
 Row x Column
 ╭──┬──┬──┬──╮
@@ -222,3 +311,280 @@ Row x Column
 ",
     );
 }
+
+#[test]
+fn d2_rc_with_corner_dim_labels() {
+    assert_rendering(
+        &d2(
+            "Row x Column - Corner",
+            [Axis3::Y, Axis3::X],
+            Some(LabelPosition::Corner),
+        ),
+        "\
+Row x Column - Corner
+╭──┬────────╮
+│  │    b   │
+│  ├──┬──┬──┤
+│a │b1│b2│b3│
+├──┼──┼──┼──┤
+│a1│ 0│ 3│ 6│
+│a2│ 1│ 4│ 7│
+│a3│ 2│ 5│ 8│
+╰──┴──┴──┴──╯
+",
+    );
+}
+
+#[test]
+fn d2_rc_with_nested_dim_labels() {
+    assert_rendering(
+        &d2(
+            "Row x Column - Nested",
+            [Axis3::Y, Axis3::X],
+            Some(LabelPosition::Nested),
+        ),
+        "\
+Row x Column - Nested
+╭────┬────────╮
+│    │    b   │
+│    ├──┬──┬──┤
+│    │b1│b2│b3│
+├────┼──┼──┼──┤
+│a a1│ 0│ 3│ 6│
+│  a2│ 1│ 4│ 7│
+│  a3│ 2│ 5│ 8│
+╰────┴──┴──┴──╯
+",
+    );
+}
+
+#[test]
+fn d2_cl() {
+    let pivot_table = d2("Column x b1", [Axis3::X, Axis3::Z], None);
+    assert_rendering(
+        &pivot_table,
+        "\
+Column x b1
+b1
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 0│ 1│ 2│
+╰──┴──┴──╯
+",
+    );
+
+    let pivot_table = pivot_table
+        .with_layer(&[1])
+        .with_title(Value::new_text("Column x b2"));
+    assert_rendering(
+        &pivot_table,
+        "\
+Column x b2
+b2
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 3│ 4│ 5│
+╰──┴──┴──╯
+",
+    );
+
+    let pivot_table = pivot_table
+        .with_all_layers()
+        .with_title(Value::new_text("Column (All Layers)"));
+    assert_rendering(
+        &pivot_table,
+        "\
+Column (All Layers)
+b1
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 0│ 1│ 2│
+╰──┴──┴──╯
+
+Column (All Layers)
+b2
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 3│ 4│ 5│
+╰──┴──┴──╯
+
+Column (All Layers)
+b3
+╭──┬──┬──╮
+│a1│a2│a3│
+├──┼──┼──┤
+│ 6│ 7│ 8│
+╰──┴──┴──╯
+",
+    );
+}
+
+#[test]
+fn d2_rl() {
+    let pivot_table = d2("Row x b1", [Axis3::Y, Axis3::Z], None);
+    assert_rendering(
+        &pivot_table,
+        "\
+Row x b1
+b1
+╭──┬─╮
+│a1│0│
+│a2│1│
+│a3│2│
+╰──┴─╯
+",
+    );
+
+    let pivot_table = pivot_table
+        .with_layer(&[1])
+        .with_title(Value::new_text("Row x b2"));
+    assert_rendering(
+        &pivot_table,
+        "\
+Row x b2
+b2
+╭──┬─╮
+│a1│3│
+│a2│4│
+│a3│5│
+╰──┴─╯
+",
+    );
+
+    let pivot_table = pivot_table
+        .with_all_layers()
+        .with_title(Value::new_text("Row (All Layers)"));
+    assert_rendering(
+        &pivot_table,
+        "\
+Row (All Layers)
+b1
+╭──┬─╮
+│a1│0│
+│a2│1│
+│a3│2│
+╰──┴─╯
+
+Row (All Layers)
+b2
+╭──┬─╮
+│a1│3│
+│a2│4│
+│a3│5│
+╰──┴─╯
+
+Row (All Layers)
+b3
+╭──┬─╮
+│a1│6│
+│a2│7│
+│a3│8│
+╰──┴─╯
+",
+    );
+}
+
+#[test]
+fn title_and_caption() {
+    let pivot_table =
+        d2("Title", [Axis3::X, Axis3::Y], None).with_caption(Value::new_text("Caption"));
+    assert_rendering(
+        &pivot_table,
+        "\
+Title
+╭──┬──┬──┬──╮
+│  │a1│a2│a3│
+├──┼──┼──┼──┤
+│b1│ 0│ 1│ 2│
+│b2│ 3│ 4│ 5│
+│b3│ 6│ 7│ 8│
+╰──┴──┴──┴──╯
+Caption
+",
+    );
+
+    let pivot_table = pivot_table.with_show_title(false);
+    assert_rendering(
+        &pivot_table,
+        "\
+╭──┬──┬──┬──╮
+│  │a1│a2│a3│
+├──┼──┼──┼──┤
+│b1│ 0│ 1│ 2│
+│b2│ 3│ 4│ 5│
+│b3│ 6│ 7│ 8│
+╰──┴──┴──┴──╯
+Caption
+",
+    );
+
+    let pivot_table = pivot_table.with_show_caption(false);
+    assert_rendering(
+        &pivot_table,
+        "\
+╭──┬──┬──┬──╮
+│  │a1│a2│a3│
+├──┼──┼──┼──┤
+│b1│ 0│ 1│ 2│
+│b2│ 3│ 4│ 5│
+│b3│ 6│ 7│ 8│
+╰──┴──┴──┴──╯
+",
+    );
+}
+
+#[test]
+fn no_dimension() {
+    let pivot_table = PivotTableBuilder::new(Value::new_text("No Dimensions"), &[])
+        .with_look(Arc::new(test_look()))
+        .build();
+    assert_rendering(
+        &pivot_table,
+        "No Dimensions
+╭╮
+╰╯
+",
+    );
+}
+
+#[test]
+fn empty_dimensions() {
+    let look = Arc::new(test_look().with_omit_empty(false));
+
+    let d1 = DimensionBuilder::new(Axis3::X, GroupBuilder::new(Value::new_text("a")));
+    let pivot_table = PivotTableBuilder::new(Value::new_text("One Empty Dimension"), &[d1])
+        .with_look(look.clone())
+        .build();
+    assert_rendering(&pivot_table, "One Empty Dimension\n");
+
+    let d1 = DimensionBuilder::new(Axis3::X, GroupBuilder::new(Value::new_text("a")));
+    let d2 = DimensionBuilder::new(
+        Axis3::X,
+        GroupBuilder::new(Value::new_text("b")).with_label_shown(),
+    );
+    let pivot_table = PivotTableBuilder::new(Value::new_text("Two Empty Dimensions"), &[d1, d2])
+        .with_look(look.clone())
+        .build();
+    assert_rendering(&pivot_table, "Two Empty Dimensions\n");
+
+    let d1 = DimensionBuilder::new(Axis3::X, GroupBuilder::new(Value::new_text("a")));
+    let d2 = DimensionBuilder::new(
+        Axis3::X,
+        GroupBuilder::new(Value::new_text("b")).with_label_shown(),
+    );
+    let d3 = DimensionBuilder::new(
+        Axis3::X,
+        GroupBuilder::new(Value::new_text("c"))
+            .with(Value::new_text("c1"))
+            .with(Value::new_text("c2")),
+    );
+    let pivot_table =
+        PivotTableBuilder::new(Value::new_text("Three Dimensions, Two Empty"), &[d1, d2, d3])
+            .with_look(look.clone())
+            .build();
+    assert_rendering(&pivot_table, "Three Dimensions, Two Empty\n");
+}
index 31118aad69103ac3c4d77d64486b9b52ae9247e4..49dbc46bb6cb24bfc3a9ed62bc52710c4bdd5cc1 100644 (file)
@@ -313,7 +313,11 @@ impl TextDriver {
 impl TextRenderer {
     fn render(&mut self, table: &PivotTable) -> Vec<TextLine> {
         let mut output = Vec::new();
-        for layer_indexes in table.layers(true) {
+        for (index, layer_indexes) in table.layers(true).enumerate() {
+            if index > 0 {
+                output.push(TextLine::new());
+            }
+
             let mut pager = Pager::new(self, table, Some(layer_indexes.as_slice()));
             while pager.has_next(self) {
                 pager.draw_next(self, usize::MAX);