use super::*;
use crate::model::{DiffLineKind, FileOperation, FileState};
#[test]
fn test_parse_log_record() {
let record = "abc12345\tdef67890\tuser@example.com\t2024-01-29T15:30:00+0900\tInitial commit\ttrue\tfalse\tmain,feature";
let change = Parser::parse_log_record(record).unwrap();
assert_eq!(change.change_id, "abc12345");
assert_eq!(change.commit_id, "def67890");
assert_eq!(change.author, "user@example.com");
assert_eq!(change.description, "Initial commit");
assert!(change.is_working_copy);
assert!(!change.is_empty);
assert_eq!(change.bookmarks, vec!["main", "feature"]);
}
#[test]
fn test_parse_log_record_no_bookmarks() {
let record =
"abc12345\tdef67890\tuser@example.com\t2024-01-29T15:30:00+0900\tTest\tfalse\ttrue\t";
let change = Parser::parse_log_record(record).unwrap();
assert!(change.bookmarks.is_empty());
assert!(change.is_empty);
}
#[test]
fn test_parse_log_record_empty_fields() {
let record = "zzzzzzzz\t00000000\t\t1970-01-01T00:00:00+0000\t\tfalse\ttrue\t";
let change = Parser::parse_log_record(record).unwrap();
assert_eq!(change.change_id, "zzzzzzzz");
assert_eq!(change.commit_id, "00000000");
assert_eq!(change.author, "");
assert_eq!(change.description, "");
assert!(!change.is_working_copy);
assert!(change.is_empty);
}
#[test]
fn test_parse_log_multiple_records() {
let output = "abc12345\tdef67890\tuser@example.com\t2024-01-29T15:30:00+0900\tFirst\ttrue\tfalse\t\n\
xyz98765\tuvw43210\tother@example.com\t2024-01-28T10:00:00+0900\tSecond\tfalse\tfalse\t\n";
let changes = Parser::parse_log(output).unwrap();
assert_eq!(changes.len(), 2);
assert_eq!(changes[0].description, "First");
assert_eq!(changes[1].description, "Second");
}
#[test]
fn test_parse_status_line_added() {
let file = Parser::parse_status_line("A new_file.rs").unwrap();
assert_eq!(file.path, "new_file.rs");
assert!(matches!(file.state, FileState::Added));
}
#[test]
fn test_parse_status_line_modified() {
let file = Parser::parse_status_line("M src/main.rs").unwrap();
assert_eq!(file.path, "src/main.rs");
assert!(matches!(file.state, FileState::Modified));
}
#[test]
fn test_parse_status_line_deleted() {
let file = Parser::parse_status_line("D old_file.txt").unwrap();
assert_eq!(file.path, "old_file.txt");
assert!(matches!(file.state, FileState::Deleted));
}
#[test]
fn test_parse_status_line_conflicted() {
let file = Parser::parse_status_line("C conflicted.rs").unwrap();
assert_eq!(file.path, "conflicted.rs");
assert!(matches!(file.state, FileState::Conflicted));
}
#[test]
fn test_parse_status_output() {
let output = r#"Working copy changes:
A new_file.rs
M src/main.rs
Working copy : abc12345 def67890 (empty) (no description set)
Parent commit: xyz98765 uvw43210 Initial commit"#;
let status = Parser::parse_status(output).unwrap();
assert_eq!(status.files.len(), 2);
assert!(!status.has_conflicts);
assert_eq!(status.working_copy_change_id, "abc12345");
assert_eq!(status.parent_change_id, "xyz98765");
}
#[test]
fn test_parse_status_with_conflict() {
let output = r#"Working copy changes:
C conflicted.rs
Working copy : abc12345 def67890 description
Parent commit: xyz98765 uvw43210 parent"#;
let status = Parser::parse_status(output).unwrap();
assert!(status.has_conflicts);
assert_eq!(status.files.len(), 1);
assert!(matches!(status.files[0].state, FileState::Conflicted));
}
#[test]
fn test_parse_status_with_markers() {
let output = r#"Working copy changes:
A new_file.rs
M src/main.rs
Working copy (@) : abc12345 def67890 (empty) (no description set)
Parent commit (@-): xyz98765 uvw43210 Initial commit"#;
let status = Parser::parse_status(output).unwrap();
assert_eq!(status.files.len(), 2);
assert_eq!(status.working_copy_change_id, "abc12345");
assert_eq!(status.parent_change_id, "xyz98765");
}
#[test]
fn test_parse_status_line_renamed() {
let file = Parser::parse_status_line("R src/{old_name.rs => new_name.rs}").unwrap();
assert_eq!(file.path, "src/new_name.rs");
match file.state {
FileState::Renamed { from } => assert_eq!(from, "src/old_name.rs"),
_ => panic!("Expected Renamed state"),
}
}
#[test]
fn test_parse_status_line_renamed_no_prefix() {
let file = Parser::parse_status_line("R {old.rs => new.rs}").unwrap();
assert_eq!(file.path, "new.rs");
match file.state {
FileState::Renamed { from } => assert_eq!(from, "old.rs"),
_ => panic!("Expected Renamed state"),
}
}
#[test]
fn test_parse_status_line_renamed_deep_path() {
let file = Parser::parse_status_line("R src/ui/views/{status.rs => status_view.rs}").unwrap();
assert_eq!(file.path, "src/ui/views/status_view.rs");
match file.state {
FileState::Renamed { from } => assert_eq!(from, "src/ui/views/status.rs"),
_ => panic!("Expected Renamed state"),
}
}
#[test]
fn test_parse_show_header() {
let output = r#"Commit ID: abc123def456
Change ID: xyz789uvw012
Author : Test User <test@example.com> (2024-01-30 12:00:00)
Committer: Test User <test@example.com> (2024-01-30 12:00:00)
Add new feature
"#;
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.commit_id, "abc123def456");
assert_eq!(content.author, "Test User <test@example.com>");
assert_eq!(content.timestamp, "2024-01-30 12:00:00");
assert_eq!(content.description, "Add new feature");
}
#[test]
fn test_parse_show_empty_no_changes() {
let output = r#"Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
(no description set)
"#;
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.commit_id, "abc123");
assert!(!content.has_changes());
assert_eq!(content.file_count(), 0);
}
#[test]
fn test_parse_show_with_file_diff() {
let output = r#"Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
Fix bug
Modified regular file src/main.rs:
10 10: fn main() {
11 : - println!("old");
11: + println!("new");
12 12: }
"#;
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.commit_id, "abc123");
assert_eq!(content.description, "Fix bug");
assert!(content.has_changes());
assert_eq!(content.file_count(), 1);
assert_eq!(content.lines[0].kind, DiffLineKind::FileHeader);
assert_eq!(content.lines[0].content, "src/main.rs");
}
#[test]
fn test_parse_show_multiple_files() {
let output = r#"Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
Add files
Added regular file src/new.rs:
1: + pub fn hello() {}
Modified regular file src/lib.rs:
10 10: mod existing;
11: + mod new;
"#;
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.file_count(), 2);
assert_eq!(content.lines[0].kind, DiffLineKind::FileHeader);
assert_eq!(content.lines[0].content, "src/new.rs");
let sep_pos = content
.lines
.iter()
.position(|l| l.kind == DiffLineKind::Separator)
.unwrap();
assert!(sep_pos > 0);
let second_header = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::FileHeader)
.nth(1)
.unwrap();
assert_eq!(second_header.content, "src/lib.rs");
}
#[test]
fn test_parse_show_conflict_diff() {
let output = "Commit ID: c285b17e
Change ID: lqxuvokn
Author : Test <test@example.com> (2026-02-05 10:33:31)
Committer: Test <test@example.com> (2026-02-05 10:33:50)
branch B
Created conflict in test.txt:
1 : branch A content
1: <<<<<<< conflict 1 of 1
2: %%%%%%% changes from initial
3: -line 1
4: +branch A content
5: +++++++ branch B
6: branch B content
7: >>>>>>> conflict 1 of 1 ends
";
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.commit_id, "c285b17e");
assert_eq!(content.description, "branch B");
assert!(content.has_changes());
assert_eq!(content.file_count(), 1);
assert_eq!(content.lines[0].kind, DiffLineKind::FileHeader);
assert_eq!(content.lines[0].content, "test.txt");
assert!(content.lines.len() > 1);
}
#[test]
fn test_extract_file_info() {
let (path, op) = Parser::extract_file_info("Modified regular file src/main.rs:").unwrap();
assert_eq!(path, "src/main.rs");
assert_eq!(op, FileOperation::Modified);
let (path, op) = Parser::extract_file_info("Added regular file src/new.rs:").unwrap();
assert_eq!(path, "src/new.rs");
assert_eq!(op, FileOperation::Added);
let (path, op) = Parser::extract_file_info("Removed regular file old.txt:").unwrap();
assert_eq!(path, "old.txt");
assert_eq!(op, FileOperation::Deleted);
let (path, op) = Parser::extract_file_info("Created conflict in test.txt:").unwrap();
assert_eq!(path, "test.txt");
assert_eq!(op, FileOperation::Modified);
let (path, op) = Parser::extract_file_info("Resolved conflict in test.txt:").unwrap();
assert_eq!(path, "test.txt");
assert_eq!(op, FileOperation::Modified);
assert!(Parser::extract_file_info("Some other line").is_none());
}
#[test]
fn test_parse_author_line() {
let (author, ts) =
Parser::parse_author_line("Test User <test@example.com> (2024-01-30 12:00:00)").unwrap();
assert_eq!(author, "Test User <test@example.com>");
assert_eq!(ts, "2024-01-30 12:00:00");
}
#[test]
fn test_parse_diff_line_context() {
let line =
Parser::parse_diff_line(" 10 10: fn main() {", FileOperation::Modified).unwrap();
assert_eq!(line.kind, DiffLineKind::Context);
assert_eq!(line.line_numbers, Some((Some(10), Some(10))));
}
#[test]
fn test_parse_diff_line_added() {
let line = Parser::parse_diff_line(
" 11: + println!(\"new\");",
FileOperation::Modified,
)
.unwrap();
assert_eq!(line.kind, DiffLineKind::Added);
}
#[test]
fn test_parse_diff_line_deleted() {
let line = Parser::parse_diff_line(
" 11 : - println!(\"old\");",
FileOperation::Modified,
)
.unwrap();
assert_eq!(line.kind, DiffLineKind::Deleted);
}
#[test]
fn test_parse_diff_line_added_file_no_prefix() {
let line = Parser::parse_diff_line(" 1: // Hotfix", FileOperation::Added).unwrap();
assert_eq!(line.kind, DiffLineKind::Added);
}
#[test]
fn test_parse_diff_line_deleted_file_no_prefix() {
let line = Parser::parse_diff_line(" 1 : old content", FileOperation::Deleted).unwrap();
assert_eq!(line.kind, DiffLineKind::Deleted);
}
#[test]
fn test_split_graph_prefix_simple() {
let (prefix, id) = Parser::split_graph_prefix("@ oqwroxvu").unwrap();
assert_eq!(prefix, "@ ");
assert_eq!(id, "oqwroxvu");
}
#[test]
fn test_split_graph_prefix_one_level() {
let (prefix, id) = Parser::split_graph_prefix("│ ○ nuzyqrpm").unwrap();
assert_eq!(prefix, "│ ○ ");
assert_eq!(id, "nuzyqrpm");
}
#[test]
fn test_split_graph_prefix_two_level() {
let (prefix, id) = Parser::split_graph_prefix("│ │ ○ uslxsspn").unwrap();
assert_eq!(prefix, "│ │ ○ ");
assert_eq!(id, "uslxsspn");
}
#[test]
fn test_split_graph_prefix_complex() {
let (prefix, id) = Parser::split_graph_prefix("│ ○ │ rnstomqt").unwrap();
assert_eq!(prefix, "│ ○ │ ");
assert_eq!(id, "rnstomqt");
}
#[test]
fn test_split_graph_prefix_no_change_id() {
let result = Parser::split_graph_prefix("│ ├─╮");
assert!(result.is_err());
}
#[test]
fn test_parse_log_with_graph_simple() {
let output = "@ oqwroxvu\t1f7a8c00\tuser@example.com\t2026-01-30T16:17:51+0900\texperimental: results\ttrue\tfalse\t\n\
○ vxvxrlkn\tdd9bda5a\tuser@example.com\t2026-01-30T16:17:51+0900\texperimental: try\tfalse\tfalse\t";
let changes = Parser::parse_log(output).unwrap();
assert_eq!(changes.len(), 2);
assert_eq!(changes[0].graph_prefix, "@ ");
assert_eq!(changes[0].change_id, "oqwroxvu");
assert!(changes[0].is_working_copy);
assert!(!changes[0].is_graph_only);
assert_eq!(changes[1].graph_prefix, "○ ");
assert_eq!(changes[1].change_id, "vxvxrlkn");
assert!(!changes[1].is_working_copy);
}
#[test]
fn test_parse_log_with_graph_branch() {
let output = "@ oqwroxvu\t1f7a8c00\tuser@example.com\t2026-01-30T16:17:51+0900\tfeature\ttrue\tfalse\t\n\
│ ○ nuzyqrpm\t8b644ab5\tuser@example.com\t2026-01-30T16:17:46+0900\tmain\tfalse\tfalse\t\n\
├─╯\n\
○ basecommit\tbase1234\tuser@example.com\t2026-01-30T16:15:24+0900\tbase\tfalse\tfalse\t";
let changes = Parser::parse_log(output).unwrap();
assert_eq!(changes.len(), 4);
assert_eq!(changes[0].graph_prefix, "@ ");
assert_eq!(changes[0].change_id, "oqwroxvu");
assert!(!changes[0].is_graph_only);
assert_eq!(changes[1].graph_prefix, "│ ○ ");
assert_eq!(changes[1].change_id, "nuzyqrpm");
assert!(!changes[1].is_graph_only);
assert_eq!(changes[2].graph_prefix, "├─╯");
assert!(changes[2].is_graph_only);
assert!(changes[2].change_id.is_empty());
assert_eq!(changes[3].graph_prefix, "○ ");
assert_eq!(changes[3].change_id, "basecommit");
assert!(!changes[3].is_graph_only);
}
#[test]
fn test_parse_log_graph_only_lines() {
let patterns = ["│ ├─╮", "├─╯ │", "├───╯", "│ │", "│"];
for pattern in patterns {
let changes = Parser::parse_log(pattern).unwrap();
assert_eq!(changes.len(), 1);
assert!(changes[0].is_graph_only);
assert_eq!(changes[0].graph_prefix, pattern);
}
}
#[test]
fn test_parse_log_empty_lines_skipped() {
let output = "@ oqwroxvu\t1f7a8c00\tuser@example.com\t2026-01-30T16:17:51+0900\ttest\ttrue\tfalse\t\n\
\n\
○ vxvxrlkn\tdd9bda5a\tuser@example.com\t2026-01-30T16:17:51+0900\ttest2\tfalse\tfalse\t";
let changes = Parser::parse_log(output).unwrap();
assert_eq!(changes.len(), 2);
}
#[test]
fn test_parse_op_log_single() {
let output = "abc123def456\tuser@example.com\t5 minutes ago\tsnapshot working copy";
let operations = Parser::parse_op_log(output).unwrap();
assert_eq!(operations.len(), 1);
assert_eq!(operations[0].id, "abc123def456");
assert_eq!(operations[0].user, "user@example.com");
assert_eq!(operations[0].timestamp, "5 minutes ago");
assert_eq!(operations[0].description, "snapshot working copy");
assert!(operations[0].is_current); }
#[test]
fn test_parse_op_log_multiple() {
let output = "abc123def456\tuser@example.com\t5 minutes ago\tsnapshot working copy\n\
xyz789uvw012\tuser@example.com\t10 minutes ago\tdescribe commit abc\n\
def456ghi789\tuser@example.com\t1 hour ago\tnew empty commit";
let operations = Parser::parse_op_log(output).unwrap();
assert_eq!(operations.len(), 3);
assert!(operations[0].is_current);
assert!(!operations[1].is_current);
assert!(!operations[2].is_current);
assert_eq!(operations[0].description, "snapshot working copy");
assert_eq!(operations[1].description, "describe commit abc");
assert_eq!(operations[2].description, "new empty commit");
}
#[test]
fn test_parse_op_log_empty_lines_skipped() {
let output = "abc123\tuser\t5 min ago\top1\n\n\nxyz789\tuser\t10 min ago\top2";
let operations = Parser::parse_op_log(output).unwrap();
assert_eq!(operations.len(), 2);
}
#[test]
fn test_parse_op_log_malformed_lines_skipped() {
let output = "abc123\tuser\t5 min ago\tvalid op\n\
malformed line\n\
xyz789\tuser\t10 min ago\tanother valid op";
let operations = Parser::parse_op_log(output).unwrap();
assert_eq!(operations.len(), 2);
}
#[test]
fn test_parse_file_annotate_basic() {
let output = "twzksoxt\taaa11111 nakamura 2026-01-30 10:43:19 1: //! Tij\n\
twzksoxt\taaa11111 nakamura 2026-01-30 10:43:19 2: //!\n\
qplomrst\tbbb22222 taro 2026-01-28 15:22:00 3: mod app;";
let content = Parser::parse_file_annotate(output, "src/main.rs").unwrap();
assert_eq!(content.file_path, "src/main.rs");
assert_eq!(content.len(), 3);
assert_eq!(content.lines[0].change_id, "twzksoxt");
assert_eq!(content.lines[0].commit_id, "aaa11111");
assert_eq!(content.lines[0].author, "nakamura");
assert_eq!(content.lines[0].line_number, 1);
assert!(content.lines[0].first_in_hunk);
assert_eq!(content.lines[0].content, "//! Tij");
assert_eq!(content.lines[1].change_id, "twzksoxt");
assert_eq!(content.lines[1].commit_id, "aaa11111");
assert_eq!(content.lines[1].line_number, 2);
assert!(!content.lines[1].first_in_hunk);
assert_eq!(content.lines[2].change_id, "qplomrst");
assert_eq!(content.lines[2].commit_id, "bbb22222");
assert_eq!(content.lines[2].line_number, 3);
assert!(content.lines[2].first_in_hunk);
}
#[test]
fn test_parse_file_annotate_with_tabs_in_content() {
let output = "twzksoxt\taaa11111 nakamura 2026-01-30 10:43:19 1: \tindented\twith\ttabs";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 1);
assert_eq!(content.lines[0].content, "\tindented\twith\ttabs");
}
#[test]
fn test_parse_file_annotate_empty_content() {
let output = "twzksoxt\taaa11111 nakamura 2026-01-30 10:43:19 1:";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 1);
assert_eq!(content.lines[0].content, "");
}
#[test]
fn test_parse_file_annotate_skips_empty_lines() {
let output = "twzksoxt\taaa11111 nakamura 2026-01-30 10:43:19 1: line1\n\n\nqplomrst\tbbb22222 taro 2026-01-28 15:22:00 4: line4";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 2);
assert_eq!(content.lines[0].line_number, 1);
assert_eq!(content.lines[1].line_number, 4); }
#[test]
fn test_parse_file_annotate_author_with_digits() {
let output = "abc12345\tccc33333 user1 2026-01-30 10:43:19 1: some content";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 1);
assert_eq!(content.lines[0].change_id, "abc12345");
assert_eq!(content.lines[0].commit_id, "ccc33333");
assert_eq!(content.lines[0].author, "user1");
assert_eq!(content.lines[0].line_number, 1);
assert_eq!(content.lines[0].content, "some content");
}
#[test]
fn test_parse_file_annotate_content_with_colon_pattern() {
let output = "twzksoxt\taaa11111 nakamura 2026-01-30 10:43:19 10: let x = 1: foo";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 1);
assert_eq!(content.lines[0].line_number, 10);
assert_eq!(content.lines[0].content, "let x = 1: foo");
}
#[test]
fn test_parse_file_annotate_variable_change_id_length() {
let output = "abcdefghij\txyz1234567 user 2026-01-30 10:43:19 1: content";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 1);
assert_eq!(content.lines[0].change_id, "abcdefghij");
assert_eq!(content.lines[0].commit_id, "xyz1234567");
let output3 = "abc\txyz user 2026-01-30 10:43:19 1: content";
let content3 = Parser::parse_file_annotate(output3, "test.rs").unwrap();
assert_eq!(content3.len(), 1);
assert_eq!(content3.lines[0].change_id, "abc");
assert_eq!(content3.lines[0].commit_id, "xyz");
}
#[test]
fn test_parse_file_annotate_complex_author_name() {
let output = "twzksoxt\taaa11111 John Doe 2026-01-30 10:43:19 1: content";
let content = Parser::parse_file_annotate(output, "test.rs").unwrap();
assert_eq!(content.len(), 1);
assert_eq!(content.lines[0].author, "John Doe");
}
#[test]
fn test_parse_resolve_list_tab_delimiter() {
let output = "test.txt\t2-sided conflict\n";
let files = Parser::parse_resolve_list(output);
assert_eq!(files.len(), 1);
assert_eq!(files[0].path, "test.txt");
assert_eq!(files[0].description, "2-sided conflict");
}
#[test]
fn test_parse_resolve_list_space_delimiter() {
let output = "test.txt 2-sided conflict\n";
let files = Parser::parse_resolve_list(output);
assert_eq!(files.len(), 1);
assert_eq!(files[0].path, "test.txt");
assert_eq!(files[0].description, "2-sided conflict");
}
#[test]
fn test_parse_resolve_list_multiple_spaces() {
let output = "src/main.rs 2-sided conflict\nsrc/lib.rs 3-sided conflict\n";
let files = Parser::parse_resolve_list(output);
assert_eq!(files.len(), 2);
assert_eq!(files[0].path, "src/main.rs");
assert_eq!(files[0].description, "2-sided conflict");
assert_eq!(files[1].path, "src/lib.rs");
assert_eq!(files[1].description, "3-sided conflict");
}
#[test]
fn test_parse_resolve_list_path_with_spaces() {
let output = "my file.txt 2-sided conflict\n";
let files = Parser::parse_resolve_list(output);
assert_eq!(files.len(), 1);
assert_eq!(files[0].path, "my file.txt");
assert_eq!(files[0].description, "2-sided conflict");
}
#[test]
fn test_parse_resolve_list_empty() {
let output = "";
let files = Parser::parse_resolve_list(output);
assert!(files.is_empty());
}
#[test]
fn test_parse_log_with_conflict() {
let record = "abc12345\tdef67890\tuser@example.com\t2026-01-01T00:00:00+0900\tdescription\ttrue\tfalse\tmain\ttrue";
let change = Parser::parse_log_record(record).unwrap();
assert!(change.has_conflict);
}
#[test]
fn test_parse_log_without_conflict() {
let record = "abc12345\tdef67890\tuser@example.com\t2026-01-01T00:00:00+0900\tdescription\ttrue\tfalse\tmain\tfalse";
let change = Parser::parse_log_record(record).unwrap();
assert!(!change.has_conflict);
}
#[test]
fn test_parse_log_missing_conflict_field() {
let record = "abc12345\tdef67890\tuser@example.com\t2026-01-01T00:00:00+0900\tdescription\ttrue\tfalse\tmain";
let change = Parser::parse_log_record(record).unwrap();
assert!(!change.has_conflict); }
#[test]
fn test_parse_show_multiline_description() {
let output = "Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
test-2
test-3
Modified regular file src/main.rs:
10 10: fn main() {
";
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.description, "test-2\n\ntest-3");
assert_eq!(content.file_count(), 1);
}
#[test]
fn test_parse_show_multiline_description_no_diff() {
let output = "Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
test-2
test-3
";
let content = Parser::parse_show(output).unwrap();
assert_eq!(content.description, "test-2\n\ntest-3");
assert_eq!(content.file_count(), 0);
}
#[test]
fn test_parse_show_three_paragraph_description() {
let output = "Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
Summary line
Detailed paragraph one.
Detailed paragraph two.
Modified regular file src/main.rs:
10 10: fn main() {
";
let content = Parser::parse_show(output).unwrap();
assert_eq!(
content.description,
"Summary line\n\nDetailed paragraph one.\n\nDetailed paragraph two."
);
}
#[test]
fn test_parse_diff_body_single_file() {
let output = "Modified regular file src/main.rs:
10 10: fn main() {
11 : - println!(\"old\");
11: + println!(\"new\");
12 12: }
";
let content = Parser::parse_diff_body(output);
assert!(content.commit_id.is_empty());
assert!(content.author.is_empty());
assert!(content.description.is_empty());
assert!(content.has_changes());
assert_eq!(content.file_count(), 1);
assert_eq!(content.lines[0].kind, DiffLineKind::FileHeader);
assert_eq!(content.lines[0].content, "src/main.rs");
}
#[test]
fn test_parse_diff_body_multiple_files() {
let output = "Modified regular file src/main.rs:
10 10: fn main() {
11 : - println!(\"old\");
11: + println!(\"new\");
Added regular file src/new.rs:
1: pub fn hello() {}
";
let content = Parser::parse_diff_body(output);
assert_eq!(content.file_count(), 2);
assert_eq!(content.lines[0].kind, DiffLineKind::FileHeader);
assert_eq!(content.lines[0].content, "src/main.rs");
}
#[test]
fn test_parse_diff_body_empty() {
let content = Parser::parse_diff_body("");
assert!(!content.has_changes());
assert_eq!(content.file_count(), 0);
}
#[test]
fn test_parse_show_stat_with_header() {
let output = "\
Commit ID: abc123
Change ID: xyz789
Author : Test User <test@test.com> (2024-01-30 12:00:00)
Committer: Test User <test@test.com> (2024-01-30 12:00:00)
Test commit message
src/main.rs | 10 ++++------
src/lib.rs | 5 +++++
2 files changed, 9 insertions(+), 6 deletions(-)";
let content = Parser::parse_show_stat(output).unwrap();
assert_eq!(content.commit_id, "abc123");
assert_eq!(content.description, "Test commit message");
assert!(content.lines.len() >= 3);
for line in &content.lines {
assert_eq!(line.kind, DiffLineKind::Context);
assert!(line.line_numbers.is_none());
}
}
#[test]
fn test_parse_show_stat_empty_commit() {
let output = "\
Commit ID: abc123
Change ID: xyz789
Author : Test User <test@test.com> (2024-01-30 12:00:00)
Committer: Test User <test@test.com> (2024-01-30 12:00:00)
Empty commit
";
let content = Parser::parse_show_stat(output).unwrap();
assert_eq!(content.lines.len(), 1);
assert_eq!(content.lines[0].content, "(no changes)");
}
#[test]
fn test_parse_diff_body_stat() {
let output = "\
src/main.rs | 10 ++++------
2 files changed, 9 insertions(+), 6 deletions(-)";
let content = Parser::parse_diff_body_stat(output);
assert_eq!(content.lines.len(), 2);
assert!(content.lines[0].content.contains("src/main.rs"));
}
#[test]
fn test_parse_diff_body_stat_empty() {
let content = Parser::parse_diff_body_stat("");
assert_eq!(content.lines.len(), 1);
assert_eq!(content.lines[0].content, "(no changes)");
}
#[test]
fn test_parse_show_git_with_header() {
let output = "\
Commit ID: abc123
Change ID: xyz789
Author : Test User <test@test.com> (2024-01-30 12:00:00)
Committer: Test User <test@test.com> (2024-01-30 12:00:00)
Test commit
diff --git a/src/main.rs b/src/main.rs
index 1234567..abcdefg 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
fn main() {
+ println!(\"hello\");
- println!(\"old\");
}";
let content = Parser::parse_show_git(output).unwrap();
assert_eq!(content.commit_id, "abc123");
assert_eq!(content.description, "Test commit");
let file_headers: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::FileHeader)
.collect();
assert_eq!(file_headers.len(), 1);
assert_eq!(file_headers[0].content, "src/main.rs");
let added: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::Added)
.collect();
assert_eq!(added.len(), 1);
assert_eq!(added[0].content, " println!(\"hello\");");
let deleted: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::Deleted)
.collect();
assert_eq!(deleted.len(), 1);
assert_eq!(deleted[0].content, " println!(\"old\");");
}
#[test]
fn test_parse_git_triple_plus_not_added() {
let output = "\
diff --git a/src/main.rs b/src/main.rs
index 1234567..abcdefg 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,3 @@
context
+added line
-deleted line";
let content = Parser::parse_diff_body_git(output);
let added: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::Added)
.collect();
assert_eq!(added.len(), 1);
assert_eq!(added[0].content, "added line");
let deleted: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::Deleted)
.collect();
assert_eq!(deleted.len(), 1);
assert_eq!(deleted[0].content, "deleted line");
}
#[test]
fn test_parse_git_hunk_header_is_context() {
let output = "\
diff --git a/src/main.rs b/src/main.rs
@@ -1,3 +1,3 @@
context";
let content = Parser::parse_diff_body_git(output);
let hunk_lines: Vec<_> = content
.lines
.iter()
.filter(|l| l.content.starts_with("@@ "))
.collect();
assert_eq!(hunk_lines.len(), 1);
assert_eq!(hunk_lines[0].kind, DiffLineKind::Context);
}
#[test]
fn test_parse_git_multiple_files() {
let output = "\
diff --git a/src/main.rs b/src/main.rs
@@ -1,1 +1,1 @@
-old
+new
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,0 +1,1 @@
+added";
let content = Parser::parse_diff_body_git(output);
let file_headers: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::FileHeader)
.collect();
assert_eq!(file_headers.len(), 2);
assert_eq!(file_headers[0].content, "src/main.rs");
assert_eq!(file_headers[1].content, "src/lib.rs");
}
#[test]
fn test_parse_git_index_line_skipped() {
let output = "\
diff --git a/file.rs b/file.rs
index 1234567..abcdefg 100644
--- a/file.rs
+++ b/file.rs
@@ -1,1 +1,1 @@
+hello";
let content = Parser::parse_diff_body_git(output);
let index_lines: Vec<_> = content
.lines
.iter()
.filter(|l| l.content.starts_with("index "))
.collect();
assert_eq!(index_lines.len(), 0);
}
#[test]
fn test_parse_show_file_op_set_on_headers() {
let output = r#"Commit ID: abc123
Change ID: xyz789
Author : Test <test@example.com> (2024-01-30 12:00:00)
Committer: Test <test@example.com> (2024-01-30 12:00:00)
Multiple file types
Added regular file src/new.rs:
1: pub fn new() {}
Modified regular file src/main.rs:
10 10: fn main() {
11: + println!("new");
11 12: }
Removed regular file old.txt:
1 : old content
"#;
let content = Parser::parse_show(output).unwrap();
let headers: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::FileHeader)
.collect();
assert_eq!(headers.len(), 3);
assert_eq!(headers[0].content, "src/new.rs");
assert_eq!(headers[0].file_op, Some(FileOperation::Added));
assert_eq!(headers[1].content, "src/main.rs");
assert_eq!(headers[1].file_op, Some(FileOperation::Modified));
assert_eq!(headers[2].content, "old.txt");
assert_eq!(headers[2].file_op, Some(FileOperation::Deleted));
}
#[test]
fn test_parse_diff_body_file_op_set_on_headers() {
let output = "\
Added regular file src/new.rs:
1: pub fn new() {}
Modified regular file src/main.rs:
10 10: fn main() {
11: + println!(\"new\");
11 12: }
Removed regular file old.txt:
1 : old content
";
let content = Parser::parse_diff_body(output);
let headers: Vec<_> = content
.lines
.iter()
.filter(|l| l.kind == DiffLineKind::FileHeader)
.collect();
assert_eq!(headers.len(), 3);
assert_eq!(headers[0].content, "src/new.rs");
assert_eq!(headers[0].file_op, Some(FileOperation::Added));
assert_eq!(headers[1].content, "src/main.rs");
assert_eq!(headers[1].file_op, Some(FileOperation::Modified));
assert_eq!(headers[2].content, "old.txt");
assert_eq!(headers[2].file_op, Some(FileOperation::Deleted));
}
#[test]
fn test_parse_diff_body_conflict_file_op() {
let output = "\
Created conflict in test.txt:
1 : original
1: <<<<<<< conflict
";
let content = Parser::parse_diff_body(output);
let header = content
.lines
.iter()
.find(|l| l.kind == DiffLineKind::FileHeader)
.unwrap();
assert_eq!(header.content, "test.txt");
assert_eq!(header.file_op, Some(FileOperation::Modified));
}
#[test]
fn test_parse_git_diff_file_op_is_none() {
let output = "\
diff --git a/src/main.rs b/src/main.rs
@@ -1,1 +1,1 @@
-old
+new";
let content = Parser::parse_diff_body_git(output);
let header = content
.lines
.iter()
.find(|l| l.kind == DiffLineKind::FileHeader)
.unwrap();
assert_eq!(header.content, "src/main.rs");
assert_eq!(header.file_op, None); }
#[test]
fn test_parse_diff_line_file_op_is_none() {
let line =
Parser::parse_diff_line(" 10 10: fn main() {", FileOperation::Modified).unwrap();
assert_eq!(line.file_op, None);
let line =
Parser::parse_diff_line(" 11: + new line", FileOperation::Modified).unwrap();
assert_eq!(line.file_op, None);
}