use text_document::TextDocument;
fn roundtrip_djot(source: &str) -> String {
let doc = TextDocument::new();
doc.set_djot_sync(source).expect("import");
doc.to_djot().expect("export")
}
fn plain_of_djot(source: &str) -> String {
let doc = TextDocument::new();
doc.set_djot_sync(source).expect("import");
doc.to_plain_text().expect("plain text")
}
#[test]
fn a_djot_image_survives_a_round_trip() {
let out = roundtrip_djot("\n");
assert!(
out.contains("assets/cat.png"),
"the image source was dropped: {out:?}"
);
assert!(
out.contains("![a black cat]"),
"the alt text was dropped: {out:?}"
);
}
#[test]
fn display_size_survives_a_round_trip() {
let out = roundtrip_djot("{width=800 height=600}\n");
assert!(out.contains("width=800"), "width lost: {out:?}");
assert!(out.contains("height=600"), "height lost: {out:?}");
}
#[test]
fn a_round_trip_is_a_fixpoint() {
let once = roundtrip_djot("{width=800 height=600}\n");
let twice = roundtrip_djot(&once);
assert_eq!(once.trim(), twice.trim());
}
#[test]
fn an_image_keeps_its_place_in_a_sentence() {
let out = roundtrip_djot("Before  after.\n");
let before = out.find("Before").expect("before");
let img = out.find("c.png").expect("image");
let after = out.find("after").expect("after");
assert!(
before < img && img < after,
"the image moved out of the sentence: {out:?}"
);
}
#[test]
fn an_image_inside_emphasis_keeps_its_place() {
let out = roundtrip_djot("_before  after_\n");
let img = out.find("c.png").expect("image");
let after = out.find("after").expect("after");
assert!(img < after, "the image jumped past its run: {out:?}");
}
#[test]
fn alt_text_does_not_leak_into_the_prose() {
let plain = plain_of_djot("Some prose  more prose.\n");
assert!(
!plain.contains("a black cat"),
"alt text became manuscript prose: {plain:?}"
);
assert!(plain.contains("Some prose"), "prose lost: {plain:?}");
assert!(plain.contains("more prose"), "prose lost: {plain:?}");
}
#[test]
fn an_image_contributes_exactly_one_character_to_the_document() {
let doc = TextDocument::new();
doc.set_djot_sync("abcd\n")
.expect("import");
assert_eq!(doc.character_count(), 5);
assert_eq!(doc.to_plain_text().unwrap(), "ab\u{FFFC}cd");
assert_eq!(doc.to_plain_text().unwrap().chars().count(), 5);
assert_eq!(
doc.to_plain_text_with(text_document::PlainTextExportOptions::presentation())
.unwrap()
.trim_end(),
"abcd"
);
}
#[test]
fn several_images_in_one_paragraph_keep_their_order() {
let out = roundtrip_djot(" then \n");
let a = out.find("a.png").expect("a");
let b = out.find("b.png").expect("b");
assert!(a < b, "images reordered: {out:?}");
}
#[test]
fn an_image_inside_a_blockquote_stays_quoted() {
let out = roundtrip_djot("> quoted  here\n");
let line = out
.lines()
.find(|l| l.contains("c.png"))
.unwrap_or_else(|| panic!("image line missing in {out:?}"));
assert!(line.starts_with('>'), "image left the blockquote: {out:?}");
}
#[test]
fn plain_text_export_omits_images_rather_than_leaking_a_sentinel() {
let doc = TextDocument::new();
doc.set_djot_sync("before  after\n").unwrap();
let exported = doc
.to_plain_text_with(text_document::PlainTextExportOptions::presentation())
.expect("plain text export");
assert!(
!exported.contains('\u{FFFC}'),
"the image sentinel leaked into plain text: {exported:?}"
);
assert!(exported.contains("before") && exported.contains("after"));
}
#[test]
fn the_addressable_view_keeps_the_image_so_offsets_do_not_drift() {
let doc = TextDocument::new();
doc.set_djot_sync("before  after\n").unwrap();
let addressable = doc.to_plain_text().expect("addressable text");
assert!(addressable.contains('\u{FFFC}'), "{addressable:?}");
assert_eq!(
addressable.chars().count(),
doc.character_count(),
"the addressable view must agree with the document, character for character"
);
}
#[test]
fn markdown_import_keeps_the_image_instead_of_leaking_its_alt_text() {
let doc = TextDocument::new();
doc.set_markdown("Some prose  more.\n")
.expect("import")
.wait()
.expect("import");
let plain = doc.to_plain_text().expect("plain");
assert!(
!plain.contains("a black cat"),
"alt text leaked into prose: {plain:?}"
);
let md = doc.to_markdown().expect("export");
assert!(md.contains("c.png"), "image dropped: {md:?}");
assert!(md.contains("a black cat"), "alt dropped: {md:?}");
}
#[test]
fn html_import_keeps_an_img_tag() {
let doc = TextDocument::new();
doc.set_html(
"<p>before <img src=\"c.png\" alt=\"a cat\" width=\"320\" height=\"240\"> after</p>",
)
.expect("import")
.wait()
.expect("import");
let html = doc.to_html().expect("export");
assert!(html.contains("c.png"), "image dropped: {html:?}");
assert!(html.contains("a cat"), "alt dropped: {html:?}");
assert!(html.contains("320"), "width dropped: {html:?}");
}
#[test]
fn html_export_always_emits_an_alt_attribute() {
let doc = TextDocument::new();
doc.set_djot_sync("\n").expect("import");
let html = doc.to_html().expect("export");
assert!(html.contains("<img "), "no image emitted: {html:?}");
assert!(html.contains("alt="), "no alt attribute: {html:?}");
}
fn insert_at(doc: &TextDocument, offset: usize, djot: &str) {
let cursor = doc.cursor();
cursor.set_position(offset, text_document::MoveMode::MoveAnchor);
cursor.insert_djot(djot).expect("insert");
}
#[test]
fn inserting_an_image_at_the_caret_actually_inserts_it() {
let doc = TextDocument::new();
doc.set_djot_sync("ab\n").expect("import");
insert_at(&doc, 2, "{width=10 height=10}");
let out = doc.to_djot().expect("export");
assert!(
out.contains("assets/x.png"),
"the image never arrived: {out:?}"
);
assert!(out.contains("width=10"), "size lost: {out:?}");
}
#[test]
fn an_inserted_image_lands_where_the_caret_is() {
let doc = TextDocument::new();
doc.set_djot_sync("ab\n").expect("import");
insert_at(&doc, 1, "");
let out = doc.to_djot().expect("export");
let a = out.find('a').expect("a");
let img = out.find("assets/x.png").expect("image");
let b = out.rfind('b').expect("b");
assert!(
a < img && img < b,
"the image did not land mid-word: {out:?}"
);
}
#[test]
fn an_inserted_image_costs_the_same_as_a_parsed_one() {
let inserted = TextDocument::new();
inserted.set_djot_sync("ab\n").expect("import");
insert_at(&inserted, 2, "{width=10 height=10}");
let parsed = TextDocument::new();
parsed
.set_djot_sync("ab{width=10 height=10}\n")
.expect("import");
assert_eq!(
inserted.character_count(),
parsed.character_count(),
"inserting an image and parsing one disagree about what it costs"
);
assert_eq!(
inserted.to_plain_text().unwrap(),
parsed.to_plain_text().unwrap()
);
}
#[test]
fn text_after_an_inserted_image_keeps_its_own_formatting() {
let doc = TextDocument::new();
doc.set_djot_sync("start\n").expect("import");
insert_at(&doc, 5, " _slanted_");
let out = doc.to_djot().expect("export");
assert!(out.contains("assets/x.png"), "{out:?}");
assert!(
out.contains("_slanted_"),
"the emphasis after the image was lost or displaced: {out:?}"
);
}
#[test]
fn inserting_an_image_twice_leaves_two_of_them() {
let doc = TextDocument::new();
doc.set_djot_sync("ab\n").expect("import");
insert_at(&doc, 2, "");
insert_at(&doc, doc.character_count(), "");
let out = doc.to_djot().expect("export");
let a = out.find("a.png").expect("first");
let b = out.find("b.png").expect("second");
assert!(a < b, "images out of order: {out:?}");
assert_eq!(doc.character_count(), 4, "two images, two characters");
}