//! Exports read the file as the visual mode does (R4.4). use super::{Pictures, page, plain}; use crate::output::{Page, html::write, markdown_tree, pictures::Placed}; fn html(text: &str) -> String { page(text, None, &Pictures::Inline).html } fn text(text: &str) -> String { plain(text).expect("plain text") } #[test] fn shortcodes_turn_as_github_turns_them() { let page = html("Go :smile: x:smile:\n\nx:smile: :smile:\n\na :smile: :smile:\n"); for part in [ "

Go 😄 x😄

", "

x:smile: :smile:

", "

a :smile: 😄

", ] { assert!(page.contains(part), "{part} in {page}"); } assert_eq!( text("Go :smile:\n\nx:smile: :smile:\n"), "Go 😄\n\nx:smile: :smile:\n" ); } #[test] fn inline_tags_keep_what_they_mean_and_nothing_runs() { let source = "a u m k s \ j e\n"; let page = html(source); for part in [ "u", "m", "k", " s ", "<script>x</script>", "j", "e", ] { assert!(page.contains(part), "{part} in {page}"); } assert!( !page.contains("b \n"), "a b \n" ); } #[test] fn an_html_block_shows_its_text() { let source = "
\n Bold and :smile:\n
\n"; let page = html(source); assert!( page.contains("

Bold and 😄

"), "{page}" ); assert!(!page.contains(" [!NOTE]\n> Read this.\n\n- > [!TIP]\n > Not here.\n"); let alert = "

Note

Read this.

"; assert!(page.contains(alert), "{page}"); assert!(!page.contains("#1a7f37"), "only root quotes alert: {page}"); assert_eq!(text("> [!WARNING]\n> Hot.\n"), "Warning\n\nHot.\n"); } #[test] fn a_toc_lists_the_headings_and_links_them() { let page = html("[toc]\n\n# A\n\n## B :smile:\n"); let toc = ""; assert!(page.contains(toc), "{page}"); assert!(page.contains("

A

") && page.contains("

B 😄

")); assert_eq!(text("[toc]\n\n# A\n\n## B\n"), "A\nB\n\nA\n\nB\n"); } #[test] fn tags_crossing_containers_read_as_drawn() { for (source, shown) in [ ("*a b* :smile: :smile:\n", "a b :smile: 😄"), ("a *b* :smile:\n", "a b 😄"), ] { assert_eq!(text(source), format!("{shown}\n")); let page = html(source); assert_eq!(page.matches("😄").count(), 1, "{page}"); assert_eq!( page.matches(":smile:").count(), shown.matches(":smile:").count(), "{page}" ); } } #[test] fn each_cell_and_block_reads_on_its_own() { let page = html("| x:smile: | :smile: |\n| --- | --- |\n| :smile: | :smile: |\n"); assert_eq!(page.matches("😄").count(), 3, "{page}"); let page = html("# a x\n\n:smile:\n"); assert!(page.contains("

😄

"), "{page}"); } #[test] fn a_block_closes_the_tags_left_open_in_it() { let page = html("a bold\n\nnormal\n"); assert!( page.contains("

a bold

normal

"), "{page}" ); let page = html("a link\n\nnormal\n"); assert!(page.ends_with("

normal

"), "{page}"); } #[test] fn every_toc_entry_has_its_heading() { let page = html("[toc]\n\n# Main\n\n[^n]:\n ## Unused\n"); let targets: Vec<&str> = page .split("href=\"#") .skip(1) .map(|rest| &rest[..rest.find('"').expect("a quote")]) .collect(); assert_eq!(targets.len(), 2, "{page}"); for target in targets { assert!( page.contains(&format!("id=\"{target}\"")), "{target} in {page}" ); } } #[test] fn a_long_html_block_writes_in_linear_time() { let took = |count: usize| { let source = format!("
{}
\n", "x ".repeat(count)); let start = std::time::Instant::now(); html(&source); start.elapsed() }; let (small, large) = (took(8_000), took(32_000)); // Four times the input; squared would be sixteen. assert!(large < small * 8, "{small:?} then {large:?}"); } #[test] fn notes_under_deep_marks_write_in_linear_time() { let took = |count: usize| { let source = format!( "start {}{} [^n]: note ", "".repeat(count), "x[^n] ".repeat(count) ); let tree = markdown_tree(&source); let mut placed = Placed::new(None, &Pictures::Inline); let start = std::time::Instant::now(); write(&tree, &source, &mut placed, &mut Page::default()).expect("written"); start.elapsed() }; // Parsing grows squared upstream; untimed. let (small, large) = (took(4_000), took(16_000)); assert!(large < small * 8, "{small:?} then {large:?}"); } #[test] fn a_blank_formula_is_dropped_with_its_text() { for source in [ "$ $\n", "$ $ ![p](p.png)\n", "| $ $ | a |\n| - | - |\n| b | c |\n", ] { html(source); } let page = page("$ $ ![p](p.png)\nnext $\\sqrt{$\n", None, &Pictures::Inline); let lines: Vec = page.formulas.iter().map(|(line, _)| *line).collect(); assert_eq!(lines, [2], "{}", page.html); } #[test] fn a_note_in_a_link_keeps_the_link_whole() { let page = html("a
b[^n] c after\n\n[^n]: note\n"); assert!( page.contains("b1 c"), "{page}" ); } #[test] fn a_reference_reaches_its_own_note() { let page = html("[^1]: unused\n\nCall[^used].\n\n[^used]: right target\n"); assert_eq!(page.matches("id=\"fn-1\"").count(), 1, "{page}"); let at = page.find("id=\"fn-1\"").expect("an anchor"); assert!( page[at..].contains("right target") && !page[at..].contains("unused"), "{page}" ); } #[test] fn plain_notes_keep_the_labels_drawn_beside_them() { assert_eq!( text("A[^b] B[^a].\n\n[^a]: Alpha\n\n[^b]: Beta\n"), "A\u{b9} B\u{b2}.\n\n[2] Alpha\n\n[1] Beta\n" ); assert_eq!(text("x\n\n[^n]: note\n"), "x\n\n[n] note\n"); } #[test] fn a_copied_fragment_carries_its_look() { let page = html("> [!NOTE]\n> Read this.\n\n[toc]\n\n# Title\n\nCall[^n].\n\n[^n]: note\n"); for part in [ "border-left:.25em solid #0969da", "font-weight:600", "list-style:none", "display:flex", ] { assert!(page.contains(part), "{part} in {page}"); } assert!( !page.contains("class="), "the look needs no page style: {page}" ); } #[test] fn a_soft_break_joins_in_exports_as_typora_exports_it() { let page = html("䜠奜\n测试 \nend\n"); assert!(page.contains("

䜠奜 测试
end

"), "{page}"); assert_eq!(text("䜠奜\n测试\n"), "䜠奜 测试\n"); } #[test] fn tex_delimiters_export_as_formulas() { let source = r"a \(x^2\) b \[ y+1 \] "; let page = page(source, None, &Pictures::Inline); assert_eq!(page.html.matches("