Add tests for backlog rendering

This commit is contained in:
Val Lorentz 2023-11-05 09:59:47 +01:00
parent b81ec4ae4f
commit 2fafdcfa40
6 changed files with 414 additions and 39 deletions

View File

@ -25,6 +25,7 @@ use crate::widgets::prerender::{PrerenderInner, PrerenderValue};
use crate::widgets::{BottomAlignedParagraph, OverlappableWidget};
use super::Component;
use crate::buffers::BufferItem;
#[derive(Default)]
pub struct Backlog {
@ -40,7 +41,7 @@ impl Component for Backlog {
kind: KeyEventKind::Press,
state: _,
} => {
self.scroll += 20; // TODO: use the component height
self.scroll_up(20); // TODO: use the component height
},
KeyEvent {
code: KeyCode::PageDown,
@ -48,7 +49,7 @@ impl Component for Backlog {
kind: KeyEventKind::Press,
state: _,
} => {
self.scroll = self.scroll.saturating_sub(20); // TODO: use the component height
self.scroll_down(20) // TODO: use the component height
},
_ => {},
}
@ -62,12 +63,37 @@ impl Component for Backlog {
area: Rect,
buffers: &crate::buffers::Buffers,
) -> Result<()> {
let active_buffer = buffers.active_buffer();
let items = active_buffer.content();
let unused_top_lines = self.draw_items(frame.buffer_mut(), area, items)?;
// There is empty room on screen, ask the buffer to fetch more backlog if it can
if unused_top_lines > 0 {
active_buffer.request_back_pagination(100);
}
Ok(())
}
}
impl Backlog {
pub fn scroll_up(&mut self, lines: u64) {
self.scroll = self.scroll.saturating_add(lines);
}
pub fn scroll_down(&mut self, lines: u64) {
self.scroll = self.scroll.saturating_sub(20);
}
/// Returns how many lines are unused at the top of the area.
pub fn draw_items<'a>(
&mut self,
frame_buffer: &mut Buffer,
area: Rect,
mut items: impl Iterator<Item = BufferItem<'a>>,
) -> Result<u16> {
let block = Block::new().borders(Borders::ALL);
let mut text_area = block.inner(area);
block.render(area, frame.buffer_mut());
block.render(area, frame_buffer);
let active_buffer = buffers.active_buffer();
let mut items = active_buffer.content();
let mut scroll = self.scroll;
// Skip widgets at the bottom (if scrolled up), and render the first visible one
@ -103,7 +129,7 @@ impl Component for Backlog {
// TODO: cache this
let widget = BottomAlignedParagraph::new(item.text).scroll(scroll);
let height = widget.render_overlap(text_area, frame.buffer_mut());
let height = widget.render_overlap(text_area, frame_buffer);
text_area.height = text_area.height.saturating_sub(height);
scroll = scroll.saturating_sub(expected_height);
@ -113,7 +139,7 @@ impl Component for Backlog {
}
if text_area.height == 0 {
// No more room to display other paragraphs, stop now
return Ok(());
return Ok(0);
}
// Render other widgets
@ -135,7 +161,7 @@ impl Component for Backlog {
width: text_area.width,
height: buf.area.height - skip_top_lines,
},
frame.buffer_mut(),
frame_buffer,
Rect {
y: text_area.y + top_padding,
height: text_area.height - top_padding,
@ -147,7 +173,7 @@ impl Component for Backlog {
},
prerender => {
let widget = BottomAlignedParagraph::new(item.text);
let height = widget.render_overlap(text_area, frame.buffer_mut());
let height = widget.render_overlap(text_area, frame_buffer);
// If the whole widget fits in the text_area, copy the drawn result to a buffer
// for caching
@ -160,7 +186,7 @@ impl Component for Backlog {
});
let top_padding = text_area.height.saturating_sub(buf.area.height);
copy_buffer(
frame.buffer_mut(),
frame_buffer,
Rect {
y: text_area.y + top_padding,
height: text_area.height - top_padding,
@ -188,14 +214,11 @@ impl Component for Backlog {
text_area.height = text_area.height.saturating_sub(height); // Remove lines at the bottom used by this paragraph
if text_area.height == 0 {
// No more room to display other paragraphs, stop now
return Ok(());
return Ok(0);
}
}
// There is empty room on screen, ask the buffer to fetch more backlog if it can
active_buffer.request_back_pagination(100);
Ok(())
Ok(text_area.height)
}
}

23
src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
pub mod action;
pub mod app;
pub mod buffers;
pub mod cli;
pub mod commands;
pub mod components;
pub mod config;
pub mod log;
pub mod mode;
pub mod plugins;
pub mod tui;
pub mod utils;
pub mod widgets;
use crate::{
app::App,
log::initialize_logging,
utils::{initialize_panic_handler, version},
};

View File

@ -1,30 +1,7 @@
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
pub mod action;
pub mod app;
pub mod buffers;
pub mod cli;
pub mod commands;
pub mod components;
pub mod config;
pub mod log;
pub mod mode;
pub mod plugins;
pub mod tui;
pub mod utils;
pub mod widgets;
use clap::Parser;
use cli::Cli;
use color_eyre::eyre::Result;
use crate::{
app::App,
log::initialize_logging,
utils::{initialize_panic_handler, version},
};
use ratatrix::{app::App, cli::Cli, log::initialize_logging, utils::initialize_panic_handler};
async fn tokio_main() -> Result<()> {
let mem_log = initialize_logging()?;

View File

@ -36,4 +36,11 @@ impl Prerender {
pub fn new() -> Prerender {
Prerender(Arc::new(Mutex::new(None)))
}
/// If this cache contains anything, returns its key
///
/// This is only meant for testing
pub fn key(&self) -> Option<u16> {
self.0.lock().unwrap().as_ref().map(|inner| inner.key)
}
}

344
tests/components/backlog.rs Normal file
View File

@ -0,0 +1,344 @@
/*
* Copyright (C) 2023 Valentin Lorentz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use color_eyre::eyre::WrapErr;
use pretty_assertions::assert_eq;
use ratatui::prelude::*;
use ratatrix::buffers::BufferItem;
use ratatrix::components::Backlog;
use ratatrix::widgets::Prerender;
fn rect(x: u16, y: u16, width: u16, height: u16) -> Rect {
Rect {
x,
y,
width,
height,
}
}
#[test]
fn test_single_item() {
let mut bl = Backlog::default();
let prerender = Prerender::new();
let item = BufferItem {
text: Text::raw("hello"),
prerender: &prerender,
};
let mut buf = Buffer::empty(rect(0, 0, 18, 8));
bl.draw_items(&mut buf, rect(3, 2, 12, 4), vec![item].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ",
" ┌──────────┐ ",
" │ │ ",
" │hello │ ",
" └──────────┘ ",
" ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_single_item_cached() {
let mut bl = Backlog::default();
let prerender = Prerender::new();
let item = BufferItem {
text: Text::raw("hello"),
prerender: &prerender,
};
let mut buf = Buffer::empty(rect(0, 0, 18, 8));
let area = rect(3, 2, 12, 4);
bl.draw_items(&mut buf, area, vec![item].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ",
" ┌──────────┐ ",
" │ │ ",
" │hello │ ",
" └──────────┘ ",
" ",
" ",
]);
assert_eq!(buf, expected);
assert_eq!(prerender.key(), Some(10));
let item = BufferItem {
text: Text::raw("hello"),
prerender: &prerender,
};
bl.draw_items(&mut buf, area, vec![item].into_iter())
.context("Failed to draw")
.unwrap();
assert_eq!(buf, expected);
}
#[test]
fn test_single_item_tight() {
let mut bl = Backlog::default();
let prerender = Prerender::new();
let item = BufferItem {
text: Text::raw("hello"),
prerender: &prerender,
};
let mut buf = Buffer::empty(rect(0, 0, 13, 7));
bl.draw_items(&mut buf, rect(3, 2, 7, 3), vec![item].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ",
" ┌─────┐ ",
" │hello│ ",
" └─────┘ ",
" ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_two_items() {
let mut bl = Backlog::default();
let prerender1 = Prerender::new();
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let prerender2 = Prerender::new();
let item2 = BufferItem {
text: Text::raw("world"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │ │ ",
" │hi │ ",
" │world │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_two_items_scroll() {
let mut bl = Backlog::default();
let prerender1 = Prerender::new();
let prerender2 = Prerender::new();
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let item2 = BufferItem {
text: Text::raw("world"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │ │ ",
" │hi │ ",
" │world │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
bl.scroll_up(1);
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let item2 = BufferItem {
text: Text::raw("world"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │ │ ",
" │ │ ",
" │hi │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
bl.scroll_up(1);
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let item2 = BufferItem {
text: Text::raw("world"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │ │ ",
" │ │ ",
" │ │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_two_items_multiline() {
let mut bl = Backlog::default();
let prerender1 = Prerender::new();
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let prerender2 = Prerender::new();
let item2 = BufferItem {
text: Text::raw("world\n!"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │hi │ ",
" │world │ ",
" │! │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_two_items_tight() {
let mut bl = Backlog::default();
let prerender1 = Prerender::new();
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let prerender2 = Prerender::new();
let item2 = BufferItem {
text: Text::raw("world"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 9, 6));
bl.draw_items(&mut buf, rect(1, 1, 7, 4), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌─────┐ ",
" │hi │ ",
" │world│ ",
" └─────┘ ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_cache_moved() {
let mut bl = Backlog::default();
let prerender1 = Prerender::new();
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
// Draw once
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │ │ ",
" │ │ ",
" │hi │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
// New item added at bottom
let item1 = BufferItem {
text: Text::raw("hi"),
prerender: &prerender1,
};
let prerender2 = Prerender::new();
let item2 = BufferItem {
text: Text::raw("world"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │ │ ",
" │hi │ ",
" │world │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
}

1
tests/components/main.rs Normal file
View File

@ -0,0 +1 @@
mod backlog;