Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions src/events.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ratatui::crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
use std::io::Result;

use crate::app::App;
Expand All @@ -19,8 +19,7 @@ pub fn handle_dialog_error_events(app: &mut App, key: KeyEvent) -> Result<bool>
Ok(false)
}

pub fn handle_events(app: &mut App) -> Result<bool> {
let event = event::read()?;
pub fn handle_events(app: &mut App, event: Event) -> Result<bool> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essa eh a principal mudanca que permite rodar esse teste sem um terminal

match event {
Event::Key(key) if key.kind == KeyEventKind::Press => {
match app.state {
Expand Down Expand Up @@ -63,3 +62,72 @@ pub fn handle_events(app: &mut App) -> Result<bool> {
}
Ok(false)
}

#[cfg(test)]
mod tests {
use crate::update_page_size;

use super::*;
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};

fn test_app() -> App {
let mut app = App::new();
app.load_file("test_data/test.bin", 0, false).unwrap();
update_page_size(&mut app, 5); // set height to 2 so we can test page up/down behavior
app
}

#[test]
fn test_normal_mode_movements() {
use KeyCode::*;
let mut app = test_app();
let end = app.file_info.size - 1;
let line_end = app.config.hex_mode_bytes_per_line - 1;
let page_size = app.reader.page_current_size;

// (name, initial offset, key code, (expected cursor x, expected cursor y), new_offset)
let cases = [
// right
("right", 0, Right, (1, 0), 1),
Comment on lines +88 to +91

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eu costumo gostar de estruturar os testes assim: uma lista de casos e um for loop testando todos.

Eu acho que ajuda a deixar mais facil de adicionar mais casos parecidos depois.

Mas tem gente que prefere um teste separado por caso, nao sei o que voce prefere 🤷

@merces merces Aug 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bem legal essa abordagem. Nunca tinha visto, mas gostei. Bem mais rápido para escrever novos testes! :)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

muito bom mesmo, eu faço isso em python com pytest.mark.parametrize, fica bem legal para ampliar a cobertura no longo prazo.

Apesar disso não ser suportado nativamente por Rust, também dá pra ter um cenário parecido usando isso
https://crates.io/crates/rstest#:~:text=42)%3B%0A%7D-,Parametrize,-You%20can%20also

("right at edge", line_end, Right, (0, 1), line_end + 1),
("right at end", end, Right, (line_end, 1), end),
// left
("left", 6, Left, (5, 0), 5),
("left at zero", 0, Left, (0, 0), 0),
("left at edge", line_end + 1, Left, (line_end, 0), line_end),
// down
("down", 0, Down, (0, 1), line_end + 1),
("down at edge", line_end + 1, Down, (0, 1), page_size),
("down at end", end, Down, (15, 1), end),
// up
("up", line_end + 3, Up, (2, 0), 2),
("up at egde", page_size + 3, Up, (3, 0), line_end + 4),
("up at start", 0, Up, (0, 0), 0),
// page up
("page up", page_size + 7, PageUp, (7, 0), 7),
(
"page up 2nd line",
page_size + line_end + 3,
PageUp,
(2, 0), // page up from 2nd line should go to first line
line_end + 3,
),
("page up at start", 0, PageUp, (0, 0), 0),
// page down
("page down", 4, PageDown, (4, 1), page_size + 4),
("page down end", end, PageDown, (15, 1), end),
];

for case in cases {
let (name, initial_offset, key_code, expected_point, expected_offset) = case;
app.goto(initial_offset); // this goto is also being tested in a way
let event = Event::Key(KeyEvent::new(key_code, KeyModifiers::NONE));
handle_events(&mut app, event).unwrap();

let cursor_pos = (app.hex_view.cursor.x, app.hex_view.cursor.y);
assert_eq!(cursor_pos, expected_point, "Test case '{name}' failed");
let new_offset = app.hex_view.offset;
assert_eq!(new_offset, expected_offset, "Test case '{name}' failed");
}
}
}
39 changes: 22 additions & 17 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod widgets;
use std::process;

use clap::Parser;
use ratatui::crossterm::event;

use app::App;

Expand Down Expand Up @@ -60,33 +61,37 @@ fn main() {
while app.running {
terminal
.draw(|f| {
// Page size is dynamically calculated as:
// frame height - (command line + status line + header) * bytes per line

// Prevent panic on underflow with small screen sizes
// Currently, we can't have them because of widgets such as Calculator,
// but we might add support for such small screen sizes in the future
let page_size = if f.area().height.checked_sub(3).is_some() {
(f.area().height - 3) as usize * app.config.hex_mode_bytes_per_line
} else {
app.config.hex_mode_bytes_per_line
};

if page_size != app.reader.page_current_size {
app.reader.page_current_size = page_size;
app.reader.page_end = app.reader.page_start + page_size.wrapping_sub(1);
}
update_page_size(&mut app, f.area().height);
app.screen = f.area();
draw::draw(f, &mut app)
})
.expect("failed to draw frame");

events::handle_events(&mut app).expect("unable to read events");
let event = event::read().expect("unable to read event");
events::handle_events(&mut app, event).expect("unable to read events");
}

ratatui::restore();
}

/// Page size is dynamically calculated as:
/// frame height - (command line + status line + header) * bytes per line
pub fn update_page_size(app: &mut App, height: u16) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extrai como funcao, pq sem rodar isso o page_end e current_size ficam setados em zero e da problema no teste.

Acho que o ideal seria o App::new() ja gerar um app pronto para usar. mas eu mudaria isso depois so

// Prevent panic on underflow with small screen sizes
// Currently, we can't have them because of widgets such as Calculator,
// but we might add support for such small screen sizes in the future
let page_size = if height.checked_sub(3).is_some() {
(height - 3) as usize * app.config.hex_mode_bytes_per_line
} else {
app.config.hex_mode_bytes_per_line
};

if page_size != app.reader.page_current_size {
app.reader.page_current_size = page_size;
app.reader.page_end = app.reader.page_start + page_size.wrapping_sub(1);
}
}

#[macro_export]
macro_rules! beep {
() => {
Expand Down
3 changes: 3 additions & 0 deletions test_data/test.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Not really a binary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um arquivo qualquer de teste. talvez de para nao ter ele e gerar um arquivo on the fly

just some dummy data
to write tests against
Loading