From 36b1da0afa398f6c69415a7e855d25d85cc828dd Mon Sep 17 00:00:00 2001 From: Takayuki Maeda <41065217+TaKO8Ki@users.noreply.github.com> Date: Sat, 9 Oct 2021 16:03:52 +0900 Subject: [PATCH] Create PropertiesComponent and redesign layout (#128) * create properties component * refactor app.rs * add a test for checking if gobang has overlappted keys * add keys for switching tabs to properties * fix tab * add serialize * update record_table * add properties group * use serialize only in tests * remove alias * remove query field --- Cargo.lock | 49 ++++++++- Cargo.toml | 3 + src/app.rs | 205 ++++++----------------------------- src/components/command.rs | 23 +++- src/components/databases.rs | 14 ++- src/components/mod.rs | 2 + src/components/properties.rs | 200 ++++++++++++++++++++++++++++++++++ src/components/sql_editor.rs | 6 +- src/components/tab.rs | 24 +--- src/config.rs | 41 +++++-- src/event/key.rs | 4 + 11 files changed, 360 insertions(+), 211 deletions(-) create mode 100644 src/components/properties.rs diff --git a/Cargo.lock b/Cargo.lock index a762513..44d5163 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.41" @@ -204,7 +213,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags", "strsim", @@ -321,6 +330,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "database-tree" version = "0.1.0-alpha.5" @@ -330,6 +349,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + [[package]] name = "digest" version = "0.9.0" @@ -582,6 +607,7 @@ dependencies = [ "easy-cast", "futures", "itertools", + "pretty_assertions", "rust_decimal", "serde", "serde_json", @@ -964,6 +990,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -1044,6 +1079,18 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "pretty_assertions" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +dependencies = [ + "ansi_term 0.12.1", + "ctor", + "diff", + "output_vt100", +] + [[package]] name = "proc-macro-error" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index 16aa221..3d06c94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,3 +42,6 @@ unicode-segmentation = "1.7" [target.'cfg(all(target_family="unix",not(target_os="macos")))'.dependencies] which = "4.1" + +[dev-dependencies] +pretty_assertions = "1.0.0" diff --git a/src/app.rs b/src/app.rs index b10b776..abfc7b1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,11 +8,10 @@ use crate::{ components::tab::Tab, components::{ command, ConnectionsComponent, DatabasesComponent, ErrorComponent, HelpComponent, - RecordTableComponent, SqlEditorComponent, TabComponent, TableComponent, + PropertiesComponent, RecordTableComponent, SqlEditorComponent, TabComponent, }, config::Config, }; -use database_tree::Database; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, @@ -26,10 +25,7 @@ pub enum Focus { } pub struct App { record_table: RecordTableComponent, - column_table: TableComponent, - constraint_table: TableComponent, - foreign_key_table: TableComponent, - index_table: TableComponent, + properties: PropertiesComponent, sql_editor: SqlEditorComponent, focus: Focus, tab: TabComponent, @@ -48,10 +44,7 @@ impl App { config: config.clone(), connections: ConnectionsComponent::new(config.key_config.clone(), config.conn), record_table: RecordTableComponent::new(config.key_config.clone()), - column_table: TableComponent::new(config.key_config.clone()), - constraint_table: TableComponent::new(config.key_config.clone()), - foreign_key_table: TableComponent::new(config.key_config.clone()), - index_table: TableComponent::new(config.key_config.clone()), + properties: PropertiesComponent::new(config.key_config.clone()), sql_editor: SqlEditorComponent::new(config.key_config.clone()), tab: TabComponent::new(config.key_config.clone()), help: HelpComponent::new(config.key_config.clone()), @@ -100,28 +93,14 @@ impl App { self.record_table .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))? } - Tab::Columns => { - self.column_table - .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))? - } - Tab::Constraints => self.constraint_table.draw( - f, - right_chunks[1], - matches!(self.focus, Focus::Table), - )?, - Tab::ForeignKeys => self.foreign_key_table.draw( - f, - right_chunks[1], - matches!(self.focus, Focus::Table), - )?, - Tab::Indexes => { - self.index_table - .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))? - } Tab::Sql => { self.sql_editor .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?; } + Tab::Properties => { + self.properties + .draw(f, right_chunks[1], matches!(self.focus, Focus::Table))?; + } } self.error.draw(f, Rect::default(), false)?; self.help.draw(f, Rect::default(), false)?; @@ -150,6 +129,7 @@ impl App { self.databases.commands(&mut res); self.record_table.commands(&mut res); + self.properties.commands(&mut res); res } @@ -172,18 +152,9 @@ impl App { SqlitePool::new(conn.database_url()?.as_str()).await?, )) }; - let databases = match &conn.database { - Some(database) => vec![Database::new( - database.clone(), - self.pool - .as_ref() - .unwrap() - .get_tables(database.clone()) - .await?, - )], - None => self.pool.as_ref().unwrap().get_databases().await?, - }; - self.databases.update(databases.as_slice()).unwrap(); + self.databases + .update(conn, self.pool.as_ref().unwrap()) + .await?; self.focus = Focus::DabataseList; self.record_table.reset(); self.tab.reset(); @@ -191,95 +162,6 @@ impl App { Ok(()) } - async fn update_table(&mut self) -> anyhow::Result<()> { - if let Some((database, table)) = self.databases.tree().selected_table() { - self.focus = Focus::Table; - self.record_table.reset(); - let (headers, records) = self - .pool - .as_ref() - .unwrap() - .get_records(&database, &table, 0, None) - .await?; - self.record_table - .update(records, headers, database.clone(), table.clone()); - - self.column_table.reset(); - let columns = self - .pool - .as_ref() - .unwrap() - .get_columns(&database, &table) - .await?; - if !columns.is_empty() { - self.column_table.update( - columns - .iter() - .map(|c| c.columns()) - .collect::>>(), - columns.get(0).unwrap().fields(), - database.clone(), - table.clone(), - ); - } - self.constraint_table.reset(); - let constraints = self - .pool - .as_ref() - .unwrap() - .get_constraints(&database, &table) - .await?; - if !constraints.is_empty() { - self.constraint_table.update( - constraints - .iter() - .map(|c| c.columns()) - .collect::>>(), - constraints.get(0).unwrap().fields(), - database.clone(), - table.clone(), - ); - } - self.foreign_key_table.reset(); - let foreign_keys = self - .pool - .as_ref() - .unwrap() - .get_foreign_keys(&database, &table) - .await?; - if !foreign_keys.is_empty() { - self.foreign_key_table.update( - foreign_keys - .iter() - .map(|c| c.columns()) - .collect::>>(), - foreign_keys.get(0).unwrap().fields(), - database.clone(), - table.clone(), - ); - } - self.index_table.reset(); - let indexes = self - .pool - .as_ref() - .unwrap() - .get_indexes(&database, &table) - .await?; - if !indexes.is_empty() { - self.index_table.update( - indexes - .iter() - .map(|c| c.columns()) - .collect::>>(), - indexes.get(0).unwrap().fields(), - database.clone(), - table.clone(), - ); - } - } - Ok(()) - } - async fn update_record_table(&mut self) -> anyhow::Result<()> { if let Some((database, table)) = self.databases.tree().selected_table() { let (headers, records) = self @@ -342,7 +224,21 @@ impl App { } if key == self.config.key_config.enter && self.databases.tree_focused() { - self.update_table().await?; + if let Some((database, table)) = self.databases.tree().selected_table() { + self.record_table.reset(); + let (headers, records) = self + .pool + .as_ref() + .unwrap() + .get_records(&database, &table, 0, None) + .await?; + self.record_table + .update(records, headers, database.clone(), table.clone()); + self.properties + .update(database.clone(), table.clone(), self.pool.as_ref().unwrap()) + .await?; + self.focus = Focus::Table; + } return Ok(EventState::Consumed); } } @@ -398,50 +294,6 @@ impl App { } }; } - Tab::Columns => { - if self.column_table.event(key)?.is_consumed() { - return Ok(EventState::Consumed); - }; - - if key == self.config.key_config.copy { - if let Some(text) = self.column_table.selected_cells() { - copy_to_clipboard(text.as_str())? - } - }; - } - Tab::Constraints => { - if self.constraint_table.event(key)?.is_consumed() { - return Ok(EventState::Consumed); - }; - - if key == self.config.key_config.copy { - if let Some(text) = self.constraint_table.selected_cells() { - copy_to_clipboard(text.as_str())? - } - }; - } - Tab::ForeignKeys => { - if self.foreign_key_table.event(key)?.is_consumed() { - return Ok(EventState::Consumed); - }; - - if key == self.config.key_config.copy { - if let Some(text) = self.foreign_key_table.selected_cells() { - copy_to_clipboard(text.as_str())? - } - }; - } - Tab::Indexes => { - if self.index_table.event(key)?.is_consumed() { - return Ok(EventState::Consumed); - }; - - if key == self.config.key_config.copy { - if let Some(text) = self.index_table.selected_cells() { - copy_to_clipboard(text.as_str())? - } - }; - } Tab::Sql => { if self.sql_editor.event(key)?.is_consumed() || self @@ -453,6 +305,11 @@ impl App { return Ok(EventState::Consumed); }; } + Tab::Properties => { + if self.properties.event(key)?.is_consumed() { + return Ok(EventState::Consumed); + }; + } }; } } diff --git a/src/components/command.rs b/src/components/command.rs index c17186c..961b8c6 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -3,6 +3,7 @@ use crate::config::KeyConfig; static CMD_GROUP_GENERAL: &str = "-- General --"; static CMD_GROUP_TABLE: &str = "-- Table --"; static CMD_GROUP_DATABASES: &str = "-- Databases --"; +static CMD_GROUP_PROPERTIES: &str = "-- Properties --"; #[derive(Clone, PartialEq, PartialOrd, Ord, Eq)] pub struct CommandText { @@ -135,17 +136,33 @@ pub fn tab_sql_editor(key: &KeyConfig) -> CommandText { CommandText::new(format!("SQL [{}]", key.tab_sql_editor), CMD_GROUP_TABLE) } +pub fn tab_properties(key: &KeyConfig) -> CommandText { + CommandText::new( + format!("Properties [{}]", key.tab_properties), + CMD_GROUP_TABLE, + ) +} + pub fn toggle_tabs(key_config: &KeyConfig) -> CommandText { CommandText::new( format!( - "Tab [{},{},{},{},{}]", - key_config.tab_records, + "Tab [{},{},{}]", + key_config.tab_records, key_config.tab_properties, key_config.tab_sql_editor + ), + CMD_GROUP_GENERAL, + ) +} + +pub fn toggle_property_tabs(key_config: &KeyConfig) -> CommandText { + CommandText::new( + format!( + "Tab [{},{},{},{}]", key_config.tab_columns, key_config.tab_constraints, key_config.tab_foreign_keys, key_config.tab_indexes ), - CMD_GROUP_GENERAL, + CMD_GROUP_PROPERTIES, ) } diff --git a/src/components/databases.rs b/src/components/databases.rs index 472f607..e4af306 100644 --- a/src/components/databases.rs +++ b/src/components/databases.rs @@ -3,7 +3,8 @@ use super::{ EventState, }; use crate::components::command::{self, CommandInfo}; -use crate::config::KeyConfig; +use crate::config::{Connection, KeyConfig}; +use crate::database::Pool; use crate::event::Key; use crate::ui::common_nav; use crate::ui::scrolllist::draw_list_block; @@ -53,8 +54,15 @@ impl DatabasesComponent { } } - pub fn update(&mut self, list: &[Database]) -> Result<()> { - self.tree = DatabaseTree::new(list, &BTreeSet::new())?; + pub async fn update(&mut self, connection: &Connection, pool: &Box) -> Result<()> { + let databases = match &connection.database { + Some(database) => vec![Database::new( + database.clone(), + pool.get_tables(database.clone()).await?, + )], + None => pool.get_databases().await?, + }; + self.tree = DatabaseTree::new(databases.as_slice(), &BTreeSet::new())?; self.filterd_tree = None; self.filter.reset(); Ok(()) diff --git a/src/components/mod.rs b/src/components/mod.rs index 4b9f187..ef70ecc 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -5,6 +5,7 @@ pub mod database_filter; pub mod databases; pub mod error; pub mod help; +pub mod properties; pub mod record_table; pub mod sql_editor; pub mod tab; @@ -24,6 +25,7 @@ pub use database_filter::DatabaseFilterComponent; pub use databases::DatabasesComponent; pub use error::ErrorComponent; pub use help::HelpComponent; +pub use properties::PropertiesComponent; pub use record_table::RecordTableComponent; pub use sql_editor::SqlEditorComponent; pub use tab::TabComponent; diff --git a/src/components/properties.rs b/src/components/properties.rs new file mode 100644 index 0000000..acd6ee4 --- /dev/null +++ b/src/components/properties.rs @@ -0,0 +1,200 @@ +use super::{Component, EventState, StatefulDrawableComponent}; +use crate::clipboard::copy_to_clipboard; +use crate::components::command::{self, CommandInfo}; +use crate::components::TableComponent; +use crate::config::KeyConfig; +use crate::database::Pool; +use crate::event::Key; +use anyhow::Result; +use async_trait::async_trait; +use database_tree::{Database, Table}; +use tui::{ + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Style}, + widgets::{Block, Borders, List, ListItem}, + Frame, +}; + +#[derive(Debug, PartialEq)] +pub enum Focus { + Column, + Constraint, + ForeignKey, + Index, +} + +impl std::fmt::Display for Focus { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +pub struct PropertiesComponent { + column_table: TableComponent, + constraint_table: TableComponent, + foreign_key_table: TableComponent, + index_table: TableComponent, + focus: Focus, + key_config: KeyConfig, +} + +impl PropertiesComponent { + pub fn new(key_config: KeyConfig) -> Self { + Self { + column_table: TableComponent::new(key_config.clone()), + constraint_table: TableComponent::new(key_config.clone()), + foreign_key_table: TableComponent::new(key_config.clone()), + index_table: TableComponent::new(key_config.clone()), + focus: Focus::Column, + key_config, + } + } + + fn focused_component(&mut self) -> &mut TableComponent { + match self.focus { + Focus::Column => &mut self.column_table, + Focus::Constraint => &mut self.constraint_table, + Focus::ForeignKey => &mut self.foreign_key_table, + Focus::Index => &mut self.index_table, + } + } + + pub async fn update( + &mut self, + database: Database, + table: Table, + pool: &Box, + ) -> Result<()> { + self.column_table.reset(); + let columns = pool.get_columns(&database, &table).await?; + if !columns.is_empty() { + self.column_table.update( + columns + .iter() + .map(|c| c.columns()) + .collect::>>(), + columns.get(0).unwrap().fields(), + database.clone(), + table.clone(), + ); + } + self.constraint_table.reset(); + let constraints = pool.get_constraints(&database, &table).await?; + if !constraints.is_empty() { + self.constraint_table.update( + constraints + .iter() + .map(|c| c.columns()) + .collect::>>(), + constraints.get(0).unwrap().fields(), + database.clone(), + table.clone(), + ); + } + self.foreign_key_table.reset(); + let foreign_keys = pool.get_foreign_keys(&database, &table).await?; + if !foreign_keys.is_empty() { + self.foreign_key_table.update( + foreign_keys + .iter() + .map(|c| c.columns()) + .collect::>>(), + foreign_keys.get(0).unwrap().fields(), + database.clone(), + table.clone(), + ); + } + self.index_table.reset(); + let indexes = pool.get_indexes(&database, &table).await?; + if !indexes.is_empty() { + self.index_table.update( + indexes + .iter() + .map(|c| c.columns()) + .collect::>>(), + indexes.get(0).unwrap().fields(), + database.clone(), + table.clone(), + ); + } + Ok(()) + } + + fn tab_names(&self) -> Vec<(Focus, String)> { + vec![ + (Focus::Column, command::tab_columns(&self.key_config).name), + ( + Focus::Constraint, + command::tab_constraints(&self.key_config).name, + ), + ( + Focus::ForeignKey, + command::tab_foreign_keys(&self.key_config).name, + ), + (Focus::Index, command::tab_indexes(&self.key_config).name), + ] + } +} + +impl StatefulDrawableComponent for PropertiesComponent { + fn draw(&mut self, f: &mut Frame, area: Rect, focused: bool) -> Result<()> { + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Length(20), Constraint::Min(1)]) + .split(area); + + let tab_names = self + .tab_names() + .iter() + .map(|(f, c)| { + ListItem::new(c.to_string()).style(if *f == self.focus { + Style::default().bg(Color::Blue) + } else { + Style::default() + }) + }) + .collect::>(); + + let tab_list = List::new(tab_names) + .block(Block::default().borders(Borders::ALL).style(if focused { + Style::default() + } else { + Style::default().fg(Color::DarkGray) + })) + .style(Style::default()); + + f.render_widget(tab_list, layout[0]); + + self.focused_component().draw(f, layout[1], focused)?; + Ok(()) + } +} + +#[async_trait] +impl Component for PropertiesComponent { + fn commands(&self, out: &mut Vec) { + out.push(CommandInfo::new(command::toggle_property_tabs( + &self.key_config, + ))); + } + + fn event(&mut self, key: Key) -> Result { + self.focused_component().event(key)?; + + if key == self.key_config.copy { + if let Some(text) = self.focused_component().selected_cells() { + copy_to_clipboard(text.as_str())? + } + } else if key == self.key_config.tab_columns { + self.focus = Focus::Column; + } else if key == self.key_config.tab_constraints { + self.focus = Focus::Constraint; + } else if key == self.key_config.tab_foreign_keys { + self.focus = Focus::ForeignKey; + } else if key == self.key_config.tab_indexes { + self.focus = Focus::Index; + } + Ok(EventState::NotConsumed) + } +} diff --git a/src/components/sql_editor.rs b/src/components/sql_editor.rs index 841ec3e..50e53cf 100644 --- a/src/components/sql_editor.rs +++ b/src/components/sql_editor.rs @@ -20,7 +20,6 @@ use unicode_width::UnicodeWidthStr; struct QueryResult { updated_rows: u64, - query: String, } impl QueryResult { @@ -275,10 +274,7 @@ impl Component for SqlEditorComponent { self.query_result = None; } ExecuteResult::Write { updated_rows } => { - self.query_result = Some(QueryResult { - updated_rows, - query: query.to_string(), - }) + self.query_result = Some(QueryResult { updated_rows }) } } return Ok(EventState::Consumed); diff --git a/src/components/tab.rs b/src/components/tab.rs index e2356c1..f6aae60 100644 --- a/src/components/tab.rs +++ b/src/components/tab.rs @@ -16,10 +16,7 @@ use tui::{ #[derive(Debug, Clone, Copy, EnumIter)] pub enum Tab { Records, - Columns, - Constraints, - ForeignKeys, - Indexes, + Properties, Sql, } @@ -49,10 +46,7 @@ impl TabComponent { fn names(&self) -> Vec { vec![ command::tab_records(&self.key_config).name, - command::tab_columns(&self.key_config).name, - command::tab_constraints(&self.key_config).name, - command::tab_foreign_keys(&self.key_config).name, - command::tab_indexes(&self.key_config).name, + command::tab_properties(&self.key_config).name, command::tab_sql_editor(&self.key_config).name, ] } @@ -82,18 +76,12 @@ impl Component for TabComponent { if key == self.key_config.tab_records { self.selected_tab = Tab::Records; return Ok(EventState::Consumed); - } else if key == self.key_config.tab_columns { - self.selected_tab = Tab::Columns; - return Ok(EventState::Consumed); - } else if key == self.key_config.tab_constraints { - self.selected_tab = Tab::Constraints; - return Ok(EventState::Consumed); - } else if key == self.key_config.tab_foreign_keys { - self.selected_tab = Tab::ForeignKeys; - return Ok(EventState::Consumed); - } else if key == self.key_config.tab_indexes { + } else if key == self.key_config.tab_sql_editor { self.selected_tab = Tab::Sql; return Ok(EventState::Consumed); + } else if key == self.key_config.tab_properties { + self.selected_tab = Tab::Properties; + return Ok(EventState::Consumed); } Ok(EventState::NotConsumed) } diff --git a/src/config.rs b/src/config.rs index fbf12b6..a7f116a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,9 @@ use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use structopt::StructOpt; +#[cfg(test)] +use serde::Serialize; + #[derive(StructOpt, Debug)] pub struct CliConfig { /// Set the config file @@ -73,6 +76,7 @@ pub struct Connection { } #[derive(Debug, Deserialize, Clone)] +#[cfg_attr(test, derive(Serialize))] pub struct KeyConfig { pub scroll_up: Key, pub scroll_down: Key, @@ -104,9 +108,10 @@ pub struct KeyConfig { pub tab_constraints: Key, pub tab_foreign_keys: Key, pub tab_indexes: Key, + pub tab_sql_editor: Key, + pub tab_properties: Key, pub extend_or_shorten_widget_width_to_right: Key, pub extend_or_shorten_widget_width_to_left: Key, - pub tab_sql_editor: Key, } impl Default for KeyConfig { @@ -138,13 +143,14 @@ impl Default for KeyConfig { extend_selection_by_one_cell_down: Key::Char('J'), extend_selection_by_one_cell_up: Key::Char('K'), tab_records: Key::Char('1'), - tab_columns: Key::Char('2'), - tab_constraints: Key::Char('3'), - tab_foreign_keys: Key::Char('4'), - tab_indexes: Key::Char('5'), + tab_properties: Key::Char('2'), + tab_sql_editor: Key::Char('3'), + tab_columns: Key::Char('4'), + tab_constraints: Key::Char('5'), + tab_foreign_keys: Key::Char('6'), + tab_indexes: Key::Char('7'), extend_or_shorten_widget_width_to_right: Key::Char('>'), extend_or_shorten_widget_width_to_left: Key::Char('<'), - tab_sql_editor: Key::Char('6'), } } } @@ -304,9 +310,30 @@ fn expand_path(path: &Path) -> Option { #[cfg(test)] mod test { - use super::{expand_path, Path, PathBuf}; + use super::{expand_path, KeyConfig, Path, PathBuf}; + use serde_json::Value; use std::env; + #[test] + fn test_overlappted_key() { + let value: Value = + serde_json::from_str(&serde_json::to_string(&KeyConfig::default()).unwrap()).unwrap(); + if let Value::Object(map) = value { + let mut values: Vec = map + .values() + .map(|v| match v { + Value::Object(map) => Some(format!("{:?}", map)), + _ => None, + }) + .flatten() + .collect(); + values.sort(); + let before_values = values.clone(); + values.dedup(); + pretty_assertions::assert_eq!(before_values, values); + } + } + #[test] #[cfg(unix)] fn test_expand_path() { diff --git a/src/event/key.rs b/src/event/key.rs index cfbe1eb..0f92fb3 100644 --- a/src/event/key.rs +++ b/src/event/key.rs @@ -2,8 +2,12 @@ use crossterm::event; use serde::Deserialize; use std::fmt; +#[cfg(test)] +use serde::Serialize; + /// Represents a key. #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Deserialize)] +#[cfg_attr(test, derive(Serialize))] pub enum Key { /// Both Enter (or Return) and numpad Enter Enter,