girt-core 2.2.1

Core modules for git-interactive-rebase-tool
Documentation
use input::KeyCode;
use view::assert_rendered_output;

use super::*;
use crate::{assert_process_result, events::Event, testutil::module_test};

fn assert_external_editor_state_eq(actual: &ExternalEditorState, expected: &ExternalEditorState) {
	let actual_state = match *actual {
		ExternalEditorState::Active => String::from("Active"),
		ExternalEditorState::Empty => String::from("Empty"),
		ExternalEditorState::Error(ref err) => format!("Error({:#})", err),
	};

	let expected_state = match *expected {
		ExternalEditorState::Active => String::from("Active"),
		ExternalEditorState::Empty => String::from("Empty"),
		ExternalEditorState::Error(ref err) => format!("Error({:#})", err),
	};

	if actual_state != expected_state {
		panic!(
			"{}",
			vec![
				"\n",
				"ExternalEditorState does not match",
				"==========",
				"Expected:",
				expected_state.as_str(),
				"Actual:",
				actual_state.as_str(),
				"==========\n"
			]
			.join("\n")
		);
	}
}

#[macro_export]
macro_rules! assert_external_editor_state_eq {
	($actual:expr, $expected:expr) => {
		assert_external_editor_state_eq(&$actual, &$expected);
	};
}

#[test]
fn activate() {
	module_test(&["pick aaa comment1", "drop bbb comment2"], &[], |test_context| {
		let mut module = ExternalEditor::new("editor");
		assert_process_result!(
			test_context.activate(&mut module, State::List),
			external_command = (String::from("editor"), vec![String::from(
				test_context.rebase_todo_file.get_filepath()
			)])
		);
		assert_eq!(test_context.rebase_todo_file.get_lines_owned(), vec![
			Line::new("pick aaa comment1").unwrap(),
			Line::new("drop bbb comment2").unwrap()
		]);
		assert!(!module.lines.is_empty());
		assert_external_editor_state_eq!(module.state, ExternalEditorState::Active);
		assert_eq!(
			module.external_command,
			(String::from("editor"), vec![String::from(
				test_context.rebase_todo_file.get_filepath()
			)])
		);
	});
}

#[test]
fn activate_write_file_fail() {
	module_test(&["pick aaa comment"], &[], |test_context| {
		let todo_path = test_context.get_todo_file_path();
		let mut module = ExternalEditor::new("editor");
		test_context.set_todo_file_readonly();
		assert_process_result!(
			test_context.activate(&mut module, State::List),
			state = State::List,
			error = anyhow!("Error opening file: {}: Permission denied (os error 13)", todo_path)
		);
	});
}

#[test]
fn activate_file_placement_marker() {
	module_test(&[], &[], |test_context| {
		let mut module = ExternalEditor::new("editor a % b");
		assert_process_result!(
			test_context.activate(&mut module, State::List),
			external_command = (String::from("editor"), vec![
				String::from("a"),
				String::from(test_context.rebase_todo_file.get_filepath()),
				String::from("b")
			])
		);
	});
}

#[test]
fn deactivate() {
	module_test(&["pick aaa comment", "drop bbb comment2"], &[], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		test_context.deactivate(&mut module);
		assert_eq!(module.lines, vec![]);
	});
}

#[test]
fn edit_success() {
	module_test(
		&["pick aaa comment"],
		&[Event::from(MetaEvent::ExternalCommandSuccess)],
		|mut test_context| {
			let mut module = ExternalEditor::new("editor");
			let _ = test_context.activate(&mut module, State::List);
			let view_data = test_context.build_view_data(&mut module);
			assert_rendered_output!(view_data, "{TITLE}", "{LEADING}", "{Normal}Editing...");
			assert_process_result!(
				test_context.handle_event(&mut module),
				event = Event::from(MetaEvent::ExternalCommandSuccess),
				state = State::List
			);
			assert_external_editor_state_eq!(module.state, ExternalEditorState::Active);
		},
	);
}

#[test]
fn empty_edit_error() {
	module_test(
		&["pick aaa comment"],
		&[Event::from('1'), Event::from(MetaEvent::ExternalCommandSuccess)],
		|mut test_context| {
			let mut module = ExternalEditor::new("editor");
			let _ = test_context.activate(&mut module, State::List);
			test_context.rebase_todo_file.set_lines(vec![]);
			test_context.rebase_todo_file.write_file().unwrap();
			let _ = test_context.handle_event(&mut module);
			assert_process_result!(
				test_context.handle_event(&mut module),
				event = Event::from(MetaEvent::ExternalCommandSuccess)
			);
			assert_external_editor_state_eq!(module.state, ExternalEditorState::Empty);
			let view_data = test_context.build_view_data(&mut module);
			assert_rendered_output!(
				view_data,
				"{TITLE}",
				"{LEADING}",
				"{Normal}The rebase file is empty.",
				"",
				"{BODY}",
				"{Normal}1) Abort rebase",
				"{Normal}2) Edit rebase file",
				"{Normal}3) Undo modifications and edit rebase file",
				"",
				"{IndicatorColor}Please choose an option."
			);
		},
	);
}

#[test]
fn empty_edit_abort_rebase() {
	module_test(&["pick aaa comment"], &[Event::from('1')], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Empty;
		assert_process_result!(
			test_context.handle_event(&mut module),
			event = Event::from('1'),
			exit_status = ExitStatus::Good
		);
	});
}

#[test]
fn empty_edit_re_edit_rebase_file() {
	module_test(&["pick aaa comment"], &[Event::from('2')], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Empty;
		assert_process_result!(
			test_context.handle_event(&mut module),
			event = Event::from('2'),
			external_command = (String::from("editor"), vec![String::from(
				test_context.rebase_todo_file.get_filepath()
			)])
		);
		assert_external_editor_state_eq!(module.state, ExternalEditorState::Active);
	});
}

#[test]
fn empty_edit_undo_and_edit() {
	module_test(
		&["pick aaa comment", "drop bbb comment"],
		&[Event::from('3')],
		|mut test_context| {
			let mut module = ExternalEditor::new("editor");
			let _ = test_context.activate(&mut module, State::List);
			module.state = ExternalEditorState::Empty;
			assert_process_result!(
				test_context.handle_event(&mut module),
				event = Event::from('3'),
				external_command = (String::from("editor"), vec![String::from(
					test_context.rebase_todo_file.get_filepath()
				)])
			);
			assert_external_editor_state_eq!(module.state, ExternalEditorState::Active);
			assert_eq!(test_context.rebase_todo_file.get_lines_owned(), vec![
				Line::new("pick aaa comment").unwrap(),
				Line::new("drop bbb comment").unwrap()
			]);
		},
	);
}

#[test]
fn empty_edit_noop() {
	module_test(&["pick aaa comment"], &[], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Empty;
		test_context.rebase_todo_file.set_lines(vec![]);
		let view_data = test_context.build_view_data(&mut module);
		assert_rendered_output!(
			view_data,
			"{TITLE}",
			"{LEADING}",
			"{Normal}The rebase file is empty.",
			"",
			"{BODY}",
			"{Normal}1) Abort rebase",
			"{Normal}2) Edit rebase file",
			"{Normal}3) Undo modifications and edit rebase file",
			"",
			"{IndicatorColor}Please choose an option."
		);
	});
}

#[test]
fn no_editor_set() {
	module_test(&["pick aaa comment"], &[], |test_context| {
		let mut module = ExternalEditor::new("");
		assert_process_result!(
			test_context.activate(&mut module, State::List),
			state = State::List,
			error = anyhow!("No editor configured: Please see the git \"core.editor\" configuration for details")
		);
	});
}

#[test]
fn editor_non_zero_exit() {
	module_test(
		&["pick aaa comment"],
		&[Event::from(MetaEvent::ExternalCommandError)],
		|mut test_context| {
			let mut module = ExternalEditor::new("editor");
			let _ = test_context.activate(&mut module, State::List);
			assert_process_result!(
				test_context.handle_event(&mut module),
				event = Event::from(MetaEvent::ExternalCommandError)
			);
			assert_external_editor_state_eq!(
				module.state,
				ExternalEditorState::Error(anyhow!("Editor returned a non-zero exit status"))
			);
			let view_data = test_context.build_view_data(&mut module);
			assert_rendered_output!(
				view_data,
				"{TITLE}",
				"{LEADING}",
				"{Normal}Editor returned a non-zero exit status",
				"",
				"{BODY}",
				"{Normal}1) Abort rebase",
				"{Normal}2) Edit rebase file",
				"{Normal}3) Restore rebase file and abort edit",
				"{Normal}4) Undo modifications and edit rebase file",
				"",
				"{IndicatorColor}Please choose an option."
			);
		},
	);
}

#[test]
fn editor_reload_error() {
	module_test(
		&["pick aaa comment"],
		&[Event::from(KeyCode::Up), Event::from(MetaEvent::ExternalCommandSuccess)],
		|mut test_context| {
			let todo_path = test_context.get_todo_file_path();
			let mut module = ExternalEditor::new("editor");
			let _ = test_context.activate(&mut module, State::List);
			test_context.delete_todo_file();
			let _ = test_context.handle_event(&mut module);
			assert_process_result!(
				test_context.handle_event(&mut module),
				event = Event::from(MetaEvent::ExternalCommandSuccess)
			);
			assert_external_editor_state_eq!(
				module.state,
				ExternalEditorState::Error(
					anyhow!("Error reading file: {}", todo_path).context("No such file or directory (os error 2)")
				)
			);
			let view_data = test_context.build_view_data(&mut module);
			assert_rendered_output!(
				view_data,
				"{TITLE}",
				"{LEADING}",
				"{Normal}No such file or directory (os error 2)",
				format!("{{Normal}}Error reading file: {}", todo_path),
				"",
				"{BODY}",
				"{Normal}1) Abort rebase",
				"{Normal}2) Edit rebase file",
				"{Normal}3) Restore rebase file and abort edit",
				"{Normal}4) Undo modifications and edit rebase file",
				"",
				"{IndicatorColor}Please choose an option."
			);
		},
	);
}

#[test]
fn error_abort_rebase() {
	module_test(&["pick aaa comment"], &[Event::from('1')], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Error(anyhow!("Error!"));
		assert_process_result!(
			test_context.handle_event(&mut module),
			event = Event::from('1'),
			exit_status = ExitStatus::Good
		);
		assert!(test_context.rebase_todo_file.is_empty());
	});
}

#[test]
fn error_edit_rebase() {
	module_test(&["pick aaa comment"], &[Event::from('2')], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Error(anyhow!("Error!"));
		assert_process_result!(
			test_context.handle_event(&mut module),
			event = Event::from('2'),
			external_command = (String::from("editor"), vec![String::from(
				test_context.rebase_todo_file.get_filepath()
			)])
		);
		assert_external_editor_state_eq!(module.state, ExternalEditorState::Active);
	});
}

#[test]
fn error_restore_and_abort() {
	module_test(&["pick aaa comment"], &[Event::from('3')], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Error(anyhow!("Error!"));
		assert_process_result!(
			test_context.handle_event(&mut module),
			event = Event::from('3'),
			state = State::List
		);
		assert_eq!(test_context.rebase_todo_file.get_lines_owned(), vec![Line::new(
			"pick aaa comment"
		)
		.unwrap()]);
	});
}

#[test]
fn error_undo_modifications_and_reedit() {
	module_test(&["pick aaa comment"], &[Event::from('4')], |mut test_context| {
		let mut module = ExternalEditor::new("editor");
		let _ = test_context.activate(&mut module, State::List);
		module.state = ExternalEditorState::Error(anyhow!("Error!"));
		assert_process_result!(
			test_context.handle_event(&mut module),
			event = Event::from('4'),
			external_command = (String::from("editor"), vec![String::from(
				test_context.rebase_todo_file.get_filepath()
			)])
		);
		assert_external_editor_state_eq!(module.state, ExternalEditorState::Active);
		assert_eq!(test_context.rebase_todo_file.get_lines_owned(), vec![Line::new(
			"pick aaa comment"
		)
		.unwrap()]);
	});
}