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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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:#c0c5ce;\">.</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;\">)</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_several_shortcode_in_row() {
  125. let permalinks_ctx = HashMap::new();
  126. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  127. let res = markdown_to_html(r#"
  128. Hello
  129. {{ youtube(id="ub36ffWAqgQ") }}
  130. {{ youtube(id="ub36ffWAqgQ", autoplay=true) }}
  131. {{ vimeo(id="210073083") }}
  132. {{ streamable(id="c0ic") }}
  133. {{ gist(url="https://gist.github.com/Keats/32d26f699dcc13ebd41b") }}
  134. "#, &context).unwrap();
  135. assert!(res.0.contains("<p>Hello</p>\n<div >"));
  136. assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ""#));
  137. assert!(res.0.contains(r#"<iframe src="https://www.youtube.com/embed/ub36ffWAqgQ?autoplay=1""#));
  138. assert!(res.0.contains(r#"<iframe src="https://www.streamable.com/e/c0ic""#));
  139. assert!(res.0.contains(r#"//player.vimeo.com/video/210073083""#));
  140. }
  141. #[test]
  142. fn errors_if_unterminated_shortcode() {
  143. let permalinks_ctx = HashMap::new();
  144. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  145. let res = markdown_to_html(r#"{{ youtube(id="w7Ft2ym_a"#, &context);
  146. assert!(res.is_err());
  147. }
  148. #[test]
  149. fn doesnt_render_shortcode_in_code_block() {
  150. let permalinks_ctx = HashMap::new();
  151. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  152. let res = markdown_to_html(r#"```{{ youtube(id="w7Ft2ymGmfc") }}```"#, &context).unwrap();
  153. assert_eq!(res.0, "<p><code>{{ youtube(id=&quot;w7Ft2ymGmfc&quot;) }}</code></p>\n");
  154. }
  155. #[test]
  156. fn can_render_shortcode_with_body() {
  157. let mut tera = Tera::default();
  158. tera.extend(&GUTENBERG_TERA).unwrap();
  159. tera.add_raw_template("shortcodes/quote.html", "<blockquote>{{ body }} - {{ author}}</blockquote>").unwrap();
  160. let permalinks_ctx = HashMap::new();
  161. let context = Context::new(&tera, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  162. let res = markdown_to_html(r#"
  163. Hello
  164. {% quote(author="Keats") %}
  165. A quote
  166. {% end %}
  167. "#, &context).unwrap();
  168. assert_eq!(res.0, "<p>Hello\n</p><blockquote>A quote - Keats</blockquote>");
  169. }
  170. #[test]
  171. fn errors_rendering_unknown_shortcode() {
  172. let tera_ctx = Tera::default();
  173. let permalinks_ctx = HashMap::new();
  174. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  175. let res = markdown_to_html("{{ hello(flash=true) }}", &context);
  176. assert!(res.is_err());
  177. }
  178. #[test]
  179. fn can_make_valid_relative_link() {
  180. let mut permalinks = HashMap::new();
  181. permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
  182. let tera_ctx = Tera::default();
  183. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks, InsertAnchor::None);
  184. let res = markdown_to_html(
  185. r#"[rel link](./pages/about.md), [abs link](https://vincent.is/about)"#,
  186. &context
  187. ).unwrap();
  188. assert!(
  189. res.0.contains(r#"<p><a href="https://vincent.is/about">rel link</a>, <a href="https://vincent.is/about">abs link</a></p>"#)
  190. );
  191. }
  192. #[test]
  193. fn can_make_relative_links_with_anchors() {
  194. let mut permalinks = HashMap::new();
  195. permalinks.insert("pages/about.md".to_string(), "https://vincent.is/about".to_string());
  196. let tera_ctx = Tera::default();
  197. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks, InsertAnchor::None);
  198. let res = markdown_to_html(r#"[rel link](./pages/about.md#cv)"#, &context).unwrap();
  199. assert!(
  200. res.0.contains(r#"<p><a href="https://vincent.is/about#cv">rel link</a></p>"#)
  201. );
  202. }
  203. #[test]
  204. fn errors_relative_link_inexistant() {
  205. let tera_ctx = Tera::default();
  206. let permalinks_ctx = HashMap::new();
  207. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  208. let res = markdown_to_html("[rel link](./pages/about.md)", &context);
  209. assert!(res.is_err());
  210. }
  211. #[test]
  212. fn can_add_id_to_headers() {
  213. let tera_ctx = Tera::default();
  214. let permalinks_ctx = HashMap::new();
  215. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  216. let res = markdown_to_html(r#"# Hello"#, &context).unwrap();
  217. assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n");
  218. }
  219. #[test]
  220. fn can_add_id_to_headers_same_slug() {
  221. let tera_ctx = Tera::default();
  222. let permalinks_ctx = HashMap::new();
  223. let context = Context::new(&tera_ctx, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  224. let res = markdown_to_html("# Hello\n# Hello", &context).unwrap();
  225. assert_eq!(res.0, "<h1 id=\"hello\">Hello</h1>\n<h1 id=\"hello-1\">Hello</h1>\n");
  226. }
  227. #[test]
  228. fn can_insert_anchor_left() {
  229. let permalinks_ctx = HashMap::new();
  230. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  231. let res = markdown_to_html("# Hello", &context).unwrap();
  232. assert_eq!(
  233. res.0,
  234. "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello</h1>\n"
  235. );
  236. }
  237. #[test]
  238. fn can_insert_anchor_right() {
  239. let permalinks_ctx = HashMap::new();
  240. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Right);
  241. let res = markdown_to_html("# Hello", &context).unwrap();
  242. assert_eq!(
  243. res.0,
  244. "<h1 id=\"hello\">Hello<a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\n</h1>\n"
  245. );
  246. }
  247. // See https://github.com/Keats/gutenberg/issues/42
  248. #[test]
  249. fn can_insert_anchor_with_exclamation_mark() {
  250. let permalinks_ctx = HashMap::new();
  251. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  252. let res = markdown_to_html("# Hello!", &context).unwrap();
  253. assert_eq!(
  254. res.0,
  255. "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello!</h1>\n"
  256. );
  257. }
  258. // See https://github.com/Keats/gutenberg/issues/53
  259. #[test]
  260. fn can_insert_anchor_with_link() {
  261. let permalinks_ctx = HashMap::new();
  262. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  263. let res = markdown_to_html("## [](#xresources)Xresources", &context).unwrap();
  264. assert_eq!(
  265. res.0,
  266. "<h2 id=\"xresources\"><a class=\"gutenberg-anchor\" href=\"#xresources\" aria-label=\"Anchor link for: xresources\">🔗</a>\nXresources</h2>\n"
  267. );
  268. }
  269. #[test]
  270. fn can_insert_anchor_with_other_special_chars() {
  271. let permalinks_ctx = HashMap::new();
  272. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::Left);
  273. let res = markdown_to_html("# Hello*_()", &context).unwrap();
  274. assert_eq!(
  275. res.0,
  276. "<h1 id=\"hello\"><a class=\"gutenberg-anchor\" href=\"#hello\" aria-label=\"Anchor link for: hello\">🔗</a>\nHello*_()</h1>\n"
  277. );
  278. }
  279. #[test]
  280. fn can_make_toc() {
  281. let permalinks_ctx = HashMap::new();
  282. let context = Context::new(
  283. &GUTENBERG_TERA,
  284. true,
  285. "base16-ocean-dark".to_string(),
  286. "https://mysite.com/something",
  287. &permalinks_ctx,
  288. InsertAnchor::Left
  289. );
  290. let res = markdown_to_html(r#"
  291. # Header 1
  292. ## Header 2
  293. ## Another Header 2
  294. ### Last one
  295. "#, &context).unwrap();
  296. let toc = res.1;
  297. assert_eq!(toc.len(), 1);
  298. assert_eq!(toc[0].children.len(), 2);
  299. assert_eq!(toc[0].children[1].children.len(), 1);
  300. }
  301. #[test]
  302. fn can_understand_backtick_in_titles() {
  303. let permalinks_ctx = HashMap::new();
  304. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  305. let res = markdown_to_html("# `Hello`", &context).unwrap();
  306. assert_eq!(
  307. res.0,
  308. "<h1 id=\"hello\"><code>Hello</code></h1>\n"
  309. );
  310. }
  311. #[test]
  312. fn can_understand_backtick_in_paragraphs() {
  313. let permalinks_ctx = HashMap::new();
  314. let context = Context::new(&GUTENBERG_TERA, true, "base16-ocean-dark".to_string(), "", &permalinks_ctx, InsertAnchor::None);
  315. let res = markdown_to_html("Hello `world`", &context).unwrap();
  316. assert_eq!(
  317. res.0,
  318. "<p>Hello <code>world</code></p>\n"
  319. );
  320. }