diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 124edecd..ed31f95e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,9 +38,11 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re - I develop primarily using _stable_ Rust. That is, whatever is the most up-to-date stable version you can get via running `rustup update stable`. +- There are some tests, they're mostly for sanity checks. Please run `cargo test` to ensure you didn't break anything important, unless the change will break the test (in which case please amend the tests). + - Note that `cargo test` will fail on anything lower than 1.43.0 due to it using a then-introduced env variable. -- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to pass CI. +- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to for PRs to be accepted. - You can check clippy using `cargo +nightly clippy`. diff --git a/Cargo.toml b/Cargo.toml index 1da73fdb..7a908c72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,12 @@ license = "MIT" categories = ["command-line-utilities", "visualization"] description = "A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows." readme = "README.md" +default-run = "btm" [[bin]] name = "btm" -path = "src/main.rs" +path = "src/bin/main.rs" +doc = false [profile.release] debug = 1 diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 00000000..fdbe7b51 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,185 @@ +#![warn(rust_2018_idioms)] +#[allow(unused_imports)] +#[macro_use] +extern crate log; + +use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *}; + +use std::{ + boxed::Box, + io::{stdout, Write}, + panic, + sync::mpsc, + thread, + time::Duration, +}; + +use crossterm::{ + event::EnableMouseCapture, + execute, + terminal::{enable_raw_mode, EnterAlternateScreen}, +}; +use tui::{backend::CrosstermBackend, Terminal}; + +fn main() -> error::Result<()> { + #[cfg(debug_assertions)] + { + utils::logging::init_logger()?; + } + let matches = get_matches(); + + let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; + + // Get widget layout separately + let (widget_layout, default_widget_id, default_widget_type_option) = + get_widget_layout(&matches, &config)?; + + // Create "app" struct, which will control most of the program and store settings/state + let mut app = build_app( + &matches, + &config, + &widget_layout, + default_widget_id, + &default_widget_type_option, + )?; + + // Create painter and set colours. + let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap); + generate_config_colours(&config, &mut painter)?; + painter.colours.generate_remaining_cpu_colours(); + painter.complete_painter_init(); + + // Set up input handling + let (sender, receiver) = mpsc::channel(); + create_input_thread(sender.clone()); + + // Cleaning loop + { + let cleaning_sender = sender.clone(); + thread::spawn(move || loop { + thread::sleep(Duration::from_millis( + constants::STALE_MAX_MILLISECONDS + 5000, + )); + if cleaning_sender.send(BottomEvent::Clean).is_err() { + break; + } + }); + } + + // Event loop + let (reset_sender, reset_receiver) = mpsc::channel(); + create_event_thread( + sender, + reset_receiver, + app.app_config_fields.use_current_cpu_total, + app.app_config_fields.update_rate_in_milliseconds, + app.app_config_fields.temperature_type.clone(), + app.app_config_fields.show_average_cpu, + app.used_widgets.clone(), + ); + + // Set up up tui and crossterm + let mut stdout_val = stdout(); + execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?; + enable_raw_mode()?; + + let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?; + terminal.hide_cursor()?; + + // Set panic hook + panic::set_hook(Box::new(|info| panic_hook(info))); + + loop { + if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) { + match recv { + BottomEvent::KeyInput(event) => { + if handle_key_event_or_break(event, &mut app, &reset_sender) { + break; + } + handle_force_redraws(&mut app); + } + BottomEvent::MouseInput(event) => { + handle_mouse_event(event, &mut app); + handle_force_redraws(&mut app); + } + BottomEvent::Update(data) => { + app.data_collection.eat_data(&data); + + if !app.is_frozen { + // Convert all data into tui-compliant components + + // Network + if app.used_widgets.use_net { + let network_data = convert_network_data_points( + &app.data_collection, + false, + app.app_config_fields.use_basic_mode + || app.app_config_fields.use_old_network_legend, + ); + app.canvas_data.network_data_rx = network_data.rx; + app.canvas_data.network_data_tx = network_data.tx; + app.canvas_data.rx_display = network_data.rx_display; + app.canvas_data.tx_display = network_data.tx_display; + if let Some(total_rx_display) = network_data.total_rx_display { + app.canvas_data.total_rx_display = total_rx_display; + } + if let Some(total_tx_display) = network_data.total_tx_display { + app.canvas_data.total_tx_display = total_tx_display; + } + } + + // Disk + if app.used_widgets.use_disk { + app.canvas_data.disk_data = convert_disk_row(&app.data_collection); + } + + // Temperatures + if app.used_widgets.use_temp { + app.canvas_data.temp_sensor_data = convert_temp_row(&app); + } + + // Memory + if app.used_widgets.use_mem { + app.canvas_data.mem_data = + convert_mem_data_points(&app.data_collection, false); + app.canvas_data.swap_data = + convert_swap_data_points(&app.data_collection, false); + let memory_and_swap_labels = convert_mem_labels(&app.data_collection); + app.canvas_data.mem_label_percent = memory_and_swap_labels.0; + app.canvas_data.mem_label_frac = memory_and_swap_labels.1; + app.canvas_data.swap_label_percent = memory_and_swap_labels.2; + app.canvas_data.swap_label_frac = memory_and_swap_labels.3; + } + + if app.used_widgets.use_cpu { + // CPU + app.canvas_data.cpu_data = + convert_cpu_data_points(&app.data_collection, false); + } + + // Processes + if app.used_widgets.use_proc { + update_all_process_lists(&mut app); + } + + // Battery + if app.used_widgets.use_battery { + app.canvas_data.battery_data = + convert_battery_harvest(&app.data_collection); + } + } + } + BottomEvent::Clean => { + app.data_collection + .clean_data(constants::STALE_MAX_MILLISECONDS); + } + } + } + + // TODO: [OPT] Should not draw if no change (ie: scroll max) + try_drawing(&mut terminal, &mut app, &mut painter)?; + } + + cleanup_terminal(&mut terminal)?; + Ok(()) +} diff --git a/src/main.rs b/src/lib.rs similarity index 76% rename from src/main.rs rename to src/lib.rs index 50ea11bc..53d7430b 100644 --- a/src/main.rs +++ b/src/lib.rs @@ -1,14 +1,7 @@ -#![warn(rust_2018_idioms)] - -#[allow(unused_imports)] -#[macro_use] -extern crate log; - use std::{ boxed::Box, io::{stdout, Write}, - panic::{self, PanicInfo}, - sync::mpsc, + panic::PanicInfo, thread, time::{Duration, Instant}, }; @@ -16,15 +9,11 @@ use std::{ use clap::*; use crossterm::{ - event::{ - poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, - KeyModifiers, MouseEvent, - }, + event::{poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent}, execute, style::Print, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + terminal::{disable_raw_mode, LeaveAlternateScreen}, }; -use tui::{backend::CrosstermBackend, Terminal}; use app::{ data_harvester::{self, processes::ProcessSorting}, @@ -38,30 +27,29 @@ use utils::error; pub mod app; -mod utils { +pub mod utils { pub mod error; pub mod gen_util; pub mod logging; } -mod canvas; -mod constants; -mod data_conversion; - +pub mod canvas; +pub mod constants; +pub mod data_conversion; pub mod options; -enum BottomEvent { +pub enum BottomEvent { KeyInput(I), MouseInput(J), Update(Box), Clean, } -enum ResetEvent { +pub enum ResetEvent { Reset, } -fn get_matches() -> clap::ArgMatches<'static> { +pub fn get_matches() -> clap::ArgMatches<'static> { clap_app!(app => (name: crate_name!()) (version: crate_version!()) @@ -96,163 +84,7 @@ fn get_matches() -> clap::ArgMatches<'static> { .get_matches() } -fn main() -> error::Result<()> { - #[cfg(debug_assertions)] - { - utils::logging::init_logger()?; - } - let matches = get_matches(); - - let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; - - // Get widget layout separately - let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?; - - // Create "app" struct, which will control most of the program and store settings/state - let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?; - - // Create painter and set colours. - let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap); - generate_config_colours(&config, &mut painter)?; - painter.colours.generate_remaining_cpu_colours(); - painter.complete_painter_init(); - - // Set up input handling - let (sender, receiver) = mpsc::channel(); - create_input_thread(sender.clone()); - - // Cleaning loop - { - let cleaning_sender = sender.clone(); - thread::spawn(move || loop { - thread::sleep(Duration::from_millis( - constants::STALE_MAX_MILLISECONDS + 5000, - )); - if cleaning_sender.send(BottomEvent::Clean).is_err() { - break; - } - }); - } - - // Event loop - let (reset_sender, reset_receiver) = mpsc::channel(); - create_event_thread( - sender, - reset_receiver, - app.app_config_fields.use_current_cpu_total, - app.app_config_fields.update_rate_in_milliseconds, - app.app_config_fields.temperature_type.clone(), - app.app_config_fields.show_average_cpu, - app.used_widgets.clone(), - ); - - // Set up up tui and crossterm - let mut stdout_val = stdout(); - execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?; - enable_raw_mode()?; - - let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?; - terminal.hide_cursor()?; - - // Set panic hook - panic::set_hook(Box::new(|info| panic_hook(info))); - - loop { - if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) { - match recv { - BottomEvent::KeyInput(event) => { - if handle_key_event_or_break(event, &mut app, &reset_sender) { - break; - } - handle_force_redraws(&mut app); - } - BottomEvent::MouseInput(event) => { - handle_mouse_event(event, &mut app); - handle_force_redraws(&mut app); - } - BottomEvent::Update(data) => { - app.data_collection.eat_data(&data); - - if !app.is_frozen { - // Convert all data into tui-compliant components - - // Network - if app.used_widgets.use_net { - let network_data = convert_network_data_points( - &app.data_collection, - false, - app.app_config_fields.use_basic_mode - || app.app_config_fields.use_old_network_legend, - ); - app.canvas_data.network_data_rx = network_data.rx; - app.canvas_data.network_data_tx = network_data.tx; - app.canvas_data.rx_display = network_data.rx_display; - app.canvas_data.tx_display = network_data.tx_display; - if let Some(total_rx_display) = network_data.total_rx_display { - app.canvas_data.total_rx_display = total_rx_display; - } - if let Some(total_tx_display) = network_data.total_tx_display { - app.canvas_data.total_tx_display = total_tx_display; - } - } - - // Disk - if app.used_widgets.use_disk { - app.canvas_data.disk_data = convert_disk_row(&app.data_collection); - } - - // Temperatures - if app.used_widgets.use_temp { - app.canvas_data.temp_sensor_data = convert_temp_row(&app); - } - - // Memory - if app.used_widgets.use_mem { - app.canvas_data.mem_data = - convert_mem_data_points(&app.data_collection, false); - app.canvas_data.swap_data = - convert_swap_data_points(&app.data_collection, false); - let memory_and_swap_labels = convert_mem_labels(&app.data_collection); - app.canvas_data.mem_label_percent = memory_and_swap_labels.0; - app.canvas_data.mem_label_frac = memory_and_swap_labels.1; - app.canvas_data.swap_label_percent = memory_and_swap_labels.2; - app.canvas_data.swap_label_frac = memory_and_swap_labels.3; - } - - if app.used_widgets.use_cpu { - // CPU - app.canvas_data.cpu_data = - convert_cpu_data_points(&app.data_collection, false); - } - - // Processes - if app.used_widgets.use_proc { - update_all_process_lists(&mut app); - } - - // Battery - if app.used_widgets.use_battery { - app.canvas_data.battery_data = - convert_battery_harvest(&app.data_collection); - } - } - } - BottomEvent::Clean => { - app.data_collection - .clean_data(constants::STALE_MAX_MILLISECONDS); - } - } - } - - // TODO: [OPT] Should not draw if no change (ie: scroll max) - try_drawing(&mut terminal, &mut app, &mut painter)?; - } - - cleanup_terminal(&mut terminal)?; - Ok(()) -} - -fn handle_mouse_event(event: MouseEvent, app: &mut App) { +pub fn handle_mouse_event(event: MouseEvent, app: &mut App) { match event { MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(), MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(), @@ -260,7 +92,7 @@ fn handle_mouse_event(event: MouseEvent, app: &mut App) { }; } -fn handle_key_event_or_break( +pub fn handle_key_event_or_break( event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender, ) -> bool { // debug!("KeyEvent: {:?}", event); @@ -348,7 +180,7 @@ fn handle_key_event_or_break( false } -fn create_config(flag_config_location: Option<&str>) -> error::Result { +pub fn create_config(flag_config_location: Option<&str>) -> error::Result { use std::{ffi::OsString, fs}; let config_path = if let Some(conf_loc) = flag_config_location { Some(OsString::from(conf_loc)) @@ -399,7 +231,7 @@ fn create_config(flag_config_location: Option<&str>) -> error::Result { } } -fn try_drawing( +pub fn try_drawing( terminal: &mut tui::terminal::Terminal>, app: &mut App, painter: &mut canvas::Painter, ) -> error::Result<()> { @@ -411,7 +243,7 @@ fn try_drawing( Ok(()) } -fn cleanup_terminal( +pub fn cleanup_terminal( terminal: &mut tui::terminal::Terminal>, ) -> error::Result<()> { disable_raw_mode()?; @@ -425,7 +257,9 @@ fn cleanup_terminal( Ok(()) } -fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> error::Result<()> { +pub fn generate_config_colours( + config: &Config, painter: &mut canvas::Painter, +) -> error::Result<()> { if let Some(colours) = &config.colors { if let Some(border_color) = &colours.border_color { painter.colours.set_border_colour(border_color)?; @@ -514,7 +348,7 @@ fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> er } /// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs -fn panic_hook(panic_info: &PanicInfo<'_>) { +pub fn panic_hook(panic_info: &PanicInfo<'_>) { let mut stdout = stdout(); let msg = match panic_info.payload().downcast_ref::<&'static str>() { @@ -543,7 +377,7 @@ fn panic_hook(panic_info: &PanicInfo<'_>) { .unwrap(); } -fn handle_force_redraws(app: &mut App) { +pub fn handle_force_redraws(app: &mut App) { // Currently we use an Option... because we might want to future-proof this // if we eventually get widget-specific redrawing! if app.proc_state.force_update_all { @@ -573,7 +407,7 @@ fn handle_force_redraws(app: &mut App) { } } -fn update_all_process_lists(app: &mut App) { +pub fn update_all_process_lists(app: &mut App) { let widget_ids = app .proc_state .widget_states @@ -586,7 +420,7 @@ fn update_all_process_lists(app: &mut App) { }); } -fn update_final_process_list(app: &mut App, widget_id: u64) { +pub fn update_final_process_list(app: &mut App, widget_id: u64) { let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) { Some(process_state) => process_state .process_search_state @@ -659,7 +493,7 @@ fn update_final_process_list(app: &mut App, widget_id: u64) { } } -fn sort_process_data( +pub fn sort_process_data( to_sort_vec: &mut Vec, proc_widget_state: &app::ProcWidgetState, ) { to_sort_vec.sort_by(|a, b| { @@ -763,7 +597,7 @@ fn sort_process_data( } } -fn create_input_thread( +pub fn create_input_thread( sender: std::sync::mpsc::Sender< BottomEvent, >, @@ -796,7 +630,7 @@ fn create_input_thread( }); } -fn create_event_thread( +pub fn create_event_thread( sender: std::sync::mpsc::Sender< BottomEvent, >, diff --git a/src/options.rs b/src/options.rs index e9fb4a29..d772ed9e 100644 --- a/src/options.rs +++ b/src/options.rs @@ -10,7 +10,7 @@ use crate::{ use layout_options::*; -mod layout_options; +pub mod layout_options; #[derive(Default, Deserialize)] pub struct Config { @@ -68,7 +68,7 @@ pub struct ConfigColours { pub fn build_app( matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout, - default_widget_id: u64, + default_widget_id: u64, default_widget_type_option: &Option, ) -> error::Result { use BottomWidgetType::*; let autohide_time = get_autohide_time(&matches, &config); @@ -96,7 +96,6 @@ pub fn build_app( None }; - let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?; let mut initial_widget_id: u64 = default_widget_id; let mut initial_widget_type = Proc; let is_custom_layout = config.row.is_some(); @@ -252,7 +251,7 @@ pub fn build_app( pub fn get_widget_layout( matches: &clap::ArgMatches<'static>, config: &Config, -) -> error::Result<(BottomLayout, u64)> { +) -> error::Result<(BottomLayout, u64, Option)> { let left_legend = get_use_left_legend(matches, config); let (default_widget_type, mut default_widget_count) = get_default_widget_and_count(matches, config)?; @@ -311,7 +310,7 @@ pub fn get_widget_layout( } }; - Ok((bottom_layout, default_widget_id)) + Ok((bottom_layout, default_widget_id, default_widget_type)) } fn get_update_rate_in_milliseconds( diff --git a/tests/layout_management_tests.rs b/tests/layout_management_tests.rs new file mode 100644 index 00000000..e4eb21f4 --- /dev/null +++ b/tests/layout_management_tests.rs @@ -0,0 +1,469 @@ +//! Mocks layout management, so we can check if we broke anything. + +use bottom::app::layout_manager::{BottomLayout, BottomWidgetType}; +use bottom::constants::{DEFAULT_BATTERY_LAYOUT, DEFAULT_LAYOUT, DEFAULT_WIDGET_ID}; +use bottom::options::{layout_options::Row, Config}; +use bottom::utils::error; + +const PROC_LAYOUT: &str = r##" +[[row]] + [[row.child]] + type="proc" +[[row]] + [[row.child]] + type="proc" + [[row.child]] + type="proc" +[[row]] + [[row.child]] + type="proc" + [[row.child]] + type="proc" +"##; + +fn test_create_layout( + rows: &Vec, default_widget_id: u64, default_widget_type: Option, + default_widget_count: u64, left_legend: bool, +) -> BottomLayout { + let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs* + let mut total_height_ratio = 0; + let mut default_widget_count = default_widget_count; + let mut default_widget_id = default_widget_id; + + let mut ret_bottom_layout = BottomLayout { + rows: rows + .iter() + .map(|row| { + row.convert_row_to_bottom_row( + &mut iter_id, + &mut total_height_ratio, + &mut default_widget_id, + &default_widget_type, + &mut default_widget_count, + left_legend, + ) + }) + .collect::>>() + .unwrap(), + total_row_height_ratio: total_height_ratio, + }; + ret_bottom_layout.get_movement_mappings(); + + ret_bottom_layout +} + +#[test] +/// Tests the default setup. +fn test_default_movement() { + let rows = toml::from_str::(DEFAULT_LAYOUT) + .unwrap() + .row + .unwrap(); + let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false); + + // Simple tests for the top CPU widget + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour, + Some(3) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour, + Some(2) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour, + None + ); + + // Test CPU legend + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour, + Some(4) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour, + Some(1) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour, + None + ); + + // Test memory->temp, temp->disk, disk->memory mappings + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour, + Some(4) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[1].children[0].children[0].down_neighbour, + Some(5) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[1].children[1].children[0].left_neighbour, + Some(3) + ); + + // Test disk -> processes, processes -> process sort, process sort -> network + assert_eq!( + ret_bottom_layout.rows[1].children[1].children[1].children[0].down_neighbour, + Some(7) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour, + Some(9) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour, + Some(6) + ); +} + +#[test] +/// Tests battery movement in the default setup. +fn test_default_battery_movement() { + let rows = toml::from_str::(DEFAULT_BATTERY_LAYOUT) + .unwrap() + .row + .unwrap(); + let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false); + + // Simple tests for the top CPU widget + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour, + Some(4) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour, + Some(2) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour, + None + ); + + // Test CPU legend + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour, + Some(5) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour, + Some(3) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour, + Some(1) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour, + None + ); +} + +#[test] +/// Tests using left_legend. +fn test_left_legend() { + let rows = toml::from_str::(DEFAULT_LAYOUT) + .unwrap() + .row + .unwrap(); + let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true); + + // Legend + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour, + Some(3) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour, + Some(1) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour, + None + ); + + // Widget + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour, + Some(3) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour, + Some(2) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour, + None + ); +} + +#[test] +/// Tests explicit default widget. +fn test_default_widget_in_layout() { + let proc_layout = r##" + [[row]] + [[row.child]] + type="proc" + [[row]] + [[row.child]] + type="proc" + [[row.child]] + type="proc" + [[row]] + [[row.child]] + type="proc" + default=true + [[row.child]] + type="proc" + "##; + let rows = toml::from_str::(proc_layout).unwrap().row.unwrap(); + let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs* + let mut total_height_ratio = 0; + let mut default_widget_count = 1; + let mut default_widget_id = DEFAULT_WIDGET_ID; + let default_widget_type = None; + let left_legend = false; + + let mut ret_bottom_layout = BottomLayout { + rows: rows + .iter() + .map(|row| { + row.convert_row_to_bottom_row( + &mut iter_id, + &mut total_height_ratio, + &mut default_widget_id, + &default_widget_type, + &mut default_widget_count, + left_legend, + ) + }) + .collect::>>() + .unwrap(), + total_row_height_ratio: total_height_ratio, + }; + ret_bottom_layout.get_movement_mappings(); + + assert_eq!(default_widget_id, 10); +} + +#[test] +/// Tests default widget by setting type and count. +fn test_default_widget_by_option() { + let rows = toml::from_str::(PROC_LAYOUT).unwrap().row.unwrap(); + let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs* + let mut total_height_ratio = 0; + let mut default_widget_count = 3; + let mut default_widget_id = DEFAULT_WIDGET_ID; + let default_widget_type = Some(BottomWidgetType::Proc); + let left_legend = false; + + let mut ret_bottom_layout = BottomLayout { + rows: rows + .iter() + .map(|row| { + row.convert_row_to_bottom_row( + &mut iter_id, + &mut total_height_ratio, + &mut default_widget_id, + &default_widget_type, + &mut default_widget_count, + left_legend, + ) + }) + .collect::>>() + .unwrap(), + total_row_height_ratio: total_height_ratio, + }; + ret_bottom_layout.get_movement_mappings(); + + assert_eq!(default_widget_id, 7); +} + +#[test] +fn test_proc_custom_layout() { + let rows = toml::from_str::(PROC_LAYOUT).unwrap().row.unwrap(); + let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false); + + // First proc widget + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour, + Some(2) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour, + Some(3) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour, + None + ); + + // Its search + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[1].children[0].down_neighbour, + Some(4) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[1].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[1].children[0].right_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[1].children[0].up_neighbour, + Some(1) + ); + + // Its sort + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour, + Some(2) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour, + Some(1) + ); + assert_eq!( + ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour, + None + ); + + // Let us now test the second row's first widget... + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[1].down_neighbour, + Some(5) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[1].left_neighbour, + Some(6) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[1].right_neighbour, + Some(9) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[1].up_neighbour, + Some(2) + ); + + // Sort + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[0].down_neighbour, + Some(5) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour, + Some(4) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[0].children[0].up_neighbour, + Some(2) + ); + + // Search + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[1].children[0].down_neighbour, + Some(10) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[1].children[0].left_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[1].children[0].right_neighbour, + Some(8) + ); + assert_eq!( + ret_bottom_layout.rows[1].children[0].children[1].children[0].up_neighbour, + Some(4) + ); + + // Third row, second + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[1].down_neighbour, + Some(14) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour, + Some(15) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[1].right_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[1].up_neighbour, + Some(8) + ); + + // Sort + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[0].down_neighbour, + Some(14) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour, + Some(10) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[0].right_neighbour, + Some(13) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[0].children[0].up_neighbour, + Some(8) + ); + + // Search + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[1].children[0].down_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[1].children[0].left_neighbour, + Some(11) + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[1].children[0].right_neighbour, + None + ); + assert_eq!( + ret_bottom_layout.rows[2].children[1].children[1].children[0].up_neighbour, + Some(13) + ); +} diff --git a/tests/widget_movement_tests.rs b/tests/widget_movement_tests.rs deleted file mode 100644 index b4bd4444..00000000 --- a/tests/widget_movement_tests.rs +++ /dev/null @@ -1 +0,0 @@ -// TODO: See if we can mock widget movements and test if they work \ No newline at end of file