use crate::app::{App, BoardFocus, DialogMode};
use crossterm::event::KeyCode;
use kanban_domain::card_lifecycle::sorted_board_columns;
use kanban_domain::commands::{
BoardCommand, CardCommand, ColumnCommand, Command, CreateColumn, DeleteColumn, MoveCard,
SetBoardTaskListView, UpdateColumn,
};
use kanban_domain::{ColumnUpdate, TaskListView};
impl App {
pub fn handle_create_column_key(&mut self) {
if self.focus.board_focus == BoardFocus::Columns {
{
if self.active_board().is_some() {
self.open_dialog(DialogMode::CreateColumn);
self.input.clear();
self.dialog_input.default_status_selection.set(Some(
kanban_view::selection_dialog::popup_index_of_default_status(None),
));
self.dialog_input.reset_create_column_focus();
}
}
}
}
pub fn handle_rename_column_key(&mut self) {
if self.focus.board_focus == BoardFocus::Columns
&& self.dialog_input.column_list.get_selected_index().is_some()
{
{
if let Some(board) = self.active_board() {
let board_id = board.id;
let board_columns = self.visible_board_columns(board_id);
if let Some(column_idx) = self.dialog_input.column_list.get_selected_index() {
if let Some(column) = board_columns.get(column_idx) {
self.input.set(column.name.clone());
self.open_dialog(DialogMode::RenameColumn);
}
}
}
}
}
}
pub fn handle_set_column_default_status_key(&mut self) {
if self.focus.board_focus == BoardFocus::Columns
&& self.dialog_input.column_list.get_selected_index().is_some()
{
if let Some(board) = self.active_board() {
let board_id = board.id;
if let Some(column_idx) = self.dialog_input.column_list.get_selected_index() {
if let Some(column) = self.visible_board_columns(board_id).get(column_idx) {
let idx = kanban_view::selection_dialog::popup_index_of_default_status(
column.default_status,
);
self.dialog_input.default_status_selection.set(Some(idx));
self.open_dialog(DialogMode::SetColumnDefaultStatus);
}
}
}
}
}
pub fn handle_delete_column_key(&mut self) {
if self.focus.board_focus == BoardFocus::Columns
&& self.dialog_input.column_list.get_selected_index().is_some()
{
{
if let Some(board) = self.active_board() {
let column_count = self
.model
.columns()
.iter()
.filter(|col| col.board_id == board.id)
.count();
if column_count > 1 {
self.open_dialog(DialogMode::DeleteColumnConfirm);
} else {
tracing::warn!("Cannot delete the last column");
}
}
}
}
}
pub fn handle_move_column_up(&mut self) {
if self.filter.column_search.is_active {
return;
}
if self.focus.board_focus == BoardFocus::Columns
&& self.dialog_input.column_list.get_selected_index().is_some()
{
{
if let Some(board) = self.active_board() {
let board_columns: Vec<(uuid::Uuid, i32)> =
sorted_board_columns(board.id, self.model.columns())
.into_iter()
.map(|col| (col.id, col.position))
.collect();
if let Some(selected_idx) = self.dialog_input.column_list.get_selected_index() {
if selected_idx > 0 && selected_idx < board_columns.len() {
let prev_col_id = board_columns[selected_idx - 1].0;
let curr_col_id = board_columns[selected_idx].0;
let prev_pos = board_columns[selected_idx - 1].1;
let curr_pos = board_columns[selected_idx].1;
let cmd1 = Command::Column(ColumnCommand::Update(UpdateColumn {
column_id: prev_col_id,
updates: ColumnUpdate {
position: Some(curr_pos),
..Default::default()
},
}));
let cmd2 = Command::Column(ColumnCommand::Update(UpdateColumn {
column_id: curr_col_id,
updates: ColumnUpdate {
position: Some(prev_pos),
..Default::default()
},
}));
if let Err(e) = self.execute_commands_batch(vec![cmd1, cmd2]) {
tracing::error!("Failed to move column: {}", e);
self.set_error(format!("Failed to move column: {}", e));
return;
}
self.reload_model();
self.dialog_input
.column_list
.update_item_count(board_columns.len());
self.dialog_input
.column_list
.set_selected_index(Some(selected_idx - 1));
tracing::info!("Moved column up");
}
}
}
}
}
}
pub fn handle_move_column_down(&mut self) {
if self.filter.column_search.is_active {
return;
}
if self.focus.board_focus == BoardFocus::Columns
&& self.dialog_input.column_list.get_selected_index().is_some()
{
{
if let Some(board) = self.active_board() {
let board_columns: Vec<(uuid::Uuid, i32)> =
sorted_board_columns(board.id, self.model.columns())
.into_iter()
.map(|col| (col.id, col.position))
.collect();
if let Some(selected_idx) = self.dialog_input.column_list.get_selected_index() {
if selected_idx < board_columns.len() - 1 {
let curr_col_id = board_columns[selected_idx].0;
let next_col_id = board_columns[selected_idx + 1].0;
let curr_pos = board_columns[selected_idx].1;
let next_pos = board_columns[selected_idx + 1].1;
let cmd1 = Command::Column(ColumnCommand::Update(UpdateColumn {
column_id: next_col_id,
updates: ColumnUpdate {
position: Some(curr_pos),
..Default::default()
},
}));
let cmd2 = Command::Column(ColumnCommand::Update(UpdateColumn {
column_id: curr_col_id,
updates: ColumnUpdate {
position: Some(next_pos),
..Default::default()
},
}));
if let Err(e) = self.execute_commands_batch(vec![cmd1, cmd2]) {
tracing::error!("Failed to move column: {}", e);
self.set_error(format!("Failed to move column: {}", e));
return;
}
self.reload_model();
let column_count = board_columns.len();
self.dialog_input
.column_list
.update_item_count(column_count);
self.dialog_input
.column_list
.set_selected_index(Some(selected_idx + 1));
tracing::info!("Moved column down");
}
}
}
}
}
}
pub fn handle_toggle_task_list_view(&mut self) {
if self.focus.active != crate::app::Focus::Cards {
return;
}
if let Some(board) = self.active_board() {
let current_view_idx = match board.task_list_view {
TaskListView::Flat => 0,
TaskListView::GroupedByColumn => 1,
TaskListView::ColumnView => 2,
};
self.dialog_input
.task_list_view_selection
.set(Some(current_view_idx));
self.open_dialog(DialogMode::SelectTaskListView);
}
}
pub fn create_column(&mut self) {
{
let board_id = self.active_board().map(|board| board.id);
if let Some(board_id) = board_id {
let column_name = self.input.as_str().trim().to_string();
if column_name.is_empty() {
tracing::warn!("Column name cannot be empty");
return;
}
let position = self
.model
.columns()
.iter()
.filter(|col| col.board_id == board_id)
.map(|col| col.position)
.max()
.unwrap_or(-1)
+ 1;
let default_status = self
.dialog_input
.default_status_selection
.get()
.and_then(kanban_view::selection_dialog::default_status_at_popup_index)
.flatten();
let cmd = Command::Column(ColumnCommand::Create(CreateColumn {
id: uuid::Uuid::new_v4(),
board_id,
name: column_name.clone(),
position,
default_status,
}));
let prior_column_count = self
.model
.columns()
.iter()
.filter(|col| col.board_id == board_id)
.count();
if let Err(e) = self.execute_command(cmd) {
tracing::error!("Failed to create column: {}", e);
self.set_error(format!("Failed to create column: {}", e));
return;
}
self.reload_model();
tracing::info!("Created column: {} (position: {})", column_name, position);
self.dialog_input
.column_list
.update_item_count(prior_column_count + 1);
self.dialog_input
.column_list
.set_selected_index(Some(prior_column_count));
}
}
}
pub fn rename_column(&mut self) {
{
let column_info = {
if let Some(board) = self.active_board() {
let board_id = board.id;
if let Some(column_idx) = self.dialog_input.column_list.get_selected_index() {
self.visible_board_columns(board_id)
.get(column_idx)
.map(|col| col.id)
} else {
None
}
} else {
None
}
};
if let Some(column_id) = column_info {
let new_name = self.input.as_str().trim().to_string();
if new_name.is_empty() {
tracing::warn!("Column name cannot be empty");
return;
}
let cmd = Command::Column(ColumnCommand::Update(UpdateColumn {
column_id,
updates: ColumnUpdate {
name: Some(new_name.clone()),
..Default::default()
},
}));
if let Err(e) = self.execute_command(cmd) {
tracing::error!("Failed to rename column: {}", e);
self.set_error(format!("Failed to rename column: {}", e));
return;
}
self.reload_model();
tracing::info!("Renamed column to: {}", new_name);
}
}
}
pub fn delete_column(&mut self) {
{
let delete_info = {
if let Some(board) = self.active_board() {
let board_id = board.id;
if let Some(column_idx) = self.dialog_input.column_list.get_selected_index() {
let all_columns: Vec<(uuid::Uuid, String)> =
sorted_board_columns(board_id, self.model.columns())
.into_iter()
.map(|col| (col.id, col.name.clone()))
.collect();
if all_columns.len() <= 1 {
return;
}
let column_to_delete = self
.visible_board_columns(board_id)
.get(column_idx)
.map(|col| (col.id, col.name.clone()));
let first_column_id = all_columns.first().map(|(id, _)| *id);
if let Some((column_id, column_name)) = column_to_delete {
let cards_to_move: Vec<(uuid::Uuid, i32)> = self
.model
.live_cards()
.iter()
.filter(|card| card.column_id == column_id)
.map(|card| (card.id, card.position))
.collect();
Some((
column_id,
column_name,
first_column_id,
cards_to_move,
column_idx,
))
} else {
None
}
} else {
None
}
} else {
None
}
};
if let Some((column_id, column_name, first_column_id, cards_to_move, column_idx)) =
delete_info
{
let remaining_after_delete = {
let columns = self.model.columns();
self.active_board()
.map(|b| {
columns
.iter()
.filter(|c| c.board_id == b.id && c.id != column_id)
.count()
})
.unwrap_or(0)
};
tracing::warn!("Cannot delete the last column");
let mut commands: Vec<Command> = Vec::new();
if let Some(target_column_id) = first_column_id {
if target_column_id != column_id {
for (card_id, position) in cards_to_move {
commands.push(Command::Card(CardCommand::Move(MoveCard {
card_id,
new_column_id: target_column_id,
new_position: position,
})));
}
}
}
commands.push(Command::Column(ColumnCommand::Delete(DeleteColumn {
column_id,
})));
if let Err(e) = self.execute_commands_batch(commands) {
tracing::error!("Failed to delete column: {}", e);
self.set_error(format!("Failed to delete column: {}", e));
return;
}
self.reload_model();
tracing::info!("Deleted column: {}", column_name);
self.dialog_input
.column_list
.update_item_count(remaining_after_delete);
if remaining_after_delete > 0 {
if column_idx >= remaining_after_delete {
self.dialog_input
.column_list
.set_selected_index(Some(remaining_after_delete - 1));
} else {
self.dialog_input
.column_list
.set_selected_index(Some(column_idx));
}
} else {
self.dialog_input.column_list.set_selected_index(None);
}
}
}
}
fn close_create_column_dialog(&mut self) {
self.pop_mode();
self.focus.board_focus = BoardFocus::Columns;
self.input.clear();
self.dialog_input.default_status_selection.clear();
self.dialog_input.reset_create_column_focus();
}
pub fn handle_create_column_dialog(&mut self, key_code: KeyCode) {
match key_code {
KeyCode::Esc => {
self.close_create_column_dialog();
return;
}
KeyCode::Enter => {
self.create_column();
self.close_create_column_dialog();
return;
}
KeyCode::Tab => {
self.dialog_input.toggle_create_column_focus();
return;
}
_ => {}
}
if self.dialog_input.create_column_focus_is_name() {
match key_code {
KeyCode::Char(c) => {
self.input.insert_char(c);
}
KeyCode::Backspace => {
self.input.backspace();
}
KeyCode::Left => {
self.input.move_left();
}
KeyCode::Right => {
self.input.move_right();
}
_ => {}
}
return;
}
match key_code {
KeyCode::Char('j') | KeyCode::Down => {
self.dialog_input
.default_status_selection
.next(kanban_view::selection_dialog::DEFAULT_STATUS_POPUP_ORDER.len());
}
KeyCode::Char('k') | KeyCode::Up => {
self.dialog_input.default_status_selection.prev();
}
_ => {}
}
}
pub fn handle_rename_column_dialog(&mut self, key_code: KeyCode) {
match key_code {
KeyCode::Esc => {
self.pop_mode();
self.focus.board_focus = BoardFocus::Columns;
self.input.clear();
}
KeyCode::Enter => {
self.rename_column();
self.pop_mode();
self.focus.board_focus = BoardFocus::Columns;
self.input.clear();
}
KeyCode::Char(c) => {
self.input.insert_char(c);
}
KeyCode::Backspace => {
self.input.backspace();
}
KeyCode::Left => {
self.input.move_left();
}
KeyCode::Right => {
self.input.move_right();
}
_ => {}
}
}
pub fn handle_delete_column_confirm_popup(&mut self, key_code: KeyCode) {
match key_code {
KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
self.delete_column();
self.pop_mode();
self.focus.board_focus = BoardFocus::Columns;
}
KeyCode::Char('n')
| KeyCode::Char('N')
| KeyCode::Char('q')
| KeyCode::Char('Q')
| KeyCode::Esc => {
self.pop_mode();
self.focus.board_focus = BoardFocus::Columns;
}
_ => {}
}
}
pub fn handle_select_task_list_view_popup(&mut self, key_code: KeyCode) {
match key_code {
KeyCode::Esc => {
self.pop_mode();
self.dialog_input.task_list_view_selection.clear();
}
KeyCode::Char('j') | KeyCode::Down => {
self.dialog_input.task_list_view_selection.next(3);
}
KeyCode::Char('k') | KeyCode::Up => {
self.dialog_input.task_list_view_selection.prev();
}
KeyCode::Enter | KeyCode::Char(' ') => {
if let Some(view_idx) = self.dialog_input.task_list_view_selection.get() {
let view = match view_idx {
0 => TaskListView::Flat,
1 => TaskListView::GroupedByColumn,
2 => TaskListView::ColumnView,
_ => TaskListView::Flat,
};
let selected_card_id = self.get_selected_card_id();
if let Some(board_id) = self.active_board().map(|b| b.id) {
{
let cmd = Command::Board(BoardCommand::SetTaskListView(
SetBoardTaskListView { board_id, view },
));
if let Err(e) = self.execute_command(cmd) {
tracing::error!("Failed to set task list view: {}", e);
self.set_error(format!("Failed to set task list view: {}", e));
self.pop_mode();
self.dialog_input.task_list_view_selection.clear();
return;
}
self.reload_model();
self.switch_view_strategy(view);
if let Some(card_id) = selected_card_id {
self.select_card_by_id(card_id);
}
tracing::info!("Updated task list view to: {:?}", view);
}
}
}
self.pop_mode();
self.dialog_input.task_list_view_selection.clear();
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use crate::app::BoardFocus;
use crate::App;
use crossterm::event::KeyCode;
fn create_named_board(app: &mut App, name: &str) {
app.input.set(name.to_string());
app.create_board();
app.input.clear();
app.selection.active_board_id = app
.model
.boards_state()
.loaded_or_empty()
.first()
.map(|b| b.id);
}
fn create_named_column(app: &mut App, name: &str) {
app.input.set(name.to_string());
app.create_column();
app.input.clear();
}
#[test]
fn test_tui_create_column_routes_through_factory() {
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
create_named_column(&mut app, "In Review");
let columns = app.ctx.data_store().list_all_columns().unwrap();
let column = columns
.iter()
.find(|c| c.board_id == board_id && c.name == "In Review")
.expect("created column present in store");
assert_eq!(column.created_at, column.updated_at);
assert_eq!(column.position, 3);
}
#[test]
fn test_tui_create_column_rejects_blank_name() {
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
let before = app
.ctx
.data_store()
.list_all_columns()
.unwrap()
.iter()
.filter(|c| c.board_id == board_id)
.count();
create_named_column(&mut app, " ");
let after = app
.ctx
.data_store()
.list_all_columns()
.unwrap()
.iter()
.filter(|c| c.board_id == board_id)
.count();
assert_eq!(after, before, "blank column name must not be written");
}
#[test]
fn test_move_column_up_swaps_correct_pair_regardless_of_model_iteration_order() {
use kanban_domain::KanbanOperations;
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
let doing_id = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.find(|c| c.name == "Doing")
.unwrap()
.id;
let complete_id = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.find(|c| c.name == "Complete")
.unwrap()
.id;
let new_col = app
.ctx
.create_column(board_id, "New".to_string(), Some(1))
.unwrap();
let mut snapshot = app.ctx.snapshot().unwrap();
let doing_idx = snapshot
.columns
.iter()
.position(|c| c.id == doing_id)
.unwrap();
let new_idx = snapshot
.columns
.iter()
.position(|c| c.id == new_col.id)
.unwrap();
snapshot.columns.swap(doing_idx, new_idx);
app.model.load_from_snapshot(snapshot);
app.selection.active_board_id = Some(board_id);
app.focus.board_focus = BoardFocus::Columns;
app.dialog_input.column_list.update_item_count(4);
app.dialog_input.column_list.set_selected_index(Some(3));
app.handle_move_column_up();
let doing = app.ctx.data_store().get_column(doing_id).unwrap().unwrap();
let new = app
.ctx
.data_store()
.get_column(new_col.id)
.unwrap()
.unwrap();
let complete = app
.ctx
.data_store()
.get_column(complete_id)
.unwrap()
.unwrap();
assert_eq!(
doing.position, 1,
"Doing is not adjacent to Complete in canonical order and must be untouched"
);
assert_eq!(
new.position, 2,
"New (Complete's canonical predecessor) must be bumped to Complete's old position"
);
assert_eq!(
complete.position, 1,
"Complete must take New's old position"
);
}
#[test]
fn test_rename_column_resolves_correct_column_regardless_of_model_iteration_order() {
use kanban_domain::KanbanOperations;
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
let doing_id = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.find(|c| c.name == "Doing")
.unwrap()
.id;
let new_col = app
.ctx
.create_column(board_id, "New".to_string(), Some(1))
.unwrap();
let mut snapshot = app.ctx.snapshot().unwrap();
let doing_idx = snapshot
.columns
.iter()
.position(|c| c.id == doing_id)
.unwrap();
let new_idx = snapshot
.columns
.iter()
.position(|c| c.id == new_col.id)
.unwrap();
snapshot.columns.swap(doing_idx, new_idx);
app.model.load_from_snapshot(snapshot);
app.selection.active_board_id = Some(board_id);
app.focus.board_focus = BoardFocus::Columns;
app.dialog_input.column_list.update_item_count(4);
app.dialog_input.column_list.set_selected_index(Some(2));
app.handle_rename_column_key();
assert_eq!(app.input.as_str(), "New");
}
#[test]
fn test_rename_column_dialog_confirm_resolves_correct_column_regardless_of_model_iteration_order(
) {
use kanban_domain::KanbanOperations;
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
let doing_id = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.find(|c| c.name == "Doing")
.unwrap()
.id;
let new_col = app
.ctx
.create_column(board_id, "New".to_string(), Some(1))
.unwrap();
let mut snapshot = app.ctx.snapshot().unwrap();
let doing_idx = snapshot
.columns
.iter()
.position(|c| c.id == doing_id)
.unwrap();
let new_idx = snapshot
.columns
.iter()
.position(|c| c.id == new_col.id)
.unwrap();
snapshot.columns.swap(doing_idx, new_idx);
app.model.load_from_snapshot(snapshot);
app.selection.active_board_id = Some(board_id);
app.focus.board_focus = BoardFocus::Columns;
app.dialog_input.column_list.update_item_count(4);
app.dialog_input.column_list.set_selected_index(Some(2));
app.handle_rename_column_key();
assert_eq!(app.input.as_str(), "New");
app.input.set("Renamed".to_string());
app.handle_rename_column_dialog(KeyCode::Enter);
let doing = app.ctx.data_store().get_column(doing_id).unwrap().unwrap();
let new_col_after = app
.ctx
.data_store()
.get_column(new_col.id)
.unwrap()
.unwrap();
assert_eq!(doing.name, "Doing", "Doing must be untouched");
assert_eq!(
new_col_after.name, "Renamed",
"New (canonical index 2) must be the one actually renamed, regardless of scrambled model order"
);
}
#[test]
fn test_move_column_down_noop_while_column_search_active() {
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
app.focus.board_focus = BoardFocus::Columns;
app.dialog_input.column_list.update_item_count(3);
app.dialog_input.column_list.set_selected_index(Some(0));
let before: Vec<(uuid::Uuid, i32)> = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.map(|c| (c.id, c.position))
.collect();
app.filter.column_search.activate();
app.handle_move_column_down();
let after: Vec<(uuid::Uuid, i32)> = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.map(|c| (c.id, c.position))
.collect();
assert_eq!(
before, after,
"reorder must not touch positions while column search is active"
);
}
#[test]
fn test_move_column_up_noop_while_column_search_active() {
let mut app = App::test_default();
create_named_board(&mut app, "Roadmap");
let board_id = app.ctx.data_store().list_boards().unwrap()[0].id;
app.focus.board_focus = BoardFocus::Columns;
app.dialog_input.column_list.update_item_count(3);
app.dialog_input.column_list.set_selected_index(Some(1));
let before: Vec<(uuid::Uuid, i32)> = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.map(|c| (c.id, c.position))
.collect();
app.filter.column_search.activate();
app.handle_move_column_up();
let after: Vec<(uuid::Uuid, i32)> = app
.ctx
.data_store()
.list_columns_by_board(board_id)
.unwrap()
.iter()
.map(|c| (c.id, c.position))
.collect();
assert_eq!(
before, after,
"reorder must not touch positions while column search is active"
);
}
#[test]
fn test_rename_column_dialog_confirm_renames_filtered_selection_not_unfiltered_index() {
use kanban_domain::KanbanOperations;
let mut app = App::test_default();
let board = app.ctx.create_board("Board".into(), None).unwrap();
for (name, position) in [
("Todo", 0),
("In Progress", 1),
("TODO Later", 2),
("Done", 3),
] {
app.ctx
.create_column(board.id, name.to_string(), Some(position))
.unwrap();
}
let board_columns = app
.ctx
.data_store()
.list_columns_by_board(board.id)
.unwrap();
let in_progress_id = board_columns
.iter()
.find(|c| c.name == "In Progress")
.unwrap()
.id;
let todo_later_id = board_columns
.iter()
.find(|c| c.name == "TODO Later")
.unwrap()
.id;
app.selection.active_board_id = Some(board.id);
app.reload_model();
app.prepare_frame();
app.focus.board_focus = BoardFocus::Columns;
app.filter.column_search.activate();
for c in "todo".chars() {
app.filter.column_search.input.insert_char(c);
}
app.dialog_input.column_list.update_item_count(2);
app.dialog_input.column_list.set_selected_index(Some(1));
app.handle_rename_column_key();
assert_eq!(
app.input.as_str(),
"TODO Later",
"the rename dialog must pre-populate with the filtered selection's name"
);
app.input.set("Renamed".to_string());
app.handle_rename_column_dialog(KeyCode::Enter);
let in_progress = app
.ctx
.data_store()
.get_column(in_progress_id)
.unwrap()
.unwrap();
let todo_later = app
.ctx
.data_store()
.get_column(todo_later_id)
.unwrap()
.unwrap();
assert_eq!(
in_progress.name, "In Progress",
"unfiltered index 1 (\"In Progress\") must not be touched by a filtered rename"
);
assert_eq!(
todo_later.name, "Renamed",
"the actually-selected filtered item (\"TODO Later\") must be the one renamed"
);
}
}