use super::*;
#[test]
fn composer_supports_readline_cursor_editing() {
let mut app = app_with(DIFF);
open_composer(&mut app);
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('c'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Left, KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('b'), KeyModifiers::NONE);
assert_eq!(composer_text(&app), "abc");
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::CONTROL);
app.on_key_compose(KeyCode::Char('X'), KeyModifiers::NONE);
assert_eq!(composer_text(&app), "Xabc");
app.on_key_compose(KeyCode::Char('e'), KeyModifiers::CONTROL);
app.on_key_compose(KeyCode::Char('Z'), KeyModifiers::NONE);
assert_eq!(composer_text(&app), "XabcZ");
}
#[test]
fn cursor_move_rebuilds_the_rendered_composer() {
let mut app = app_with(DIFF);
app.toggle_view(); open_composer(&mut app);
app.comment_wrap = 40;
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('b'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Left, KeyModifiers::NONE);
let body = app
.rows
.iter()
.find_map(|r| match &r.kind {
RowKind::Composer(ComposerLine {
kind: ComposerKind::Body(s),
}) if s.contains(COMPOSER_CARET) => Some(s.clone()),
_ => None,
})
.expect("a composer body row carrying the caret");
assert_eq!(
body,
format!("a{COMPOSER_CARET}b"),
"the caret marker must follow the cursor"
);
}
#[test]
fn composer_caret_renders_at_the_cursor() {
let mut app = app_with(DIFF);
open_composer(&mut app);
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('b'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::CONTROL); let spec = app.composer_spec().expect("composer spec");
assert_eq!(
spec.body,
format!("{COMPOSER_CARET}ab"),
"caret renders at the cursor, not the end"
);
}
#[test]
fn composer_body_overlay_fills_exactly_the_inner_width() {
let app = app_with(DIFF);
let width = 12; let cl = ComposerLine {
kind: ComposerKind::Body(format!("{COMPOSER_CARET}abcdefghijklmnopqrstuvwxyz")),
};
let line = app.composer_line_to_line(&cl, width);
let total: usize = line.spans.iter().map(|s| str_width(&s.content)).sum();
assert_eq!(
total, width,
"composer body line must fill exactly the row width, even when truncated"
);
}
#[test]
fn ctrl_d_deletes_forward_and_does_not_cancel() {
let mut app = app_with(DIFF);
open_composer(&mut app);
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('b'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::CONTROL); app.on_key_compose(KeyCode::Char('d'), KeyModifiers::CONTROL); assert!(app.composer.is_some(), "Ctrl+D must not cancel");
assert_eq!(composer_text(&app), "b");
}
#[test]
fn composer_keeps_caret_visible_when_taller_than_viewport() {
let mut app = app_with(DIFF);
app.height = 6; app.toggle_view(); assert!(matches!(app.view, View::Unified));
open_composer(&mut app);
for _ in 0..200 {
app.on_key_compose(KeyCode::Enter, KeyModifiers::NONE);
}
app.on_key_compose(KeyCode::Char('x'), KeyModifiers::NONE);
app.ensure_composer_visible();
let (s, e) = app.file_range();
let caret = (s..e)
.rev()
.find(|&i| {
matches!(
app.rows.get(i).map(|r| &r.kind),
Some(RowKind::Composer(ComposerLine {
kind: ComposerKind::Body(_)
}))
)
})
.expect("composer body row");
assert!(
caret >= app.scroll && caret < app.scroll + app.height,
"caret row {caret} must stay within view [{}, {})",
app.scroll,
app.scroll + app.height
);
}
#[test]
fn composer_caret_visible_on_a_tiny_viewport() {
let mut app = app_with(DIFF);
app.height = 2;
app.toggle_view(); open_composer(&mut app);
for _ in 0..50 {
app.on_key_compose(KeyCode::Enter, KeyModifiers::NONE);
}
app.on_key_compose(KeyCode::Char('x'), KeyModifiers::NONE);
app.ensure_composer_visible();
let (s, e) = app.file_range();
let caret = (s..e)
.find(|&i| app.is_composer_caret_at(i))
.expect("composer caret row");
assert!(
caret >= app.scroll && caret < app.scroll + app.height,
"caret row {caret} must stay within view [{}, {}) on a tiny viewport",
app.scroll,
app.scroll + app.height
);
}
#[test]
fn ensure_composer_visible_follows_caret_upward() {
let mut app = app_with(DIFF);
app.height = 4;
app.toggle_view(); open_composer(&mut app);
app.comment_wrap = 40;
for _ in 0..40 {
app.on_key_compose(KeyCode::Enter, KeyModifiers::NONE);
}
app.on_key_compose(KeyCode::Char('x'), KeyModifiers::NONE);
for _ in 0..45 {
app.on_key_compose(KeyCode::Up, KeyModifiers::NONE);
}
let (_, e) = app.file_range();
app.scroll = e.saturating_sub(app.height); app.ensure_composer_visible();
let (s, e) = app.file_range();
let caret = (s..e)
.find(|&i| app.is_composer_caret_at(i))
.expect("composer caret row");
assert!(
caret >= app.scroll && caret < app.scroll + app.height,
"caret row {caret} must stay within view [{}, {}) when the cursor is up",
app.scroll,
app.scroll + app.height
);
}
#[test]
fn bare_enter_inserts_a_newline_in_the_composer() {
let mut app = app_with(DIFF);
open_composer(&mut app);
app.on_key_compose(KeyCode::Char('a'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Enter, KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('b'), KeyModifiers::NONE);
assert!(app.composer.is_some(), "bare Enter should not submit");
assert_eq!(
app.composer.as_ref().unwrap().textarea.lines().join("\n"),
"a\nb"
);
}
#[test]
fn ctrl_enter_submits_the_composer() {
let (mut app, _tid, _reply) = app_with_thread(3);
let before = app.comments.threads.len();
goto(&mut app, Side::New, 1);
app.open_new_thread();
app.on_key_compose(KeyCode::Char('h'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('i'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Enter, KeyModifiers::CONTROL);
assert!(app.composer.is_none(), "Ctrl+Enter should submit");
assert_eq!(app.comments.threads.len(), before + 1);
}
#[test]
fn ctrl_s_submits_the_composer() {
let (mut app, _tid, _reply) = app_with_thread(3);
let before = app.comments.threads.len();
goto(&mut app, Side::New, 1);
app.open_new_thread();
app.on_key_compose(KeyCode::Char('h'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('i'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Char('s'), KeyModifiers::CONTROL);
assert!(app.composer.is_none(), "Ctrl+S should submit");
assert_eq!(app.comments.threads.len(), before + 1);
}
#[test]
fn shift_enter_does_not_submit_the_composer() {
let mut app = app_with(DIFF);
open_composer(&mut app);
app.on_key_compose(KeyCode::Char('x'), KeyModifiers::NONE);
app.on_key_compose(KeyCode::Enter, KeyModifiers::SHIFT);
assert!(app.composer.is_some(), "Shift+Enter must not submit");
assert_eq!(
app.composer.as_ref().unwrap().textarea.lines().join("\n"),
"x\n"
);
}