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

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