helix-mirror/helix-term/tests/test/commands.rs
Michael Davis c74b97447f
Fix range offsets for multiple shell insertions (#4619)
d6323b7cbc introduced a regression for
shell commands like `|`, `!`, and `<A-!>` which caused the new
selections to be incorrect. This caused a panic when piping (`|`)
would cause the new range to extend past the document end.

The paste version of this bug was fixed in
48a3965ab4.

This change also inherits the direction of the new range from the old
range and adds integration tests to ensure that the behavior isn't
broken in the future.
2022-11-14 09:45:52 +09:00

286 lines
6.7 KiB
Rust

use std::ops::RangeInclusive;
use helix_core::diagnostic::Severity;
use helix_term::application::Application;
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn test_write_quit_fail() -> anyhow::Result<()> {
let file = helpers::new_readonly_tempfile()?;
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.build()?;
test_key_sequence(
&mut app,
Some("ihello<esc>:wq<ret>"),
Some(&|app| {
let mut docs: Vec<_> = app.editor.documents().collect();
assert_eq!(1, docs.len());
let doc = docs.pop().unwrap();
assert_eq!(Some(file.path()), doc.path().map(PathBuf::as_path));
assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
}),
false,
)
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
test_key_sequences(
&mut helpers::AppBuilder::new().build()?,
vec![
(
None,
Some(&|app| {
assert_eq!(1, app.editor.documents().count());
assert!(!app.editor.is_err());
}),
),
(
Some("ihello<esc>:new<ret>"),
Some(&|app| {
assert_eq!(2, app.editor.documents().count());
assert!(!app.editor.is_err());
}),
),
(
Some(":buffer<minus>close<ret>"),
Some(&|app| {
assert_eq!(1, app.editor.documents().count());
assert!(!app.editor.is_err());
}),
),
],
false,
)
.await?;
// verify if writes are queued up, it finishes them before closing the buffer
let mut file = tempfile::NamedTempFile::new()?;
let mut command = String::new();
const RANGE: RangeInclusive<i32> = 1..=1000;
for i in RANGE {
let cmd = format!("%c{}<esc>:w<ret>", i);
command.push_str(&cmd);
}
command.push_str(":buffer<minus>close<ret>");
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.build()?;
test_key_sequence(
&mut app,
Some(&command),
Some(&|app| {
assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status());
let doc = app.editor.document_by_path(file.path());
assert!(doc.is_none(), "found doc: {:?}", doc);
}),
false,
)
.await?;
helpers::assert_file_has_content(file.as_file_mut(), &RANGE.end().to_string())?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_selection_duplication() -> anyhow::Result<()> {
// Forward
test((
platform_line(indoc! {"\
#[lo|]#rem
ipsum
dolor
"})
.as_str(),
"CC",
platform_line(indoc! {"\
#(lo|)#rem
#(ip|)#sum
#[do|]#lor
"})
.as_str(),
))
.await?;
// Backward
test((
platform_line(indoc! {"\
#[|lo]#rem
ipsum
dolor
"})
.as_str(),
"CC",
platform_line(indoc! {"\
#(|lo)#rem
#(|ip)#sum
#[|do]#lor
"})
.as_str(),
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_goto_file_impl() -> anyhow::Result<()> {
let file = tempfile::NamedTempFile::new()?;
fn match_paths(app: &Application, matches: Vec<&str>) -> usize {
app.editor
.documents()
.filter_map(|d| d.path()?.file_name())
.filter(|n| matches.iter().any(|m| *m == n.to_string_lossy()))
.count()
}
// Single selection
test_key_sequence(
&mut AppBuilder::new().with_file(file.path(), None).build()?,
Some("ione.js<esc>%gf"),
Some(&|app| {
assert_eq!(1, match_paths(app, vec!["one.js"]));
}),
false,
)
.await?;
// Multiple selection
test_key_sequence(
&mut AppBuilder::new().with_file(file.path(), None).build()?,
Some("ione.js<ret>two.js<esc>%<A-s>gf"),
Some(&|app| {
assert_eq!(2, match_paths(app, vec!["one.js", "two.js"]));
}),
false,
)
.await?;
// Cursor on first quote
test_key_sequence(
&mut AppBuilder::new().with_file(file.path(), None).build()?,
Some("iimport 'one.js'<esc>B;gf"),
Some(&|app| {
assert_eq!(1, match_paths(app, vec!["one.js"]));
}),
false,
)
.await?;
// Cursor on last quote
test_key_sequence(
&mut AppBuilder::new().with_file(file.path(), None).build()?,
Some("iimport 'one.js'<esc>bgf"),
Some(&|app| {
assert_eq!(1, match_paths(app, vec!["one.js"]));
}),
false,
)
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_multi_selection_paste() -> anyhow::Result<()> {
test((
platform_line(indoc! {"\
#[|lorem]#
#(|ipsum)#
#(|dolor)#
"})
.as_str(),
"yp",
platform_line(indoc! {"\
lorem#[|lorem]#
ipsum#(|ipsum)#
dolor#(|dolor)#
"})
.as_str(),
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_multi_selection_shell_commands() -> anyhow::Result<()> {
// pipe
test((
platform_line(indoc! {"\
#[|lorem]#
#(|ipsum)#
#(|dolor)#
"})
.as_str(),
"|echo foo<ret>",
platform_line(indoc! {"\
#[|foo
]#
#(|foo
)#
#(|foo
)#
"})
.as_str(),
))
.await?;
// insert-output
test((
platform_line(indoc! {"\
#[|lorem]#
#(|ipsum)#
#(|dolor)#
"})
.as_str(),
"!echo foo<ret>",
platform_line(indoc! {"\
#[|foo
]#lorem
#(|foo
)#ipsum
#(|foo
)#dolor
"})
.as_str(),
))
.await?;
// append-output
test((
platform_line(indoc! {"\
#[|lorem]#
#(|ipsum)#
#(|dolor)#
"})
.as_str(),
"<A-!>echo foo<ret>",
platform_line(indoc! {"\
lorem#[|foo
]#
ipsum#(|foo
)#
dolor#(|foo
)#
"})
.as_str(),
))
.await?;
Ok(())
}