use text_document::{
DjotExportOptions, DjotImportOptions, FindOptions, ReplaceFormatPolicy, ReplaceOptions,
TextDocument,
};
fn replace_and_export(
djot: &str,
query: &str,
replacement: &str,
policy: ReplaceFormatPolicy,
) -> String {
let doc = TextDocument::new();
doc.set_djot_with_options(djot, DjotImportOptions::default())
.and_then(|op| op.wait())
.expect("set_djot");
let opts = ReplaceOptions::new(FindOptions::default()).with_format_policy(policy);
let count = doc
.replace_text(query, replacement, true, &opts)
.expect("replace_text");
assert!(count > 0, "the query must actually match");
doc.to_djot_with_options(DjotExportOptions::default())
.expect("to_djot")
.trim()
.to_string()
}
#[test]
fn the_default_policy_drops_the_styling_it_overwrites() {
let out = replace_and_export(
"She called *Aurélien* into the trees.",
"Aurélien",
"Aurélian",
ReplaceFormatPolicy::InheritPreceding,
);
assert_eq!(
out, "She called Aurélian into the trees.",
"the historical default loses the emphasis — pinned so it cannot change silently"
);
}
#[test]
fn preserve_if_fully_covered_keeps_a_wholly_styled_name() {
let out = replace_and_export(
"She called *Aurélien* into the trees.",
"Aurélien",
"Aurélian",
ReplaceFormatPolicy::PreserveIfFullyCovered,
);
assert_eq!(
out, "She called *Aurélian* into the trees.",
"a name that was entirely emphasised must stay emphasised across the rename"
);
}
#[test]
fn preserve_if_fully_covered_does_not_guess_on_a_partly_styled_range() {
let out = replace_and_export(
"She called Aurélien *Dubois* home.",
"Aurélien Dubois",
"Aurélien Duval",
ReplaceFormatPolicy::PreserveIfFullyCovered,
);
assert_eq!(
out, "She called Aurélien Duval home.",
"partly styled → fall back, do not paint the whole replacement with a style that \
only covered part of it"
);
}
#[test]
fn preserve_nothing_strips_the_styling() {
let out = replace_and_export(
"She called *Aurélien* into the trees.",
"Aurélien",
"Aurélian",
ReplaceFormatPolicy::PreserveNothing,
);
assert_eq!(out, "She called Aurélian into the trees.");
}
#[test]
fn keep_dominant_run_keeps_the_style_that_covered_most_of_the_range() {
let out = replace_and_export(
"She called *Aurélien* into the trees.",
"Aurélien",
"Aurélian",
ReplaceFormatPolicy::KeepDominantRun,
);
assert_eq!(
out, "She called *Aurélian* into the trees.",
"the emphasis covered the whole name, so it dominates"
);
}
#[test]
fn no_policy_disturbs_the_formatting_around_the_replacement() {
for policy in [
ReplaceFormatPolicy::InheritPreceding,
ReplaceFormatPolicy::PreserveIfFullyCovered,
ReplaceFormatPolicy::KeepDominantRun,
ReplaceFormatPolicy::PreserveNothing,
] {
let out = replace_and_export(
"A *bold* word, then Aurélien, then _emphasis_.",
"Aurélien",
"Aurélian",
policy,
);
assert!(
out.contains("*bold*"),
"{policy:?} damaged the strong run before the replacement: {out:?}"
);
assert!(
out.contains("_emphasis_"),
"{policy:?} damaged the emphasis after the replacement: {out:?}"
);
assert!(
out.contains("Aurélian"),
"{policy:?} did not perform the replacement: {out:?}"
);
}
}
#[test]
fn undo_restores_the_formatting_a_replace_changed() {
let source = "She called *Aurélien* into the trees.";
let doc = TextDocument::new();
doc.set_djot_with_options(source, DjotImportOptions::default())
.and_then(|op| op.wait())
.expect("set_djot");
let opts = ReplaceOptions::new(FindOptions::default())
.with_format_policy(ReplaceFormatPolicy::PreserveIfFullyCovered);
doc.replace_text("Aurélien", "Aurélian", true, &opts)
.expect("replace");
assert_eq!(
doc.to_djot_with_options(DjotExportOptions::default())
.unwrap()
.trim(),
"She called *Aurélian* into the trees."
);
doc.undo().expect("undo");
assert_eq!(
doc.to_djot_with_options(DjotExportOptions::default())
.unwrap()
.trim(),
source,
"undo must restore the prose AND its emphasis, exactly"
);
}