use super::*;
use crate::config::ReadOnly;
use std::path::PathBuf;
fn read_only(ctx: &mut TestContext) {
ctx.config().general.read_only = ReadOnly::On;
}
fn porcelain_status(ctx: &TestContext) -> String {
run(&ctx.dir, &["git", "status", "--porcelain"])
}
fn colocate_jj(ctx: &TestContext) {
fs::create_dir(ctx.dir.join(".jj")).unwrap();
fs::write(ctx.dir.join(".jj/.gitignore"), "/*\n").unwrap();
}
#[test]
fn the_help_menu_offers_nothing_that_writes() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
commit(&ctx.dir, "file", "a\n");
fs::write(ctx.dir.join("file"), "b\n").unwrap();
let mut app = ctx.init_app();
ctx.resize(&mut app, &[super::HELP_FITS]);
ctx.update(&mut app, keys("jj?"));
let buffer = ctx.redact_buffer();
let buffer = buffer
.lines()
.skip_while(|row| !row.starts_with('─'))
.collect::<Vec<_>>()
.join("\n");
for writes in [
"Branch",
"Commit",
"Fetch",
"Merge",
"Remote",
"Pull",
"Push",
"Rebase",
"Reset",
"Revert",
"Cherry-pick",
"Stash",
"Stage",
"Discard",
] {
assert!(!buffer.contains(writes), "{writes}\n{buffer}");
}
for reads in ["Help", "Log", "Show Refs", "Show"] {
assert!(buffer.contains(reads), "{reads}\n{buffer}");
}
}
#[test]
fn stage_does_nothing() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
run(&ctx.dir, &["touch", "new-file"]);
let mut app = ctx.init_app();
ctx.update(&mut app, keys("js"));
assert_eq!(porcelain_status(&ctx), "?? new-file\n");
}
#[test]
fn discard_does_nothing() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
commit(&ctx.dir, "file", "a\n");
fs::write(ctx.dir.join("file"), "b\n").unwrap();
let mut app = ctx.init_app();
ctx.update(&mut app, keys("jjKy"));
assert_eq!(fs::read_to_string(ctx.dir.join("file")).unwrap(), "b\n");
}
#[test]
fn the_commit_menu_does_not_open() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
let mut app = ctx.init_app();
ctx.update(&mut app, keys("c"));
let buffer = ctx.redact_buffer();
assert!(!buffer.contains("Commit"), "{buffer}");
}
#[test]
fn a_jj_repository_is_read_only() {
let mut ctx = setup_clone!();
colocate_jj(&ctx);
run(&ctx.dir, &["touch", "new-file"]);
let mut app = ctx.init_app();
ctx.update(&mut app, keys("js"));
assert_eq!(porcelain_status(&ctx), "?? new-file\n");
}
#[test]
fn read_only_turned_off_writes_in_a_jj_repository() {
let mut ctx = setup_clone!();
ctx.config().general.read_only = ReadOnly::Off;
colocate_jj(&ctx);
run(&ctx.dir, &["touch", "new-file"]);
let mut app = ctx.init_app();
ctx.update(&mut app, keys("js"));
assert_eq!(porcelain_status(&ctx), "A new-file\n");
}
#[test]
fn a_theme_chosen_is_not_saved() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
run(&ctx.dir, &["git", "config", "delta.theme-a.dark", "true"]);
run(
&ctx.dir,
&["git", "config", "delta-tui.visit-context-lines", "true"],
);
let path = ctx.dir.join(".git/config");
let before = fs::read_to_string(&path).unwrap();
let mut app = ctx.init_app();
ctx.update(&mut app, keys("T<down><enter>"));
assert_eq!(app.state.theme.as_deref(), Some("theme-a"));
assert_eq!(fs::read_to_string(&path).unwrap(), before);
}
#[test]
fn a_command_typed_is_not_saved_to_the_history() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
let mut app = ctx.init_app_as_pager_of(&["git", "diff"]);
ctx.update(&mut app, keys(": HEAD<enter>"));
assert!(!ctx.dir.join(".git/gitu").exists());
}
#[test]
fn read_only_is_read_from_git_config() {
let dir = temp_dir::TempDir::new().unwrap();
let path = dir.path().join("gitconfig");
for (value, expected) in [
("true", ReadOnly::On),
("off", ReadOnly::Off),
("auto", ReadOnly::Auto),
] {
fs::write(&path, format!("[delta-tui]\n\tread-only = {value}\n")).unwrap();
let git_config = git2::Config::open(&path).unwrap();
let config = crate::config::init_config_from(
PathBuf::from("/nonexistent/config.toml"),
Some(&git_config),
)
.unwrap();
assert_eq!(config.general.read_only, expected, "{value}");
}
}
fn notice_under(buffer: &str, heading: &str, notice: &str) -> bool {
let rows = buffer.lines().collect::<Vec<_>>();
let Some((heading_row, column)) = rows
.iter()
.enumerate()
.find_map(|(i, row)| Some((i, row.find(&format!(" {heading}"))? + 3)))
else {
return false;
};
let text = rows[heading_row + 1..]
.iter()
.filter_map(|row| row.get(column..))
.map(|rest| rest.trim_end_matches('|').trim())
.filter(|rest| !rest.is_empty())
.collect::<Vec<_>>()
.join(" ");
text.contains(notice)
}
#[test]
fn the_help_menu_says_it_is_read_only() {
let mut ctx = setup_clone!();
read_only(&mut ctx);
let mut app = ctx.init_app();
ctx.resize(&mut app, &[super::HELP_FITS]);
ctx.update(&mut app, keys("?"));
let buffer = ctx.redact_buffer();
assert!(
notice_under(
&buffer,
"Section",
"Read-only: commands that change the repository are hidden"
),
"{buffer}"
);
}
#[test]
fn the_help_menu_says_a_jj_repository_is_read_only() {
let mut ctx = setup_clone!();
colocate_jj(&ctx);
let mut app = ctx.init_app();
ctx.resize(&mut app, &[super::HELP_FITS]);
ctx.update(&mut app, keys("?"));
let buffer = ctx.redact_buffer();
assert!(
notice_under(
&buffer,
"Section",
"Read-only (.jj found): commands that change the repository are hidden"
),
"{buffer}"
);
}