Add tests for table rendering

This commit is contained in:
Matias Fontanini 2023-10-07 07:25:57 -07:00
parent 3163d310be
commit 0500d51f8c
6 changed files with 54 additions and 18 deletions

View File

@ -329,7 +329,7 @@ impl<'a> PresentationBuilder<'a> {
} }
if !texts.is_empty() { if !texts.is_empty() {
self.slide_operations.push(RenderOperation::RenderTextLine { self.slide_operations.push(RenderOperation::RenderTextLine {
texts: WeightedLine::from(texts), line: WeightedLine::from(texts),
alignment: alignment.clone(), alignment: alignment.clone(),
}); });
} }
@ -408,12 +408,15 @@ impl<'a> PresentationBuilder<'a> {
let mut separator = Text { chunks: Vec::new() }; let mut separator = Text { chunks: Vec::new() };
for (index, width) in widths.iter().enumerate() { for (index, width) in widths.iter().enumerate() {
let mut contents = String::new(); let mut contents = String::new();
let mut extra_lines = 1; let mut margin = 1;
if index > 0 { if index > 0 {
contents.push('┼'); contents.push('┼');
extra_lines += 1; // Append an extra dash to have 1 column margin on both sides
if index < widths.len() - 1 {
margin += 1;
} }
contents.extend(iter::repeat("").take(*width + extra_lines)); }
contents.extend(iter::repeat("").take(*width + margin));
separator.chunks.push(StyledText::from(contents)); separator.chunks.push(StyledText::from(contents));
} }
@ -480,7 +483,7 @@ impl AsRenderOperations for FooterGenerator {
operations.extend([ operations.extend([
RenderOperation::JumpToWindowBottom, RenderOperation::JumpToWindowBottom,
RenderOperation::RenderTextLine { RenderOperation::RenderTextLine {
texts: vec![Self::render_template(left, &current_slide, &context, colors.clone())].into(), line: vec![Self::render_template(left, &current_slide, &context, colors.clone())].into(),
alignment: Alignment::Left { margin: 1 }, alignment: Alignment::Left { margin: 1 },
}, },
]); ]);
@ -489,7 +492,7 @@ impl AsRenderOperations for FooterGenerator {
operations.extend([ operations.extend([
RenderOperation::JumpToWindowBottom, RenderOperation::JumpToWindowBottom,
RenderOperation::RenderTextLine { RenderOperation::RenderTextLine {
texts: vec![Self::render_template(right, &current_slide, &context, colors.clone())].into(), line: vec![Self::render_template(right, &current_slide, &context, colors.clone())].into(),
alignment: Alignment::Right { margin: 1 }, alignment: Alignment::Right { margin: 1 },
}, },
]); ]);
@ -505,7 +508,7 @@ impl AsRenderOperations for FooterGenerator {
let bar = vec![WeightedText::from(StyledText::new(bar, TextStyle::default().colors(colors.clone())))]; let bar = vec![WeightedText::from(StyledText::new(bar, TextStyle::default().colors(colors.clone())))];
vec![ vec![
RenderOperation::JumpToWindowBottom, RenderOperation::JumpToWindowBottom,
RenderOperation::RenderTextLine { texts: bar.into(), alignment: Alignment::Left { margin: 0 } }, RenderOperation::RenderTextLine { line: bar.into(), alignment: Alignment::Left { margin: 0 } },
] ]
} }
FooterStyle::Empty => vec![], FooterStyle::Empty => vec![],
@ -564,6 +567,20 @@ mod test {
} }
} }
fn extract_text_lines(operations: &[RenderOperation]) -> Vec<String> {
let mut output = Vec::new();
for operation in operations {
match operation {
RenderOperation::RenderTextLine { line, .. } => {
let texts: Vec<_> = line.iter_texts().map(|text| text.text.text.clone()).collect();
output.push(texts.join(""));
}
_ => (),
};
}
output
}
#[test] #[test]
fn prelude_appears_once() { fn prelude_appears_once() {
let elements = vec![ let elements = vec![
@ -629,4 +646,18 @@ mod test {
assert_eq!(lengths[0], (width, width)); assert_eq!(lengths[0], (width, width));
assert_eq!(lengths[1], (width, width)); assert_eq!(lengths[1], (width, width));
} }
#[test]
fn table() {
let elements = vec![MarkdownElement::Table(Table {
header: TableRow(vec![Text::from("key"), Text::from("value"), Text::from("other")]),
rows: vec![TableRow(vec![Text::from("potato"), Text::from("bar"), Text::from("yes")])],
})];
let slides = build_presentation(elements).into_slides();
let operations: Vec<_> =
slides.into_iter().next().unwrap().render_operations.into_iter().filter(|op| is_visible(op)).collect();
let lines = extract_text_lines(&operations);
let expected_lines = &["key │ value │ other", "───────┼───────┼──────", "potato │ bar │ yes "];
assert_eq!(lines, expected_lines);
}
} }

View File

@ -50,7 +50,7 @@ impl ContentDiff for RenderOperation {
match (self, other) { match (self, other) {
(SetColors(original), SetColors(updated)) if original != updated => false, (SetColors(original), SetColors(updated)) if original != updated => false,
(RenderTextLine { texts: original, .. }, RenderTextLine { texts: updated, .. }) if original != updated => { (RenderTextLine { line: original, .. }, RenderTextLine { line: updated, .. }) if original != updated => {
true true
} }
(RenderTextLine { alignment: original, .. }, RenderTextLine { alignment: updated, .. }) (RenderTextLine { alignment: original, .. }, RenderTextLine { alignment: updated, .. })
@ -114,7 +114,7 @@ mod test {
#[case(RenderOperation::RenderSeparator)] #[case(RenderOperation::RenderSeparator)]
#[case(RenderOperation::RenderLineBreak)] #[case(RenderOperation::RenderLineBreak)]
#[case(RenderOperation::SetColors(Colors{background: None, foreground: None}))] #[case(RenderOperation::SetColors(Colors{background: None, foreground: None}))]
#[case(RenderOperation::RenderTextLine{texts: String::from("asd").into(), alignment: Default::default()})] #[case(RenderOperation::RenderTextLine{line: String::from("asd").into(), alignment: Default::default()})]
#[case(RenderOperation::RenderPreformattedLine( #[case(RenderOperation::RenderPreformattedLine(
PreformattedLine{ PreformattedLine{
text: "asd".into(), text: "asd".into(),
@ -131,19 +131,19 @@ mod test {
#[test] #[test]
fn different_text() { fn different_text() {
let lhs = RenderOperation::RenderTextLine { texts: String::from("foo").into(), alignment: Default::default() }; let lhs = RenderOperation::RenderTextLine { line: String::from("foo").into(), alignment: Default::default() };
let rhs = RenderOperation::RenderTextLine { texts: String::from("bar").into(), alignment: Default::default() }; let rhs = RenderOperation::RenderTextLine { line: String::from("bar").into(), alignment: Default::default() };
assert!(lhs.is_content_different(&rhs)); assert!(lhs.is_content_different(&rhs));
} }
#[test] #[test]
fn different_text_alignment() { fn different_text_alignment() {
let lhs = RenderOperation::RenderTextLine { let lhs = RenderOperation::RenderTextLine {
texts: String::from("foo").into(), line: String::from("foo").into(),
alignment: Alignment::Left { margin: 42 }, alignment: Alignment::Left { margin: 42 },
}; };
let rhs = RenderOperation::RenderTextLine { let rhs = RenderOperation::RenderTextLine {
texts: String::from("foo").into(), line: String::from("foo").into(),
alignment: Alignment::Left { margin: 1337 }, alignment: Alignment::Left { margin: 1337 },
}; };
assert!(!lhs.is_content_different(&rhs)); assert!(!lhs.is_content_different(&rhs));

View File

@ -18,6 +18,11 @@ impl WeightedLine {
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
self.0.iter().map(|text| text.width()).sum() self.0.iter().map(|text| text.width()).sum()
} }
/// Get an iterator to the underlying text chunks.
pub fn iter_texts(&self) -> impl Iterator<Item = &WeightedText> {
self.0.iter()
}
} }
impl From<Vec<WeightedText>> for WeightedLine { impl From<Vec<WeightedText>> for WeightedLine {
@ -42,7 +47,7 @@ struct CharAccumulator {
/// A piece of weighted text. /// A piece of weighted text.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct WeightedText { pub struct WeightedText {
text: StyledText, pub text: StyledText,
accumulators: Vec<CharAccumulator>, accumulators: Vec<CharAccumulator>,
} }

View File

@ -168,7 +168,7 @@ pub enum RenderOperation {
JumpToSlideBottom, JumpToSlideBottom,
/// Render a line of text. /// Render a line of text.
RenderTextLine { texts: WeightedLine, alignment: Alignment }, RenderTextLine { line: WeightedLine, alignment: Alignment },
/// Render a horizontal separator line. /// Render a horizontal separator line.
RenderSeparator, RenderSeparator,

View File

@ -64,10 +64,10 @@ where
RenderOperation::ClearScreen, RenderOperation::ClearScreen,
RenderOperation::SetColors(Colors { foreground: Some(Color::Red), background: Some(Color::Black) }), RenderOperation::SetColors(Colors { foreground: Some(Color::Red), background: Some(Color::Black) }),
RenderOperation::JumpToVerticalCenter, RenderOperation::JumpToVerticalCenter,
RenderOperation::RenderTextLine { texts: WeightedLine::from(heading), alignment: alignment.clone() }, RenderOperation::RenderTextLine { line: WeightedLine::from(heading), alignment: alignment.clone() },
RenderOperation::RenderLineBreak, RenderOperation::RenderLineBreak,
RenderOperation::RenderLineBreak, RenderOperation::RenderLineBreak,
RenderOperation::RenderTextLine { texts: WeightedLine::from(error), alignment: alignment.clone() }, RenderOperation::RenderTextLine { line: WeightedLine::from(error), alignment: alignment.clone() },
]; ];
let mut operator = RenderOperator::new(&mut self.handle, dimensions.clone(), dimensions, Default::default()); let mut operator = RenderOperator::new(&mut self.handle, dimensions.clone(), dimensions, Default::default());
for operation in operations { for operation in operations {

View File

@ -44,7 +44,7 @@ where
RenderOperation::JumpToVerticalCenter => self.jump_to_vertical_center(), RenderOperation::JumpToVerticalCenter => self.jump_to_vertical_center(),
RenderOperation::JumpToSlideBottom => self.jump_to_slide_bottom(), RenderOperation::JumpToSlideBottom => self.jump_to_slide_bottom(),
RenderOperation::JumpToWindowBottom => self.jump_to_window_bottom(), RenderOperation::JumpToWindowBottom => self.jump_to_window_bottom(),
RenderOperation::RenderTextLine { texts, alignment } => self.render_text(texts, alignment), RenderOperation::RenderTextLine { line: texts, alignment } => self.render_text(texts, alignment),
RenderOperation::RenderSeparator => self.render_separator(), RenderOperation::RenderSeparator => self.render_separator(),
RenderOperation::RenderLineBreak => self.render_line_break(), RenderOperation::RenderLineBreak => self.render_line_break(),
RenderOperation::RenderImage(image) => self.render_image(image), RenderOperation::RenderImage(image) => self.render_image(image),