slack_blocks_render/
text.rs

1use slack_morphism::prelude::*;
2
3use crate::{
4    references::SlackReferences,
5    visitor::{
6        visit_slack_block_mark_down_text, visit_slack_block_plain_text, visit_slack_context_block,
7        visit_slack_divider_block, visit_slack_header_block, visit_slack_markdown_block,
8        visit_slack_section_block, visit_slack_video_block, SlackRichTextBlock, Visitor,
9    },
10};
11
12/// TODO: document this function
13///
14pub fn render_blocks_as_text(blocks: Vec<SlackBlock>, slack_references: SlackReferences) -> String {
15    let mut block_renderer = TextRenderer::new(slack_references);
16    for block in blocks {
17        block_renderer.visit_slack_block(&block);
18    }
19    block_renderer.sub_texts.join("")
20}
21
22struct TextRenderer {
23    pub sub_texts: Vec<String>,
24    pub slack_references: SlackReferences,
25}
26
27impl TextRenderer {
28    pub fn new(slack_references: SlackReferences) -> Self {
29        TextRenderer {
30            sub_texts: vec![],
31            slack_references,
32        }
33    }
34}
35
36impl Visitor for TextRenderer {
37    fn visit_slack_section_block(&mut self, slack_section_block: &SlackSectionBlock) {
38        let mut section_renderer = TextRenderer::new(self.slack_references.clone());
39        visit_slack_section_block(&mut section_renderer, slack_section_block);
40        self.sub_texts.push(section_renderer.sub_texts.join(""));
41    }
42
43    fn visit_slack_block_plain_text(&mut self, slack_block_plain_text: &SlackBlockPlainText) {
44        self.sub_texts.push(slack_block_plain_text.text.clone());
45        visit_slack_block_plain_text(self, slack_block_plain_text);
46    }
47
48    fn visit_slack_header_block(&mut self, slack_header_block: &SlackHeaderBlock) {
49        let mut header_renderer = TextRenderer::new(self.slack_references.clone());
50        visit_slack_header_block(&mut header_renderer, slack_header_block);
51        self.sub_texts.push(header_renderer.sub_texts.join(""));
52    }
53
54    fn visit_slack_divider_block(&mut self, slack_divider_block: &SlackDividerBlock) {
55        self.sub_texts.push("---\n".to_string());
56        visit_slack_divider_block(self, slack_divider_block);
57    }
58
59    fn visit_slack_block_mark_down_text(
60        &mut self,
61        slack_block_mark_down_text: &SlackBlockMarkDownText,
62    ) {
63        self.sub_texts.push(slack_block_mark_down_text.text.clone());
64        visit_slack_block_mark_down_text(self, slack_block_mark_down_text);
65    }
66
67    fn visit_slack_context_block(&mut self, slack_context_block: &SlackContextBlock) {
68        let mut section_renderer = TextRenderer::new(self.slack_references.clone());
69        visit_slack_context_block(&mut section_renderer, slack_context_block);
70        self.sub_texts.push(section_renderer.sub_texts.join(""));
71    }
72
73    fn visit_slack_rich_text_block(&mut self, slack_rich_text_block: &SlackRichTextBlock) {
74        self.sub_texts.push(render_rich_text_block_as_text(
75            slack_rich_text_block.json_value.clone(),
76            &self.slack_references,
77        ));
78    }
79
80    fn visit_slack_video_block(&mut self, slack_video_block: &SlackVideoBlock) {
81        let title: SlackBlockText = slack_video_block.title.clone().into();
82        let title = match title {
83            SlackBlockText::Plain(plain_text) => plain_text.text,
84            SlackBlockText::MarkDown(md_text) => md_text.text,
85        };
86        self.sub_texts.push(title);
87
88        if let Some(description) = slack_video_block.description.clone() {
89            let description: SlackBlockText = description.into();
90            let description = match description {
91                SlackBlockText::Plain(plain_text) => plain_text.text,
92                SlackBlockText::MarkDown(md_text) => md_text.text,
93            };
94            self.sub_texts.push(format!("\n{}", description));
95        }
96
97        visit_slack_video_block(self, slack_video_block);
98    }
99
100    fn visit_slack_markdown_block(&mut self, slack_markdown_block: &SlackMarkdownBlock) {
101        self.sub_texts.push(slack_markdown_block.text.clone());
102        visit_slack_markdown_block(self, slack_markdown_block);
103    }
104}
105
106fn render_rich_text_block_as_text(
107    json_value: serde_json::Value,
108    slack_references: &SlackReferences,
109) -> String {
110    match json_value.get("elements") {
111        Some(serde_json::Value::Array(elements)) => elements
112            .iter()
113            .map(|element| {
114                match (
115                    element.get("type").map(|t| t.as_str()),
116                    element.get("style"),
117                    element.get("elements"),
118                ) {
119                    (
120                        Some(Some("rich_text_section")),
121                        None,
122                        Some(serde_json::Value::Array(elements)),
123                    ) => render_rich_text_section_elements(elements, slack_references),
124                    (
125                        Some(Some("rich_text_list")),
126                        Some(serde_json::Value::String(style)),
127                        Some(serde_json::Value::Array(elements)),
128                    ) => render_rich_text_list_elements(elements, style, slack_references),
129                    (
130                        Some(Some("rich_text_preformatted")),
131                        None,
132                        Some(serde_json::Value::Array(elements)),
133                    ) => render_rich_text_preformatted_elements(elements, slack_references),
134                    (
135                        Some(Some("rich_text_quote")),
136                        None,
137                        Some(serde_json::Value::Array(elements)),
138                    ) => render_rich_text_quote_elements(elements, slack_references),
139                    _ => "".to_string(),
140                }
141            })
142            .collect::<Vec<String>>()
143            .join(""),
144        _ => "".to_string(),
145    }
146}
147
148fn render_rich_text_section_elements(
149    elements: &[serde_json::Value],
150    slack_references: &SlackReferences,
151) -> String {
152    elements
153        .iter()
154        .map(|e| render_rich_text_section_element(e, slack_references))
155        .collect::<Vec<String>>()
156        .join("")
157}
158
159fn render_rich_text_list_elements(
160    elements: &[serde_json::Value],
161    style: &str,
162    slack_references: &SlackReferences,
163) -> String {
164    let list_style = if style == "ordered" { "1." } else { "-" };
165    elements
166        .iter()
167        .filter_map(|element| {
168            if let Some(serde_json::Value::Array(elements)) = element.get("elements") {
169                Some(render_rich_text_section_elements(
170                    elements,
171                    slack_references,
172                ))
173            } else {
174                None
175            }
176        })
177        .map(|element| format!("{list_style} {element}"))
178        .collect::<Vec<String>>()
179        .join("\n")
180}
181
182fn render_rich_text_preformatted_elements(
183    elements: &[serde_json::Value],
184    slack_references: &SlackReferences,
185) -> String {
186    render_rich_text_section_elements(elements, slack_references)
187}
188
189fn render_rich_text_quote_elements(
190    elements: &[serde_json::Value],
191    slack_references: &SlackReferences,
192) -> String {
193    render_rich_text_section_elements(elements, slack_references)
194}
195
196fn render_rich_text_section_element(
197    element: &serde_json::Value,
198    slack_references: &SlackReferences,
199) -> String {
200    match element.get("type").map(|t| t.as_str()) {
201        Some(Some("text")) => {
202            let Some(serde_json::Value::String(text)) = element.get("text") else {
203                return "".to_string();
204            };
205            text.to_string()
206        }
207        Some(Some("channel")) => {
208            let Some(serde_json::Value::String(channel_id)) = element.get("channel_id") else {
209                return "".to_string();
210            };
211            let channel_rendered = if let Some(Some(channel_name)) = slack_references
212                .channels
213                .get(&SlackChannelId(channel_id.clone()))
214            {
215                channel_name
216            } else {
217                channel_id
218            };
219            format!("#{channel_rendered}")
220        }
221        Some(Some("user")) => {
222            let Some(serde_json::Value::String(user_id)) = element.get("user_id") else {
223                return "".to_string();
224            };
225            let user_rendered = if let Some(Some(user_name)) =
226                slack_references.users.get(&SlackUserId(user_id.clone()))
227            {
228                user_name
229            } else {
230                user_id
231            };
232            format!("@{user_rendered}")
233        }
234        Some(Some("usergroup")) => {
235            let Some(serde_json::Value::String(usergroup_id)) = element.get("usergroup_id") else {
236                return "".to_string();
237            };
238            let usergroup_rendered = if let Some(Some(usergroup_name)) = slack_references
239                .usergroups
240                .get(&SlackUserGroupId(usergroup_id.clone()))
241            {
242                usergroup_name
243            } else {
244                usergroup_id
245            };
246            format!("@{usergroup_rendered}")
247        }
248        Some(Some("emoji")) => {
249            let Some(serde_json::Value::String(name)) = element.get("name") else {
250                return "".to_string();
251            };
252            let splitted = name.split("::skin-tone-").collect::<Vec<&str>>();
253            let Some(first) = splitted.first() else {
254                return "".to_string();
255            };
256            let Some(emoji) = emojis::get_by_shortcode(first) else {
257                return "".to_string();
258            };
259            let Some(skin_tone) = splitted.get(1).and_then(|s| s.parse::<usize>().ok()) else {
260                return emoji.to_string();
261            };
262            let Some(mut skin_tones) = emoji.skin_tones() else {
263                return emoji.to_string();
264            };
265            let Some(skinned_emoji) = skin_tones.nth(skin_tone - 1) else {
266                return emoji.to_string();
267            };
268            skinned_emoji.to_string()
269        }
270        Some(Some("link")) => {
271            let Some(serde_json::Value::String(text)) = element.get("text") else {
272                return "".to_string();
273            };
274            text.to_string()
275        }
276        _ => "".to_string(),
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use std::collections::HashMap;
283
284    use url::Url;
285
286    use super::*;
287
288    #[test]
289    fn test_empty_input() {
290        assert_eq!(
291            render_blocks_as_text(vec![], SlackReferences::default()),
292            "".to_string()
293        );
294    }
295
296    #[test]
297    fn test_with_image() {
298        let blocks = vec![
299            SlackBlock::Image(SlackImageBlock::new(
300                Url::parse("https://example.com/image.png").unwrap(),
301                "Image".to_string(),
302            )),
303            SlackBlock::Image(SlackImageBlock::new(
304                Url::parse("https://example.com/image2.png").unwrap(),
305                "Image2".to_string(),
306            )),
307        ];
308        assert_eq!(
309            render_blocks_as_text(blocks, SlackReferences::default()),
310            "".to_string()
311        );
312    }
313
314    #[test]
315    fn test_with_divider() {
316        let blocks = vec![
317            SlackBlock::Divider(SlackDividerBlock::new()),
318            SlackBlock::Divider(SlackDividerBlock::new()),
319        ];
320        assert_eq!(
321            render_blocks_as_text(blocks, SlackReferences::default()),
322            "---\n---\n".to_string()
323        );
324    }
325
326    #[test]
327    fn test_with_input() {
328        // No rendering
329        let blocks = vec![SlackBlock::Input(SlackInputBlock::new(
330            "label".into(),
331            SlackInputBlockElement::PlainTextInput(SlackBlockPlainTextInputElement::new(
332                "id".into(),
333            )),
334        ))];
335        assert_eq!(
336            render_blocks_as_text(blocks, SlackReferences::default()),
337            "".to_string()
338        );
339    }
340
341    #[test]
342    fn test_with_action() {
343        // No rendering
344        let blocks = vec![SlackBlock::Actions(SlackActionsBlock::new(vec![]))];
345        assert_eq!(
346            render_blocks_as_text(blocks, SlackReferences::default()),
347            "".to_string()
348        );
349    }
350
351    #[test]
352    fn test_with_file() {
353        // No rendering
354        let blocks = vec![SlackBlock::File(SlackFileBlock::new("external_id".into()))];
355        assert_eq!(
356            render_blocks_as_text(blocks, SlackReferences::default()),
357            "".to_string()
358        );
359    }
360
361    #[test]
362    fn test_with_video() {
363        let blocks = vec![SlackBlock::Video(
364            SlackVideoBlock::new(
365                "alt text".into(),
366                "Video title".into(),
367                "https://example.com/thumbnail.jpg".parse().unwrap(),
368                "https://example.com/video_embed.avi".parse().unwrap(),
369            )
370            .with_description("Video description".into())
371            .with_title_url("https://example.com/video".parse().unwrap()),
372        )];
373        assert_eq!(
374            render_blocks_as_text(blocks, SlackReferences::default()),
375            r#"Video title
376Video description"#
377                .to_string()
378        );
379    }
380
381    #[test]
382    fn test_with_video_minimal() {
383        let blocks = vec![SlackBlock::Video(SlackVideoBlock::new(
384            "alt text".into(),
385            "Video title".into(),
386            "https://example.com/thumbnail.jpg".parse().unwrap(),
387            "https://example.com/video_embed.avi".parse().unwrap(),
388        ))];
389        assert_eq!(
390            render_blocks_as_text(blocks, SlackReferences::default()),
391            "Video title".to_string()
392        );
393    }
394
395    #[test]
396    fn test_with_event() {
397        // No rendering
398        let blocks = vec![SlackBlock::Event(serde_json::json!({}))];
399        assert_eq!(
400            render_blocks_as_text(blocks, SlackReferences::default()),
401            "".to_string()
402        );
403    }
404
405    #[test]
406    fn test_header() {
407        let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
408        assert_eq!(
409            render_blocks_as_text(blocks, SlackReferences::default()),
410            "Text".to_string()
411        );
412    }
413
414    mod section {
415        use super::*;
416
417        #[test]
418        fn test_with_plain_text() {
419            let blocks = vec![
420                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
421                    SlackBlockPlainText::new("Text".to_string()),
422                ))),
423                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
424                    SlackBlockPlainText::new("Text2".to_string()),
425                ))),
426            ];
427            assert_eq!(
428                render_blocks_as_text(blocks, SlackReferences::default()),
429                "TextText2".to_string()
430            );
431        }
432
433        #[test]
434        fn test_with_markdown() {
435            let blocks = vec![
436                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
437                    SlackBlockMarkDownText::new("Text".to_string()),
438                ))),
439                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
440                    SlackBlockMarkDownText::new("Text2".to_string()),
441                ))),
442            ];
443            assert_eq!(
444                render_blocks_as_text(blocks, SlackReferences::default()),
445                "TextText2".to_string()
446            );
447        }
448
449        #[test]
450        fn test_with_fields() {
451            let blocks = vec![
452                SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
453                    SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
454                    SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
455                ])),
456                SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
457                    SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
458                    SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
459                ])),
460            ];
461            assert_eq!(
462                render_blocks_as_text(blocks, SlackReferences::default()),
463                "Text11Text12Text21Text22".to_string()
464            );
465        }
466
467        #[test]
468        fn test_with_fields_and_text() {
469            let blocks = vec![
470                SlackBlock::Section(
471                    SlackSectionBlock::new()
472                        .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
473                            "Text1".to_string(),
474                        )))
475                        .with_fields(vec![
476                            SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
477                            SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
478                        ]),
479                ),
480                SlackBlock::Section(
481                    SlackSectionBlock::new()
482                        .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
483                            "Text2".to_string(),
484                        )))
485                        .with_fields(vec![
486                            SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
487                            SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
488                        ]),
489                ),
490            ];
491            assert_eq!(
492                render_blocks_as_text(blocks, SlackReferences::default()),
493                "Text1Text11Text12Text2Text21Text22".to_string()
494            );
495        }
496    }
497
498    mod context {
499        use super::*;
500
501        #[test]
502        fn test_with_image() {
503            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
504                SlackContextBlockElement::Image(SlackBlockImageElement::new(
505                    "https://example.com/image.png".to_string(),
506                    "Image".to_string(),
507                )),
508                SlackContextBlockElement::Image(SlackBlockImageElement::new(
509                    "https://example.com/image2.png".to_string(),
510                    "Image2".to_string(),
511                )),
512            ]))];
513            assert_eq!(
514                render_blocks_as_text(blocks, SlackReferences::default()),
515                "".to_string()
516            );
517        }
518
519        #[test]
520        fn test_with_plain_text() {
521            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
522                SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
523                SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
524            ]))];
525            assert_eq!(
526                render_blocks_as_text(blocks, SlackReferences::default()),
527                "TextText2".to_string()
528            );
529        }
530
531        #[test]
532        fn test_with_markdown() {
533            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
534                SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
535                SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
536                    "Text2".to_string(),
537                )),
538            ]))];
539            assert_eq!(
540                render_blocks_as_text(blocks, SlackReferences::default()),
541                "TextText2".to_string()
542            );
543        }
544    }
545
546    mod rich_text {
547        use super::*;
548
549        #[test]
550        fn test_with_empty_json() {
551            let blocks = vec![
552                SlackBlock::RichText(serde_json::json!({})),
553                SlackBlock::RichText(serde_json::json!({})),
554            ];
555            assert_eq!(
556                render_blocks_as_text(blocks, SlackReferences::default()),
557                "".to_string()
558            );
559        }
560
561        mod rich_text_section {
562            use super::*;
563
564            mod text_element {
565                use super::*;
566
567                #[test]
568                fn test_with_text() {
569                    let blocks = vec![
570                        SlackBlock::RichText(serde_json::json!({
571                            "type": "rich_text",
572                            "elements": [
573                                {
574                                    "type": "rich_text_section",
575                                    "elements": [
576                                        {
577                                            "type": "text",
578                                            "text": "Text111"
579                                        },
580                                        {
581                                            "type": "text",
582                                            "text": "Text112"
583                                        }
584                                    ]
585                                },
586                                {
587                                    "type": "rich_text_section",
588                                    "elements": [
589                                        {
590                                            "type": "text",
591                                            "text": "Text121"
592                                        },
593                                        {
594                                            "type": "text",
595                                            "text": "Text122"
596                                        }
597                                    ]
598                                }
599                            ]
600                        })),
601                        SlackBlock::RichText(serde_json::json!({
602                            "type": "rich_text",
603                            "elements": [
604                                {
605                                    "type": "rich_text_section",
606                                    "elements": [
607                                        {
608                                            "type": "text",
609                                            "text": "Text211"
610                                        },
611                                        {
612                                            "type": "text",
613                                            "text": "Text212"
614                                        }
615                                    ]
616                                },
617                                {
618                                    "type": "rich_text_section",
619                                    "elements": [
620                                        {
621                                            "type": "text",
622                                            "text": "Text221"
623                                        },
624                                        {
625                                            "type": "text",
626                                            "text": "Text222"
627                                        }
628                                    ]
629                                }
630                            ]
631                        })),
632                    ];
633                    assert_eq!(
634                        render_blocks_as_text(blocks, SlackReferences::default()),
635                        "Text111Text112Text121Text122Text211Text212Text221Text222".to_string()
636                    );
637                }
638
639                #[test]
640                fn test_with_bold_text() {
641                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
642                        "type": "rich_text",
643                        "elements": [
644                            {
645                                "type": "rich_text_section",
646                                "elements": [
647                                    {
648                                        "type": "text",
649                                        "text": "Text",
650                                        "style": {
651                                            "bold": true
652                                        }
653                                    }
654                                ]
655                            }
656                        ]
657                    }))];
658                    assert_eq!(
659                        render_blocks_as_text(blocks, SlackReferences::default()),
660                        "Text".to_string()
661                    );
662                }
663
664                #[test]
665                fn test_with_italic_text() {
666                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
667                        "type": "rich_text",
668                        "elements": [
669                            {
670                                "type": "rich_text_section",
671                                "elements": [
672                                    {
673                                        "type": "text",
674                                        "text": "Text",
675                                        "style": {
676                                            "italic": true
677                                        }
678                                    }
679                                ]
680                            }
681                        ]
682                    }))];
683                    assert_eq!(
684                        render_blocks_as_text(blocks, SlackReferences::default()),
685                        "Text".to_string()
686                    );
687                }
688
689                #[test]
690                fn test_with_strike_text() {
691                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
692                        "type": "rich_text",
693                        "elements": [
694                            {
695                                "type": "rich_text_section",
696                                "elements": [
697                                    {
698                                        "type": "text",
699                                        "text": "Text",
700                                        "style": {
701                                            "strike": true
702                                        }
703                                    }
704                                ]
705                            }
706                        ]
707                    }))];
708                    assert_eq!(
709                        render_blocks_as_text(blocks, SlackReferences::default()),
710                        "Text".to_string()
711                    );
712                }
713
714                #[test]
715                fn test_with_code_text() {
716                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
717                        "type": "rich_text",
718                        "elements": [
719                            {
720                                "type": "rich_text_section",
721                                "elements": [
722                                    {
723                                        "type": "text",
724                                        "text": "Text",
725                                        "style": {
726                                            "code": true
727                                        }
728                                    }
729                                ]
730                            }
731                        ]
732                    }))];
733                    assert_eq!(
734                        render_blocks_as_text(blocks, SlackReferences::default()),
735                        "Text".to_string()
736                    );
737                }
738
739                #[test]
740                fn test_with_styled_text() {
741                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
742                        "type": "rich_text",
743                        "elements": [
744                            {
745                                "type": "rich_text_section",
746                                "elements": [
747                                    {
748                                        "type": "text",
749                                        "text": "Text",
750                                        "style": {
751                                            "bold": true,
752                                            "italic": true,
753                                            "strike": true
754                                        }
755                                    }
756                                ]
757                            }
758                        ]
759                    }))];
760                    assert_eq!(
761                        render_blocks_as_text(blocks, SlackReferences::default()),
762                        "Text".to_string()
763                    );
764                }
765            }
766
767            mod channel_element {
768                use super::*;
769
770                #[test]
771                fn test_with_channel_id() {
772                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
773                        "type": "rich_text",
774                        "elements": [
775                            {
776                                "type": "rich_text_section",
777                                "elements": [
778                                    {
779                                        "type": "channel",
780                                        "channel_id": "C0123456"
781                                    }
782                                ]
783                            }
784                        ]
785                    }))];
786                    assert_eq!(
787                        render_blocks_as_text(blocks, SlackReferences::default()),
788                        "#C0123456".to_string()
789                    );
790                }
791
792                #[test]
793                fn test_with_channel_id_and_reference() {
794                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
795                        "type": "rich_text",
796                        "elements": [
797                            {
798                                "type": "rich_text_section",
799                                "elements": [
800                                    {
801                                        "type": "channel",
802                                        "channel_id": "C0123456"
803                                    }
804                                ]
805                            }
806                        ]
807                    }))];
808                    assert_eq!(
809                        render_blocks_as_text(
810                            blocks,
811                            SlackReferences {
812                                channels: HashMap::from([(
813                                    SlackChannelId("C0123456".to_string()),
814                                    Some("general".to_string())
815                                )]),
816                                ..SlackReferences::default()
817                            }
818                        ),
819                        "#general".to_string()
820                    );
821                }
822            }
823
824            mod user_element {
825                use super::*;
826
827                #[test]
828                fn test_with_user_id() {
829                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
830                        "type": "rich_text",
831                        "elements": [
832                            {
833                                "type": "rich_text_section",
834                                "elements": [
835                                    {
836                                        "type": "user",
837                                        "user_id": "user1"
838                                    }
839                                ]
840                            }
841                        ]
842                    }))];
843                    assert_eq!(
844                        render_blocks_as_text(blocks, SlackReferences::default()),
845                        "@user1".to_string()
846                    );
847                }
848
849                #[test]
850                fn test_with_user_id_and_reference() {
851                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
852                        "type": "rich_text",
853                        "elements": [
854                            {
855                                "type": "rich_text_section",
856                                "elements": [
857                                    {
858                                        "type": "user",
859                                        "user_id": "user1"
860                                    }
861                                ]
862                            }
863                        ]
864                    }))];
865                    assert_eq!(
866                        render_blocks_as_text(
867                            blocks,
868                            SlackReferences {
869                                users: HashMap::from([(
870                                    SlackUserId("user1".to_string()),
871                                    Some("John Doe".to_string())
872                                )]),
873                                ..SlackReferences::default()
874                            }
875                        ),
876                        "@John Doe".to_string()
877                    );
878                }
879            }
880
881            mod usergroup_element {
882                use super::*;
883
884                #[test]
885                fn test_with_usergroup_id() {
886                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
887                        "type": "rich_text",
888                        "elements": [
889                            {
890                                "type": "rich_text_section",
891                                "elements": [
892                                    {
893                                        "type": "usergroup",
894                                        "usergroup_id": "group1"
895                                    }
896                                ]
897                            }
898                        ]
899                    }))];
900                    assert_eq!(
901                        render_blocks_as_text(blocks, SlackReferences::default()),
902                        "@group1".to_string()
903                    );
904                }
905
906                #[test]
907                fn test_with_usergroup_id_and_reference() {
908                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
909                        "type": "rich_text",
910                        "elements": [
911                            {
912                                "type": "rich_text_section",
913                                "elements": [
914                                    {
915                                        "type": "usergroup",
916                                        "usergroup_id": "group1"
917                                    }
918                                ]
919                            }
920                        ]
921                    }))];
922                    assert_eq!(
923                        render_blocks_as_text(
924                            blocks,
925                            SlackReferences {
926                                usergroups: HashMap::from([(
927                                    SlackUserGroupId("group1".to_string()),
928                                    Some("Admins".to_string())
929                                )]),
930                                ..SlackReferences::default()
931                            }
932                        ),
933                        "@Admins".to_string()
934                    );
935                }
936            }
937
938            mod link_element {
939                use super::*;
940
941                #[test]
942                fn test_with_url() {
943                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
944                        "type": "rich_text",
945                        "elements": [
946                            {
947                                "type": "rich_text_section",
948                                "elements": [
949                                    {
950                                        "type": "link",
951                                        "text": "example",
952                                        "url": "https://example.com"
953                                    }
954                                ]
955                            }
956                        ]
957                    }))];
958                    assert_eq!(
959                        render_blocks_as_text(blocks, SlackReferences::default()),
960                        "example".to_string()
961                    );
962                }
963            }
964
965            mod emoji_element {
966                use super::*;
967
968                #[test]
969                fn test_with_emoji() {
970                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
971                        "type": "rich_text",
972                        "elements": [
973                            {
974                                "type": "rich_text_section",
975                                "elements": [
976                                    {
977                                        "type": "emoji",
978                                        "name": "wave"
979                                    }
980                                ]
981                            }
982                        ]
983                    }))];
984                    assert_eq!(
985                        render_blocks_as_text(blocks, SlackReferences::default()),
986                        "👋".to_string()
987                    );
988                }
989
990                #[test]
991                fn test_with_emoji_with_skin_tone() {
992                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
993                        "type": "rich_text",
994                        "elements": [
995                            {
996                                "type": "rich_text_section",
997                                "elements": [
998                                    {
999                                        "type": "emoji",
1000                                        "name": "wave::skin-tone-2"
1001                                    }
1002                                ]
1003                            }
1004                        ]
1005                    }))];
1006                    assert_eq!(
1007                        render_blocks_as_text(blocks, SlackReferences::default()),
1008                        "👋🏻".to_string()
1009                    );
1010                }
1011
1012                #[test]
1013                fn test_with_emoji_with_unknown_skin_tone() {
1014                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
1015                        "type": "rich_text",
1016                        "elements": [
1017                            {
1018                                "type": "rich_text_section",
1019                                "elements": [
1020                                    {
1021                                        "type": "emoji",
1022                                        "name": "wave::skin-tone-42"
1023                                    }
1024                                ]
1025                            }
1026                        ]
1027                    }))];
1028                    assert_eq!(
1029                        render_blocks_as_text(blocks, SlackReferences::default()),
1030                        "👋".to_string()
1031                    );
1032                }
1033
1034                #[test]
1035                fn test_with_unknown_emoji() {
1036                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
1037                        "type": "rich_text",
1038                        "elements": [
1039                            {
1040                                "type": "rich_text_section",
1041                                "elements": [
1042                                    {
1043                                        "type": "emoji",
1044                                        "name": "bbb"
1045                                    }
1046                                ]
1047                            }
1048                        ]
1049                    }))];
1050                    assert_eq!(
1051                        render_blocks_as_text(blocks, SlackReferences::default()),
1052                        "".to_string()
1053                    );
1054                }
1055            }
1056        }
1057
1058        mod rich_text_list {
1059            use super::*;
1060
1061            #[test]
1062            fn test_with_ordered_list() {
1063                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1064                    "type": "rich_text",
1065                    "elements": [
1066                        {
1067                            "type": "rich_text_list",
1068                            "style": "ordered",
1069                            "elements": [
1070                                {
1071                                    "type": "rich_text_section",
1072                                    "elements": [
1073                                        {
1074                                            "type": "text",
1075                                            "text": "Text1"
1076                                        }
1077                                    ]
1078                                },
1079                                {
1080                                    "type": "rich_text_section",
1081                                    "elements": [
1082                                        {
1083                                            "type": "text",
1084                                            "text": "Text2"
1085                                        }
1086                                    ]
1087                                }
1088                            ]
1089                         },
1090                    ]
1091                }))];
1092                assert_eq!(
1093                    render_blocks_as_text(blocks, SlackReferences::default()),
1094                    "1. Text1\n1. Text2".to_string()
1095                );
1096            }
1097
1098            #[test]
1099            fn test_with_bullet_list() {
1100                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1101                    "type": "rich_text",
1102                    "elements": [
1103                        {
1104                            "type": "rich_text_list",
1105                            "style": "bullet",
1106                            "elements": [
1107                                {
1108                                    "type": "rich_text_section",
1109                                    "elements": [
1110                                        {
1111                                            "type": "text",
1112                                            "text": "Text1"
1113                                        }
1114                                    ]
1115                                },
1116                                {
1117                                    "type": "rich_text_section",
1118                                    "elements": [
1119                                        {
1120                                            "type": "text",
1121                                            "text": "Text2"
1122                                        }
1123                                    ]
1124                                }
1125                            ]
1126                        },
1127                    ]
1128                }))];
1129                assert_eq!(
1130                    render_blocks_as_text(blocks, SlackReferences::default()),
1131                    "- Text1\n- Text2".to_string()
1132                );
1133            }
1134        }
1135
1136        mod rich_text_preformatted {
1137            use super::*;
1138
1139            #[test]
1140            fn test_with_text() {
1141                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1142                    "type": "rich_text",
1143                    "elements": [
1144                        {
1145                            "type": "rich_text_preformatted",
1146                            "elements": [
1147                                {
1148                                    "type": "text",
1149                                    "text": "Text1"
1150                                },
1151                                {
1152                                    "type": "text",
1153                                    "text": "Text2"
1154                                }
1155                            ]
1156                        },
1157                    ]
1158                }))];
1159                assert_eq!(
1160                    render_blocks_as_text(blocks, SlackReferences::default()),
1161                    "Text1Text2".to_string()
1162                );
1163            }
1164        }
1165
1166        mod rich_text_quote {
1167            use super::*;
1168
1169            #[test]
1170            fn test_with_text() {
1171                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1172                    "type": "rich_text",
1173                    "elements": [
1174                        {
1175                            "type": "rich_text_quote",
1176                            "elements": [
1177                                {
1178                                    "type": "text",
1179                                    "text": "Text1"
1180                                },
1181                                {
1182                                    "type": "text",
1183                                    "text": "Text2"
1184                                }
1185                            ]
1186                        },
1187                    ]
1188                }))];
1189                assert_eq!(
1190                    render_blocks_as_text(blocks, SlackReferences::default()),
1191                    "Text1Text2".to_string()
1192                );
1193            }
1194        }
1195    }
1196}