use std::collections::HashMap; use rangemap::RangeMap;use unindent::Unindent as _;use warp_editor::multiline::{MultilineStr, MultilineString}; use super::{DiffModel, compute_unified_diff};use crate::code::editor::diff::ChangeType; #[test]fn test_diff_generation() { use warpui::App; App::test((), |_| async move { let (change_mapping, deletion_mapping) = DiffModel::compute_diff_internal( MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.") .unwrap(), MultilineStr::try_new( "Hallo Welt\nThis is the second line.\nThis is life.\nMoar and more", ) .unwrap(), ) .await; assert_eq!( change_mapping, RangeMap::from_iter([ ( 0..1, ChangeType::Replacement { replaced_range: 0..1, insertion: vec![0..5, 6..10], deletion: vec![0..5, 6..11] } ), ( 2..4, ChangeType::Replacement { replaced_range: 2..3, insertion: vec![8..13, 14..22, 23..27,], deletion: vec![8..11, 12..18] } ) ]) ); assert!(deletion_mapping.is_empty()); let (change_mapping, deletion_mapping) = DiffModel::compute_diff_internal( MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.") .unwrap(), MultilineStr::try_new("Hello World\nThis is the third.").unwrap(), ) .await; assert!(change_mapping.is_empty()); assert_eq!(deletion_mapping, HashMap::from([(1, 1..2)])); let (change_mapping, deletion_mapping) = DiffModel::compute_diff_internal( MultilineStr::try_new("Hello\nWorld\n").unwrap(), MultilineStr::try_new("Hallo\nWorlds\n").unwrap(), ) .await; assert_eq!( change_mapping, RangeMap::from_iter([( 0..2, ChangeType::Replacement { replaced_range: 0..2, insertion: vec![0..5, 6..12], deletion: vec![0..5, 6..11] } ),]) ); assert!(deletion_mapping.is_empty()); });} #[test]fn test_reverse_action() { use warpui::App; App::test((), |_| async move { let mut diff_model = DiffModel::new(); diff_model.set_base(MultilineString::apply( "Hello World\nThis is the second line\n", )); diff_model .compute_diff_for_test("Hallo World\nThis is the second line\nNew".to_string()) .await; assert_eq!(diff_model.diff_hunk_count(), 2); assert_eq!( diff_model.reverse_action_by_diff_hunk_index(0), Some((0..1, "Hello World\n".to_string())) ); assert_eq!( diff_model.reverse_action_by_diff_hunk_index(1), Some((2..3, "".to_string())) ); diff_model .compute_diff_for_test("Hello World\n".to_string()) .await; assert_eq!( diff_model.reverse_action_by_diff_hunk_index(0), Some((1..1, "This is the second line\n".to_string())) ); });} #[test]fn test_reverse_action_replaced_newlines() { use warpui::App; App::test((), |_| async move { let mut diff_model = DiffModel::new(); let base_text = r" abc def ghi jkl mno pqr stu " .unindent(); diff_model.set_base(MultilineString::apply(&base_text)); // Replace with text: // * Leading newline before "abc" // * "def" and the following line // * Newline between "def" and "ghi" // * "pqr" and the preceding line // * Trailing newline at end of file let modified_text = r" replaced first empty line abc changed def changed line after def ghi changed line between ghi and jkl jkl mno changed line before pqr changed pqr stu replaced last empty line " .unindent(); diff_model.compute_diff_for_test(modified_text).await; assert_eq!(diff_model.diff_hunk_count(), 5); // Reversing the leading newline change assert_eq!( diff_model.reverse_action_by_diff_hunk_index(0), Some((0..1, "\n".to_string())) ); // Reversing "def" and following newline assert_eq!( diff_model.reverse_action_by_diff_hunk_index(1), Some((2..4, "def\n\n".to_string())) ); // Reversing the line changed between "ghi" and "jkl" assert_eq!( diff_model.reverse_action_by_diff_hunk_index(2), Some((5..6, "\n".to_string())) ); // Reversing changing "pqr" and the line before it assert_eq!( diff_model.reverse_action_by_diff_hunk_index(3), Some((9..11, "\npqr\n".to_string())) ); // Reversing changing the last line assert_eq!( diff_model.reverse_action_by_diff_hunk_index(4), Some((13..14, "\n".to_string())) ); });} #[test]fn test_reverse_action_replaced_text() { use warpui::App; App::test((), |_| async move { let mut diff_model = DiffModel::new(); let base_text = r" abc def ghi jkl mno pqr stu vwx yz " .unindent(); diff_model.set_base(MultilineString::apply(&base_text)); // Replace with a newline: // * First line "abc" // * "ghi", which is followed by a newline // * "mno" // * "stu", which is preceded by a newline // * Last line "yz" let modified_text = r" def jkl pqr vwx " .unindent(); diff_model.compute_diff_for_test(modified_text).await; assert_eq!(diff_model.diff_hunk_count(), 5); // Reversing the leading newline change assert_eq!( diff_model.reverse_action_by_diff_hunk_index(0), Some((0..1, "abc\n".to_string())) ); // Reversing "ghi" assert_eq!( diff_model.reverse_action_by_diff_hunk_index(1), Some((3..4, "ghi\n".to_string())) ); // Reversing "mno" assert_eq!( diff_model.reverse_action_by_diff_hunk_index(2), Some((5..6, "mno\n".to_string())) ); // Reversing "stu" assert_eq!( diff_model.reverse_action_by_diff_hunk_index(3), Some((8..9, "stu\n".to_string())) ); // Reversing changing the last line assert_eq!( diff_model.reverse_action_by_diff_hunk_index(4), Some((10..11, "yz\n".to_string())) ); });} #[test]fn test_reverse_action_deleted_lines() { use warpui::App; App::test((), |_| async move { let mut diff_model = DiffModel::new(); let base_text = r" abc def ghi jkl mno pqr stu vwx yz " .unindent(); diff_model.set_base(MultilineString::apply(&base_text)); // Delete: // * Leading newline before "abc" // * Newline between "def" and "ghi" // * "mno" followed by a newline // * newline followed by "stu" // * Trailing newline after "yz" let modified_text = r" abc def ghi jkl pqr vwx yz " .unindent(); diff_model.compute_diff_for_test(modified_text).await; assert_eq!(diff_model.diff_hunk_count(), 5); // Reversing the leading newline deletion assert_eq!( diff_model.reverse_action_by_diff_hunk_index(0), Some((0..0, "\n".to_string())) ); // Reversing the newline deletion between "def" and "ghi" assert_eq!( diff_model.reverse_action_by_diff_hunk_index(1), Some((2..2, "\n".to_string())) ); // Reversing the deletion of "mno" followed by a newline assert_eq!( diff_model.reverse_action_by_diff_hunk_index(2), Some((5..5, "mno\n\n".to_string())) ); // Reversing the deletion of a newline followed by "stu" assert_eq!( diff_model.reverse_action_by_diff_hunk_index(3), Some((8..8, "\nstu\n".to_string())) ); // Reversing the trailing newline deletion assert_eq!( diff_model.reverse_action_by_diff_hunk_index(4), Some((11..11, "\n".to_string())) ); });} #[test]fn test_diff_count_before_line() { use warpui::App; App::test((), |_| async move { let mut diff_model = DiffModel::new(); diff_model.set_base( "Hello World\nThis is the second line\n" .to_owned() .try_into() .unwrap(), ); diff_model .compute_diff_for_test("Hallo World\nThis is the second line\nNew".to_string()) .await; assert_eq!(diff_model.diff_hunk_count_before_line(0), 0); assert_eq!(diff_model.diff_hunk_count_before_line(1), 1); assert_eq!(diff_model.diff_hunk_count_before_line(2), 1); });} #[test]fn test_unified_diff() { use warpui::App; App::test((), |_| async move { let diff = compute_unified_diff( MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.") .unwrap(), MultilineStr::try_new( "Hallo Welt\nThis is the second line.\nThis is life.\nMoar and more", ) .unwrap(), "test.rs", ) .await; assert_eq!( diff.unified_diff, "--- test.rs\n+++ test.rs\n@@ -1,3 +1,4 @@\n-Hello World\n+Hallo Welt\n This is the second line.\n-This is the third.\n+This is life.\n+Moar and more\n" ); assert_eq!(diff.lines_added, 3); assert_eq!(diff.lines_removed, 2); });} /// Test coalesce_replacements with a case where the `similar` library is known/// to produce duplicate deletion and insertion hunks for what is logically a replacement.#[test]fn test_coalesce_replacements() { use warpui::App; App::test((), |_| async move { let mut diff_model = DiffModel::new(); let base_text = r" abc def ghi jkl mno pqr stu vwx yz " .unindent(); diff_model.set_base(MultilineString::apply(&base_text)); // Replace with a newline: // * First line "abc" // * "ghi", which is followed by a newline // * "mno" // * "stu", which is preceded by a newline // * Last line "yz" let modified_text = r" def jkl pqr vwx " .unindent(); diff_model.compute_diff_for_test(modified_text).await; assert_eq!(diff_model.diff_hunk_count(), 5); // Replacing "abc" assert_eq!(diff_model.diff_by_index(0), Some((0..1, true))); // Replacing "ghi" assert_eq!(diff_model.diff_by_index(1), Some((3..4, true))); // Replacing "mno" assert_eq!(diff_model.diff_by_index(2), Some((5..6, true))); // Replacing "stu" assert_eq!(diff_model.diff_by_index(3), Some((8..9, true))); // Replacing "yz" assert_eq!(diff_model.diff_by_index(4), Some((10..11, true))); });}
use std::collections::HashMap;
use rangemap::RangeMap;
use unindent::Unindent as _;
use warp_editor::multiline::{MultilineStr, MultilineString};
use super::{DiffModel, compute_unified_diff};
use crate::code::editor::diff::ChangeType;
#[test]
fn test_diff_generation() {
use warpui::App;
App::test((), |_| async move {
let (change_mapping, deletion_mapping) = DiffModel::compute_diff_internal(
MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.")
.unwrap(),
MultilineStr::try_new(
"Hallo Welt\nThis is the second line.\nThis is life.\nMoar and more",
)
.unwrap(),
)
.await;
assert_eq!(
change_mapping,
RangeMap::from_iter([
(
0..1,
ChangeType::Replacement {
replaced_range: 0..1,
insertion: vec![0..5, 6..10],
deletion: vec![0..5, 6..11]
}
),
(
2..4,
ChangeType::Replacement {
replaced_range: 2..3,
insertion: vec![8..13, 14..22, 23..27,],
deletion: vec![8..11, 12..18]
}
)
])
);
assert!(deletion_mapping.is_empty());
let (change_mapping, deletion_mapping) = DiffModel::compute_diff_internal(
MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.")
.unwrap(),
MultilineStr::try_new("Hello World\nThis is the third.").unwrap(),
)
.await;
assert!(change_mapping.is_empty());
assert_eq!(deletion_mapping, HashMap::from([(1, 1..2)]));
let (change_mapping, deletion_mapping) = DiffModel::compute_diff_internal(
MultilineStr::try_new("Hello\nWorld\n").unwrap(),
MultilineStr::try_new("Hallo\nWorlds\n").unwrap(),
)
.await;
assert_eq!(
change_mapping,
RangeMap::from_iter([(
0..2,
ChangeType::Replacement {
replaced_range: 0..2,
insertion: vec![0..5, 6..12],
deletion: vec![0..5, 6..11]
}
),])
);
assert!(deletion_mapping.is_empty());
});
}
#[test]
fn test_reverse_action() {
use warpui::App;
App::test((), |_| async move {
let mut diff_model = DiffModel::new();
diff_model.set_base(MultilineString::apply(
"Hello World\nThis is the second line\n",
));
diff_model
.compute_diff_for_test("Hallo World\nThis is the second line\nNew".to_string())
.await;
assert_eq!(diff_model.diff_hunk_count(), 2);
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(0),
Some((0..1, "Hello World\n".to_string()))
);
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(1),
Some((2..3, "".to_string()))
);
diff_model
.compute_diff_for_test("Hello World\n".to_string())
.await;
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(0),
Some((1..1, "This is the second line\n".to_string()))
);
});
}
#[test]
fn test_reverse_action_replaced_newlines() {
use warpui::App;
App::test((), |_| async move {
let mut diff_model = DiffModel::new();
let base_text = r"
abc
def
ghi
jkl
mno
pqr
stu
"
.unindent();
diff_model.set_base(MultilineString::apply(&base_text));
// Replace with text:
// * Leading newline before "abc"
// * "def" and the following line
// * Newline between "def" and "ghi"
// * "pqr" and the preceding line
// * Trailing newline at end of file
let modified_text = r"
replaced first empty line
abc
changed def
changed line after def
ghi
changed line between ghi and jkl
jkl
mno
changed line before pqr
changed pqr
stu
replaced last empty line
"
.unindent();
diff_model.compute_diff_for_test(modified_text).await;
assert_eq!(diff_model.diff_hunk_count(), 5);
// Reversing the leading newline change
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(0),
Some((0..1, "\n".to_string()))
);
// Reversing "def" and following newline
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(1),
Some((2..4, "def\n\n".to_string()))
);
// Reversing the line changed between "ghi" and "jkl"
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(2),
Some((5..6, "\n".to_string()))
);
// Reversing changing "pqr" and the line before it
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(3),
Some((9..11, "\npqr\n".to_string()))
);
// Reversing changing the last line
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(4),
Some((13..14, "\n".to_string()))
);
});
}
#[test]
fn test_reverse_action_replaced_text() {
use warpui::App;
App::test((), |_| async move {
let mut diff_model = DiffModel::new();
let base_text = r"
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz
"
.unindent();
diff_model.set_base(MultilineString::apply(&base_text));
// Replace with a newline:
// * First line "abc"
// * "ghi", which is followed by a newline
// * "mno"
// * "stu", which is preceded by a newline
// * Last line "yz"
let modified_text = r"
def
jkl
pqr
vwx
"
.unindent();
diff_model.compute_diff_for_test(modified_text).await;
assert_eq!(diff_model.diff_hunk_count(), 5);
// Reversing the leading newline change
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(0),
Some((0..1, "abc\n".to_string()))
);
// Reversing "ghi"
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(1),
Some((3..4, "ghi\n".to_string()))
);
// Reversing "mno"
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(2),
Some((5..6, "mno\n".to_string()))
);
// Reversing "stu"
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(3),
Some((8..9, "stu\n".to_string()))
);
// Reversing changing the last line
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(4),
Some((10..11, "yz\n".to_string()))
);
});
}
#[test]
fn test_reverse_action_deleted_lines() {
use warpui::App;
App::test((), |_| async move {
let mut diff_model = DiffModel::new();
let base_text = r"
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz
"
.unindent();
diff_model.set_base(MultilineString::apply(&base_text));
// Delete:
// * Leading newline before "abc"
// * Newline between "def" and "ghi"
// * "mno" followed by a newline
// * newline followed by "stu"
// * Trailing newline after "yz"
let modified_text = r"
abc
def
ghi
jkl
pqr
vwx
yz
"
.unindent();
diff_model.compute_diff_for_test(modified_text).await;
assert_eq!(diff_model.diff_hunk_count(), 5);
// Reversing the leading newline deletion
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(0),
Some((0..0, "\n".to_string()))
);
// Reversing the newline deletion between "def" and "ghi"
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(1),
Some((2..2, "\n".to_string()))
);
// Reversing the deletion of "mno" followed by a newline
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(2),
Some((5..5, "mno\n\n".to_string()))
);
// Reversing the deletion of a newline followed by "stu"
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(3),
Some((8..8, "\nstu\n".to_string()))
);
// Reversing the trailing newline deletion
assert_eq!(
diff_model.reverse_action_by_diff_hunk_index(4),
Some((11..11, "\n".to_string()))
);
});
}
#[test]
fn test_diff_count_before_line() {
use warpui::App;
App::test((), |_| async move {
let mut diff_model = DiffModel::new();
diff_model.set_base(
"Hello World\nThis is the second line\n"
.to_owned()
.try_into()
.unwrap(),
);
diff_model
.compute_diff_for_test("Hallo World\nThis is the second line\nNew".to_string())
.await;
assert_eq!(diff_model.diff_hunk_count_before_line(0), 0);
assert_eq!(diff_model.diff_hunk_count_before_line(1), 1);
assert_eq!(diff_model.diff_hunk_count_before_line(2), 1);
});
}
#[test]
fn test_unified_diff() {
use warpui::App;
App::test((), |_| async move {
let diff = compute_unified_diff(
MultilineStr::try_new("Hello World\nThis is the second line.\nThis is the third.")
.unwrap(),
MultilineStr::try_new(
"Hallo Welt\nThis is the second line.\nThis is life.\nMoar and more",
)
.unwrap(),
"test.rs",
)
.await;
assert_eq!(
diff.unified_diff,
"--- test.rs\n+++ test.rs\n@@ -1,3 +1,4 @@\n-Hello World\n+Hallo Welt\n This is the second line.\n-This is the third.\n+This is life.\n+Moar and more\n"
);
assert_eq!(diff.lines_added, 3);
assert_eq!(diff.lines_removed, 2);
});
}
/// Test coalesce_replacements with a case where the `similar` library is known
/// to produce duplicate deletion and insertion hunks for what is logically a replacement.
#[test]
fn test_coalesce_replacements() {
use warpui::App;
App::test((), |_| async move {
let mut diff_model = DiffModel::new();
let base_text = r"
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz
"
.unindent();
diff_model.set_base(MultilineString::apply(&base_text));
// Replace with a newline:
// * First line "abc"
// * "ghi", which is followed by a newline
// * "mno"
// * "stu", which is preceded by a newline
// * Last line "yz"
let modified_text = r"
def
jkl
pqr
vwx
"
.unindent();
diff_model.compute_diff_for_test(modified_text).await;
assert_eq!(diff_model.diff_hunk_count(), 5);
// Replacing "abc"
assert_eq!(diff_model.diff_by_index(0), Some((0..1, true)));
// Replacing "ghi"
assert_eq!(diff_model.diff_by_index(1), Some((3..4, true)));
// Replacing "mno"
assert_eq!(diff_model.diff_by_index(2), Some((5..6, true)));
// Replacing "stu"
assert_eq!(diff_model.diff_by_index(3), Some((8..9, true)));
// Replacing "yz"
assert_eq!(diff_model.diff_by_index(4), Some((10..11, true)));
});
}