mod common;
#[cfg(unix)]
use common::{Terminal, has_askpass};
#[test]
#[cfg(unix)]
fn test_pty_readline() {
let mut terminal =
Terminal::spawn_with_args(&["--no-auto-match"]).expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line("cat('hello'); readline('input> ')")
.expect("Should send readline command");
terminal.expect("hello").expect("Should see cat output");
terminal
.expect("input> ")
.expect("Should see readline prompt");
terminal
.send_line("user_answer")
.expect("Should send readline input");
terminal
.expect(r#""user_answer""#)
.expect("readline should return the input");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_askpass() {
if !has_askpass() {
eprintln!("Skipping test_pty_askpass: askpass package not installed");
return;
}
let mut terminal =
Terminal::spawn_with_args(&["--no-auto-match"]).expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line("requireNamespace('askpass', quietly = TRUE)")
.expect("Should check askpass");
terminal
.expect("TRUE")
.expect("askpass package should be available");
terminal
.send_line(r#"askpass::askpass(paste0("Enter ", "password: "))"#)
.expect("Should send askpass command");
terminal
.expect("Enter password:")
.expect("Should see askpass prompt");
terminal
.send_line("secret_answer")
.expect("Should send askpass input");
terminal
.expect(r#""secret_answer""#)
.expect("askpass should return the input");
terminal.wait_for_prompt().expect("Should return to prompt");
let output = terminal.get_output().expect("Should get output");
let bare_occurrences = output.matches("secret_answer").count();
let quoted_occurrences = output.matches(r#""secret_answer""#).count();
assert_eq!(
bare_occurrences, quoted_occurrences,
"Password was echoed in plaintext.\n\
bare={bare_occurrences}, quoted={quoted_occurrences}\n\
output:\n{output}",
);
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_mode() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send_line(":shell").expect("Should send :shell");
terminal
.expect("Shell mode enabled")
.expect("Should show shell mode message");
terminal
.expect("] $")
.expect("Should show shell mode prompt");
terminal
.send_line("echo hello")
.expect("Should send shell command");
terminal.expect("hello").expect("Should see shell output");
terminal.send_line(":r").expect("Should send :r");
terminal
.expect("Returned to R mode")
.expect("Should show R mode message");
terminal.send_line("42").expect("Should send R expression");
terminal.expect("[1] 42").expect("Should evaluate R code");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_system_command() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":system echo test_system_output")
.expect("Should send :system");
terminal
.expect("test_system_output")
.expect("Should see system output");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal.send_line("100").expect("Should send R expression");
terminal
.clear_and_expect("[1] 100")
.expect("Should evaluate R code");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_mode_ctrl_c_exit() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send_line(":shell").expect("Should send :shell");
terminal
.clear_and_expect("Shell mode enabled")
.expect("Should show shell mode message");
terminal.clear_buffer().expect("Should clear buffer");
terminal.send_interrupt().expect("Should send Ctrl+C");
terminal
.expect("Returned to R mode")
.expect("Should return to R mode");
terminal.send_line("200").expect("Should send R expression");
terminal
.clear_and_expect("[1] 200")
.expect("Should evaluate R code");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_semicolon_shortcut() {
use std::io::Write;
use tempfile::NamedTempFile;
let mut config_file = NamedTempFile::new().expect("Failed to create temp config file");
writeln!(
config_file,
r#"
[experimental]
shell_semicolon_shortcut = true
"#
)
.expect("Failed to write config file");
let mut terminal = Terminal::spawn_with_args(&[
"--config",
config_file.path().to_str().unwrap(),
"--no-auto-match",
"--no-completion",
])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send(";").expect("Should send semicolon");
terminal
.expect("Shell mode enabled")
.expect("Should show shell mode message");
terminal
.expect("] $")
.expect("Should show shell mode prompt");
terminal
.send_line("echo semicolon_test")
.expect("Should send shell command");
terminal
.expect("semicolon_test")
.expect("Should see shell output");
terminal.send_line(":r").expect("Should send :r");
terminal
.expect("Returned to R mode")
.expect("Should return to R mode");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_semicolon_shortcut_nonempty_buffer() {
use std::io::Write;
use tempfile::NamedTempFile;
let mut config_file = NamedTempFile::new().expect("Failed to create temp config file");
writeln!(
config_file,
r#"
[experimental]
shell_semicolon_shortcut = true
"#
)
.expect("Failed to write config file");
let mut terminal = Terminal::spawn_with_args(&[
"--config",
config_file.path().to_str().unwrap(),
"--no-auto-match",
"--no-completion",
])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send("1").expect("Should send 1");
terminal.send(";").expect("Should send semicolon");
terminal.send_line("2").expect("Should send 2");
terminal
.expect("[1] 2")
.expect("Semicolon should not trigger shell mode; 1;2 should evaluate as R expression");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_help_browser() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send_line(":h").expect("Should send :h command");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal
.expect("Help Search")
.expect("Should show help browser header");
terminal
.send("\x1b")
.expect("Should send Esc to exit browser");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal.send_line("42").expect("Should send R expression");
terminal
.clear_and_expect("[1] 42")
.expect("Should evaluate R code after browser exit");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_history_schema_pager() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":history schema")
.expect("Should send :history schema");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal
.line(0)
.assert_contains("History Schema")
.expect("Should show pager header");
terminal.send("q").expect("Should send q to exit pager");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal.send_line("42").expect("Should send R expression");
terminal
.clear_and_expect("[1] 42")
.expect("Should evaluate R code after pager exit");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_history_schema_pager_copy() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":history schema")
.expect("Should send :history schema");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal
.line(0)
.assert_contains("History Schema")
.expect("Should show pager header");
terminal.send("c").expect("Should send c to copy");
std::thread::sleep(std::time::Duration::from_millis(200));
terminal
.line(23)
.assert_contains("Copied R example to clipboard")
.expect("Should show copy feedback message");
terminal.send("q").expect("Should send q to exit pager");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_history_schema_pager_mouse_scroll() {
let mut terminal = Terminal::spawn_with_args(&["--no-auto-match", "--no-completion"])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":history schema")
.expect("Should send :history schema");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal
.line(0)
.assert_contains("History Schema")
.expect("Should show pager header");
terminal
.line(0)
.assert_contains("[1/")
.expect("Should show position 1 initially");
terminal
.send("\x1b[<65;10;10M")
.expect("Should send mouse scroll down");
std::thread::sleep(std::time::Duration::from_millis(200));
terminal
.line(0)
.assert_contains("[2/")
.expect("Should show position 2 after scroll down");
terminal
.send("\x1b[<64;10;10M")
.expect("Should send mouse scroll up");
std::thread::sleep(std::time::Duration::from_millis(200));
terminal
.line(0)
.assert_contains("[1/")
.expect("Should show position 1 after scroll up");
terminal.send("q").expect("Should send q to exit pager");
std::thread::sleep(std::time::Duration::from_millis(500));
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_history_browser_persists_across_sessions() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let history_dir = temp_dir.path().to_string_lossy().to_string();
{
let mut terminal = Terminal::spawn_with_args(&[
"--no-auto-match",
"--no-completion",
"--history-dir",
&history_dir,
])
.expect("Failed to spawn arf (session 1)");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line("unique_test_value_12345 <- 999")
.expect("Should send assignment");
terminal
.clear_and_expect("unique_test_value_12345")
.expect("Should see variable name");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.quit().expect("Should quit cleanly (session 1)");
}
{
let mut terminal = Terminal::spawn_with_args(&[
"--no-auto-match",
"--no-completion",
"--history-dir",
&history_dir,
])
.expect("Failed to spawn arf (session 2)");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":history browse")
.expect("Should send :history browse");
terminal
.expect("History Browser")
.expect("Should show history browser header");
terminal
.expect("unique_test_value_12345")
.expect("Should see command from session 1 in history browser");
terminal.send("q").expect("Should send q to exit browser");
terminal
.clear_and_expect("> ")
.expect("Should return to prompt after browser exit");
terminal.quit().expect("Should quit cleanly (session 2)");
}
}
#[test]
#[cfg(unix)]
fn test_pty_history_browser() {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let history_dir = temp_dir.path().to_string_lossy().to_string();
let mut terminal = Terminal::spawn_with_args(&[
"--no-auto-match",
"--no-completion",
"--history-dir",
&history_dir,
])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line("1 + 1")
.expect("Should send R expression");
terminal
.clear_and_expect("[1] 2")
.expect("Should evaluate R code");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line("print('hello')")
.expect("Should send print command");
terminal
.clear_and_expect("[1] \"hello\"")
.expect("Should print hello");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":history browse")
.expect("Should send :history browse");
terminal
.expect("History Browser")
.expect("Should show history browser header");
terminal
.expect("[R]")
.expect("Should show R mode indicator");
terminal.send("q").expect("Should send q to exit browser");
terminal
.clear_and_expect("> ")
.expect("Should return to prompt after browser exit");
terminal.send_line("42").expect("Should send R expression");
terminal
.clear_and_expect("[1] 42")
.expect("Should evaluate R code after browser exit");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line(":history browse")
.expect("Should send :history browse again");
terminal
.expect("History Browser")
.expect("Should show history browser header on second open");
terminal.send("q").expect("Should send q to exit browser");
terminal
.clear_and_expect("> ")
.expect("Should return to prompt after second browser exit");
terminal.send_line("99").expect("Should send R expression");
terminal
.clear_and_expect("[1] 99")
.expect("Should evaluate R code after second browser exit");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_abbreviations_expand_in_shell_mode() {
use std::io::Write;
use tempfile::NamedTempFile;
let mut config_file = NamedTempFile::new().expect("Failed to create temp config file");
writeln!(
config_file,
r#"
[experimental.shell_abbreviations]
"abbr_test" = "echo abbr_expanded"
"#
)
.expect("Failed to write config file");
let mut terminal = Terminal::spawn_with_args(&[
"--config",
config_file.path().to_str().unwrap(),
"--no-auto-match",
"--no-completion",
])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send_line(":shell").expect("Should send :shell");
terminal
.expect("] $")
.expect("Should show shell mode prompt");
terminal
.send("abbr_test")
.expect("Should type abbreviation");
terminal
.expect("abbr_test")
.expect("Terminal should echo the typed abbreviation");
terminal.send_line("").expect("Should press Enter");
terminal
.expect("abbr_expanded\r\n")
.expect("Shell should execute 'echo abbr_expanded' and produce output on its own line");
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_abbreviations_do_not_expand_in_r_mode() {
use std::io::Write;
use tempfile::NamedTempFile;
let mut config_file = NamedTempFile::new().expect("Failed to create temp config file");
writeln!(
config_file,
r#"
[experimental.shell_abbreviations]
"abbr_test" = "echo abbr_expanded"
"#
)
.expect("Failed to write config file");
let mut terminal = Terminal::spawn_with_args(&[
"--config",
config_file.path().to_str().unwrap(),
"--no-auto-match",
"--no-completion",
])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal
.send_line("abbr_test")
.expect("Should send abbreviation word at R prompt");
terminal.expect("not found").expect(
"R should produce object-not-found error, proving no abbreviation expansion occurred",
);
terminal.quit().expect("Should quit cleanly");
}
#[test]
#[cfg(unix)]
fn test_pty_shell_abbreviations_expand_on_space_in_shell_mode() {
use std::io::Write;
use tempfile::NamedTempFile;
let mut config_file = NamedTempFile::new().expect("Failed to create temp config file");
writeln!(
config_file,
r#"
[experimental.shell_abbreviations]
"abbr_sp" = "echo space_expanded"
"#
)
.expect("Failed to write config file");
let mut terminal = Terminal::spawn_with_args(&[
"--config",
config_file.path().to_str().unwrap(),
"--no-auto-match",
"--no-completion",
])
.expect("Failed to spawn arf");
terminal.wait_for_prompt().expect("Should show prompt");
terminal.send_line(":shell").expect("Should send :shell");
terminal
.expect("] $")
.expect("Should show shell mode prompt");
terminal.send("abbr_sp").expect("Should type abbreviation");
terminal
.expect("abbr_sp")
.expect("Terminal should echo the typed abbreviation");
terminal
.send(" ")
.expect("Should press Space to trigger expansion");
terminal
.expect("echo space_expanded")
.expect("Buffer repaint should show expanded text after Space");
terminal
.clear_buffer()
.expect("Should clear accumulated output before checking shell output");
terminal.send_line("").expect("Should press Enter");
terminal
.expect("space_expanded")
.expect("Shell should execute 'echo space_expanded' and produce this output");
terminal.quit().expect("Should quit cleanly");
}