1use serde_json::Value;
2use slack_morphism::prelude::*;
3
4use crate::{
5 references::SlackReferences,
6 visitor::{
7 visit_slack_block_image_element, visit_slack_block_mark_down_text,
8 visit_slack_block_plain_text, visit_slack_context_block, visit_slack_divider_block,
9 visit_slack_header_block, visit_slack_image_block, visit_slack_markdown_block,
10 visit_slack_section_block, visit_slack_video_block, SlackRichTextBlock, Visitor,
11 },
12};
13
14pub fn render_blocks_as_markdown(
17 blocks: Vec<SlackBlock>,
18 slack_references: SlackReferences,
19 handle_delimiter: Option<String>,
20) -> String {
21 let mut block_renderer = MarkdownRenderer::new(slack_references, handle_delimiter);
22 for block in blocks {
23 block_renderer.visit_slack_block(&block);
24 }
25 block_renderer.sub_texts.join("\n")
26}
27
28struct MarkdownRenderer {
29 pub sub_texts: Vec<String>,
30 pub slack_references: SlackReferences,
31 pub handle_delimiter: Option<String>,
32}
33
34impl MarkdownRenderer {
35 pub fn new(slack_references: SlackReferences, handle_delimiter: Option<String>) -> Self {
36 MarkdownRenderer {
37 sub_texts: vec![],
38 slack_references,
39 handle_delimiter,
40 }
41 }
42}
43
44fn join(mut texts: Vec<String>, join_str: &str) -> String {
45 for i in 0..texts.len() {
46 if i < texts.len() - 1 {
47 if texts[i].ends_with('`') && texts[i + 1].starts_with('`') {
48 texts[i].pop();
49 texts[i + 1].remove(0);
50 }
51 if texts[i].ends_with('~') && texts[i + 1].starts_with('~') {
52 texts[i].pop();
53 texts[i + 1].remove(0);
54 }
55 if texts[i].ends_with('_') && texts[i + 1].starts_with('_') {
56 texts[i].pop();
57 texts[i + 1].remove(0);
58 }
59 if texts[i].ends_with('*') && texts[i + 1].starts_with('*') {
60 texts[i].pop();
61 texts[i + 1].remove(0);
62 }
63 if texts[i].starts_with("> ") && !texts[i + 1].starts_with("> ") {
64 texts[i].push('\n');
65 }
66 }
67 }
68 texts.join(join_str)
69}
70
71impl Visitor for MarkdownRenderer {
72 fn visit_slack_section_block(&mut self, slack_section_block: &SlackSectionBlock) {
73 let mut section_renderer =
74 MarkdownRenderer::new(self.slack_references.clone(), self.handle_delimiter.clone());
75 visit_slack_section_block(&mut section_renderer, slack_section_block);
76 self.sub_texts.push(join(section_renderer.sub_texts, ""));
77 }
78
79 fn visit_slack_block_plain_text(&mut self, slack_block_plain_text: &SlackBlockPlainText) {
80 self.sub_texts.push(slack_block_plain_text.text.clone());
81 visit_slack_block_plain_text(self, slack_block_plain_text);
82 }
83
84 fn visit_slack_header_block(&mut self, slack_header_block: &SlackHeaderBlock) {
85 let mut header_renderer =
86 MarkdownRenderer::new(self.slack_references.clone(), self.handle_delimiter.clone());
87 visit_slack_header_block(&mut header_renderer, slack_header_block);
88 self.sub_texts
89 .push(format!("## {}", join(header_renderer.sub_texts, "")));
90 }
91
92 fn visit_slack_divider_block(&mut self, slack_divider_block: &SlackDividerBlock) {
93 self.sub_texts.push("---\n".to_string());
94 visit_slack_divider_block(self, slack_divider_block);
95 }
96
97 fn visit_slack_image_block(&mut self, slack_image_block: &SlackImageBlock) {
98 if let Some(image_url) = slack_image_block.image_url_or_file.image_url() {
99 self.sub_texts
100 .push(format!("", slack_image_block.alt_text, image_url));
101 }
102 visit_slack_image_block(self, slack_image_block);
103 }
104
105 fn visit_slack_block_image_element(
106 &mut self,
107 slack_block_image_element: &SlackBlockImageElement,
108 ) {
109 if let Some(image_url) = slack_block_image_element.image_url_or_file.image_url() {
110 self.sub_texts.push(format!(
111 "",
112 slack_block_image_element.alt_text, image_url
113 ));
114 }
115 visit_slack_block_image_element(self, slack_block_image_element);
116 }
117
118 fn visit_slack_block_mark_down_text(
119 &mut self,
120 slack_block_mark_down_text: &SlackBlockMarkDownText,
121 ) {
122 self.sub_texts.push(slack_block_mark_down_text.text.clone());
123 visit_slack_block_mark_down_text(self, slack_block_mark_down_text);
124 }
125
126 fn visit_slack_context_block(&mut self, slack_context_block: &SlackContextBlock) {
127 let mut section_renderer =
128 MarkdownRenderer::new(self.slack_references.clone(), self.handle_delimiter.clone());
129 visit_slack_context_block(&mut section_renderer, slack_context_block);
130 self.sub_texts.push(section_renderer.sub_texts.join(""));
131 }
132
133 fn visit_slack_rich_text_block(&mut self, slack_rich_text_block: &SlackRichTextBlock) {
134 self.sub_texts.push(render_rich_text_block_as_markdown(
135 slack_rich_text_block.json_value.clone(),
136 self,
137 ));
138 }
139
140 fn visit_slack_video_block(&mut self, slack_video_block: &SlackVideoBlock) {
141 let title: SlackBlockText = slack_video_block.title.clone().into();
142 let title = match title {
143 SlackBlockText::Plain(plain_text) => plain_text.text,
144 SlackBlockText::MarkDown(md_text) => md_text.text,
145 };
146 if let Some(ref title_url) = slack_video_block.title_url {
147 self.sub_texts
148 .push(format!("*[{}]({})*\n", title, title_url));
149 } else {
150 self.sub_texts.push(format!("*{}*\n", title));
151 }
152
153 if let Some(description) = slack_video_block.description.clone() {
154 let description: SlackBlockText = description.into();
155 let description = match description {
156 SlackBlockText::Plain(plain_text) => plain_text.text,
157 SlackBlockText::MarkDown(md_text) => md_text.text,
158 };
159 self.sub_texts.push(format!("{}\n", description));
160 }
161
162 self.sub_texts.push(format!(
163 "",
164 slack_video_block.alt_text, slack_video_block.thumbnail_url
165 ));
166
167 visit_slack_video_block(self, slack_video_block);
168 }
169
170 fn visit_slack_markdown_block(&mut self, slack_markdown_block: &SlackMarkdownBlock) {
171 self.sub_texts.push(slack_markdown_block.text.clone());
172 visit_slack_markdown_block(self, slack_markdown_block);
173 }
174}
175
176fn render_rich_text_block_as_markdown(
177 json_value: serde_json::Value,
178 renderer: &MarkdownRenderer,
179) -> String {
180 match json_value.get("elements") {
181 Some(serde_json::Value::Array(elements)) => join(
182 elements
183 .iter()
184 .map(|element| {
185 match (
186 element.get("type").map(|t| t.as_str()),
187 element.get("style"),
188 element.get("elements"),
189 element.get("indent"),
190 ) {
191 (
192 Some(Some("rich_text_section")),
193 _,
194 Some(serde_json::Value::Array(elements)),
195 _,
196 ) => render_rich_text_section_elements(elements, renderer, true),
197 (
198 Some(Some("rich_text_list")),
199 Some(serde_json::Value::String(style)),
200 Some(serde_json::Value::Array(elements)),
201 Some(serde_json::Value::Number(indent)),
202 ) => render_rich_text_list_elements(
203 elements,
204 style,
205 indent
206 .as_u64()
207 .unwrap_or_default()
208 .try_into()
209 .unwrap_or_default(),
210 renderer,
211 ),
212 (
213 Some(Some("rich_text_list")),
214 Some(serde_json::Value::String(style)),
215 Some(serde_json::Value::Array(elements)),
216 _,
217 ) => render_rich_text_list_elements(elements, style, 0, renderer),
218 (
219 Some(Some("rich_text_preformatted")),
220 _,
221 Some(serde_json::Value::Array(elements)),
222 _,
223 ) => render_rich_text_preformatted_elements(elements, renderer),
224
225 (
226 Some(Some("rich_text_quote")),
227 _,
228 Some(serde_json::Value::Array(elements)),
229 _,
230 ) => render_rich_text_quote_elements(elements, renderer),
231
232 _ => "".to_string(),
233 }
234 })
235 .collect::<Vec<String>>(),
236 "\n",
237 ),
238 _ => "".to_string(),
239 }
240}
241
242fn render_rich_text_section_elements(
243 elements: &[serde_json::Value],
244 renderer: &MarkdownRenderer,
245 fix_newlines_in_text: bool,
246) -> String {
247 let result = join(
248 elements
249 .iter()
250 .map(|e| render_rich_text_section_element(e, renderer))
251 .collect::<Vec<String>>(),
252 "",
253 );
254 if fix_newlines_in_text {
255 fix_newlines(result)
256 } else {
257 result
258 }
259}
260
261fn render_rich_text_list_elements(
262 elements: &[serde_json::Value],
263 style: &str,
264 indent: usize,
265 renderer: &MarkdownRenderer,
266) -> String {
267 let list_style = if style == "ordered" { "1." } else { "-" };
268 let space_per_level = if style == "ordered" { 3 } else { 2 };
269 let indent_prefix = " ".repeat(space_per_level * indent);
270 elements
271 .iter()
272 .filter_map(|element| {
273 if let Some(serde_json::Value::Array(elements)) = element.get("elements") {
274 Some(render_rich_text_section_elements(elements, renderer, true))
275 } else {
276 None
277 }
278 })
279 .map(|element| format!("{indent_prefix}{list_style} {element}"))
280 .collect::<Vec<String>>()
281 .join("\n")
282}
283
284fn render_rich_text_preformatted_elements(
285 elements: &[serde_json::Value],
286 renderer: &MarkdownRenderer,
287) -> String {
288 format!(
289 "```\n{}\n```",
290 render_rich_text_section_elements(elements, renderer, false)
291 )
292}
293
294fn render_rich_text_quote_elements(
295 elements: &[serde_json::Value],
296 renderer: &MarkdownRenderer,
297) -> String {
298 format!(
299 "> {}",
300 render_rich_text_section_elements(elements, renderer, true)
301 )
302}
303
304fn render_rich_text_section_element(
305 element: &serde_json::Value,
306 renderer: &MarkdownRenderer,
307) -> String {
308 let handle_delimiter = renderer.handle_delimiter.clone().unwrap_or_default();
309 match element.get("type").map(|t| t.as_str()) {
310 Some(Some("text")) => {
311 let Some(serde_json::Value::String(text)) = element.get("text") else {
312 return "".to_string();
313 };
314 let style = element.get("style");
315 apply_all_styles(text.to_string(), style)
316 }
317 Some(Some("channel")) => {
318 let Some(serde_json::Value::String(channel_id)) = element.get("channel_id") else {
319 return "".to_string();
320 };
321 let channel_rendered = if let Some(Some(channel_name)) = renderer
322 .slack_references
323 .channels
324 .get(&SlackChannelId(channel_id.clone()))
325 {
326 channel_name
327 } else {
328 channel_id
329 };
330 let style = element.get("style");
331 apply_all_styles(format!("#{channel_rendered}"), style)
332 }
333 Some(Some("user")) => {
334 let Some(serde_json::Value::String(user_id)) = element.get("user_id") else {
335 return "".to_string();
336 };
337 let user_rendered = if let Some(Some(user_name)) = renderer
338 .slack_references
339 .users
340 .get(&SlackUserId(user_id.clone()))
341 {
342 user_name
343 } else {
344 user_id
345 };
346 let style = element.get("style");
347 apply_all_styles(
348 format!("{handle_delimiter}@{user_rendered}{handle_delimiter}"),
349 style,
350 )
351 }
352 Some(Some("usergroup")) => {
353 let Some(serde_json::Value::String(usergroup_id)) = element.get("usergroup_id") else {
354 return "".to_string();
355 };
356 let usergroup_rendered = if let Some(Some(usergroup_name)) = renderer
357 .slack_references
358 .usergroups
359 .get(&SlackUserGroupId(usergroup_id.clone()))
360 {
361 usergroup_name
362 } else {
363 usergroup_id
364 };
365 let style = element.get("style");
366 apply_all_styles(
367 format!("{handle_delimiter}@{usergroup_rendered}{handle_delimiter}"),
368 style,
369 )
370 }
371 Some(Some("emoji")) => {
372 let Some(serde_json::Value::String(name)) = element.get("name") else {
373 return "".to_string();
374 };
375 let style = element.get("style");
376 render_emoji(
377 &SlackEmojiName(name.to_string()),
378 &renderer.slack_references,
379 style,
380 )
381 }
382 Some(Some("link")) => {
383 let Some(serde_json::Value::String(url)) = element.get("url") else {
384 return "".to_string();
385 };
386 let Some(serde_json::Value::String(text)) = element.get("text") else {
387 return render_url_as_markdown(url, url);
388 };
389 let style = element.get("style");
390 apply_all_styles(render_url_as_markdown(url, text), style)
391 }
392 _ => "".to_string(),
393 }
394}
395
396fn render_url_as_markdown(url: &str, text: &str) -> String {
397 format!("[{}]({})", text, url)
398}
399
400fn render_emoji(
401 emoji_name: &SlackEmojiName,
402 slack_references: &SlackReferences,
403 style: Option<&Value>,
404) -> String {
405 if let Some(Some(emoji)) = slack_references.emojis.get(emoji_name) {
406 match emoji {
407 SlackEmojiRef::Alias(alias) => {
408 return render_emoji(alias, slack_references, style);
409 }
410 SlackEmojiRef::Url(url) => {
411 return apply_all_styles(format!("", emoji_name.0, url), style);
412 }
413 }
414 }
415 let name = &emoji_name.0;
416
417 let splitted = name.split("::skin-tone-").collect::<Vec<&str>>();
418 let Some(first) = splitted.first() else {
419 return apply_all_styles(format!(":{}:", name), style);
420 };
421 let Some(emoji) = emojis::get_by_shortcode(first) else {
422 return apply_all_styles(format!(":{}:", name), style);
423 };
424 let Some(skin_tone) = splitted.get(1).and_then(|s| s.parse::<usize>().ok()) else {
425 return apply_all_styles(emoji.to_string(), style);
426 };
427 let Some(mut skin_tones) = emoji.skin_tones() else {
428 return apply_all_styles(emoji.to_string(), style);
429 };
430 let Some(skinned_emoji) = skin_tones.nth(skin_tone - 1) else {
431 return apply_all_styles(emoji.to_string(), style);
432 };
433 apply_all_styles(skinned_emoji.to_string(), style)
434}
435
436fn apply_all_styles(text: String, style: Option<&serde_json::Value>) -> String {
437 let text = apply_bold_style(text, style);
438 let text = apply_italic_style(text, style);
439 let text = apply_strike_style(text, style);
440 apply_code_style(text, style)
441}
442
443fn apply_bold_style(text: String, style: Option<&serde_json::Value>) -> String {
444 if is_styled(style, "bold") {
445 format!("*{}*", text)
446 } else {
447 text
448 }
449}
450
451fn apply_italic_style(text: String, style: Option<&serde_json::Value>) -> String {
452 if is_styled(style, "italic") {
453 format!("_{}_", text)
454 } else {
455 text
456 }
457}
458
459fn apply_strike_style(text: String, style: Option<&serde_json::Value>) -> String {
460 if is_styled(style, "strike") {
461 format!("~{}~", text)
462 } else {
463 text
464 }
465}
466
467fn apply_code_style(text: String, style: Option<&serde_json::Value>) -> String {
468 if is_styled(style, "code") {
469 format!("`{}`", text)
470 } else {
471 text
472 }
473}
474
475fn is_styled(style: Option<&serde_json::Value>, style_name: &str) -> bool {
476 style
477 .and_then(|s| s.get(style_name).map(|b| b.as_bool()))
478 .flatten()
479 .unwrap_or_default()
480}
481
482fn fix_newlines(text: String) -> String {
483 text.replace("\n", "\\\n")
484 .trim_end_matches("\\\n")
485 .to_string()
486}
487
488#[cfg(test)]
489mod tests {
490 use std::collections::HashMap;
491
492 use url::Url;
493
494 use super::*;
495
496 #[test]
497 fn test_empty_input() {
498 assert_eq!(
499 render_blocks_as_markdown(vec![], SlackReferences::default(), None),
500 "".to_string()
501 );
502 }
503
504 #[test]
505 fn test_with_image() {
506 let blocks = vec![
507 SlackBlock::Image(SlackImageBlock::new(
508 SlackImageUrlOrFile::ImageUrl {
509 image_url: Url::parse("https://example.com/image.png").unwrap(),
510 },
511 "Image".to_string(),
512 )),
513 SlackBlock::Image(SlackImageBlock::new(
514 SlackImageUrlOrFile::ImageUrl {
515 image_url: Url::parse("https://example.com/image2.png").unwrap(),
516 },
517 "Image2".to_string(),
518 )),
519 ];
520 assert_eq!(
521 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
522 "\n"
523 .to_string()
524 );
525 }
526
527 #[test]
528 fn test_with_divider() {
529 let blocks = vec![
530 SlackBlock::Divider(SlackDividerBlock::new()),
531 SlackBlock::Divider(SlackDividerBlock::new()),
532 ];
533 assert_eq!(
534 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
535 "---\n\n---\n".to_string()
536 );
537 }
538
539 #[test]
540 fn test_with_input() {
541 let blocks = vec![SlackBlock::Input(SlackInputBlock::new(
543 "label".into(),
544 SlackInputBlockElement::PlainTextInput(SlackBlockPlainTextInputElement::new(
545 "id".into(),
546 )),
547 ))];
548 assert_eq!(
549 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
550 "".to_string()
551 );
552 }
553
554 #[test]
555 fn test_with_action() {
556 let blocks = vec![SlackBlock::Actions(SlackActionsBlock::new(vec![]))];
558 assert_eq!(
559 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
560 "".to_string()
561 );
562 }
563
564 #[test]
565 fn test_with_file() {
566 let blocks = vec![SlackBlock::File(SlackFileBlock::new("external_id".into()))];
568 assert_eq!(
569 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
570 "".to_string()
571 );
572 }
573
574 #[test]
575 fn test_with_video() {
576 let blocks = vec![SlackBlock::Video(
577 SlackVideoBlock::new(
578 "alt text".into(),
579 "Video title".into(),
580 "https://example.com/thumbnail.jpg".parse().unwrap(),
581 "https://example.com/video_embed.avi".parse().unwrap(),
582 )
583 .with_description("Video description".into())
584 .with_title_url("https://example.com/video".parse().unwrap()),
585 )];
586 assert_eq!(
587 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
588 r#"*[Video title](https://example.com/video)*
589
590Video description
591
592"#
593 .to_string()
594 );
595 }
596
597 #[test]
598 fn test_with_video_minimal() {
599 let blocks = vec![SlackBlock::Video(SlackVideoBlock::new(
600 "alt text".into(),
601 "Video title".into(),
602 "https://example.com/thumbnail.jpg".parse().unwrap(),
603 "https://example.com/video_embed.avi".parse().unwrap(),
604 ))];
605 assert_eq!(
606 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
607 r#"*Video title*
608
609"#
610 .to_string()
611 );
612 }
613
614 #[test]
615 fn test_with_event() {
616 let blocks = vec![SlackBlock::Event(serde_json::json!({}))];
618 assert_eq!(
619 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
620 "".to_string()
621 );
622 }
623
624 #[test]
625 fn test_header() {
626 let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
627 assert_eq!(
628 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
629 "## Text".to_string()
630 );
631 }
632
633 mod section {
634 use super::*;
635
636 #[test]
637 fn test_with_plain_text() {
638 let blocks = vec![
639 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
640 SlackBlockPlainText::new("Text".to_string()),
641 ))),
642 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
643 SlackBlockPlainText::new("Text2".to_string()),
644 ))),
645 ];
646 assert_eq!(
647 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
648 "Text\nText2".to_string()
649 );
650 }
651
652 #[test]
653 fn test_with_markdown() {
654 let blocks = vec![
655 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
656 SlackBlockMarkDownText::new("Text".to_string()),
657 ))),
658 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
659 SlackBlockMarkDownText::new("Text2".to_string()),
660 ))),
661 ];
662 assert_eq!(
663 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
664 "Text\nText2".to_string()
665 );
666 }
667
668 #[test]
669 fn test_with_fields() {
670 let blocks = vec![
671 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
672 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
673 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
674 ])),
675 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
676 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
677 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
678 ])),
679 ];
680 assert_eq!(
681 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
682 "Text11Text12\nText21Text22".to_string()
683 );
684 }
685
686 #[test]
687 fn test_with_fields_and_text() {
688 let blocks = vec![
689 SlackBlock::Section(
690 SlackSectionBlock::new()
691 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
692 "Text1".to_string(),
693 )))
694 .with_fields(vec![
695 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
696 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
697 ]),
698 ),
699 SlackBlock::Section(
700 SlackSectionBlock::new()
701 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
702 "Text2".to_string(),
703 )))
704 .with_fields(vec![
705 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
706 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
707 ]),
708 ),
709 ];
710 assert_eq!(
711 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
712 "Text1Text11Text12\nText2Text21Text22".to_string()
713 );
714 }
715 }
716
717 mod context {
718 use super::*;
719
720 #[test]
721 fn test_with_image() {
722 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
723 SlackContextBlockElement::Image(SlackBlockImageElement::new(
724 SlackImageUrlOrFile::ImageUrl {
725 image_url: Url::parse("https://example.com/image.png").unwrap(),
726 },
727 "Image".to_string(),
728 )),
729 SlackContextBlockElement::Image(SlackBlockImageElement::new(
730 SlackImageUrlOrFile::ImageUrl {
731 image_url: Url::parse("https://example.com/image2.png").unwrap(),
732 },
733 "Image2".to_string(),
734 )),
735 ]))];
736 assert_eq!(
737 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
738 ""
739 .to_string()
740 );
741 }
742
743 #[test]
744 fn test_with_plain_text() {
745 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
746 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
747 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
748 ]))];
749 assert_eq!(
750 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
751 "TextText2".to_string()
752 );
753 }
754
755 #[test]
756 fn test_with_markdown() {
757 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
758 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
759 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
760 "Text2".to_string(),
761 )),
762 ]))];
763 assert_eq!(
764 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
765 "TextText2".to_string()
766 );
767 }
768 }
769
770 mod rich_text {
771 use super::*;
772
773 #[test]
774 fn test_with_empty_json() {
775 let blocks = vec![
776 SlackBlock::RichText(serde_json::json!({})),
777 SlackBlock::RichText(serde_json::json!({})),
778 ];
779 assert_eq!(
780 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
781 "\n".to_string()
782 );
783 }
784
785 mod rich_text_section {
786 use super::*;
787
788 mod text_element {
789 use super::*;
790
791 #[test]
792 fn test_with_text() {
793 let blocks = vec![
794 SlackBlock::RichText(serde_json::json!({
795 "type": "rich_text",
796 "elements": [
797 {
798 "type": "rich_text_section",
799 "elements": [
800 {
801 "type": "text",
802 "text": "Text111"
803 },
804 {
805 "type": "text",
806 "text": "Text112"
807 }
808 ]
809 },
810 {
811 "type": "rich_text_section",
812 "elements": [
813 {
814 "type": "text",
815 "text": "Text121"
816 },
817 {
818 "type": "text",
819 "text": "Text122"
820 }
821 ]
822 }
823 ]
824 })),
825 SlackBlock::RichText(serde_json::json!({
826 "type": "rich_text",
827 "elements": [
828 {
829 "type": "rich_text_section",
830 "elements": [
831 {
832 "type": "text",
833 "text": "Text211"
834 },
835 {
836 "type": "text",
837 "text": "Text212"
838 }
839 ]
840 },
841 {
842 "type": "rich_text_section",
843 "elements": [
844 {
845 "type": "text",
846 "text": "Text221"
847 },
848 {
849 "type": "text",
850 "text": "Text222"
851 }
852 ]
853 }
854 ]
855 })),
856 ];
857 assert_eq!(
858 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
859 "Text111Text112\nText121Text122\nText211Text212\nText221Text222"
860 .to_string()
861 );
862 }
863
864 #[test]
865 fn test_with_text_with_newline() {
866 let blocks = vec![SlackBlock::RichText(serde_json::json!({
867 "type": "rich_text",
868 "elements": [
869 {
870 "type": "rich_text_section",
871 "elements": [
872 {
873 "type": "text",
874 "text": "Text111\nText112\n"
875 },
876 {
877 "type": "text",
878 "text": "Text211\nText212\n"
879 }
880 ]
881 }
882 ]
883 }))];
884 assert_eq!(
885 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
886 "Text111\\\nText112\\\nText211\\\nText212".to_string()
887 );
888 }
889
890 #[test]
891 fn test_with_text_with_newline_char() {
892 let blocks = vec![SlackBlock::RichText(serde_json::json!({
893 "type": "rich_text",
894 "elements": [
895 {
896 "type": "rich_text_section",
897 "elements": [
898 {
899 "type": "text",
900 "text": "Text111\\nText112"
901 }
902 ]
903 }
904 ]
905 }))];
906 assert_eq!(
907 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
908 "Text111\\nText112".to_string()
909 );
910 }
911
912 #[test]
913 fn test_with_text_with_only_newline() {
914 let blocks = vec![SlackBlock::RichText(serde_json::json!({
915 "type": "rich_text",
916 "elements": [
917 {
918 "type": "rich_text_section",
919 "elements": [
920 {
921 "type": "text",
922 "text": "\n"
923 }
924 ]
925 }
926 ]
927 }))];
928 assert_eq!(
929 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
930 "".to_string()
931 );
932 }
933
934 #[test]
935 fn test_with_bold_text() {
936 let blocks = vec![SlackBlock::RichText(serde_json::json!({
937 "type": "rich_text",
938 "elements": [
939 {
940 "type": "rich_text_section",
941 "elements": [
942 {
943 "type": "text",
944 "text": "Text",
945 "style": {
946 "bold": true
947 }
948 }
949 ]
950 }
951 ]
952 }))];
953 assert_eq!(
954 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
955 "*Text*".to_string()
956 );
957 }
958
959 #[test]
960 fn test_with_consecutive_bold_text() {
961 let blocks = vec![SlackBlock::RichText(serde_json::json!({
962 "type": "rich_text",
963 "elements": [
964 {
965 "type": "rich_text_section",
966 "elements": [
967 {
968 "type": "text",
969 "text": "Hello",
970 "style": {
971 "bold": true
972 }
973 },
974 {
975 "type": "text",
976 "text": " ",
977 "style": {
978 "bold": true
979 }
980 },
981 {
982 "type": "text",
983 "text": "World!",
984 "style": {
985 "bold": true
986 }
987 }
988 ]
989 }
990 ]
991 }))];
992 assert_eq!(
993 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
994 "*Hello World!*".to_string()
995 );
996 }
997
998 #[test]
999 fn test_with_italic_text() {
1000 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1001 "type": "rich_text",
1002 "elements": [
1003 {
1004 "type": "rich_text_section",
1005 "elements": [
1006 {
1007 "type": "text",
1008 "text": "Text",
1009 "style": {
1010 "italic": true
1011 }
1012 }
1013 ]
1014 }
1015 ]
1016 }))];
1017 assert_eq!(
1018 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1019 "_Text_".to_string()
1020 );
1021 }
1022
1023 #[test]
1024 fn test_with_consecutive_italic_text() {
1025 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1026 "type": "rich_text",
1027 "elements": [
1028 {
1029 "type": "rich_text_section",
1030 "elements": [
1031 {
1032 "type": "text",
1033 "text": "Hello",
1034 "style": {
1035 "italic": true
1036 }
1037 },
1038 {
1039 "type": "text",
1040 "text": " ",
1041 "style": {
1042 "italic": true
1043 }
1044 },
1045 {
1046 "type": "text",
1047 "text": "World!",
1048 "style": {
1049 "italic": true
1050 }
1051 }
1052 ]
1053 }
1054 ]
1055 }))];
1056 assert_eq!(
1057 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1058 "_Hello World!_".to_string()
1059 );
1060 }
1061
1062 #[test]
1063 fn test_with_strike_text() {
1064 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1065 "type": "rich_text",
1066 "elements": [
1067 {
1068 "type": "rich_text_section",
1069 "elements": [
1070 {
1071 "type": "text",
1072 "text": "Text",
1073 "style": {
1074 "strike": true
1075 }
1076 }
1077 ]
1078 }
1079 ]
1080 }))];
1081 assert_eq!(
1082 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1083 "~Text~".to_string()
1084 );
1085 }
1086
1087 #[test]
1088 fn test_with_consecutive_strike_text() {
1089 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1090 "type": "rich_text",
1091 "elements": [
1092 {
1093 "type": "rich_text_section",
1094 "elements": [
1095 {
1096 "type": "text",
1097 "text": "Hello",
1098 "style": {
1099 "strike": true
1100 }
1101 },
1102 {
1103 "type": "text",
1104 "text": " ",
1105 "style": {
1106 "strike": true
1107 }
1108 },
1109 {
1110 "type": "text",
1111 "text": "World!",
1112 "style": {
1113 "strike": true
1114 }
1115 }
1116 ]
1117 }
1118 ]
1119 }))];
1120 assert_eq!(
1121 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1122 "~Hello World!~".to_string()
1123 );
1124 }
1125
1126 #[test]
1127 fn test_with_code_text() {
1128 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1129 "type": "rich_text",
1130 "elements": [
1131 {
1132 "type": "rich_text_section",
1133 "elements": [
1134 {
1135 "type": "text",
1136 "text": "Text",
1137 "style": {
1138 "code": true
1139 }
1140 }
1141 ]
1142 }
1143 ]
1144 }))];
1145 assert_eq!(
1146 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1147 "`Text`".to_string()
1148 );
1149 }
1150
1151 #[test]
1152 fn test_with_consecutive_code_text() {
1153 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1154 "type": "rich_text",
1155 "elements": [
1156 {
1157 "type": "rich_text_section",
1158 "elements": [
1159 {
1160 "type": "text",
1161 "text": "Text1",
1162 "style": {
1163 "code": true
1164 }
1165 },
1166 {
1167 "type": "text",
1168 "text": "Text2",
1169 "style": {
1170 "code": true
1171 }
1172 }
1173 ]
1174 }
1175 ]
1176 }))];
1177 assert_eq!(
1178 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1179 "`Text1Text2`".to_string()
1180 );
1181 }
1182
1183 #[test]
1184 fn test_with_styled_text() {
1185 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1186 "type": "rich_text",
1187 "elements": [
1188 {
1189 "type": "rich_text_section",
1190 "elements": [
1191 {
1192 "type": "text",
1193 "text": "Text",
1194 "style": {
1195 "bold": true,
1196 "italic": true,
1197 "strike": true
1198 }
1199 }
1200 ]
1201 }
1202 ]
1203 }))];
1204 assert_eq!(
1205 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1206 "~_*Text*_~".to_string()
1207 );
1208 }
1209
1210 #[test]
1211 fn test_with_consecutive_styled_text() {
1212 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1213 "type": "rich_text",
1214 "elements": [
1215 {
1216 "type": "rich_text_section",
1217 "elements": [
1218 {
1219 "name": "chart_with_upwards_trend",
1220 "type": "emoji",
1221 "style": {
1222 "bold": true,
1223 "italic": true,
1224 "strike": true
1225 },
1226 },
1227 {
1228 "type": "text",
1229 "text": "Hello",
1230 "style": {
1231 "bold": true,
1232 "italic": true,
1233 "strike": true
1234 }
1235 },
1236 {
1237 "type": "text",
1238 "text": " ",
1239 "style": {
1240 "bold": true,
1241 "italic": true,
1242 "strike": true
1243 }
1244 },
1245 {
1246 "type": "text",
1247 "text": "World!",
1248 "style": {
1249 "bold": true,
1250 "italic": true,
1251 "strike": true
1252 }
1253 }
1254 ]
1255 }
1256 ]
1257 }))];
1258 assert_eq!(
1259 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1260 "~_*📈Hello World!*_~".to_string()
1261 );
1262 }
1263 }
1264
1265 mod channel_element {
1266 use super::*;
1267
1268 #[test]
1269 fn test_with_channel_id() {
1270 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1271 "type": "rich_text",
1272 "elements": [
1273 {
1274 "type": "rich_text_section",
1275 "elements": [
1276 {
1277 "type": "channel",
1278 "channel_id": "C0123456"
1279 }
1280 ]
1281 }
1282 ]
1283 }))];
1284 assert_eq!(
1285 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1286 "#C0123456".to_string()
1287 );
1288 }
1289
1290 #[test]
1291 fn test_with_channel_id_and_reference() {
1292 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1293 "type": "rich_text",
1294 "elements": [
1295 {
1296 "type": "rich_text_section",
1297 "elements": [
1298 {
1299 "type": "channel",
1300 "channel_id": "C0123456"
1301 }
1302 ]
1303 }
1304 ]
1305 }))];
1306 assert_eq!(
1307 render_blocks_as_markdown(
1308 blocks,
1309 SlackReferences {
1310 channels: HashMap::from([(
1311 SlackChannelId("C0123456".to_string()),
1312 Some("general".to_string())
1313 )]),
1314 ..SlackReferences::default()
1315 },
1316 None
1317 ),
1318 "#general".to_string()
1319 );
1320 }
1321 }
1322
1323 mod user_element {
1324 use super::*;
1325
1326 #[test]
1327 fn test_with_user_id() {
1328 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1329 "type": "rich_text",
1330 "elements": [
1331 {
1332 "type": "rich_text_section",
1333 "elements": [
1334 {
1335 "type": "user",
1336 "user_id": "user1"
1337 }
1338 ]
1339 }
1340 ]
1341 }))];
1342 assert_eq!(
1343 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1344 "@user1".to_string()
1345 );
1346 }
1347
1348 #[test]
1349 fn test_with_user_id_and_custom_delimiter() {
1350 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1351 "type": "rich_text",
1352 "elements": [
1353 {
1354 "type": "rich_text_section",
1355 "elements": [
1356 {
1357 "type": "user",
1358 "user_id": "user1"
1359 }
1360 ]
1361 }
1362 ]
1363 }))];
1364 assert_eq!(
1365 render_blocks_as_markdown(
1366 blocks,
1367 SlackReferences::default(),
1368 Some("@".to_string())
1369 ),
1370 "@@user1@".to_string()
1371 );
1372 }
1373
1374 #[test]
1375 fn test_with_user_id_and_reference() {
1376 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1377 "type": "rich_text",
1378 "elements": [
1379 {
1380 "type": "rich_text_section",
1381 "elements": [
1382 {
1383 "type": "user",
1384 "user_id": "user1"
1385 }
1386 ]
1387 }
1388 ]
1389 }))];
1390 assert_eq!(
1391 render_blocks_as_markdown(
1392 blocks,
1393 SlackReferences {
1394 users: HashMap::from([(
1395 SlackUserId("user1".to_string()),
1396 Some("John Doe".to_string())
1397 )]),
1398 ..SlackReferences::default()
1399 },
1400 None
1401 ),
1402 "@John Doe".to_string()
1403 );
1404 }
1405
1406 #[test]
1407 fn test_with_user_id_and_reference_and_custom_delimiter() {
1408 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1409 "type": "rich_text",
1410 "elements": [
1411 {
1412 "type": "rich_text_section",
1413 "elements": [
1414 {
1415 "type": "user",
1416 "user_id": "user1"
1417 }
1418 ]
1419 }
1420 ]
1421 }))];
1422 assert_eq!(
1423 render_blocks_as_markdown(
1424 blocks,
1425 SlackReferences {
1426 users: HashMap::from([(
1427 SlackUserId("user1".to_string()),
1428 Some("John Doe".to_string())
1429 )]),
1430 ..SlackReferences::default()
1431 },
1432 Some("@".to_string())
1433 ),
1434 "@@John Doe@".to_string()
1435 );
1436 }
1437 }
1438
1439 mod usergroup_element {
1440 use super::*;
1441
1442 #[test]
1443 fn test_with_usergroup_id() {
1444 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1445 "type": "rich_text",
1446 "elements": [
1447 {
1448 "type": "rich_text_section",
1449 "elements": [
1450 {
1451 "type": "usergroup",
1452 "usergroup_id": "group1"
1453 }
1454 ]
1455 }
1456 ]
1457 }))];
1458 assert_eq!(
1459 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1460 "@group1".to_string()
1461 );
1462 }
1463
1464 #[test]
1465 fn test_with_usergroup_id_and_custom_delimiter() {
1466 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1467 "type": "rich_text",
1468 "elements": [
1469 {
1470 "type": "rich_text_section",
1471 "elements": [
1472 {
1473 "type": "usergroup",
1474 "usergroup_id": "group1"
1475 }
1476 ]
1477 }
1478 ]
1479 }))];
1480 assert_eq!(
1481 render_blocks_as_markdown(
1482 blocks,
1483 SlackReferences::default(),
1484 Some("@".to_string())
1485 ),
1486 "@@group1@".to_string()
1487 );
1488 }
1489
1490 #[test]
1491 fn test_with_usergroup_id_and_reference() {
1492 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1493 "type": "rich_text",
1494 "elements": [
1495 {
1496 "type": "rich_text_section",
1497 "elements": [
1498 {
1499 "type": "usergroup",
1500 "usergroup_id": "group1"
1501 }
1502 ]
1503 }
1504 ]
1505 }))];
1506 assert_eq!(
1507 render_blocks_as_markdown(
1508 blocks,
1509 SlackReferences {
1510 usergroups: HashMap::from([(
1511 SlackUserGroupId("group1".to_string()),
1512 Some("Admins".to_string())
1513 )]),
1514 ..SlackReferences::default()
1515 },
1516 None
1517 ),
1518 "@Admins".to_string()
1519 );
1520 }
1521
1522 #[test]
1523 fn test_with_usergroup_id_and_reference_and_custom_delimiter() {
1524 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1525 "type": "rich_text",
1526 "elements": [
1527 {
1528 "type": "rich_text_section",
1529 "elements": [
1530 {
1531 "type": "usergroup",
1532 "usergroup_id": "group1"
1533 }
1534 ]
1535 }
1536 ]
1537 }))];
1538 assert_eq!(
1539 render_blocks_as_markdown(
1540 blocks,
1541 SlackReferences {
1542 usergroups: HashMap::from([(
1543 SlackUserGroupId("group1".to_string()),
1544 Some("Admins".to_string())
1545 )]),
1546 ..SlackReferences::default()
1547 },
1548 Some("@".to_string())
1549 ),
1550 "@@Admins@".to_string()
1551 );
1552 }
1553 }
1554
1555 mod link_element {
1556 use super::*;
1557
1558 #[test]
1559 fn test_with_url() {
1560 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1561 "type": "rich_text",
1562 "elements": [
1563 {
1564 "type": "rich_text_section",
1565 "elements": [
1566 {
1567 "type": "link",
1568 "text": "example",
1569 "url": "https://example.com"
1570 }
1571 ]
1572 }
1573 ]
1574 }))];
1575 assert_eq!(
1576 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1577 "[example](https://example.com)".to_string()
1578 );
1579 }
1580
1581 #[test]
1582 fn test_with_url_without_text() {
1583 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1584 "type": "rich_text",
1585 "elements": [
1586 {
1587 "type": "rich_text_section",
1588 "elements": [
1589 {
1590 "type": "link",
1591 "url": "https://example.com"
1592 }
1593 ]
1594 }
1595 ]
1596 }))];
1597 assert_eq!(
1598 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1599 "[https://example.com](https://example.com)".to_string()
1600 );
1601 }
1602 }
1603
1604 mod emoji_element {
1605 use super::*;
1606
1607 #[test]
1608 fn test_with_emoji() {
1609 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1610 "type": "rich_text",
1611 "elements": [
1612 {
1613 "type": "rich_text_section",
1614 "elements": [
1615 {
1616 "type": "emoji",
1617 "name": "wave"
1618 }
1619 ]
1620 }
1621 ]
1622 }))];
1623 assert_eq!(
1624 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1625 "👋".to_string()
1626 );
1627 }
1628
1629 #[test]
1630 fn test_with_emoji_with_skin_tone() {
1631 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1632 "type": "rich_text",
1633 "elements": [
1634 {
1635 "type": "rich_text_section",
1636 "elements": [
1637 {
1638 "type": "emoji",
1639 "name": "wave::skin-tone-2"
1640 }
1641 ]
1642 }
1643 ]
1644 }))];
1645 assert_eq!(
1646 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1647 "👋🏻".to_string()
1648 );
1649 }
1650
1651 #[test]
1652 fn test_with_emoji_with_unknown_skin_tone() {
1653 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1654 "type": "rich_text",
1655 "elements": [
1656 {
1657 "type": "rich_text_section",
1658 "elements": [
1659 {
1660 "type": "emoji",
1661 "name": "wave::skin-tone-42"
1662 }
1663 ]
1664 }
1665 ]
1666 }))];
1667 assert_eq!(
1668 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1669 "👋".to_string()
1670 );
1671 }
1672
1673 #[test]
1674 fn test_with_unknown_emoji() {
1675 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1676 "type": "rich_text",
1677 "elements": [
1678 {
1679 "type": "rich_text_section",
1680 "elements": [
1681 {
1682 "type": "emoji",
1683 "name": "unknown1"
1684 }
1685 ]
1686 }
1687 ]
1688 }))];
1689 assert_eq!(
1690 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1691 ":unknown1:".to_string()
1692 );
1693 }
1694
1695 #[test]
1696 fn test_with_unknown_emoji_with_slack_reference_alias() {
1697 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1698 "type": "rich_text",
1699 "elements": [
1700 {
1701 "type": "rich_text_section",
1702 "elements": [
1703 {
1704 "type": "emoji",
1705 "name": "unknown1"
1706 }
1707 ]
1708 }
1709 ]
1710 }))];
1711 assert_eq!(
1712 render_blocks_as_markdown(
1713 blocks,
1714 SlackReferences {
1715 emojis: HashMap::from([(
1716 SlackEmojiName("unknown1".to_string()),
1717 Some(SlackEmojiRef::Alias(SlackEmojiName("wave".to_string())))
1718 )]),
1719 ..SlackReferences::default()
1720 },
1721 None
1722 ),
1723 "👋".to_string()
1724 );
1725 }
1726
1727 #[test]
1728 fn test_with_unknown_emoji_with_slack_reference_alias_to_custom_emoji() {
1729 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1730 "type": "rich_text",
1731 "elements": [
1732 {
1733 "type": "rich_text_section",
1734 "elements": [
1735 {
1736 "type": "emoji",
1737 "name": "unknown1"
1738 }
1739 ]
1740 }
1741 ]
1742 }))];
1743 assert_eq!(
1744 render_blocks_as_markdown(
1745 blocks,
1746 SlackReferences {
1747 emojis: HashMap::from([
1748 (
1749 SlackEmojiName("unknown1".to_string()),
1750 Some(SlackEmojiRef::Alias(SlackEmojiName(
1751 "unknown2".to_string()
1752 )))
1753 ),
1754 (
1755 SlackEmojiName("unknown2".to_string()),
1756 Some(SlackEmojiRef::Url(
1757 "https://emoji.com/unknown2.png".parse().unwrap()
1758 ))
1759 )
1760 ]),
1761 ..SlackReferences::default()
1762 },
1763 None
1764 ),
1765 "".to_string()
1766 );
1767 }
1768
1769 #[test]
1770 fn test_with_unknown_emoji_with_slack_reference_image_url() {
1771 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1772 "type": "rich_text",
1773 "elements": [
1774 {
1775 "type": "rich_text_section",
1776 "elements": [
1777 {
1778 "type": "emoji",
1779 "name": "unknown1"
1780 }
1781 ]
1782 }
1783 ]
1784 }))];
1785 assert_eq!(
1786 render_blocks_as_markdown(
1787 blocks,
1788 SlackReferences {
1789 emojis: HashMap::from([(
1790 SlackEmojiName("unknown1".to_string()),
1791 Some(SlackEmojiRef::Url(
1792 "https://emoji.com/unknown1.png".parse().unwrap()
1793 ))
1794 )]),
1795 ..SlackReferences::default()
1796 },
1797 None
1798 ),
1799 "".to_string()
1800 );
1801 }
1802 }
1803 }
1804
1805 mod rich_text_list {
1806 use super::*;
1807
1808 #[test]
1809 fn test_with_ordered_list() {
1810 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1811 "type": "rich_text",
1812 "elements": [
1813 {
1814 "type": "rich_text_list",
1815 "style": "ordered",
1816 "elements": [
1817 {
1818 "type": "rich_text_section",
1819 "elements": [
1820 {
1821 "type": "text",
1822 "text": "Text1"
1823 }
1824 ]
1825 },
1826 {
1827 "type": "rich_text_section",
1828 "elements": [
1829 {
1830 "type": "text",
1831 "text": "Text2"
1832 }
1833 ]
1834 }
1835 ]
1836 },
1837 ]
1838 }))];
1839 assert_eq!(
1840 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1841 "1. Text1\n1. Text2".to_string()
1842 );
1843 }
1844
1845 #[test]
1846 fn test_with_nested_ordered_list() {
1847 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1848 "type": "rich_text",
1849 "elements": [
1850 {
1851 "type": "rich_text_list",
1852 "style": "ordered",
1853 "elements": [
1854 {
1855 "type": "rich_text_section",
1856 "elements": [
1857 {
1858 "type": "text",
1859 "text": "Text1"
1860 }
1861 ]
1862 },
1863 ]
1864 },
1865 {
1866 "type": "rich_text_list",
1867 "style": "ordered",
1868 "elements": [
1869 {
1870 "type": "rich_text_section",
1871 "elements": [
1872 {
1873 "type": "text",
1874 "text": "Text2"
1875 }
1876 ]
1877 },
1878 ]
1879 },
1880 {
1881 "type": "rich_text_list",
1882 "style": "ordered",
1883 "indent": 1,
1884 "elements": [
1885 {
1886 "type": "rich_text_section",
1887 "elements": [
1888 {
1889 "type": "text",
1890 "text": "Text2.1"
1891 }
1892 ]
1893 },
1894 ]
1895 },
1896 {
1897 "type": "rich_text_list",
1898 "style": "ordered",
1899 "indent": 2,
1900 "elements": [
1901 {
1902 "type": "rich_text_section",
1903 "elements": [
1904 {
1905 "type": "text",
1906 "text": "Text2.1.1"
1907 }
1908 ]
1909 },
1910 ]
1911 }
1912 ]
1913 }))];
1914 assert_eq!(
1915 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1916 "1. Text1\n1. Text2\n 1. Text2.1\n 1. Text2.1.1".to_string()
1917 );
1918 }
1919
1920 #[test]
1921 fn test_with_bullet_list() {
1922 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1923 "type": "rich_text",
1924 "elements": [
1925 {
1926 "type": "rich_text_list",
1927 "style": "bullet",
1928 "elements": [
1929 {
1930 "type": "rich_text_section",
1931 "elements": [
1932 {
1933 "type": "text",
1934 "text": "Text1"
1935 }
1936 ]
1937 },
1938 {
1939 "type": "rich_text_section",
1940 "elements": [
1941 {
1942 "type": "text",
1943 "text": "Text2"
1944 }
1945 ]
1946 }
1947 ]
1948 },
1949 ]
1950 }))];
1951 assert_eq!(
1952 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1953 "- Text1\n- Text2".to_string()
1954 );
1955 }
1956 }
1957
1958 #[test]
1959 fn test_with_nested_bullet_list() {
1960 let blocks = vec![SlackBlock::RichText(serde_json::json!({
1961 "type": "rich_text",
1962 "elements": [
1963 {
1964 "type": "rich_text_list",
1965 "style": "bullet",
1966 "elements": [
1967 {
1968 "type": "rich_text_section",
1969 "elements": [
1970 {
1971 "type": "text",
1972 "text": "Text1"
1973 }
1974 ]
1975 },
1976 ]
1977 },
1978 {
1979 "type": "rich_text_list",
1980 "style": "bullet",
1981 "elements": [
1982 {
1983 "type": "rich_text_section",
1984 "elements": [
1985 {
1986 "type": "text",
1987 "text": "Text2"
1988 }
1989 ]
1990 },
1991 ]
1992 },
1993 {
1994 "type": "rich_text_list",
1995 "style": "bullet",
1996 "indent": 1,
1997 "elements": [
1998 {
1999 "type": "rich_text_section",
2000 "elements": [
2001 {
2002 "type": "text",
2003 "text": "Text2.1"
2004 }
2005 ]
2006 },
2007 ]
2008 },
2009 {
2010 "type": "rich_text_list",
2011 "style": "bullet",
2012 "indent": 2,
2013 "elements": [
2014 {
2015 "type": "rich_text_section",
2016 "elements": [
2017 {
2018 "type": "text",
2019 "text": "Text2.1.1"
2020 }
2021 ]
2022 },
2023 ]
2024 }
2025 ]
2026 }))];
2027 assert_eq!(
2028 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2029 "- Text1\n- Text2\n - Text2.1\n - Text2.1.1".to_string()
2030 );
2031 }
2032
2033 mod rich_text_preformatted {
2034 use super::*;
2035
2036 #[test]
2037 fn test_with_text() {
2038 let blocks = vec![SlackBlock::RichText(serde_json::json!({
2039 "type": "rich_text",
2040 "elements": [
2041 {
2042 "type": "rich_text_preformatted",
2043 "elements": [
2044 {
2045 "type": "text",
2046 "text": "Text1"
2047 },
2048 {
2049 "type": "text",
2050 "text": "Text2"
2051 }
2052 ]
2053 },
2054 ]
2055 }))];
2056 assert_eq!(
2057 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2058 "```\nText1Text2\n```".to_string()
2059 );
2060 }
2061
2062 #[test]
2063 fn test_with_text_and_newline() {
2064 let blocks = vec![SlackBlock::RichText(serde_json::json!({
2065 "type": "rich_text",
2066 "elements": [
2067 {
2068 "type": "rich_text_preformatted",
2069 "elements": [
2070 {
2071 "type": "text",
2072 "text": "test:\n sub: value"
2073 }
2074 ]
2075 }
2076 ]
2077 }))];
2078 assert_eq!(
2079 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2080 "```\ntest:\n sub: value\n```".to_string()
2081 );
2082 }
2083
2084 #[test]
2085 fn test_with_preformatted_text_followed_by_text() {
2086 let blocks = vec![SlackBlock::RichText(serde_json::json!({
2087 "type": "rich_text",
2088 "elements": [
2089 {
2090 "type": "rich_text_preformatted",
2091 "elements": [
2092 {
2093 "type": "text",
2094 "text": "Text1"
2095 }
2096 ]
2097 },
2098 {
2099 "type": "rich_text_section",
2100 "elements": [
2101 {
2102 "type": "text",
2103 "text": "Text2"
2104 }
2105 ]
2106 },
2107 ]
2108 }))];
2109 assert_eq!(
2110 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2111 "```\nText1\n```\nText2".to_string()
2112 );
2113 }
2114 }
2115
2116 mod rich_text_quote {
2117 use super::*;
2118
2119 #[test]
2120 fn test_with_text() {
2121 let blocks = vec![SlackBlock::RichText(serde_json::json!({
2122 "type": "rich_text",
2123 "elements": [
2124 {
2125 "type": "rich_text_quote",
2126 "elements": [
2127 {
2128 "type": "text",
2129 "text": "Text1"
2130 },
2131 {
2132 "type": "text",
2133 "text": "Text2"
2134 }
2135 ]
2136 },
2137 ]
2138 }))];
2139 assert_eq!(
2140 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2141 "> Text1Text2".to_string()
2142 );
2143 }
2144
2145 #[test]
2146 fn test_with_quoted_text_followed_by_quoted_text() {
2147 let blocks = vec![SlackBlock::RichText(serde_json::json!({
2148 "type": "rich_text",
2149 "elements": [
2150 {
2151 "type": "rich_text_quote",
2152 "elements": [
2153 {
2154 "type": "text",
2155 "text": "Text1"
2156 },
2157 ]
2158 },
2159 {
2160 "type": "rich_text_quote",
2161 "elements": [
2162 {
2163 "type": "text",
2164 "text": "Text2"
2165 }
2166 ]
2167 },
2168 ]
2169 }))];
2170 assert_eq!(
2171 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2172 "> Text1\n> Text2".to_string()
2173 );
2174 }
2175
2176 #[test]
2177 fn test_with_quoted_text_followed_by_non_quoted_text() {
2178 let blocks = vec![SlackBlock::RichText(serde_json::json!({
2179 "type": "rich_text",
2180 "elements": [
2181 {
2182 "type": "rich_text_quote",
2183 "elements": [
2184 {
2185 "text": "Text1",
2186 "type": "text"
2187 }
2188 ]
2189 },
2190 {
2191 "type": "rich_text_section",
2192 "elements": [
2193 {
2194 "text": "Text2",
2195 "type": "text"
2196 },
2197 ]
2198 }
2199 ]
2200 }))];
2201
2202 assert_eq!(
2203 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2204 "> Text1\n\nText2".to_string()
2205 );
2206 }
2207 }
2208 }
2209}