mirror of
https://github.com/feigeCode/navop.git
synced 2026-09-22 00:01:25 +00:00
205 lines
6.8 KiB
Rust
205 lines
6.8 KiB
Rust
use markdown_source::{
|
|
PatchError, SourceEdit, SourceEditOrigin, SourceHistory, SourceMarkdownDocument,
|
|
SourceTransaction,
|
|
};
|
|
|
|
fn title_edit(document: &SourceMarkdownDocument) -> SourceTransaction {
|
|
SourceTransaction {
|
|
edits: vec![SourceEdit::new(2..7, "New Title", document.revision)],
|
|
origin: SourceEditOrigin::RichTextTyping,
|
|
allowed_ranges: vec![2..7],
|
|
selection_before: markdown_source::SourceSelection::default(),
|
|
selection_after: markdown_source::SourceSelection::default(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn transaction_changes_only_the_allowed_source_range() {
|
|
let document = SourceMarkdownDocument::parse("# Title\n\n_italic_\n").unwrap();
|
|
let applied = document.apply_transaction(&title_edit(&document)).unwrap();
|
|
assert_eq!("# New Title\n\n_italic_\n", applied.document.source);
|
|
assert_eq!(1, applied.document.revision);
|
|
assert_eq!("Title", applied.inverse_edits[0].replacement);
|
|
}
|
|
|
|
#[test]
|
|
fn stale_and_overlapping_edits_are_rejected() {
|
|
let document = SourceMarkdownDocument::parse("abcdef").unwrap();
|
|
let stale = SourceTransaction {
|
|
edits: vec![SourceEdit::new(0..1, "x", 99)],
|
|
origin: SourceEditOrigin::SourceEditor,
|
|
allowed_ranges: vec![0..1],
|
|
selection_before: markdown_source::SourceSelection::default(),
|
|
selection_after: markdown_source::SourceSelection::default(),
|
|
};
|
|
assert_eq!(
|
|
PatchError::StaleRevision,
|
|
document.apply_transaction(&stale).unwrap_err()
|
|
);
|
|
|
|
let overlapping = SourceTransaction {
|
|
edits: vec![SourceEdit::new(0..3, "x", 0), SourceEdit::new(2..4, "y", 0)],
|
|
origin: SourceEditOrigin::SourceEditor,
|
|
allowed_ranges: vec![0..4],
|
|
selection_before: markdown_source::SourceSelection::default(),
|
|
selection_after: markdown_source::SourceSelection::default(),
|
|
};
|
|
assert_eq!(
|
|
PatchError::OverlappingEdits,
|
|
document.apply_transaction(&overlapping).unwrap_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn undo_and_redo_restore_exact_markdown_spelling() {
|
|
let document = SourceMarkdownDocument::parse("# Title\n\n_italic_\n").unwrap();
|
|
let mut history = SourceHistory::new(document.clone());
|
|
history.apply(&title_edit(&document)).unwrap();
|
|
assert!(history.undo().unwrap().is_some());
|
|
assert_eq!(document.source, history.document().source);
|
|
assert!(history.redo().unwrap().is_some());
|
|
assert_eq!("# New Title\n\n_italic_\n", history.document().source);
|
|
}
|
|
|
|
#[test]
|
|
fn undo_and_redo_restore_source_selections() {
|
|
let document = SourceMarkdownDocument::parse("before").unwrap();
|
|
let mut history = SourceHistory::new(document.clone());
|
|
let transaction = SourceTransaction {
|
|
edits: vec![SourceEdit::new(0..6, "after", document.revision)],
|
|
origin: SourceEditOrigin::RichTextTyping,
|
|
allowed_ranges: vec![0..6],
|
|
selection_before: markdown_source::SourceSelection { anchor: 1, head: 4 },
|
|
selection_after: markdown_source::SourceSelection { anchor: 5, head: 5 },
|
|
};
|
|
history.apply(&transaction).unwrap();
|
|
assert_eq!(
|
|
transaction.selection_before,
|
|
history.undo().unwrap().unwrap()
|
|
);
|
|
assert_eq!(
|
|
transaction.selection_after,
|
|
history.redo().unwrap().unwrap()
|
|
);
|
|
assert_eq!(
|
|
transaction.selection_before,
|
|
history.undo().unwrap().unwrap()
|
|
);
|
|
assert_eq!(
|
|
transaction.selection_after,
|
|
history.redo().unwrap().unwrap()
|
|
);
|
|
}
|
|
|
|
fn insertion(
|
|
document: &SourceMarkdownDocument,
|
|
offset: usize,
|
|
value: &str,
|
|
origin: SourceEditOrigin,
|
|
) -> SourceTransaction {
|
|
SourceTransaction {
|
|
edits: vec![SourceEdit::new(offset..offset, value, document.revision)],
|
|
origin,
|
|
allowed_ranges: vec![offset..offset],
|
|
selection_before: markdown_source::SourceSelection {
|
|
anchor: offset,
|
|
head: offset,
|
|
},
|
|
selection_after: markdown_source::SourceSelection {
|
|
anchor: offset + value.len(),
|
|
head: offset + value.len(),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn continuous_typing_is_undone_and_redone_as_one_step() {
|
|
let document = SourceMarkdownDocument::parse("").unwrap();
|
|
let mut history = SourceHistory::new(document);
|
|
history
|
|
.apply(&insertion(
|
|
history.document(),
|
|
0,
|
|
"你",
|
|
SourceEditOrigin::RichTextTyping,
|
|
))
|
|
.unwrap();
|
|
history
|
|
.apply(&insertion(
|
|
history.document(),
|
|
"你".len(),
|
|
"好",
|
|
SourceEditOrigin::RichTextTyping,
|
|
))
|
|
.unwrap();
|
|
history
|
|
.apply(&insertion(
|
|
history.document(),
|
|
"你好".len(),
|
|
"🙂",
|
|
SourceEditOrigin::RichTextTyping,
|
|
))
|
|
.unwrap();
|
|
|
|
assert_eq!("你好🙂", history.document().source);
|
|
assert!(history.undo().unwrap().is_some());
|
|
assert_eq!("", history.document().source);
|
|
assert!(history.undo().unwrap().is_none());
|
|
assert!(history.redo().unwrap().is_some());
|
|
assert_eq!("你好🙂", history.document().source);
|
|
}
|
|
|
|
#[test]
|
|
fn paste_is_a_typing_history_barrier() {
|
|
let document = SourceMarkdownDocument::parse("").unwrap();
|
|
let mut history = SourceHistory::new(document);
|
|
history
|
|
.apply(&insertion(
|
|
history.document(),
|
|
0,
|
|
"a",
|
|
SourceEditOrigin::RichTextTyping,
|
|
))
|
|
.unwrap();
|
|
history
|
|
.apply(&insertion(
|
|
history.document(),
|
|
1,
|
|
"pasted",
|
|
SourceEditOrigin::Paste,
|
|
))
|
|
.unwrap();
|
|
|
|
history.undo().unwrap();
|
|
assert_eq!("a", history.document().source);
|
|
history.undo().unwrap();
|
|
assert_eq!("", history.document().source);
|
|
}
|
|
|
|
#[test]
|
|
fn selection_discontinuity_prevents_typing_coalescing() {
|
|
let document = SourceMarkdownDocument::parse("ab").unwrap();
|
|
let mut history = SourceHistory::new(document);
|
|
let first = SourceTransaction {
|
|
edits: vec![SourceEdit::new(1..1, "x", history.document().revision)],
|
|
origin: SourceEditOrigin::RichTextTyping,
|
|
allowed_ranges: vec![1..1],
|
|
selection_before: markdown_source::SourceSelection { anchor: 1, head: 1 },
|
|
selection_after: markdown_source::SourceSelection { anchor: 2, head: 2 },
|
|
};
|
|
history.apply(&first).unwrap();
|
|
let second = SourceTransaction {
|
|
edits: vec![SourceEdit::new(0..0, "y", history.document().revision)],
|
|
origin: SourceEditOrigin::RichTextTyping,
|
|
allowed_ranges: vec![0..0],
|
|
selection_before: markdown_source::SourceSelection { anchor: 0, head: 0 },
|
|
selection_after: markdown_source::SourceSelection { anchor: 1, head: 1 },
|
|
};
|
|
history.apply(&second).unwrap();
|
|
|
|
history.undo().unwrap();
|
|
assert_eq!("axb", history.document().source);
|
|
history.undo().unwrap();
|
|
assert_eq!("ab", history.document().source);
|
|
}
|