You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

markdown.rs 15KB

7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. extern crate tera;
  2. extern crate front_matter;
  3. extern crate templates;
  4. extern crate rendering;
  5. use std::collections::HashMap;
  6. use tera::Tera;
  7. use front_matter::InsertAnchor;
  8. use templates::GUTENBERG_TERA;
  9. use rendering::{Context, markdown_to_html};
  10. #[test]
  11. fn can_do_markdown_to_html_simple() {
  12. let tera_ctx = Tera::default();
  13. let permalinks_ctx = HashMap::new();
  14. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  15. let res = markdown_to_html("hello", &context).unwrap();
  16. assert_eq!(res.0, "<p>hello</p>\n");
  17. }
  18. #[test]
  19. fn doesnt_highlight_code_block_with_highlighting_off() {
  20. let tera_ctx = Tera::default();
  21. let permalinks_ctx = HashMap::new();
  22. let mut context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  23. context.highlight_code = false;
  24. let res = markdown_to_html("```\n$ gutenberg server\n```", &context).unwrap();
  25. assert_eq!(
  26. res.0,
  27. "<pre><code>$ gutenberg server\n</code></pre>\n"
  28. );
  29. }
  30. #[test]
  31. fn can_highlight_code_block_no_lang() {
  32. let tera_ctx = Tera::default();
  33. let permalinks_ctx = HashMap::new();
  34. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  35. let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", &context).unwrap();
  36. assert_eq!(
  37. res.0,
  38. "<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>"
  39. );
  40. }
  41. #[test]
  42. fn can_highlight_code_block_with_lang() {
  43. let tera_ctx = Tera::default();
  44. let permalinks_ctx = HashMap::new();
  45. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  46. let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap();
  47. assert_eq!(
  48. res.0,
  49. "<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)\n</span></pre>"
  50. );
  51. }
  52. #[test]
  53. fn can_higlight_code_block_with_unknown_lang() {
  54. let tera_ctx = Tera::default();
  55. let permalinks_ctx = HashMap::new();
  56. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  57. let res = markdown_to_html("```yolo\nlist.append(1)\n```", &context).unwrap();
  58. // defaults to plain text
  59. assert_eq!(
  60. res.0,
  61. "<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>"
  62. );
  63. }
  64. #[test]
  65. fn can_render_shortcode() {
  66. let permalinks_ctx = HashMap::new();
  67. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  68. let res = markdown_to_html(r#"
  69. Hello
  70. {{ youtube(id="ub36ffWAqgQ") }}
  71. "#, &context).unwrap();
  72. assert!(res.0.contains("<p>Hello</p>\n<div >"));
  73. assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
  74. }
  75. #[test]
  76. fn can_render_shortcode_with_markdown_char_in_args_name() {
  77. let permalinks_ctx = HashMap::new();
  78. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  79. let input = vec![
  80. "name",
  81. "na_me",
  82. "n_a_me",
  83. "n1",
  84. ];
  85. for i in input {
  86. let res = markdown_to_html(&format!("{{{{ youtube(id=\"hey\", {}=1) }}}}", i), &context).unwrap();
  87. assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/hey""#));
  88. }
  89. }
  90. #[test]
  91. fn can_render_shortcode_with_markdown_char_in_args_value() {
  92. let permalinks_ctx = HashMap::new();
  93. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  94. let input = vec![
  95. "ub36ffWAqgQ-hey",
  96. "ub36ffWAqgQ_hey",
  97. "ub36ffWAqgQ_he_y",
  98. "ub36ffWAqgQ*hey",
  99. "ub36ffWAqgQ#hey",
  100. ];
  101. for i in input {
  102. let res = markdown_to_html(&format!("{{{{ youtube(id=\"{}\") }}}}", i), &context).unwrap();
  103. assert!(res.0.contains(&format!(r#"<iframe src="https://www.youtube.com/embed/{}""#, i)));
  104. }
  105. }
  106. #[test]
  107. fn can_render_body_shortcode_with_markdown_char_in_name() {
  108. let permalinks_ctx = HashMap::new();
  109. let mut tera = Tera::default();
  110. tera.extend(&GUTENBERG_TERA).unwrap();
  111. let input = vec![
  112. "quo_te",
  113. "qu_o_te",
  114. ];
  115. for i in input {
  116. tera.add_raw_template(&format!("shortcodes/{}.html", i), "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap();
  117. let context = Context::new(&tera, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  118. let res = markdown_to_html(&format!("{{% {}(author=\"Bob\") %}}\nhey\n{{% end %}}", i), &context).unwrap();
  119. println!("{:?}", res);
  120. assert!(res.0.contains("<blockquote>hey - Bob</blockquote>"));
  121. }
  122. }
  123. #[test]
  124. fn can_render_body_shortcode_and_paragraph_after() {
  125. let permalinks_ctx = HashMap::new();
  126. let mut tera = Tera::default();
  127. tera.extend(&GUTENBERG_TERA).unwrap();
  128. let shortcode = "<p>{{ body }}</p>";
  129. let markdown_string = r#"
  130. {% figure() %}
  131. This is a figure caption.
  132. {% end %}
  133. Here is another paragraph.
  134. "#;
  135. let expected = "<p>This is a figure caption.</p>
  136. <p>Here is another paragraph.</p>
  137. ";
  138. tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap();
  139. let context = Context::new(&tera, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  140. let res = markdown_to_html(markdown_string, &context).unwrap();
  141. println!("{:?}", res);
  142. assert_eq!(res.0, expected);
  143. }
  144. #[test]
  145. fn can_render_two_body_shortcode_and_paragraph_after_with_line_break_between() {
  146. let permalinks_ctx = HashMap::new();
  147. let mut tera = Tera::default();
  148. tera.extend(&GUTENBERG_TERA).unwrap();
  149. let shortcode = "<p>{{ body }}</p>";
  150. let markdown_string = r#"
  151. {% figure() %}
  152. This is a figure caption.
  153. {% end %}
  154. {% figure() %}
  155. This is a figure caption.
  156. {% end %}
  157. Here is another paragraph.
  158. "#;
  159. let expected = "<p>This is a figure caption.</p>
  160. <p>This is a figure caption.</p>
  161. <p>Here is another paragraph.</p>
  162. ";
  163. tera.add_raw_template(&format!("shortcodes/{}.html", "figure"), shortcode).unwrap();
  164. let context = Context::new(&tera, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  165. let res = markdown_to_html(markdown_string, &context).unwrap();
  166. println!("{:?}", res);
  167. assert_eq!(res.0, expected);
  168. }
  169. #[test]
  170. fn can_render_several_shortcode_in_row() {
  171. let permalinks_ctx = HashMap::new();
  172. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  173. let res = markdown_to_html(r#"
  174. Hello
  175. {{ youtube(id="ub36ffWAqgQ") }}
  176. {{ youtube(id="ub36ffWAqgQ", autoplay=true) }}
  177. {{ vimeo(id="210073083") }}
  178. {{ streamable(id="c0ic") }}
  179. {{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }}
  180. "#, &context).unwrap();
  181. assert!(res.0.contains("<p>Hello</p>\n<div >"));
  182. assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
  183. assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#));
  184. assert!(res.0.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#));
  185. assert!(res.0.contains(r#"//player.vimeo.com/video/210073083""#));
  186. }
  187. #[test]
  188. fn errors_if_unterminated_shortcode() {
  189. let permalinks_ctx = HashMap::new();
  190. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  191. let res = markdown_to_html(r#"{{ youtube(id="w7Ft2ym_a"#, &context);
  192. assert!(res.is_err());
  193. }
  194. #[test]
  195. fn doesnt_render_shortcode_in_code_block() {
  196. let permalinks_ctx = HashMap::new();
  197. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  198. let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &context).unwrap();
  199. assert_eq!(res.0, "<p><code>{{ youtube(id=&quot;w7Ft2ymGmfc&quot;) }}</code></p>\n");
  200. }
  201. #[test]
  202. fn can_render_shortcode_with_body() {
  203. let mut tera = Tera::default();
  204. tera.extend(&GUTENBERG_TERA).unwrap();
  205. tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author }}</blockquote>").unwrap();
  206. let permalinks_ctx = HashMap::new();
  207. let context = Context::new(&tera, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  208. let res = markdown_to_html(r#"
  209. Hello
  210. {% quote(author="Keats") %}
  211. A quote
  212. {% end %}
  213. "#, &context).unwrap();
  214. assert_eq!(res.0, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>");
  215. }
  216. #[test]
  217. fn errors_rendering_unknown_shortcode() {
  218. let tera_ctx = Tera::default();
  219. let permalinks_ctx = HashMap::new();
  220. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  221. let res = markdown_to_html("{{ hello(flash=true) }}", &context);
  222. assert!(res.is_err());
  223. }
  224. #[test]
  225. fn can_make_valid_relative_link() {
  226. let mut permalinks = HashMap::new();
  227. permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
  228. let tera_ctx = Tera::default();
  229. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks, InsertAnchor::None);
  230. let res = markdown_to_html(
  231. r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
  232. &context
  233. ).unwrap();
  234. assert!(
  235. res.0.contains(r#"<p><a href="https://vincent.is/about">rel link</a>, <a href="https://vincent.is/about">abs link</a></p>"#)
  236. );
  237. }
  238. #[test]
  239. fn can_make_relative_links_with_anchors() {
  240. let mut permalinks = HashMap::new();
  241. permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
  242. let tera_ctx = Tera::default();
  243. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks, InsertAnchor::None);
  244. let res = markdown_to_html(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap();
  245. assert!(
  246. res.0.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#)
  247. );
  248. }
  249. #[test]
  250. fn errors_relative_link_inexistant() {
  251. let tera_ctx = Tera::default();
  252. let permalinks_ctx = HashMap::new();
  253. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  254. let res = markdown_to_html("[rel link](./pages/about.md)", &context);
  255. assert!(res.is_err());
  256. }
  257. #[test]
  258. fn can_add_id_to_headers() {
  259. let tera_ctx = Tera::default();
  260. let permalinks_ctx = HashMap::new();
  261. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  262. let res = markdown_to_html(r#"# Hello"#, &context).unwrap();
  263. assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n");
  264. }
  265. #[test]
  266. fn can_add_id_to_headers_same_slug() {
  267. let tera_ctx = Tera::default();
  268. let permalinks_ctx = HashMap::new();
  269. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  270. let res = markdown_to_html("# Hello\n# Hello", &context).unwrap();
  271. assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
  272. }
  273. #[test]
  274. fn can_insert_anchor_left() {
  275. let permalinks_ctx = HashMap::new();
  276. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  277. let res = markdown_to_html("# Hello", &context).unwrap();
  278. assert_eq!(
  279. res.0,
  280. "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello</h1>\n"
  281. );
  282. }
  283. #[test]
  284. fn can_insert_anchor_right() {
  285. let permalinks_ctx = HashMap::new();
  286. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Right);
  287. let res = markdown_to_html("# Hello", &context).unwrap();
  288. assert_eq!(
  289. res.0,
  290. "<h1 id=\"hello\">Hello<a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\n</h1>\n"
  291. );
  292. }
  293. // See https://github.com/Keats/gutenberg/issues/42
  294. #[test]
  295. fn can_insert_anchor_with_exclamation_mark() {
  296. let permalinks_ctx = HashMap::new();
  297. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  298. let res = markdown_to_html("# Hello!", &context).unwrap();
  299. assert_eq!(
  300. res.0,
  301. "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello!</h1>\n"
  302. );
  303. }
  304. // See https://github.com/Keats/gutenberg/issues/53
  305. #[test]
  306. fn can_insert_anchor_with_link() {
  307. let permalinks_ctx = HashMap::new();
  308. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  309. let res = markdown_to_html("## [](#xresources)Xresources", &context).unwrap();
  310. assert_eq!(
  311. res.0,
  312. "<h2 id=\"xresources\"><a class=\"gutenberg-anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">🔗</a>\nXresources</h2>\n"
  313. );
  314. }
  315. #[test]
  316. fn can_insert_anchor_with_other_special_chars() {
  317. let permalinks_ctx = HashMap::new();
  318. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  319. let res = markdown_to_html("# Hello*_()", &context).unwrap();
  320. assert_eq!(
  321. res.0,
  322. "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello*_()</h1>\n"
  323. );
  324. }
  325. #[test]
  326. fn can_make_toc() {
  327. let permalinks_ctx = HashMap::new();
  328. let context = Context::new(
  329. &GUTENBERG_TERA,
  330. true,
  331. "base16-ocean-dark".to_string(),
  332. "https://mysite.com/something",
  333. &permalinks_ctx,
  334. InsertAnchor::Left
  335. );
  336. let res = markdown_to_html(r#"
  337. # Header 1
  338. ## Header 2
  339. ## Another Header 2
  340. ### Last one
  341. "#, &context).unwrap();
  342. let toc = res.1;
  343. assert_eq!(toc.len(), 1);
  344. assert_eq!(toc[0].children.len(), 2);
  345. assert_eq!(toc[0].children[1].children.len(), 1);
  346. }
  347. #[test]
  348. fn can_understand_backtick_in_titles() {
  349. let permalinks_ctx = HashMap::new();
  350. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  351. let res = markdown_to_html("# `Hello`", &context).unwrap();
  352. assert_eq!(
  353. res.0,
  354. "<h1 id=\"hello\"><code>Hello</code></h1>\n"
  355. );
  356. }
  357. #[test]
  358. fn can_understand_backtick_in_paragraphs() {
  359. let permalinks_ctx = HashMap::new();
  360. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  361. let res = markdown_to_html("Hello `world`", &context).unwrap();
  362. assert_eq!(
  363. res.0,
  364. "<p>Hello <code>world</code></p>\n"
  365. );
  366. }