Skip to main content

slack_blocks_render/
markdown.rs

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
14/// TODO: document this function
15///
16pub 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    use crate::test_utils::rich_text_block;
496
497    #[test]
498    fn test_empty_input() {
499        assert_eq!(
500            render_blocks_as_markdown(vec![], SlackReferences::default(), None),
501            "".to_string()
502        );
503    }
504
505    #[test]
506    fn test_with_image() {
507        let blocks = vec![
508            SlackBlock::Image(SlackImageBlock::new(
509                SlackImageUrlOrFile::ImageUrl {
510                    image_url: Url::parse("https://example.com/image.png").unwrap(),
511                },
512                "Image".to_string(),
513            )),
514            SlackBlock::Image(SlackImageBlock::new(
515                SlackImageUrlOrFile::ImageUrl {
516                    image_url: Url::parse("https://example.com/image2.png").unwrap(),
517                },
518                "Image2".to_string(),
519            )),
520        ];
521        assert_eq!(
522            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
523            "![Image](https://example.com/image.png)\n![Image2](https://example.com/image2.png)"
524                .to_string()
525        );
526    }
527
528    #[test]
529    fn test_with_divider() {
530        let blocks = vec![
531            SlackBlock::Divider(SlackDividerBlock::new()),
532            SlackBlock::Divider(SlackDividerBlock::new()),
533        ];
534        assert_eq!(
535            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
536            "---\n\n---\n".to_string()
537        );
538    }
539
540    #[test]
541    fn test_with_input() {
542        // No rendering
543        let blocks = vec![SlackBlock::Input(SlackInputBlock::new(
544            "label".into(),
545            SlackInputBlockElement::PlainTextInput(SlackBlockPlainTextInputElement::new(
546                "id".into(),
547            )),
548        ))];
549        assert_eq!(
550            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
551            "".to_string()
552        );
553    }
554
555    #[test]
556    fn test_with_action() {
557        // No rendering
558        let blocks = vec![SlackBlock::Actions(SlackActionsBlock::new(vec![]))];
559        assert_eq!(
560            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
561            "".to_string()
562        );
563    }
564
565    #[test]
566    fn test_with_file() {
567        // No rendering
568        let blocks = vec![SlackBlock::File(SlackFileBlock::new("external_id".into()))];
569        assert_eq!(
570            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
571            "".to_string()
572        );
573    }
574
575    #[test]
576    fn test_with_video() {
577        let blocks = vec![SlackBlock::Video(
578            SlackVideoBlock::new(
579                "alt text".into(),
580                "Video title".into(),
581                "https://example.com/thumbnail.jpg".parse().unwrap(),
582                "https://example.com/video_embed.avi".parse().unwrap(),
583            )
584            .with_description("Video description".into())
585            .with_title_url("https://example.com/video".parse().unwrap()),
586        )];
587        assert_eq!(
588            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
589            r#"*[Video title](https://example.com/video)*
590
591Video description
592
593![alt text](https://example.com/thumbnail.jpg)"#
594                .to_string()
595        );
596    }
597
598    #[test]
599    fn test_with_video_minimal() {
600        let blocks = vec![SlackBlock::Video(SlackVideoBlock::new(
601            "alt text".into(),
602            "Video title".into(),
603            "https://example.com/thumbnail.jpg".parse().unwrap(),
604            "https://example.com/video_embed.avi".parse().unwrap(),
605        ))];
606        assert_eq!(
607            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
608            r#"*Video title*
609
610![alt text](https://example.com/thumbnail.jpg)"#
611                .to_string()
612        );
613    }
614
615    #[test]
616    fn test_with_event() {
617        // No rendering
618        let blocks = vec![SlackBlock::Event(serde_json::json!({}))];
619        assert_eq!(
620            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
621            "".to_string()
622        );
623    }
624
625    #[test]
626    fn test_header() {
627        let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
628        assert_eq!(
629            render_blocks_as_markdown(blocks, SlackReferences::default(), None),
630            "## Text".to_string()
631        );
632    }
633
634    mod section {
635        use super::*;
636
637        #[test]
638        fn test_with_plain_text() {
639            let blocks = vec![
640                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
641                    SlackBlockPlainText::new("Text".to_string()),
642                ))),
643                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
644                    SlackBlockPlainText::new("Text2".to_string()),
645                ))),
646            ];
647            assert_eq!(
648                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
649                "Text\nText2".to_string()
650            );
651        }
652
653        #[test]
654        fn test_with_markdown() {
655            let blocks = vec![
656                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
657                    SlackBlockMarkDownText::new("Text".to_string()),
658                ))),
659                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
660                    SlackBlockMarkDownText::new("Text2".to_string()),
661                ))),
662            ];
663            assert_eq!(
664                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
665                "Text\nText2".to_string()
666            );
667        }
668
669        #[test]
670        fn test_with_fields() {
671            let blocks = vec![
672                SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
673                    SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
674                    SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
675                ])),
676                SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
677                    SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
678                    SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
679                ])),
680            ];
681            assert_eq!(
682                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
683                "Text11Text12\nText21Text22".to_string()
684            );
685        }
686
687        #[test]
688        fn test_with_fields_and_text() {
689            let blocks = vec![
690                SlackBlock::Section(
691                    SlackSectionBlock::new()
692                        .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
693                            "Text1".to_string(),
694                        )))
695                        .with_fields(vec![
696                            SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
697                            SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
698                        ]),
699                ),
700                SlackBlock::Section(
701                    SlackSectionBlock::new()
702                        .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
703                            "Text2".to_string(),
704                        )))
705                        .with_fields(vec![
706                            SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
707                            SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
708                        ]),
709                ),
710            ];
711            assert_eq!(
712                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
713                "Text1Text11Text12\nText2Text21Text22".to_string()
714            );
715        }
716    }
717
718    mod context {
719        use super::*;
720
721        #[test]
722        fn test_with_image() {
723            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
724                SlackContextBlockElement::Image(SlackBlockImageElement::new(
725                    SlackImageUrlOrFile::ImageUrl {
726                        image_url: Url::parse("https://example.com/image.png").unwrap(),
727                    },
728                    "Image".to_string(),
729                )),
730                SlackContextBlockElement::Image(SlackBlockImageElement::new(
731                    SlackImageUrlOrFile::ImageUrl {
732                        image_url: Url::parse("https://example.com/image2.png").unwrap(),
733                    },
734                    "Image2".to_string(),
735                )),
736            ]))];
737            assert_eq!(
738                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
739                "![Image](https://example.com/image.png)![Image2](https://example.com/image2.png)"
740                    .to_string()
741            );
742        }
743
744        #[test]
745        fn test_with_plain_text() {
746            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
747                SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
748                SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
749            ]))];
750            assert_eq!(
751                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
752                "TextText2".to_string()
753            );
754        }
755
756        #[test]
757        fn test_with_markdown() {
758            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
759                SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
760                SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
761                    "Text2".to_string(),
762                )),
763            ]))];
764            assert_eq!(
765                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
766                "TextText2".to_string()
767            );
768        }
769    }
770
771    mod rich_text {
772        use super::*;
773
774        #[test]
775        fn test_with_empty_json() {
776            let blocks = vec![
777                rich_text_block(serde_json::json!({})),
778                rich_text_block(serde_json::json!({})),
779            ];
780            assert_eq!(
781                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
782                "\n".to_string()
783            );
784        }
785
786        mod rich_text_section {
787            use super::*;
788
789            mod text_element {
790                use super::*;
791
792                #[test]
793                fn test_with_text() {
794                    let blocks = vec![
795                        rich_text_block(serde_json::json!({
796                            "type": "rich_text",
797                            "elements": [
798                                {
799                                    "type": "rich_text_section",
800                                    "elements": [
801                                        {
802                                            "type": "text",
803                                            "text": "Text111"
804                                        },
805                                        {
806                                            "type": "text",
807                                            "text": "Text112"
808                                        }
809                                    ]
810                                },
811                                {
812                                    "type": "rich_text_section",
813                                    "elements": [
814                                        {
815                                            "type": "text",
816                                            "text": "Text121"
817                                        },
818                                        {
819                                            "type": "text",
820                                            "text": "Text122"
821                                        }
822                                    ]
823                                }
824                            ]
825                        })),
826                        rich_text_block(serde_json::json!({
827                            "type": "rich_text",
828                            "elements": [
829                                {
830                                    "type": "rich_text_section",
831                                    "elements": [
832                                        {
833                                            "type": "text",
834                                            "text": "Text211"
835                                        },
836                                        {
837                                            "type": "text",
838                                            "text": "Text212"
839                                        }
840                                    ]
841                                },
842                                {
843                                    "type": "rich_text_section",
844                                    "elements": [
845                                        {
846                                            "type": "text",
847                                            "text": "Text221"
848                                        },
849                                        {
850                                            "type": "text",
851                                            "text": "Text222"
852                                        }
853                                    ]
854                                }
855                            ]
856                        })),
857                    ];
858                    assert_eq!(
859                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
860                        "Text111Text112\nText121Text122\nText211Text212\nText221Text222"
861                            .to_string()
862                    );
863                }
864
865                #[test]
866                fn test_with_text_with_newline() {
867                    let blocks = vec![rich_text_block(serde_json::json!({
868                        "type": "rich_text",
869                        "elements": [
870                            {
871                                "type": "rich_text_section",
872                                "elements": [
873                                    {
874                                        "type": "text",
875                                        "text": "Text111\nText112\n"
876                                    },
877                                    {
878                                        "type": "text",
879                                        "text": "Text211\nText212\n"
880                                    }
881                                ]
882                            }
883                        ]
884                    }))];
885                    assert_eq!(
886                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
887                        "Text111\\\nText112\\\nText211\\\nText212".to_string()
888                    );
889                }
890
891                #[test]
892                fn test_with_text_with_newline_char() {
893                    let blocks = vec![rich_text_block(serde_json::json!({
894                        "type": "rich_text",
895                        "elements": [
896                            {
897                                "type": "rich_text_section",
898                                "elements": [
899                                    {
900                                        "type": "text",
901                                        "text": "Text111\\nText112"
902                                    }
903                                ]
904                            }
905                        ]
906                    }))];
907                    assert_eq!(
908                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
909                        "Text111\\nText112".to_string()
910                    );
911                }
912
913                #[test]
914                fn test_with_text_with_only_newline() {
915                    let blocks = vec![rich_text_block(serde_json::json!({
916                        "type": "rich_text",
917                        "elements": [
918                            {
919                                "type": "rich_text_section",
920                                "elements": [
921                                    {
922                                        "type": "text",
923                                        "text": "\n"
924                                    }
925                                ]
926                            }
927                        ]
928                    }))];
929                    assert_eq!(
930                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
931                        "".to_string()
932                    );
933                }
934
935                #[test]
936                fn test_with_bold_text() {
937                    let blocks = vec![rich_text_block(serde_json::json!({
938                        "type": "rich_text",
939                        "elements": [
940                            {
941                                "type": "rich_text_section",
942                                "elements": [
943                                    {
944                                        "type": "text",
945                                        "text": "Text",
946                                        "style": {
947                                            "bold": true
948                                        }
949                                    }
950                                ]
951                            }
952                        ]
953                    }))];
954                    assert_eq!(
955                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
956                        "*Text*".to_string()
957                    );
958                }
959
960                #[test]
961                fn test_with_consecutive_bold_text() {
962                    let blocks = vec![rich_text_block(serde_json::json!({
963                        "type": "rich_text",
964                        "elements": [
965                            {
966                                "type": "rich_text_section",
967                                "elements": [
968                                    {
969                                        "type": "text",
970                                        "text": "Hello",
971                                        "style": {
972                                            "bold": true
973                                        }
974                                    },
975                                    {
976                                        "type": "text",
977                                        "text": " ",
978                                        "style": {
979                                            "bold": true
980                                        }
981                                    },
982                                    {
983                                        "type": "text",
984                                        "text": "World!",
985                                        "style": {
986                                            "bold": true
987                                        }
988                                    }
989                                ]
990                            }
991                        ]
992                    }))];
993                    assert_eq!(
994                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
995                        "*Hello World!*".to_string()
996                    );
997                }
998
999                #[test]
1000                fn test_with_italic_text() {
1001                    let blocks = vec![rich_text_block(serde_json::json!({
1002                        "type": "rich_text",
1003                        "elements": [
1004                            {
1005                                "type": "rich_text_section",
1006                                "elements": [
1007                                    {
1008                                        "type": "text",
1009                                        "text": "Text",
1010                                        "style": {
1011                                            "italic": true
1012                                        }
1013                                    }
1014                                ]
1015                            }
1016                        ]
1017                    }))];
1018                    assert_eq!(
1019                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1020                        "_Text_".to_string()
1021                    );
1022                }
1023
1024                #[test]
1025                fn test_with_consecutive_italic_text() {
1026                    let blocks = vec![rich_text_block(serde_json::json!({
1027                        "type": "rich_text",
1028                        "elements": [
1029                            {
1030                                "type": "rich_text_section",
1031                                "elements": [
1032                                    {
1033                                        "type": "text",
1034                                        "text": "Hello",
1035                                        "style": {
1036                                            "italic": true
1037                                        }
1038                                    },
1039                                    {
1040                                        "type": "text",
1041                                        "text": " ",
1042                                        "style": {
1043                                            "italic": true
1044                                        }
1045                                    },
1046                                    {
1047                                        "type": "text",
1048                                        "text": "World!",
1049                                        "style": {
1050                                            "italic": true
1051                                        }
1052                                    }
1053                                ]
1054                            }
1055                        ]
1056                    }))];
1057                    assert_eq!(
1058                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1059                        "_Hello World!_".to_string()
1060                    );
1061                }
1062
1063                #[test]
1064                fn test_with_strike_text() {
1065                    let blocks = vec![rich_text_block(serde_json::json!({
1066                        "type": "rich_text",
1067                        "elements": [
1068                            {
1069                                "type": "rich_text_section",
1070                                "elements": [
1071                                    {
1072                                        "type": "text",
1073                                        "text": "Text",
1074                                        "style": {
1075                                            "strike": true
1076                                        }
1077                                    }
1078                                ]
1079                            }
1080                        ]
1081                    }))];
1082                    assert_eq!(
1083                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1084                        "~Text~".to_string()
1085                    );
1086                }
1087
1088                #[test]
1089                fn test_with_consecutive_strike_text() {
1090                    let blocks = vec![rich_text_block(serde_json::json!({
1091                        "type": "rich_text",
1092                        "elements": [
1093                            {
1094                                "type": "rich_text_section",
1095                                "elements": [
1096                                    {
1097                                        "type": "text",
1098                                        "text": "Hello",
1099                                        "style": {
1100                                            "strike": true
1101                                        }
1102                                    },
1103                                    {
1104                                        "type": "text",
1105                                        "text": " ",
1106                                        "style": {
1107                                            "strike": true
1108                                        }
1109                                    },
1110                                    {
1111                                        "type": "text",
1112                                        "text": "World!",
1113                                        "style": {
1114                                            "strike": true
1115                                        }
1116                                    }
1117                                ]
1118                            }
1119                        ]
1120                    }))];
1121                    assert_eq!(
1122                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1123                        "~Hello World!~".to_string()
1124                    );
1125                }
1126
1127                #[test]
1128                fn test_with_code_text() {
1129                    let blocks = vec![rich_text_block(serde_json::json!({
1130                        "type": "rich_text",
1131                        "elements": [
1132                            {
1133                                "type": "rich_text_section",
1134                                "elements": [
1135                                    {
1136                                        "type": "text",
1137                                        "text": "Text",
1138                                        "style": {
1139                                            "code": true
1140                                        }
1141                                    }
1142                                ]
1143                            }
1144                        ]
1145                    }))];
1146                    assert_eq!(
1147                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1148                        "`Text`".to_string()
1149                    );
1150                }
1151
1152                #[test]
1153                fn test_with_consecutive_code_text() {
1154                    let blocks = vec![rich_text_block(serde_json::json!({
1155                        "type": "rich_text",
1156                        "elements": [
1157                            {
1158                                "type": "rich_text_section",
1159                                "elements": [
1160                                    {
1161                                        "type": "text",
1162                                        "text": "Text1",
1163                                        "style": {
1164                                            "code": true
1165                                        }
1166                                    },
1167                                    {
1168                                        "type": "text",
1169                                        "text": "Text2",
1170                                        "style": {
1171                                            "code": true
1172                                        }
1173                                    }
1174                                ]
1175                            }
1176                        ]
1177                    }))];
1178                    assert_eq!(
1179                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1180                        "`Text1Text2`".to_string()
1181                    );
1182                }
1183
1184                #[test]
1185                fn test_with_styled_text() {
1186                    let blocks = vec![rich_text_block(serde_json::json!({
1187                        "type": "rich_text",
1188                        "elements": [
1189                            {
1190                                "type": "rich_text_section",
1191                                "elements": [
1192                                    {
1193                                        "type": "text",
1194                                        "text": "Text",
1195                                        "style": {
1196                                            "bold": true,
1197                                            "italic": true,
1198                                            "strike": true
1199                                        }
1200                                    }
1201                                ]
1202                            }
1203                        ]
1204                    }))];
1205                    assert_eq!(
1206                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1207                        "~_*Text*_~".to_string()
1208                    );
1209                }
1210
1211                #[test]
1212                fn test_with_consecutive_styled_text() {
1213                    let blocks = vec![rich_text_block(serde_json::json!({
1214                        "type": "rich_text",
1215                        "elements": [
1216                            {
1217                                "type": "rich_text_section",
1218                                "elements": [
1219                                    {
1220                                        "name": "chart_with_upwards_trend",
1221                                        "type": "emoji",
1222                                        "style": {
1223                                            "bold": true,
1224                                            "italic": true,
1225                                            "strike": true
1226                                        },
1227                                    },
1228                                    {
1229                                        "type": "text",
1230                                        "text": "Hello",
1231                                        "style": {
1232                                            "bold": true,
1233                                            "italic": true,
1234                                            "strike": true
1235                                        }
1236                                    },
1237                                    {
1238                                        "type": "text",
1239                                        "text": " ",
1240                                        "style": {
1241                                            "bold": true,
1242                                            "italic": true,
1243                                            "strike": true
1244                                        }
1245                                    },
1246                                    {
1247                                        "type": "text",
1248                                        "text": "World!",
1249                                        "style": {
1250                                            "bold": true,
1251                                            "italic": true,
1252                                            "strike": true
1253                                        }
1254                                    }
1255                                ]
1256                            }
1257                        ]
1258                    }))];
1259                    assert_eq!(
1260                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1261                        "📈~_*Hello World!*_~".to_string()
1262                    );
1263                }
1264            }
1265
1266            mod channel_element {
1267                use super::*;
1268
1269                #[test]
1270                fn test_with_channel_id() {
1271                    let blocks = vec![rich_text_block(serde_json::json!({
1272                        "type": "rich_text",
1273                        "elements": [
1274                            {
1275                                "type": "rich_text_section",
1276                                "elements": [
1277                                    {
1278                                        "type": "channel",
1279                                        "channel_id": "C0123456"
1280                                    }
1281                                ]
1282                            }
1283                        ]
1284                    }))];
1285                    assert_eq!(
1286                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1287                        "#C0123456".to_string()
1288                    );
1289                }
1290
1291                #[test]
1292                fn test_with_channel_id_and_reference() {
1293                    let blocks = vec![rich_text_block(serde_json::json!({
1294                        "type": "rich_text",
1295                        "elements": [
1296                            {
1297                                "type": "rich_text_section",
1298                                "elements": [
1299                                    {
1300                                        "type": "channel",
1301                                        "channel_id": "C0123456"
1302                                    }
1303                                ]
1304                            }
1305                        ]
1306                    }))];
1307                    assert_eq!(
1308                        render_blocks_as_markdown(
1309                            blocks,
1310                            SlackReferences {
1311                                channels: HashMap::from([(
1312                                    SlackChannelId("C0123456".to_string()),
1313                                    Some("general".to_string())
1314                                )]),
1315                                ..SlackReferences::default()
1316                            },
1317                            None
1318                        ),
1319                        "#general".to_string()
1320                    );
1321                }
1322            }
1323
1324            mod user_element {
1325                use super::*;
1326
1327                #[test]
1328                fn test_with_user_id() {
1329                    let blocks = vec![rich_text_block(serde_json::json!({
1330                        "type": "rich_text",
1331                        "elements": [
1332                            {
1333                                "type": "rich_text_section",
1334                                "elements": [
1335                                    {
1336                                        "type": "user",
1337                                        "user_id": "user1"
1338                                    }
1339                                ]
1340                            }
1341                        ]
1342                    }))];
1343                    assert_eq!(
1344                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1345                        "@user1".to_string()
1346                    );
1347                }
1348
1349                #[test]
1350                fn test_with_user_id_and_custom_delimiter() {
1351                    let blocks = vec![rich_text_block(serde_json::json!({
1352                        "type": "rich_text",
1353                        "elements": [
1354                            {
1355                                "type": "rich_text_section",
1356                                "elements": [
1357                                    {
1358                                        "type": "user",
1359                                        "user_id": "user1"
1360                                    }
1361                                ]
1362                            }
1363                        ]
1364                    }))];
1365                    assert_eq!(
1366                        render_blocks_as_markdown(
1367                            blocks,
1368                            SlackReferences::default(),
1369                            Some("@".to_string())
1370                        ),
1371                        "@@user1@".to_string()
1372                    );
1373                }
1374
1375                #[test]
1376                fn test_with_user_id_and_reference() {
1377                    let blocks = vec![rich_text_block(serde_json::json!({
1378                        "type": "rich_text",
1379                        "elements": [
1380                            {
1381                                "type": "rich_text_section",
1382                                "elements": [
1383                                    {
1384                                        "type": "user",
1385                                        "user_id": "user1"
1386                                    }
1387                                ]
1388                            }
1389                        ]
1390                    }))];
1391                    assert_eq!(
1392                        render_blocks_as_markdown(
1393                            blocks,
1394                            SlackReferences {
1395                                users: HashMap::from([(
1396                                    SlackUserId("user1".to_string()),
1397                                    Some("John Doe".to_string())
1398                                )]),
1399                                ..SlackReferences::default()
1400                            },
1401                            None
1402                        ),
1403                        "@John Doe".to_string()
1404                    );
1405                }
1406
1407                #[test]
1408                fn test_with_user_id_and_reference_and_custom_delimiter() {
1409                    let blocks = vec![rich_text_block(serde_json::json!({
1410                        "type": "rich_text",
1411                        "elements": [
1412                            {
1413                                "type": "rich_text_section",
1414                                "elements": [
1415                                    {
1416                                        "type": "user",
1417                                        "user_id": "user1"
1418                                    }
1419                                ]
1420                            }
1421                        ]
1422                    }))];
1423                    assert_eq!(
1424                        render_blocks_as_markdown(
1425                            blocks,
1426                            SlackReferences {
1427                                users: HashMap::from([(
1428                                    SlackUserId("user1".to_string()),
1429                                    Some("John Doe".to_string())
1430                                )]),
1431                                ..SlackReferences::default()
1432                            },
1433                            Some("@".to_string())
1434                        ),
1435                        "@@John Doe@".to_string()
1436                    );
1437                }
1438            }
1439
1440            mod usergroup_element {
1441                use super::*;
1442
1443                #[test]
1444                fn test_with_usergroup_id() {
1445                    let blocks = vec![rich_text_block(serde_json::json!({
1446                        "type": "rich_text",
1447                        "elements": [
1448                            {
1449                                "type": "rich_text_section",
1450                                "elements": [
1451                                    {
1452                                        "type": "usergroup",
1453                                        "usergroup_id": "group1"
1454                                    }
1455                                ]
1456                            }
1457                        ]
1458                    }))];
1459                    assert_eq!(
1460                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1461                        "@group1".to_string()
1462                    );
1463                }
1464
1465                #[test]
1466                fn test_with_usergroup_id_and_custom_delimiter() {
1467                    let blocks = vec![rich_text_block(serde_json::json!({
1468                        "type": "rich_text",
1469                        "elements": [
1470                            {
1471                                "type": "rich_text_section",
1472                                "elements": [
1473                                    {
1474                                        "type": "usergroup",
1475                                        "usergroup_id": "group1"
1476                                    }
1477                                ]
1478                            }
1479                        ]
1480                    }))];
1481                    assert_eq!(
1482                        render_blocks_as_markdown(
1483                            blocks,
1484                            SlackReferences::default(),
1485                            Some("@".to_string())
1486                        ),
1487                        "@@group1@".to_string()
1488                    );
1489                }
1490
1491                #[test]
1492                fn test_with_usergroup_id_and_reference() {
1493                    let blocks = vec![rich_text_block(serde_json::json!({
1494                        "type": "rich_text",
1495                        "elements": [
1496                            {
1497                                "type": "rich_text_section",
1498                                "elements": [
1499                                    {
1500                                        "type": "usergroup",
1501                                        "usergroup_id": "group1"
1502                                    }
1503                                ]
1504                            }
1505                        ]
1506                    }))];
1507                    assert_eq!(
1508                        render_blocks_as_markdown(
1509                            blocks,
1510                            SlackReferences {
1511                                usergroups: HashMap::from([(
1512                                    SlackUserGroupId("group1".to_string()),
1513                                    Some("Admins".to_string())
1514                                )]),
1515                                ..SlackReferences::default()
1516                            },
1517                            None
1518                        ),
1519                        "@Admins".to_string()
1520                    );
1521                }
1522
1523                #[test]
1524                fn test_with_usergroup_id_and_reference_and_custom_delimiter() {
1525                    let blocks = vec![rich_text_block(serde_json::json!({
1526                        "type": "rich_text",
1527                        "elements": [
1528                            {
1529                                "type": "rich_text_section",
1530                                "elements": [
1531                                    {
1532                                        "type": "usergroup",
1533                                        "usergroup_id": "group1"
1534                                    }
1535                                ]
1536                            }
1537                        ]
1538                    }))];
1539                    assert_eq!(
1540                        render_blocks_as_markdown(
1541                            blocks,
1542                            SlackReferences {
1543                                usergroups: HashMap::from([(
1544                                    SlackUserGroupId("group1".to_string()),
1545                                    Some("Admins".to_string())
1546                                )]),
1547                                ..SlackReferences::default()
1548                            },
1549                            Some("@".to_string())
1550                        ),
1551                        "@@Admins@".to_string()
1552                    );
1553                }
1554            }
1555
1556            mod link_element {
1557                use super::*;
1558
1559                #[test]
1560                fn test_with_url() {
1561                    let blocks = vec![rich_text_block(serde_json::json!({
1562                        "type": "rich_text",
1563                        "elements": [
1564                            {
1565                                "type": "rich_text_section",
1566                                "elements": [
1567                                    {
1568                                        "type": "link",
1569                                        "text": "example",
1570                                        "url": "https://example.com"
1571                                    }
1572                                ]
1573                            }
1574                        ]
1575                    }))];
1576                    assert_eq!(
1577                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1578                        "[example](https://example.com/)".to_string()
1579                    );
1580                }
1581
1582                #[test]
1583                fn test_with_url_without_text() {
1584                    let blocks = vec![rich_text_block(serde_json::json!({
1585                        "type": "rich_text",
1586                        "elements": [
1587                            {
1588                                "type": "rich_text_section",
1589                                "elements": [
1590                                    {
1591                                        "type": "link",
1592                                        "url": "https://example.com"
1593                                    }
1594                                ]
1595                            }
1596                        ]
1597                    }))];
1598                    assert_eq!(
1599                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1600                        "[https://example.com/](https://example.com/)".to_string()
1601                    );
1602                }
1603            }
1604
1605            mod emoji_element {
1606                use super::*;
1607
1608                #[test]
1609                fn test_with_emoji() {
1610                    let blocks = vec![rich_text_block(serde_json::json!({
1611                        "type": "rich_text",
1612                        "elements": [
1613                            {
1614                                "type": "rich_text_section",
1615                                "elements": [
1616                                    {
1617                                        "type": "emoji",
1618                                        "name": "wave"
1619                                    }
1620                                ]
1621                            }
1622                        ]
1623                    }))];
1624                    assert_eq!(
1625                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1626                        "👋".to_string()
1627                    );
1628                }
1629
1630                #[test]
1631                fn test_with_emoji_with_skin_tone() {
1632                    let blocks = vec![rich_text_block(serde_json::json!({
1633                        "type": "rich_text",
1634                        "elements": [
1635                            {
1636                                "type": "rich_text_section",
1637                                "elements": [
1638                                    {
1639                                        "type": "emoji",
1640                                        "name": "wave::skin-tone-2"
1641                                    }
1642                                ]
1643                            }
1644                        ]
1645                    }))];
1646                    assert_eq!(
1647                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1648                        "👋🏻".to_string()
1649                    );
1650                }
1651
1652                #[test]
1653                fn test_with_emoji_with_unknown_skin_tone() {
1654                    let blocks = vec![rich_text_block(serde_json::json!({
1655                        "type": "rich_text",
1656                        "elements": [
1657                            {
1658                                "type": "rich_text_section",
1659                                "elements": [
1660                                    {
1661                                        "type": "emoji",
1662                                        "name": "wave::skin-tone-42"
1663                                    }
1664                                ]
1665                            }
1666                        ]
1667                    }))];
1668                    assert_eq!(
1669                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1670                        "👋".to_string()
1671                    );
1672                }
1673
1674                #[test]
1675                fn test_with_unknown_emoji() {
1676                    let blocks = vec![rich_text_block(serde_json::json!({
1677                        "type": "rich_text",
1678                        "elements": [
1679                            {
1680                                "type": "rich_text_section",
1681                                "elements": [
1682                                    {
1683                                        "type": "emoji",
1684                                        "name": "unknown1"
1685                                    }
1686                                ]
1687                            }
1688                        ]
1689                    }))];
1690                    assert_eq!(
1691                        render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1692                        ":unknown1:".to_string()
1693                    );
1694                }
1695
1696                #[test]
1697                fn test_with_unknown_emoji_with_slack_reference_alias() {
1698                    let blocks = vec![rich_text_block(serde_json::json!({
1699                        "type": "rich_text",
1700                        "elements": [
1701                            {
1702                                "type": "rich_text_section",
1703                                "elements": [
1704                                    {
1705                                        "type": "emoji",
1706                                        "name": "unknown1"
1707                                    }
1708                                ]
1709                            }
1710                        ]
1711                    }))];
1712                    assert_eq!(
1713                        render_blocks_as_markdown(
1714                            blocks,
1715                            SlackReferences {
1716                                emojis: HashMap::from([(
1717                                    SlackEmojiName("unknown1".to_string()),
1718                                    Some(SlackEmojiRef::Alias(SlackEmojiName("wave".to_string())))
1719                                )]),
1720                                ..SlackReferences::default()
1721                            },
1722                            None
1723                        ),
1724                        "👋".to_string()
1725                    );
1726                }
1727
1728                #[test]
1729                fn test_with_unknown_emoji_with_slack_reference_alias_to_custom_emoji() {
1730                    let blocks = vec![rich_text_block(serde_json::json!({
1731                        "type": "rich_text",
1732                        "elements": [
1733                            {
1734                                "type": "rich_text_section",
1735                                "elements": [
1736                                    {
1737                                        "type": "emoji",
1738                                        "name": "unknown1"
1739                                    }
1740                                ]
1741                            }
1742                        ]
1743                    }))];
1744                    assert_eq!(
1745                        render_blocks_as_markdown(
1746                            blocks,
1747                            SlackReferences {
1748                                emojis: HashMap::from([
1749                                    (
1750                                        SlackEmojiName("unknown1".to_string()),
1751                                        Some(SlackEmojiRef::Alias(SlackEmojiName(
1752                                            "unknown2".to_string()
1753                                        )))
1754                                    ),
1755                                    (
1756                                        SlackEmojiName("unknown2".to_string()),
1757                                        Some(SlackEmojiRef::Url(
1758                                            "https://emoji.com/unknown2.png".parse().unwrap()
1759                                        ))
1760                                    )
1761                                ]),
1762                                ..SlackReferences::default()
1763                            },
1764                            None
1765                        ),
1766                        "![:unknown2:](https://emoji.com/unknown2.png)".to_string()
1767                    );
1768                }
1769
1770                #[test]
1771                fn test_with_unknown_emoji_with_slack_reference_image_url() {
1772                    let blocks = vec![rich_text_block(serde_json::json!({
1773                        "type": "rich_text",
1774                        "elements": [
1775                            {
1776                                "type": "rich_text_section",
1777                                "elements": [
1778                                    {
1779                                        "type": "emoji",
1780                                        "name": "unknown1"
1781                                    }
1782                                ]
1783                            }
1784                        ]
1785                    }))];
1786                    assert_eq!(
1787                        render_blocks_as_markdown(
1788                            blocks,
1789                            SlackReferences {
1790                                emojis: HashMap::from([(
1791                                    SlackEmojiName("unknown1".to_string()),
1792                                    Some(SlackEmojiRef::Url(
1793                                        "https://emoji.com/unknown1.png".parse().unwrap()
1794                                    ))
1795                                )]),
1796                                ..SlackReferences::default()
1797                            },
1798                            None
1799                        ),
1800                        "![:unknown1:](https://emoji.com/unknown1.png)".to_string()
1801                    );
1802                }
1803            }
1804        }
1805
1806        mod rich_text_list {
1807            use super::*;
1808
1809            #[test]
1810            fn test_with_ordered_list() {
1811                let blocks = vec![rich_text_block(serde_json::json!({
1812                    "type": "rich_text",
1813                    "elements": [
1814                        {
1815                            "type": "rich_text_list",
1816                            "style": "ordered",
1817                            "elements": [
1818                                {
1819                                    "type": "rich_text_section",
1820                                    "elements": [
1821                                        {
1822                                            "type": "text",
1823                                            "text": "Text1"
1824                                        }
1825                                    ]
1826                                },
1827                                {
1828                                    "type": "rich_text_section",
1829                                    "elements": [
1830                                        {
1831                                            "type": "text",
1832                                            "text": "Text2"
1833                                        }
1834                                    ]
1835                                }
1836                            ]
1837                         },
1838                    ]
1839                }))];
1840                assert_eq!(
1841                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1842                    "1. Text1\n1. Text2".to_string()
1843                );
1844            }
1845
1846            #[test]
1847            fn test_with_nested_ordered_list() {
1848                let blocks = vec![rich_text_block(serde_json::json!({
1849                    "type": "rich_text",
1850                    "elements": [
1851                        {
1852                            "type": "rich_text_list",
1853                            "style": "ordered",
1854                            "elements": [
1855                                {
1856                                    "type": "rich_text_section",
1857                                    "elements": [
1858                                        {
1859                                            "type": "text",
1860                                            "text": "Text1"
1861                                        }
1862                                    ]
1863                                },
1864                            ]
1865                        },
1866                        {
1867                            "type": "rich_text_list",
1868                            "style": "ordered",
1869                            "elements": [
1870                                {
1871                                    "type": "rich_text_section",
1872                                    "elements": [
1873                                        {
1874                                            "type": "text",
1875                                            "text": "Text2"
1876                                        }
1877                                    ]
1878                                },
1879                            ]
1880                        },
1881                        {
1882                            "type": "rich_text_list",
1883                            "style": "ordered",
1884                            "indent": 1,
1885                            "elements": [
1886                                {
1887                                    "type": "rich_text_section",
1888                                    "elements": [
1889                                        {
1890                                            "type": "text",
1891                                            "text": "Text2.1"
1892                                        }
1893                                    ]
1894                                },
1895                            ]
1896                        },
1897                        {
1898                            "type": "rich_text_list",
1899                            "style": "ordered",
1900                            "indent": 2,
1901                            "elements": [
1902                                {
1903                                    "type": "rich_text_section",
1904                                    "elements": [
1905                                        {
1906                                            "type": "text",
1907                                            "text": "Text2.1.1"
1908                                        }
1909                                    ]
1910                                },
1911                            ]
1912                        }
1913                    ]
1914                }))];
1915                assert_eq!(
1916                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1917                    "1. Text1\n1. Text2\n   1. Text2.1\n      1. Text2.1.1".to_string()
1918                );
1919            }
1920
1921            #[test]
1922            fn test_with_bullet_list() {
1923                let blocks = vec![rich_text_block(serde_json::json!({
1924                    "type": "rich_text",
1925                    "elements": [
1926                        {
1927                            "type": "rich_text_list",
1928                            "style": "bullet",
1929                            "elements": [
1930                                {
1931                                    "type": "rich_text_section",
1932                                    "elements": [
1933                                        {
1934                                            "type": "text",
1935                                            "text": "Text1"
1936                                        }
1937                                    ]
1938                                },
1939                                {
1940                                    "type": "rich_text_section",
1941                                    "elements": [
1942                                        {
1943                                            "type": "text",
1944                                            "text": "Text2"
1945                                        }
1946                                    ]
1947                                }
1948                            ]
1949                        },
1950                    ]
1951                }))];
1952                assert_eq!(
1953                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1954                    "- Text1\n- Text2".to_string()
1955                );
1956            }
1957        }
1958
1959        #[test]
1960        fn test_with_nested_bullet_list() {
1961            let blocks = vec![rich_text_block(serde_json::json!({
1962                "type": "rich_text",
1963                "elements": [
1964                    {
1965                        "type": "rich_text_list",
1966                        "style": "bullet",
1967                        "elements": [
1968                            {
1969                                "type": "rich_text_section",
1970                                "elements": [
1971                                    {
1972                                        "type": "text",
1973                                        "text": "Text1"
1974                                    }
1975                                ]
1976                            },
1977                        ]
1978                    },
1979                    {
1980                        "type": "rich_text_list",
1981                        "style": "bullet",
1982                        "elements": [
1983                            {
1984                                "type": "rich_text_section",
1985                                "elements": [
1986                                    {
1987                                        "type": "text",
1988                                        "text": "Text2"
1989                                    }
1990                                ]
1991                            },
1992                        ]
1993                    },
1994                    {
1995                        "type": "rich_text_list",
1996                        "style": "bullet",
1997                        "indent": 1,
1998                        "elements": [
1999                            {
2000                                "type": "rich_text_section",
2001                                "elements": [
2002                                    {
2003                                        "type": "text",
2004                                        "text": "Text2.1"
2005                                    }
2006                                ]
2007                            },
2008                        ]
2009                    },
2010                    {
2011                        "type": "rich_text_list",
2012                        "style": "bullet",
2013                        "indent": 2,
2014                        "elements": [
2015                            {
2016                                "type": "rich_text_section",
2017                                "elements": [
2018                                    {
2019                                        "type": "text",
2020                                        "text": "Text2.1.1"
2021                                    }
2022                                ]
2023                            },
2024                        ]
2025                    }
2026                ]
2027            }))];
2028            assert_eq!(
2029                render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2030                "- Text1\n- Text2\n  - Text2.1\n    - Text2.1.1".to_string()
2031            );
2032        }
2033
2034        mod rich_text_preformatted {
2035            use super::*;
2036
2037            #[test]
2038            fn test_with_text() {
2039                let blocks = vec![rich_text_block(serde_json::json!({
2040                    "type": "rich_text",
2041                    "elements": [
2042                        {
2043                            "type": "rich_text_preformatted",
2044                            "elements": [
2045                                {
2046                                    "type": "text",
2047                                    "text": "Text1"
2048                                },
2049                                {
2050                                    "type": "text",
2051                                    "text": "Text2"
2052                                }
2053                            ]
2054                        },
2055                    ]
2056                }))];
2057                assert_eq!(
2058                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2059                    "```\nText1Text2\n```".to_string()
2060                );
2061            }
2062
2063            #[test]
2064            fn test_with_text_and_newline() {
2065                let blocks = vec![rich_text_block(serde_json::json!({
2066                    "type": "rich_text",
2067                    "elements": [
2068                        {
2069                            "type": "rich_text_preformatted",
2070                            "elements": [
2071                                {
2072                                    "type": "text",
2073                                    "text": "test:\n  sub: value"
2074                                }
2075                            ]
2076                        }
2077                    ]
2078                }))];
2079                assert_eq!(
2080                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2081                    "```\ntest:\n  sub: value\n```".to_string()
2082                );
2083            }
2084
2085            #[test]
2086            fn test_with_preformatted_text_followed_by_text() {
2087                let blocks = vec![rich_text_block(serde_json::json!({
2088                    "type": "rich_text",
2089                    "elements": [
2090                        {
2091                            "type": "rich_text_preformatted",
2092                            "elements": [
2093                                {
2094                                    "type": "text",
2095                                    "text": "Text1"
2096                                }
2097                            ]
2098                        },
2099                        {
2100                            "type": "rich_text_section",
2101                            "elements": [
2102                                {
2103                                    "type": "text",
2104                                    "text": "Text2"
2105                                }
2106                            ]
2107                        },
2108                    ]
2109                }))];
2110                assert_eq!(
2111                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2112                    "```\nText1\n```\nText2".to_string()
2113                );
2114            }
2115        }
2116
2117        mod rich_text_quote {
2118            use super::*;
2119
2120            #[test]
2121            fn test_with_text() {
2122                let blocks = vec![rich_text_block(serde_json::json!({
2123                    "type": "rich_text",
2124                    "elements": [
2125                        {
2126                            "type": "rich_text_quote",
2127                            "elements": [
2128                                {
2129                                    "type": "text",
2130                                    "text": "Text1"
2131                                },
2132                                {
2133                                    "type": "text",
2134                                    "text": "Text2"
2135                                }
2136                            ]
2137                        },
2138                    ]
2139                }))];
2140                assert_eq!(
2141                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2142                    "> Text1Text2".to_string()
2143                );
2144            }
2145
2146            #[test]
2147            fn test_with_quoted_text_followed_by_quoted_text() {
2148                let blocks = vec![rich_text_block(serde_json::json!({
2149                    "type": "rich_text",
2150                    "elements": [
2151                        {
2152                            "type": "rich_text_quote",
2153                            "elements": [
2154                                {
2155                                    "type": "text",
2156                                    "text": "Text1"
2157                                },
2158                            ]
2159                        },
2160                        {
2161                            "type": "rich_text_quote",
2162                            "elements": [
2163                                {
2164                                    "type": "text",
2165                                    "text": "Text2"
2166                                }
2167                            ]
2168                        },
2169                    ]
2170                }))];
2171                assert_eq!(
2172                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2173                    "> Text1\n> Text2".to_string()
2174                );
2175            }
2176
2177            #[test]
2178            fn test_with_quoted_text_followed_by_non_quoted_text() {
2179                let blocks = vec![rich_text_block(serde_json::json!({
2180                    "type": "rich_text",
2181                    "elements": [
2182                        {
2183                            "type": "rich_text_quote",
2184                            "elements": [
2185                                {
2186                                    "text": "Text1",
2187                                    "type": "text"
2188                                }
2189                            ]
2190                        },
2191                        {
2192                            "type": "rich_text_section",
2193                            "elements": [
2194                                {
2195                                    "text": "Text2",
2196                                    "type": "text"
2197                                },
2198                            ]
2199                        }
2200                    ]
2201                }))];
2202
2203                assert_eq!(
2204                    render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2205                    "> Text1\n\nText2".to_string()
2206                );
2207            }
2208        }
2209    }
2210}