Line data Source code
1 : use super::*;
2 :
3 : /// A compact rendering of a document: `type[attrs](children)`
4 105 : fn render(node: &MarkdownNode) -> String {
5 : // The names of the snapshots, `LIST_ITEM` is `listItem`
6 105 : let mut out = String::new();
7 117 : for (i, part) in node.node_type.variant_name().unwrap_or("?").split('_').enumerate() {
8 117 : let part = part.to_ascii_lowercase();
9 117 : if i == 0 {
10 105 : out += ∂
11 105 : } else {
12 12 : out += &part[..1].to_ascii_uppercase();
13 12 : out += &part[1..];
14 12 : }
15 : }
16 105 : let mut attrs = Vec::new();
17 420 : for (key, value) in [("value", &node.value), ("url", &node.url), ("title", &node.title), ("lang", &node.language)] {
18 420 : if !value.is_empty() {
19 52 : attrs.push(format!("{key}={value:?}"));
20 368 : }
21 : }
22 105 : if node.node_type == MarkdownNodeType::HEADING {
23 2 : attrs.push(format!("level={}", node.level));
24 103 : }
25 105 : if node.node_type == MarkdownNodeType::LIST {
26 3 : attrs.push(format!("ordered={} tight={} start={}", node.ordered, node.tight, node.start));
27 102 : }
28 105 : if !attrs.is_empty() {
29 55 : out += &format!("[{}]", attrs.join(" "));
30 55 : }
31 105 : if !node.children.is_empty() {
32 60 : out += &format!("({})", node.children.iter().map(render).collect::<Vec<_>>().join(", "));
33 60 : }
34 105 : out
35 105 : }
36 :
37 18 : fn compile(template: &str) -> String {
38 18 : render(&compile_markdown_template(template).document)
39 18 : }
40 :
41 : #[test]
42 1 : fn tokenizes_fields() {
43 1 : let document = compile_markdown_template("{{ a }} {{b}} {{a}} {{}} {{ ");
44 1 : assert_eq!(document.fields, vec!["a", "b"]);
45 1 : assert_eq!(
46 1 : render(&document.document),
47 : "document(paragraph(field[value=\"a\"], text[value=\" \"], field[value=\"b\"], text[value=\" \"], \
48 : field[value=\"a\"], text[value=\" {{}} {{\"]))"
49 : );
50 : // The token prefix grows until it does not occur in the template
51 1 : let (markdown, _) = tokenize_fields("DASHQLMARKDOWNFIELD {{x}}");
52 1 : assert_eq!(markdown, "DASHQLMARKDOWNFIELD DASHQLMARKDOWNFIELDX0TOKEN");
53 : // Fields in code, and fields in raw HTML are dropped with it
54 1 : assert_eq!(
55 1 : compile("`{{a}}` <b title=\"{{b}}\">x</b>"),
56 : "document(paragraph(inlineCode(field[value=\"a\"]), text[value=\" x\"]))"
57 : );
58 1 : }
59 :
60 : #[test]
61 1 : fn merges_text() {
62 1 : assert_eq!(compile("a\\*b & c"), "document(paragraph(text[value=\"a*b & c\"]))");
63 1 : assert_eq!(compile("&bogus; A �"), "document(paragraph(text[value=\"&bogus; A \u{fffd}\"]))");
64 1 : assert_eq!(compile("a\0b"), "document(paragraph(text[value=\"a\u{fffd}b\"]))");
65 1 : assert_eq!(compile("a\nb <!-- c --> {{x}}"), "document(paragraph(text[value=\"a\\nb \"], field[value=\"x\"]))");
66 1 : }
67 :
68 : #[test]
69 1 : fn builds_blocks() {
70 1 : assert_eq!(
71 1 : compile("# H1 #\n\nSetext\n---"),
72 : "document(heading[level=1](text[value=\"H1\"]), heading[level=2](text[value=\"Setext\"]))"
73 : );
74 1 : assert_eq!(
75 1 : compile("- a\n- b\n\n1) c"),
76 : "document(list[ordered=false tight=true start=0](listItem(text[value=\"a\"]), listItem(text[value=\"b\"])), \
77 : list[ordered=true tight=true start=1](listItem(text[value=\"c\"])))"
78 : );
79 1 : assert_eq!(
80 1 : compile("- a\n\n- b"),
81 : "document(list[ordered=false tight=false start=0](listItem(paragraph(text[value=\"a\"])), listItem(paragraph(text[value=\"b\"]))))"
82 : );
83 1 : assert_eq!(
84 1 : compile("```sql x\nselect\n1\n```\n\n code"),
85 : "document(codeBlock[lang=\"sql\"](text[value=\"select\\n1\\n\"]), codeBlock(text[value=\"code\"]))"
86 : );
87 1 : assert_eq!(compile("<div>\nhtml\n</div>\n\n***"), "document(thematicBreak)");
88 1 : assert_eq!(compile("> q\n"), "document(blockquote(paragraph(text[value=\"q\"])))");
89 1 : }
90 :
91 : #[test]
92 1 : fn resolves_links() {
93 1 : assert_eq!(
94 1 : compile("[a][ref]  <https://x.y> <me@x.y> [bad](javascript:x)\n\n[REF]: http://r 'T'"),
95 : "document(paragraph(link[url=\"http://r\" title=\"T\"](text[value=\"a\"]), text[value=\" \"], \
96 : image[url=\"/x.png\" title=\"t\"](text[value=\"img\"]), text[value=\" \"], link[url=\"https://x.y\"](text[value=\"https://x.y\"]), \
97 : text[value=\" \"], link[url=\"mailto:me@x.y\"](text[value=\"me@x.y\"]), text[value=\" \"], span[url=\"javascript:x\"](text[value=\"bad\"])))"
98 : );
99 1 : assert_eq!(
100 1 : compile("[x]({{f}} \"{{f}}\")"),
101 : "document(paragraph(span[url=\"DASHQLMARKDOWNFIELD0TOKEN\"](text[value=\"x\"])))"
102 : );
103 1 : assert_eq!(compile("[x](/a&b\\) \"t {{f}}\")"), "document(paragraph(link[url=\"/a&b)\"](text[value=\"x\"])))");
104 1 : }
105 :
106 : #[test]
107 1 : fn resolves_emphasis() {
108 1 : assert_eq!(compile("***a** b*"), "document(paragraph(emphasis(strong(text[value=\"a\"]), text[value=\" b\"])))");
109 1 : assert_eq!(compile("a_b_c *x*"), "document(paragraph(text[value=\"a_b_c \"], emphasis(text[value=\"x\"])))");
110 1 : assert_eq!(compile("`` a`b ``"), "document(paragraph(inlineCode(text[value=\"a`b\"])))");
111 1 : assert_eq!(
112 1 : compile("a \nb\\\nc"),
113 : "document(paragraph(text[value=\"a\"], hardBreak, text[value=\"b\"], hardBreak, text[value=\"c\"]))"
114 : );
115 1 : }
116 :
117 : #[test]
118 1 : fn checks_urls() {
119 1 : assert!(is_safe_url("https://x"));
120 1 : assert!(is_safe_url("/a:b"));
121 1 : assert!(is_safe_url("a?b:c"));
122 1 : assert!(is_safe_url(" MailTo:x"));
123 1 : assert!(!is_safe_url("java\nscript:alert(1)"));
124 1 : assert!(!is_safe_url("data:text"));
125 1 : }
126 :
127 : #[test]
128 1 : fn packs_documents() {
129 1 : let document = compile_markdown_template("# {{a}}");
130 1 : let mut builder = FlatBufferBuilder::new();
131 1 : let spec = pack_markdown_spec(&mut builder, &document);
132 1 : builder.finish(spec, None);
133 1 : let spec = flatbuffers::root::<MarkdownSpec>(builder.finished_data()).unwrap();
134 1 : assert_eq!(spec.template(), Some("# {{a}}"));
135 1 : let root = spec.document().unwrap();
136 1 : assert_eq!(root.node_type(), MarkdownNodeType::DOCUMENT);
137 1 : assert_eq!(root.value(), None);
138 1 : let heading = root.children().unwrap().get(0);
139 1 : assert_eq!(heading.level(), 1);
140 1 : assert_eq!(heading.children().unwrap().get(0).value(), Some("a"));
141 1 : }
|