#[allow(clippy::wildcard_imports)]
use super::super::*;
#[allow(clippy::wildcard_imports)]
use super::helpers::*;
#[test]
fn m_opens_planning_modal_from_board() {
let (_tmp, mut app) = make_app();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
assert!(
matches!(app.mode, Mode::PlanningManagement),
"m flips board mode to planning management"
);
assert!(app.planning.is_some(), "planning modal state is opened");
}
#[test]
fn q_and_esc_close_planning_modal() {
let (_tmp, mut app) = make_app();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('q'))).unwrap();
assert!(matches!(app.mode, Mode::Board));
assert!(app.planning.is_none());
let (_tmp, mut app) = make_app();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
assert!(matches!(app.mode, Mode::Board), "Esc closes the modal");
assert!(app.planning.is_none());
}
#[test]
fn esc_cancels_planning_form_back_to_modal() {
let (_tmp, mut app) = make_app();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
assert!(matches!(app.mode, Mode::PlanningForm));
assert!(app.planning_form.is_some());
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
assert!(
matches!(app.mode, Mode::PlanningManagement),
"Esc in the form returns to the modal"
);
assert!(app.planning_form.is_none());
assert!(app.planning.is_some(), "the modal stays open underneath");
}
#[test]
fn planning_browse_navigates_and_switches_segments() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('j'))).unwrap();
assert_eq!(app.planning.as_ref().unwrap().selected, 1);
let _ = app.handle_key(press(KeyCode::Char('j'))).unwrap();
assert_eq!(
app.planning.as_ref().unwrap().selected,
1,
"cursor clamps at the last cycle"
);
let _ = app.handle_key(press(KeyCode::Char('k'))).unwrap();
assert_eq!(app.planning.as_ref().unwrap().selected, 0);
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let modal = app.planning.as_ref().unwrap();
assert_eq!(
modal.segment,
crate::ui::planning::PlanningSegment::Milestones,
"Tab switches to the milestones segment"
);
assert_eq!(modal.selected, 0, "segment switch resets the row cursor");
let _ = app.handle_key(press(KeyCode::Char('j'))).unwrap();
assert_eq!(
app.planning.as_ref().unwrap().selected,
0,
"a single milestone clamps the cursor"
);
let _ = app.handle_key(press(KeyCode::BackTab)).unwrap();
assert_eq!(
app.planning.as_ref().unwrap().segment,
crate::ui::planning::PlanningSegment::Cycles,
"BackTab switches back to the cycles segment"
);
let _ = app.handle_key(press(KeyCode::Char('q'))).unwrap();
assert!(
matches!(app.mode, Mode::Board),
"q cancels out of the modal"
);
assert!(app.planning.is_none());
}
#[test]
fn planning_h_l_and_arrows_switch_segments() {
use crate::ui::planning::PlanningSegment;
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let segment = |app: &App| app.planning.as_ref().unwrap().segment;
assert_eq!(segment(&app), PlanningSegment::Cycles);
let _ = app.handle_key(press(KeyCode::Char('l'))).unwrap();
assert_eq!(
segment(&app),
PlanningSegment::Milestones,
"`l` moves right"
);
let _ = app.handle_key(press(KeyCode::Char('h'))).unwrap();
assert_eq!(segment(&app), PlanningSegment::Cycles, "`h` moves left");
let _ = app.handle_key(press(KeyCode::Right)).unwrap();
assert_eq!(segment(&app), PlanningSegment::Milestones, "→ moves right");
let _ = app.handle_key(press(KeyCode::Left)).unwrap();
assert_eq!(segment(&app), PlanningSegment::Cycles, "← moves left");
}
#[test]
fn planning_n_opens_create_form_for_active_segment() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
assert!(matches!(app.mode, Mode::PlanningForm));
assert_eq!(
app.planning_form.as_ref().unwrap().kind,
crate::ui::planning::PlanningKind::Cycle,
"n on the cycles segment opens a cycle form"
);
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
assert_eq!(
app.planning_form.as_ref().unwrap().kind,
crate::ui::planning::PlanningKind::Milestone,
"n on the milestones segment opens a milestone form"
);
}
#[test]
fn planning_e_opens_edit_form_prefilled() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('e'))).unwrap();
let form = app.planning_form.as_ref().unwrap().clone();
assert_eq!(form.kind, crate::ui::planning::PlanningKind::Cycle);
assert_eq!(form.name(), "Cycle 1");
assert_eq!(form.editing.as_deref(), Some("Cycle 1"));
assert_eq!(form.description(), "kickoff");
assert_eq!(form.date_a(), "2026-10-01");
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Char('e'))).unwrap();
let form = app.planning_form.as_ref().unwrap().clone();
assert_eq!(form.kind, crate::ui::planning::PlanningKind::Milestone);
assert_eq!(form.name(), "GA");
assert_eq!(form.editing.as_deref(), Some("GA"));
assert_eq!(form.date_a(), "2026-12-31");
}
#[test]
fn planning_x_shows_referencing_count_and_cancel_keeps_item() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('x'))).unwrap();
match &app.planning.as_ref().unwrap().prompt {
Some(crate::ui::planning::PlanningPrompt::ConfirmDelete { name, referencing }) => {
assert_eq!(name, "Cycle 1");
assert_eq!(*referencing, 1, "one issue references Cycle 1");
}
other => panic!("x raises a confirm prompt, got {other:?}"),
}
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
assert!(
app.planning.as_ref().unwrap().prompt.is_none(),
"n dismisses the prompt"
);
assert!(
matches!(app.mode, Mode::PlanningManagement),
"cancelling stays in the modal"
);
assert_eq!(
app.planning_rows_len(),
2,
"the cancelled delete leaves the cycle in the registry"
);
}
#[test]
fn planning_renders_rows_with_fields_and_counts() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let cells = render_to_cells(&app, 120, 40).unwrap();
let lines: Vec<String> = (0..40).map(|y| flush_line(&cells, 120, y)).collect();
assert!(
lines
.iter()
.any(|l| l.contains("Cycle 1 | kickoff | 2026-10-01 | 2999-12-31 | 1 issue")),
"cycle rows show fields and count: {lines:?}"
);
assert!(
lines
.iter()
.any(|l| l.contains("Cycle 2 | next | 2999-01-01 | 2999-01-15 | 0 issues")),
"unreferenced cycles render a zero count"
);
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let cells = render_to_cells(&app, 120, 40).unwrap();
let lines: Vec<String> = (0..40).map(|y| flush_line(&cells, 120, y)).collect();
assert!(
lines
.iter()
.any(|l| l.contains("GA | ship it | 2026-12-31 | 1 issue")),
"milestone rows show fields and count: {lines:?}"
);
}
#[test]
fn planning_form_creates_cycle_with_blank_fields_rejected() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(
matches!(app.mode, Mode::PlanningForm),
"blank-name submit keeps the form open"
);
let status = app.status_msg.as_deref().unwrap_or("");
assert!(
status.contains("a name is required"),
"blank names surface an error status: {status:?}"
);
assert_eq!(app.planning_rows_len(), 2, "nothing was created");
for c in "Cycle 9".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(
matches!(app.mode, Mode::PlanningManagement),
"a valid create returns to the modal"
);
assert!(app.planning_form.is_none());
assert!(
app.store.config.cycles.contains_key("Cycle 9"),
"the new cycle is persisted to tix.toml and reloaded into the store"
);
let (cycles, _) = app.planning_rows();
assert!(
cycles.iter().any(|r| r.name == "Cycle 9"),
"the modal re-query shows the new cycle"
);
}
#[test]
fn planning_form_creates_milestone_and_refreshes_pickers() {
let (tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
for c in "Beta".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::F(2))).unwrap();
assert!(matches!(app.mode, Mode::DatePicker { .. }));
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let milestones_dir = tmp.path().join("docs/milestones");
let created = milestones_dir
.read_dir()
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().contains("beta.md"))
.count();
assert_eq!(created, 1, "a milestone registry file is written");
let (_, milestones) = app.planning_rows();
assert!(
milestones
.iter()
.any(|r| { r.name == "Beta" && r.date_a == today() }),
"the modal re-query shows the milestone with its target date"
);
let options = app.picker_options(FormPickerField::Milestones);
assert!(
options.iter().any(|o| o.id == "Beta"),
"a freshly built picker lists the new milestone (no restart)"
);
}
#[test]
fn planning_form_date_row_masked_typing_and_picker() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
for c in "Gamma".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(matches!(app.mode, Mode::DatePicker { .. }));
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
assert!(matches!(app.mode, Mode::PlanningForm));
for c in "20261130".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
assert_eq!(app.planning_form.as_ref().unwrap().date_a(), "2026-11-30");
let _ = app.handle_key(press(KeyCode::Backspace)).unwrap();
assert_eq!(app.planning_form.as_ref().unwrap().date_a(), "2026-11-3");
let _ = app.handle_key(press(KeyCode::Char('0'))).unwrap();
assert_eq!(app.planning_form.as_ref().unwrap().date_a(), "2026-11-30");
let _ = app.handle_key(press(KeyCode::F(2))).unwrap();
assert!(matches!(app.mode, Mode::DatePicker { .. }));
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert_eq!(app.planning_form.as_ref().unwrap().date_a(), "2026-11-30");
}
#[test]
fn planning_form_creates_cycle_visible_to_pickers() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
for c in "Cycle 9".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let options = app.picker_options(crate::app::FormPickerField::Cycles);
assert!(
options.iter().any(|o| o.id == "Cycle 9"),
"a freshly built cycle picker lists the new cycle"
);
}
#[test]
fn planning_form_create_surfaces_an_overlap_in_the_status() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
for c in "October".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
for c in "20261020".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(
app.store.config.cycles.contains_key("October"),
"the overlapping cycle is still created"
);
let status = app.status_msg.clone().unwrap_or_default();
assert!(
status.contains("overlaps existing cycle(s)") && status.contains("Cycle 1"),
"the overlap is surfaced as a status note: {status:?}"
);
}
#[test]
fn planning_form_edits_cycle_fields() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('e'))).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
for _ in 0..7 {
let _ = app.handle_key(press(KeyCode::Backspace)).unwrap();
}
for c in "sprint".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let cycle = &app.store.config.cycles["Cycle 1"];
assert_eq!(cycle.description.as_deref(), Some("sprint"));
assert_eq!(cycle.start_date.as_deref(), Some("2026-10-01"));
assert_eq!(cycle.due_date.as_deref(), Some("2999-12-31"));
assert!(
app.store.config.cycles.contains_key("Cycle 1")
&& !app.store.config.cycles.contains_key("sprint"),
"field edits never rename the cycle"
);
}
#[test]
fn planning_form_renames_cycle_cascade_rewrites_issues_and_refreshes() {
let (tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('e'))).unwrap();
for _ in 0..7 {
let _ = app.handle_key(press(KeyCode::Backspace)).unwrap();
}
for c in "Cycle One".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(
!app.store.config.cycles.contains_key("Cycle 1"),
"old name gone"
);
assert!(
app.store.config.cycles.contains_key("Cycle One"),
"new name persisted"
);
let issue = app.store.get(IssueSelector::Number(1)).unwrap();
assert_eq!(issue.cycles.unwrap(), vec!["Cycle One".to_string()]);
let (cycles, _) = app.planning_rows();
assert!(
cycles.iter().any(|r| r.name == "Cycle One"),
"the modal re-query shows the renamed cycle"
);
let options = app.picker_options(crate::app::FormPickerField::Cycles);
assert!(options.iter().any(|o| o.id == "Cycle One"));
assert!(!options.iter().any(|o| o.id == "Cycle 1"));
app.apply_filter("cycle:One");
let hit: usize = (0..app.board.total_cols())
.map(|c| app.board.column_cards(c).len())
.sum();
assert_eq!(hit, 1, "the board corpus picks up the renamed cycle");
let raw = std::fs::read_to_string(tmp.path().join("issues/alice/2026-01-01-a.md")).unwrap();
assert!(
raw.contains("Cycle One"),
"issue file carries the new spelling"
);
assert!(!raw.contains("Cycle 1"), "stale spelling fully rewritten");
}
#[test]
fn planning_form_duplicate_name_surfaces_error_keeps_form() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('e'))).unwrap();
for _ in 0..7 {
let _ = app.handle_key(press(KeyCode::Backspace)).unwrap();
}
for c in "Cycle 2".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(matches!(app.mode, Mode::PlanningForm));
let status = app.status_msg.as_deref().unwrap_or("");
assert!(
status.contains("already exists"),
"duplicate names surface an error status: {status:?}"
);
assert!(app.store.config.cycles.contains_key("Cycle 1"));
assert!(app.store.config.cycles.contains_key("Cycle 2"));
assert_eq!(
app.store.config.cycles.len(),
2,
"the rejected rename changed nothing"
);
}
#[test]
fn planning_form_renames_milestone_cascade_edge_to_edge() {
let (tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Char('e'))).unwrap();
for _ in 0..2 {
let _ = app.handle_key(press(KeyCode::Backspace)).unwrap();
}
for c in "GAX".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
for _ in 0..7 {
let _ = app.handle_key(press(KeyCode::Backspace)).unwrap();
}
for c in "beta".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::F(2))).unwrap();
assert!(matches!(app.mode, Mode::DatePicker { .. }));
let _ = app.handle_key(press(KeyCode::PageDown)).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let milestones_dir = tmp.path().join("docs/milestones");
let files: Vec<String> = milestones_dir
.read_dir()
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().to_string())
.collect();
assert!(
files.iter().any(|f| f.ends_with("-gax.md")),
"the milestone registry file is renamed: {files:?}"
);
assert!(
!files.iter().any(|f| f.ends_with("-ga.md")),
"the old registry file is gone: {files:?}"
);
let issue = app.store.get(IssueSelector::Number(1)).unwrap();
assert_eq!(issue.milestones.unwrap(), vec!["GAX".to_string()]);
let planner = Planner::load_from(&tmp.path().join("tix.toml")).unwrap();
let ms = planner.milestone("GAX").unwrap();
assert_eq!(ms.target_date.as_str(), "2027-01-31");
assert!(ms.description.contains("beta"), "description was edited");
}
#[test]
fn planning_delete_confirm_removes_cycle_and_refreshes_grouping() {
let (_tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('x'))).unwrap();
match &app.planning.as_ref().unwrap().prompt {
Some(crate::ui::planning::PlanningPrompt::ConfirmDelete { name, referencing }) => {
assert_eq!(name, "Cycle 1");
assert_eq!(*referencing, 1, "the confirm prompt reports the count");
}
other => panic!("x raises a confirm prompt, got {other:?}"),
}
let _ = app.handle_key(press(KeyCode::Esc)).unwrap();
assert!(
app.store.config.cycles.contains_key("Cycle 1"),
"cancelling keeps the item"
);
let _ = app.handle_key(press(KeyCode::Char('x'))).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(!app.store.config.cycles.contains_key("Cycle 1"));
assert!(app.store.config.cycles.contains_key("Cycle 2"));
let (cycles, _) = app.planning_rows();
assert!(
cycles.iter().all(|r| r.name != "Cycle 1"),
"the modal re-query drops the deleted cycle"
);
let issue = app.store.get(IssueSelector::Number(1)).unwrap();
assert_eq!(
issue.cycles.unwrap(),
vec!["Cycle 1".to_string()],
"references preserved"
);
let options = app.picker_options(crate::app::FormPickerField::Cycles);
assert!(
!options.iter().any(|o| o.id == "Cycle 1"),
"no stale picker entry"
);
app.apply_filter("cycle:Cycle");
let hit: usize = (0..app.board.total_cols())
.map(|c| app.board.column_cards(c).len())
.sum();
assert_eq!(hit, 1, "the referencing issue stays grouped on the board");
}
#[test]
fn planning_delete_confirm_removes_milestone_file_preserves_issues() {
let (tmp, mut app) = make_app_with_planning();
let _ = app.handle_key(press(KeyCode::Char('m'))).unwrap();
let _ = app.handle_key(press(KeyCode::Tab)).unwrap();
let _ = app.handle_key(press(KeyCode::Char('x'))).unwrap();
match &app.planning.as_ref().unwrap().prompt {
Some(crate::ui::planning::PlanningPrompt::ConfirmDelete { name, referencing }) => {
assert_eq!(name, "GA");
assert_eq!(*referencing, 1);
}
other => panic!("x raises a confirm prompt, got {other:?}"),
}
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
let milestones_dir = tmp.path().join("docs/milestones");
assert!(
milestones_dir.join("2026-01-01-ga.md").exists(),
"cancel keeps the milestone file"
);
let _ = app.handle_key(press(KeyCode::Char('x'))).unwrap();
let _ = app.handle_key(press(KeyCode::Char('y'))).unwrap();
assert!(
!milestones_dir.join("2026-01-01-ga.md").exists(),
"confirm removes the registry file"
);
let (_, milestones) = app.planning_rows();
assert!(milestones.is_empty(), "the milestone segment is now empty");
let issue = app.store.get(IssueSelector::Number(1)).unwrap();
assert_eq!(issue.milestones.unwrap(), vec!["GA".to_string()]);
let options = app.picker_options(FormPickerField::Milestones);
assert!(
!options.iter().any(|o| o.id == "GA"),
"no stale picker entry"
);
}
#[test]
fn form_persists_planning_fields() {
let (_tmp, mut app) = make_app();
let _ = app.handle_key(press(KeyCode::Char('n'))).unwrap();
for c in "Planning fields".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
for c in "Cycle 1,Cycle 2".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
for c in "Backlog".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
for c in "alice".chars() {
let _ = app.handle_key(press(KeyCode::Char(c))).unwrap();
}
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::F(2))).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let _ = app.handle_key(press(KeyCode::Down)).unwrap();
let _ = app.handle_key(press(KeyCode::F(2))).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
let _ = app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(matches!(app.mode, Mode::Board));
let issues = app.store.list().unwrap();
assert_eq!(issues.len(), 1);
let issue = &issues[0];
assert_eq!(
issue.cycles.as_deref(),
Some(&["Cycle 1".to_string(), "Cycle 2".to_string()][..])
);
assert_eq!(
issue.milestones.as_deref(),
Some(&["Backlog".to_string()][..])
);
assert_eq!(issue.reviewer.as_deref(), Some("alice"));
assert_eq!(issue.start_date.as_deref(), Some(today().as_str()));
assert_eq!(issue.due_date.as_deref(), Some(today().as_str()));
}
#[test]
fn planning_management_opens_from_cycle_mode_and_returns_to_it() {
let (_tmp, mut app) = make_cycle_app();
app.handle_key(press(KeyCode::F(4))).unwrap();
app.select_cycle_row(1);
app.handle_key(press(KeyCode::Char('m'))).unwrap();
assert!(
matches!(app.mode, Mode::PlanningManagement),
"m opens the planning surface from the Cycle mode"
);
assert!(app.planning.is_some());
app.handle_key(press(KeyCode::Esc)).unwrap();
assert!(
matches!(app.mode, Mode::Cycle),
"Esc returns to the Cycle mode, got {:?}",
app.mode
);
assert!(app.planning.is_none());
assert_eq!(
app.cycle_state.unwrap().selected,
1,
"the mode is shown again in its previous state"
);
}