Add cursor moving (#19)

This commit is contained in:
Andras Schmelczer 2025-04-02 22:06:38 +01:00 committed by GitHub
parent 29d8779786
commit 1f9728d893
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 1105 additions and 141 deletions

View file

@ -0,0 +1,98 @@
use std::{fs, path::Path};
use pretty_assertions::assert_eq;
use reconcile::{CursorPosition, TextWithCursors};
use serde::Deserialize;
/// `ExampleDocument` represents a test case for the reconciliation process.
/// It contains a parent string, left and right strings with cursor positions,
/// and the expected result after reconciliation.
///
/// '|' characters in the left, right, and expected strings are treated as
/// cursor positions and are converted into `CursorPosition` objects.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct ExampleDocument {
parent: String,
left: String,
right: String,
expected: String,
}
impl ExampleDocument {
/// Creates a new `ExampleDocument` instance from a YAML file.
///
/// # Panics
///
/// If the file cannot be opened or parsed, the program will panic.
#[must_use]
pub fn from_yaml(path: &Path) -> Self {
let file = fs::File::open(path).expect("Failed to open example file");
serde_yaml::from_reader(file).expect("Failed to parse example file")
}
#[must_use]
pub fn parent(&self) -> String { self.parent.clone() }
#[must_use]
pub fn left(&self) -> TextWithCursors<'static> {
ExampleDocument::string_to_text_with_cursors(&self.left)
}
#[must_use]
pub fn right(&self) -> TextWithCursors<'static> {
ExampleDocument::string_to_text_with_cursors(&self.right)
}
/// Asserts that the result string matches the expected string,
/// including cursor positions.
///
/// # Panics
///
/// If the result string does not match the expected string, the program
/// will panic.
pub fn assert_eq(&self, result: &TextWithCursors<'static>) {
let result_str = ExampleDocument::text_with_cursors_to_string(result);
assert_eq!(result_str, self.expected);
}
/// Asserts that the result string matches the expected string,
/// ignoring cursor positions.
///
/// # Panics
///
/// If the result string does not match the expected string, the program
/// will panic.
pub fn assert_eq_without_cursors(&self, result: &str) {
assert_eq!(
result,
ExampleDocument::string_to_text_with_cursors(&self.expected).text,
);
}
fn text_with_cursors_to_string(text: &TextWithCursors<'_>) -> String {
let mut result = text.text.clone().into_owned();
for (i, cursor) in text.cursors.iter().enumerate() {
result.insert(cursor.char_index + i, '|');
}
result
}
fn string_to_text_with_cursors(text: &str) -> TextWithCursors<'static> {
let cursors = Self::parse_cursors(text);
let text = text.replace('|', "");
TextWithCursors::new_owned(text, cursors)
}
fn parse_cursors(text: &str) -> Vec<CursorPosition> {
let mut cursors = Vec::new();
for (i, c) in text.chars().enumerate() {
if c == '|' {
cursors.push(CursorPosition {
id: 0,
char_index: i - cursors.len(),
});
}
}
cursors
}
}

View file

@ -0,0 +1,6 @@
# The `|` characters denote cursor positions which are stripped before the actual reconcile logic is run
---
parent: You're Annual Savings Statement is available in our online portal
left: Your| annual record is available in our online portal|
right: You're Annual Savings information| is available online
expected: Your| annual record information| is available online|

View file

@ -0,0 +1,4 @@
parent: marketplace
left: market| place
right: market|space
expected: market| placemarket|space

View file

@ -0,0 +1,4 @@
parent: Please remember to bring your laptop and charger
left: Please remember to bring your laptop|
right: Please remember to bring your |new |laptop and charger
expected: Please remember to bring your |new |laptop|

View file

@ -0,0 +1,4 @@
parent: Party A shall pay Party B
left: Party C shall pay Party B
right: Party A shall receive from Party B
expected: Party C shall receive from Party B

View file

@ -0,0 +1,4 @@
parent: Please submit your assignment by Friday
left: Please submit your |completed |assignment by Friday
right: Please submit your assignment |online |by Friday
expected: Please submit your |completed |assignment |online |by Friday

View file

@ -0,0 +1,4 @@
parent:
left: hi my friend|
right: hi there|
expected: hi my friend| there|

View file

@ -0,0 +1,4 @@
parent: Buy milk and eggs
left: Buy organic milk| and eggs|
right: Buy milk and eggs| and bread
expected: Buy organic milk| and eggs|| and bread

View file

@ -0,0 +1,4 @@
parent: Meeting at 2pm in 会议室
left: Meeting at |3pm in the 会议室
right: Team meeting at 2pm in conference room|
expected: Team meeting at |3pm in conference room| the

View file

@ -0,0 +1,4 @@
parent: Send the report to the team
left: Send the |detailed |report to the |entire |team
right: Send the |quarterly |detailed |report to the team
expected: Send the |detailed |quarterly |detailed ||report to the |entire |team

View file

@ -0,0 +1,4 @@
parent: Ready, Set go
left: Ready! Set go|
right: Ready, Set, go!|
expected: Ready! Set, go!||

View file

@ -0,0 +1,4 @@
parent: "Total: $100"
left: "Total: |$150"
right: "Total: |€100"
expected: "Total: |$150 |€100"

View file

@ -0,0 +1,4 @@
parent: Start middle end
left: Start [important] middle end|
right: Start middle [critical] end|
expected: Start [important] middle [critical] end||

View file

@ -0,0 +1,4 @@
parent: A B C D
left: A X B D|
right: A B Y|
expected: A X B Y||

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
mod example_document;
use std::{fs, path::Path};
use example_document::ExampleDocument;
use reconcile::{reconcile, reconcile_with_cursors};
#[test]
fn test_with_examples() {
let examples_dir = Path::new("tests/examples");
let mut entries = fs::read_dir(examples_dir)
.expect("Failed to read examples directory")
.collect::<Vec<_>>();
entries.sort_by_key(|entry| {
let path = entry
.as_ref()
.expect("Failed to read directory entry")
.path();
path.file_name()
.and_then(|name| name.to_str())
.and_then(|name| name.split('.').next().unwrap().parse::<i32>().ok())
.unwrap_or_default()
});
for entry in entries {
let entry = entry.expect("Failed to read directory entry");
let path = entry.path();
if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("yml") {
let doc = ExampleDocument::from_yaml(&path);
println!("Testing with example from {}", path.display());
doc.assert_eq_without_cursors(&reconcile(
&doc.parent(),
&doc.left().text,
&doc.right().text,
));
doc.assert_eq(&reconcile_with_cursors(
&doc.parent(),
doc.left(),
doc.right(),
));
}
}
}