use super::*; fn test_sources(sides: usize) -> Vec { (0..sides * 2 - 1) .map(|index| Source { label: format!("source {index}"), base: index % 2 == 1, kind: FileKind::Text, bytes: b"historical full file; must never replace working text\n".to_vec(), tree_id: String::new(), term: index / 2, sides, bases: sides - 1, node: Node::Absent, }) .collect() } fn snapshot(left: &str, base: &str, right: &str) -> String { format!( "<<<<<<< conflict\n+++++++ side 1\n{left}------- base\n{base}+++++++ side 2\n{right}>>>>>>> conflict ends\n" ) } #[test] fn current_working_projections_keep_partial_resolutions_and_multiple_regions() { let sources = test_sources(2); let raw = format!( "unsaved heading\nalready manually resolved\n{}auto-merged middle\n<<<<<<< conflict\n%%%%%%% Changes from base to side 1\n\\\\\\\\\\\\\\ continuation\n-old two\n+left two\n+++++++ side 2\nright two\n>>>>>>> conflict ends\nunsaved tail", snapshot("left one\n", "old one\n", "right one\n") ); for (index, first, second) in [ (0, "left one", "left two"), (1, "old one", "old two"), (2, "right one", "right two"), ] { assert_eq!( project(&raw, &sources[index]).unwrap(), format!( "unsaved heading\nalready manually resolved\n{first}\nauto-merged middle\n{second}\nunsaved tail" ) ); } } #[test] fn alternatives_are_frozen_from_the_same_working_text_as_the_seed() { let sources = test_sources(2); let raw = format!( "unsaved heading\n{}auto-merged middle\n{}unsaved tail", snapshot("left-one\n", "old-one\n", "right-one\n"), snapshot("left-two\n", "old-two\n", "right-two\n") ); let projection = Projection::new(&raw, &sources, 0).unwrap(); let mut draft = projection.seed().to_owned(); draft = draft.replace("unsaved heading", "later draft edit"); draft.push_str("\nnew draft ending\n"); assert_eq!( projection.alternative(2).unwrap(), "unsaved heading\nright-one\nauto-merged middle\nright-two\nunsaved tail" ); assert_eq!( projection.alternative(1).unwrap(), "unsaved heading\nold-one\nauto-merged middle\nold-two\nunsaved tail" ); assert_eq!( projection.seed(), "unsaved heading\nleft-one\nauto-merged middle\nleft-two\nunsaved tail" ); assert_eq!( draft, "later draft edit\nleft-one\nauto-merged middle\nleft-two\nunsaved tail\nnew draft ending\n" ); } #[test] fn all_multiway_sides_and_bases_project_the_same_current_context() { let sources = test_sources(3); let raw = "unsaved α\n<<<<<<<<<<< conflict\n+++++++++++ side 1\none\n----------- base 1\nold one\n+++++++++++ side 2\ntwo\n----------- base 2\nold two\n+++++++++++ side 3\nthree\n>>>>>>>>>>> conflict ends\nunsaved ω"; let projection = Projection::new(raw, &sources, 0).unwrap(); for (index, choice) in ["one", "old one", "two", "old two", "three"] .into_iter() .enumerate() { let expected = format!("unsaved α\n{choice}\nunsaved ω"); assert_eq!(project(raw, &sources[index]).unwrap(), expected); assert_eq!(projection.alternative(index).unwrap(), expected); } assert_eq!( Projection::new(raw, &sources, 4).unwrap().seed(), "unsaved α\nthree\nunsaved ω" ); } #[test] fn projections_preserve_mixed_eof_and_crlf_terms() { let sources = test_sources(2); for eol in ["\n", "\r\n"] { let choices = [ format!("left{eol}"), "old".to_owned(), format!("right{eol}{eol}"), ]; // jj appends one separator EOL to *each* term when any lacks an EOL, // and omits the EOL on the final marker. Existing term EOLs remain. let raw = format!( "heading{eol}<<<<<<< conflict{eol}+++++++ side 1{eol}{}{eol}------- base{eol}{}{eol}+++++++ side 2{eol}{}{eol}>>>>>>> conflict ends", choices[0], choices[1], choices[2] ); let projection = Projection::new(&raw, &sources, 0).unwrap(); for (index, choice) in choices.iter().enumerate() { assert_eq!( project(&raw, &sources[index]).unwrap(), format!("heading{eol}{choice}") ); assert_eq!( projection.alternative(index).unwrap(), format!("heading{eol}{choice}") ); } } } #[test] fn empty_choices_markerfree_text_and_invalid_choices_are_handled() { let mut sources = test_sources(2); sources[1].kind = FileKind::Deleted; let raw = format!("before\n{}after\n", snapshot("", "", "new\n")); let projection = Projection::new(&raw, &sources, 0).unwrap(); assert_eq!(projection.seed(), "before\nafter\n"); assert_eq!(projection.alternative(1).unwrap(), "before\nafter\n"); assert_eq!(projection.alternative(2).unwrap(), "before\nnew\nafter\n"); let resolved = Projection::new("manually resolved, no final EOL", &sources, 0).unwrap(); assert_eq!( resolved.alternative(2).unwrap(), "manually resolved, no final EOL" ); assert!(resolved.alternative(sources.len()).is_err()); assert!(Projection::new(&raw, &sources, sources.len()).is_err()); assert!(Projection::new(&raw, &test_sources(3), 0).is_err()); sources[0].kind = FileKind::Binary; assert!(project(&raw, &sources[0]).is_err()); }