#[allow(clippy::wildcard_imports)]
use super::super::*;
#[allow(clippy::wildcard_imports)]
use super::helpers::*;
#[test]
fn opening_an_issue_with_a_mention_shows_the_notice() {
let (_tmp, mut app, number) = app_with_mention("blocked on @alice");
app.open_issue_by_number(number).unwrap();
assert!(matches!(app.mode, Mode::MentionNotice));
let view = app.mentions.as_ref().expect("the notice is open");
assert_eq!(view.rows.len(), 1);
assert_eq!(view.rows[0].body, "alice");
assert!(
view.rows[0].context.contains("@alice"),
"{:?}",
view.rows[0].context
);
let text = frame_text(&app, 100, 20);
assert!(text.contains("Mentions for you"), "{text}");
assert!(
text.contains("@alice") || text.contains("blocked on @alice"),
"{text}"
);
}
#[test]
fn an_issue_with_no_mentions_for_the_user_opens_no_notice() {
let (_tmp, mut app, number) = app_with_mention("cc @someone-else");
app.open_issue_by_number(number).unwrap();
assert!(
matches!(app.mode, Mode::Detail),
"no notice for a stranger's mention"
);
assert!(app.mentions.is_none());
}
#[test]
fn ignoring_a_mention_writes_nothing_and_shows_it_again() {
let (_tmp, mut app, number) = app_with_mention("blocked on @alice");
app.open_issue_by_number(number).unwrap();
app.handle_key(press(KeyCode::Char('i'))).unwrap();
assert!(
matches!(app.mode, Mode::Detail),
"ignore returns to the pane"
);
assert!(app.mentions.is_none());
recount_mentions(&mut app);
assert_eq!(app.unread_mentions, 1);
app.open_issue_by_number(number).unwrap();
assert!(matches!(app.mode, Mode::MentionNotice));
}
#[test]
fn marking_a_mention_read_persists_and_is_not_shown_again() {
let (_tmp, mut app, number) = app_with_mention("blocked on @alice");
app.open_issue_by_number(number).unwrap();
app.handle_key(press(KeyCode::Char('r'))).unwrap();
assert!(
app.mentions.is_none(),
"the last pending mention closes the notice"
);
assert!(matches!(app.mode, Mode::Detail));
assert_eq!(app.unread_mentions, 0, "the count is recomputed");
app.open_issue_by_number(number).unwrap();
assert!(
matches!(app.mode, Mode::Detail),
"a read mention is not shown again"
);
}
#[test]
fn jumping_to_a_mention_scrolls_it_to_the_top_and_marks_the_target() {
let filler = (1..=12)
.map(|n| format!("paragraph {n}"))
.collect::<Vec<_>>();
let body = format!("{}\n\nblocked on @alice\n", filler.join("\n\n"));
let (_tmp, mut app, number) = app_with_mention(&body);
app.open_issue_by_number(number).unwrap();
app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(app.mentions.is_none(), "the notice closes on jump");
let detail = app.detail.as_ref().unwrap();
assert_eq!(
detail.mention_body.as_deref(),
Some("alice"),
"the target is marked for the renderer"
);
assert!(
detail.focus_pending.get(),
"the jump defers the scroll to the renderer"
);
let text = frame_text(&app, 100, 24);
let detail = app.detail.as_ref().unwrap();
assert!(!detail.focus_pending.get(), "the draw consumes the focus");
assert!(
detail.scroll_offset.get() > 0,
"scrolled down to the mention: {text}"
);
assert!(
text.contains("blocked on @alice"),
"the mention is on screen: {text}"
);
}
#[test]
fn the_indicator_row_appears_with_unread_mentions_and_goes_when_read() {
let (_tmp, mut app, number) = app_with_mention("blocked on @alice");
assert_eq!(mention_row_height(&app), 1);
let text = frame_text(&app, 100, 20);
assert!(text.contains("1 unread mention"), "{text}");
assert!(text.contains("press i to open"), "{text}");
app.handle_key(press(KeyCode::Char('i'))).unwrap();
assert!(matches!(app.mode, Mode::Mentions));
app.handle_key(press(KeyCode::Char('r'))).unwrap();
assert_eq!(app.unread_mentions, 0);
assert_eq!(mention_row_height(&app), 0);
let text = frame_text(&app, 100, 20);
assert!(!text.contains("unread mention"), "{text}");
let _ = number;
}
#[test]
fn no_unread_mentions_means_no_indicator_row() {
let (_tmp, app, _number) = app_with_mention("nothing addressed here");
assert_eq!(mention_row_height(&app), 0);
let text = frame_text(&app, 100, 20);
assert!(!text.contains("unread mention"), "{text}");
}
#[test]
fn the_board_chord_opens_the_mentions_view_across_issues() {
let (_tmp, mut app) = make_app();
let first = seed_issue(&mut app, "blocked on @alice");
let _second = seed_issue(&mut app, "also blocked on @alice");
app.mention_candidates = vec!["alice".to_string()];
recount_mentions(&mut app);
assert_eq!(app.unread_mentions, 2);
app.handle_key(press(KeyCode::Char('i'))).unwrap();
assert!(
matches!(app.mode, Mode::Mentions),
"the chord opens the view"
);
let rows = app
.mentions_view
.as_ref()
.expect("the view is open")
.rows
.len();
assert_eq!(rows, 2, "both issues are listed");
let text = frame_text(&app, 100, 20);
assert!(text.contains("Pending mentions"), "{text}");
app.handle_key(press(KeyCode::Enter)).unwrap();
assert!(matches!(app.mode, Mode::Detail));
assert!(
app.mentions.is_none(),
"the opened issue's own notice does not preempt"
);
assert_eq!(app.detail.as_ref().unwrap().issue.number, first);
}
#[test]
fn the_mentions_view_reports_nothing_to_show_rather_than_opening_empty() {
let (_tmp, mut app, _number) = app_with_mention("nothing addressed here");
app.handle_key(press(KeyCode::Char('i'))).unwrap();
assert!(matches!(app.mode, Mode::Board));
assert!(app.mentions_view.is_none());
assert_eq!(app.status_msg.as_deref(), Some("no pending mentions"));
}
#[test]
fn mentions_disabled_means_no_notice_no_count_and_no_indicator() {
let (_tmp, mut app, number) = app_with_mention("blocked on @alice");
assert_eq!(app.unread_mentions, 1, "the mention is there to begin with");
app.store.config.mentions.enabled = false;
app.mention_candidates = vec!["alice".to_string()];
recount_mentions(&mut app);
assert_eq!(app.unread_mentions, 0);
assert_eq!(mention_row_height(&app), 0);
app.open_issue_by_number(number).unwrap();
assert!(
matches!(app.mode, Mode::Detail),
"no notice when the feature is off"
);
app.mode = Mode::Board;
app.handle_key(press(KeyCode::Char('i'))).unwrap();
assert!(matches!(app.mode, Mode::Board));
assert!(
app.status_msg
.as_deref()
.is_some_and(|m| m.contains("disabled")),
"{:?}",
app.status_msg
);
}
#[test]
fn a_new_mention_opens_the_view_from_the_board() {
let (_tmp, mut app) = make_app();
let _first = seed_issue(&mut app, "blocked on @alice");
app.mention_candidates = vec!["alice".to_string()];
recount_mentions(&mut app);
assert_eq!(app.unread_mentions, 1);
let _second = seed_issue(&mut app, "also blocked on @alice");
app.refresh_mentions_after_change();
assert!(
matches!(app.mode, Mode::Mentions),
"a grown count opens the modal: {:?}",
app.mode
);
assert_eq!(app.mentions_view.as_ref().unwrap().rows.len(), 2);
}
#[test]
fn a_new_mention_refreshes_an_already_open_view() {
let (_tmp, mut app) = make_app();
let _first = seed_issue(&mut app, "blocked on @alice");
let _second = seed_issue(&mut app, "also blocked on @alice");
app.mention_candidates = vec!["alice".to_string()];
recount_mentions(&mut app);
assert_eq!(app.unread_mentions, 2);
app.handle_key(press(KeyCode::Char('i'))).unwrap();
assert!(matches!(app.mode, Mode::Mentions));
app.handle_key(press(KeyCode::Char('j'))).unwrap(); assert_eq!(app.mentions_view.as_ref().unwrap().selected, 1);
let _third = seed_issue(&mut app, "ping @alice");
app.refresh_mentions_after_change();
assert!(matches!(app.mode, Mode::Mentions));
let view = app.mentions_view.as_ref().unwrap();
assert_eq!(view.rows.len(), 3, "the new row is folded in");
assert_eq!(view.selected, 1, "the cursor survives the refresh");
}
#[test]
fn a_new_mention_does_not_interrupt_a_form() {
let (_tmp, mut app) = make_app();
let _first = seed_issue(&mut app, "blocked on @alice");
app.mention_candidates = vec!["alice".to_string()];
recount_mentions(&mut app);
assert_eq!(app.unread_mentions, 1);
app.handle_key(press(KeyCode::Char('n'))).unwrap();
assert!(matches!(app.mode, Mode::Form), "the create form is open");
let _second = seed_issue(&mut app, "also blocked on @alice");
app.refresh_mentions_after_change();
assert!(
matches!(app.mode, Mode::Form),
"a form is not interrupted: {:?}",
app.mode
);
assert!(app.mentions_view.is_none());
}