From 7a3901556479218c4ac27c6dc7ee6ded88b868cd Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 10 Apr 2025 17:25:13 -0700 Subject: [PATCH] more tests --- rust/pspp/src/output/pivot/mod.rs | 54 ++++ rust/pspp/src/output/pivot/test.rs | 400 +++++++++++++++++++++++++++-- rust/pspp/src/output/text.rs | 6 +- 3 files changed, 442 insertions(+), 18 deletions(-) diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index 5cbc719126..64e806b8ce 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -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, } +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> + '_> { + 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))) diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index 4a131c408f..92318a2b74 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -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 { +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) -> 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"); +} diff --git a/rust/pspp/src/output/text.rs b/rust/pspp/src/output/text.rs index 31118aad69..49dbc46bb6 100644 --- a/rust/pspp/src/output/text.rs +++ b/rust/pspp/src/output/text.rs @@ -313,7 +313,11 @@ impl TextDriver { impl TextRenderer { fn render(&mut self, table: &PivotTable) -> Vec { 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); -- 2.30.2