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                SlackImageUrlOrFile::ImageUrl {
301                    image_url: Url::parse("https://example.com/image.png").unwrap(),
302                },
303                "Image".to_string(),
304            )),
305            SlackBlock::Image(SlackImageBlock::new(
306                SlackImageUrlOrFile::ImageUrl {
307                    image_url: Url::parse("https://example.com/image2.png").unwrap(),
308                },
309                "Image2".to_string(),
310            )),
311        ];
312        assert_eq!(
313            render_blocks_as_text(blocks, SlackReferences::default()),
314            "".to_string()
315        );
316    }
317
318    #[test]
319    fn test_with_divider() {
320        let blocks = vec![
321            SlackBlock::Divider(SlackDividerBlock::new()),
322            SlackBlock::Divider(SlackDividerBlock::new()),
323        ];
324        assert_eq!(
325            render_blocks_as_text(blocks, SlackReferences::default()),
326            "---\n---\n".to_string()
327        );
328    }
329
330    #[test]
331    fn test_with_input() {
332        // No rendering
333        let blocks = vec![SlackBlock::Input(SlackInputBlock::new(
334            "label".into(),
335            SlackInputBlockElement::PlainTextInput(SlackBlockPlainTextInputElement::new(
336                "id".into(),
337            )),
338        ))];
339        assert_eq!(
340            render_blocks_as_text(blocks, SlackReferences::default()),
341            "".to_string()
342        );
343    }
344
345    #[test]
346    fn test_with_action() {
347        // No rendering
348        let blocks = vec![SlackBlock::Actions(SlackActionsBlock::new(vec![]))];
349        assert_eq!(
350            render_blocks_as_text(blocks, SlackReferences::default()),
351            "".to_string()
352        );
353    }
354
355    #[test]
356    fn test_with_file() {
357        // No rendering
358        let blocks = vec![SlackBlock::File(SlackFileBlock::new("external_id".into()))];
359        assert_eq!(
360            render_blocks_as_text(blocks, SlackReferences::default()),
361            "".to_string()
362        );
363    }
364
365    #[test]
366    fn test_with_video() {
367        let blocks = vec![SlackBlock::Video(
368            SlackVideoBlock::new(
369                "alt text".into(),
370                "Video title".into(),
371                "https://example.com/thumbnail.jpg".parse().unwrap(),
372                "https://example.com/video_embed.avi".parse().unwrap(),
373            )
374            .with_description("Video description".into())
375            .with_title_url("https://example.com/video".parse().unwrap()),
376        )];
377        assert_eq!(
378            render_blocks_as_text(blocks, SlackReferences::default()),
379            r#"Video title
380Video description"#
381                .to_string()
382        );
383    }
384
385    #[test]
386    fn test_with_video_minimal() {
387        let blocks = vec![SlackBlock::Video(SlackVideoBlock::new(
388            "alt text".into(),
389            "Video title".into(),
390            "https://example.com/thumbnail.jpg".parse().unwrap(),
391            "https://example.com/video_embed.avi".parse().unwrap(),
392        ))];
393        assert_eq!(
394            render_blocks_as_text(blocks, SlackReferences::default()),
395            "Video title".to_string()
396        );
397    }
398
399    #[test]
400    fn test_with_event() {
401        // No rendering
402        let blocks = vec![SlackBlock::Event(serde_json::json!({}))];
403        assert_eq!(
404            render_blocks_as_text(blocks, SlackReferences::default()),
405            "".to_string()
406        );
407    }
408
409    #[test]
410    fn test_header() {
411        let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
412        assert_eq!(
413            render_blocks_as_text(blocks, SlackReferences::default()),
414            "Text".to_string()
415        );
416    }
417
418    mod section {
419        use super::*;
420
421        #[test]
422        fn test_with_plain_text() {
423            let blocks = vec![
424                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
425                    SlackBlockPlainText::new("Text".to_string()),
426                ))),
427                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
428                    SlackBlockPlainText::new("Text2".to_string()),
429                ))),
430            ];
431            assert_eq!(
432                render_blocks_as_text(blocks, SlackReferences::default()),
433                "TextText2".to_string()
434            );
435        }
436
437        #[test]
438        fn test_with_markdown() {
439            let blocks = vec![
440                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
441                    SlackBlockMarkDownText::new("Text".to_string()),
442                ))),
443                SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
444                    SlackBlockMarkDownText::new("Text2".to_string()),
445                ))),
446            ];
447            assert_eq!(
448                render_blocks_as_text(blocks, SlackReferences::default()),
449                "TextText2".to_string()
450            );
451        }
452
453        #[test]
454        fn test_with_fields() {
455            let blocks = vec![
456                SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
457                    SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
458                    SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
459                ])),
460                SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
461                    SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
462                    SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
463                ])),
464            ];
465            assert_eq!(
466                render_blocks_as_text(blocks, SlackReferences::default()),
467                "Text11Text12Text21Text22".to_string()
468            );
469        }
470
471        #[test]
472        fn test_with_fields_and_text() {
473            let blocks = vec![
474                SlackBlock::Section(
475                    SlackSectionBlock::new()
476                        .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
477                            "Text1".to_string(),
478                        )))
479                        .with_fields(vec![
480                            SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
481                            SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
482                        ]),
483                ),
484                SlackBlock::Section(
485                    SlackSectionBlock::new()
486                        .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
487                            "Text2".to_string(),
488                        )))
489                        .with_fields(vec![
490                            SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
491                            SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
492                        ]),
493                ),
494            ];
495            assert_eq!(
496                render_blocks_as_text(blocks, SlackReferences::default()),
497                "Text1Text11Text12Text2Text21Text22".to_string()
498            );
499        }
500    }
501
502    mod context {
503        use super::*;
504
505        #[test]
506        fn test_with_image() {
507            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
508                SlackContextBlockElement::Image(SlackBlockImageElement::new(
509                    SlackImageUrlOrFile::ImageUrl {
510                        image_url: Url::parse("https://example.com/image.png").unwrap(),
511                    },
512                    "Image".to_string(),
513                )),
514                SlackContextBlockElement::Image(SlackBlockImageElement::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_text(blocks, SlackReferences::default()),
523                "".to_string()
524            );
525        }
526
527        #[test]
528        fn test_with_plain_text() {
529            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
530                SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
531                SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
532            ]))];
533            assert_eq!(
534                render_blocks_as_text(blocks, SlackReferences::default()),
535                "TextText2".to_string()
536            );
537        }
538
539        #[test]
540        fn test_with_markdown() {
541            let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
542                SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
543                SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
544                    "Text2".to_string(),
545                )),
546            ]))];
547            assert_eq!(
548                render_blocks_as_text(blocks, SlackReferences::default()),
549                "TextText2".to_string()
550            );
551        }
552    }
553
554    mod rich_text {
555        use super::*;
556
557        #[test]
558        fn test_with_empty_json() {
559            let blocks = vec![
560                SlackBlock::RichText(serde_json::json!({})),
561                SlackBlock::RichText(serde_json::json!({})),
562            ];
563            assert_eq!(
564                render_blocks_as_text(blocks, SlackReferences::default()),
565                "".to_string()
566            );
567        }
568
569        mod rich_text_section {
570            use super::*;
571
572            mod text_element {
573                use super::*;
574
575                #[test]
576                fn test_with_text() {
577                    let blocks = vec![
578                        SlackBlock::RichText(serde_json::json!({
579                            "type": "rich_text",
580                            "elements": [
581                                {
582                                    "type": "rich_text_section",
583                                    "elements": [
584                                        {
585                                            "type": "text",
586                                            "text": "Text111"
587                                        },
588                                        {
589                                            "type": "text",
590                                            "text": "Text112"
591                                        }
592                                    ]
593                                },
594                                {
595                                    "type": "rich_text_section",
596                                    "elements": [
597                                        {
598                                            "type": "text",
599                                            "text": "Text121"
600                                        },
601                                        {
602                                            "type": "text",
603                                            "text": "Text122"
604                                        }
605                                    ]
606                                }
607                            ]
608                        })),
609                        SlackBlock::RichText(serde_json::json!({
610                            "type": "rich_text",
611                            "elements": [
612                                {
613                                    "type": "rich_text_section",
614                                    "elements": [
615                                        {
616                                            "type": "text",
617                                            "text": "Text211"
618                                        },
619                                        {
620                                            "type": "text",
621                                            "text": "Text212"
622                                        }
623                                    ]
624                                },
625                                {
626                                    "type": "rich_text_section",
627                                    "elements": [
628                                        {
629                                            "type": "text",
630                                            "text": "Text221"
631                                        },
632                                        {
633                                            "type": "text",
634                                            "text": "Text222"
635                                        }
636                                    ]
637                                }
638                            ]
639                        })),
640                    ];
641                    assert_eq!(
642                        render_blocks_as_text(blocks, SlackReferences::default()),
643                        "Text111Text112Text121Text122Text211Text212Text221Text222".to_string()
644                    );
645                }
646
647                #[test]
648                fn test_with_bold_text() {
649                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
650                        "type": "rich_text",
651                        "elements": [
652                            {
653                                "type": "rich_text_section",
654                                "elements": [
655                                    {
656                                        "type": "text",
657                                        "text": "Text",
658                                        "style": {
659                                            "bold": true
660                                        }
661                                    }
662                                ]
663                            }
664                        ]
665                    }))];
666                    assert_eq!(
667                        render_blocks_as_text(blocks, SlackReferences::default()),
668                        "Text".to_string()
669                    );
670                }
671
672                #[test]
673                fn test_with_italic_text() {
674                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
675                        "type": "rich_text",
676                        "elements": [
677                            {
678                                "type": "rich_text_section",
679                                "elements": [
680                                    {
681                                        "type": "text",
682                                        "text": "Text",
683                                        "style": {
684                                            "italic": true
685                                        }
686                                    }
687                                ]
688                            }
689                        ]
690                    }))];
691                    assert_eq!(
692                        render_blocks_as_text(blocks, SlackReferences::default()),
693                        "Text".to_string()
694                    );
695                }
696
697                #[test]
698                fn test_with_strike_text() {
699                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
700                        "type": "rich_text",
701                        "elements": [
702                            {
703                                "type": "rich_text_section",
704                                "elements": [
705                                    {
706                                        "type": "text",
707                                        "text": "Text",
708                                        "style": {
709                                            "strike": true
710                                        }
711                                    }
712                                ]
713                            }
714                        ]
715                    }))];
716                    assert_eq!(
717                        render_blocks_as_text(blocks, SlackReferences::default()),
718                        "Text".to_string()
719                    );
720                }
721
722                #[test]
723                fn test_with_code_text() {
724                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
725                        "type": "rich_text",
726                        "elements": [
727                            {
728                                "type": "rich_text_section",
729                                "elements": [
730                                    {
731                                        "type": "text",
732                                        "text": "Text",
733                                        "style": {
734                                            "code": true
735                                        }
736                                    }
737                                ]
738                            }
739                        ]
740                    }))];
741                    assert_eq!(
742                        render_blocks_as_text(blocks, SlackReferences::default()),
743                        "Text".to_string()
744                    );
745                }
746
747                #[test]
748                fn test_with_styled_text() {
749                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
750                        "type": "rich_text",
751                        "elements": [
752                            {
753                                "type": "rich_text_section",
754                                "elements": [
755                                    {
756                                        "type": "text",
757                                        "text": "Text",
758                                        "style": {
759                                            "bold": true,
760                                            "italic": true,
761                                            "strike": true
762                                        }
763                                    }
764                                ]
765                            }
766                        ]
767                    }))];
768                    assert_eq!(
769                        render_blocks_as_text(blocks, SlackReferences::default()),
770                        "Text".to_string()
771                    );
772                }
773            }
774
775            mod channel_element {
776                use super::*;
777
778                #[test]
779                fn test_with_channel_id() {
780                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
781                        "type": "rich_text",
782                        "elements": [
783                            {
784                                "type": "rich_text_section",
785                                "elements": [
786                                    {
787                                        "type": "channel",
788                                        "channel_id": "C0123456"
789                                    }
790                                ]
791                            }
792                        ]
793                    }))];
794                    assert_eq!(
795                        render_blocks_as_text(blocks, SlackReferences::default()),
796                        "#C0123456".to_string()
797                    );
798                }
799
800                #[test]
801                fn test_with_channel_id_and_reference() {
802                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
803                        "type": "rich_text",
804                        "elements": [
805                            {
806                                "type": "rich_text_section",
807                                "elements": [
808                                    {
809                                        "type": "channel",
810                                        "channel_id": "C0123456"
811                                    }
812                                ]
813                            }
814                        ]
815                    }))];
816                    assert_eq!(
817                        render_blocks_as_text(
818                            blocks,
819                            SlackReferences {
820                                channels: HashMap::from([(
821                                    SlackChannelId("C0123456".to_string()),
822                                    Some("general".to_string())
823                                )]),
824                                ..SlackReferences::default()
825                            }
826                        ),
827                        "#general".to_string()
828                    );
829                }
830            }
831
832            mod user_element {
833                use super::*;
834
835                #[test]
836                fn test_with_user_id() {
837                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
838                        "type": "rich_text",
839                        "elements": [
840                            {
841                                "type": "rich_text_section",
842                                "elements": [
843                                    {
844                                        "type": "user",
845                                        "user_id": "user1"
846                                    }
847                                ]
848                            }
849                        ]
850                    }))];
851                    assert_eq!(
852                        render_blocks_as_text(blocks, SlackReferences::default()),
853                        "@user1".to_string()
854                    );
855                }
856
857                #[test]
858                fn test_with_user_id_and_reference() {
859                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
860                        "type": "rich_text",
861                        "elements": [
862                            {
863                                "type": "rich_text_section",
864                                "elements": [
865                                    {
866                                        "type": "user",
867                                        "user_id": "user1"
868                                    }
869                                ]
870                            }
871                        ]
872                    }))];
873                    assert_eq!(
874                        render_blocks_as_text(
875                            blocks,
876                            SlackReferences {
877                                users: HashMap::from([(
878                                    SlackUserId("user1".to_string()),
879                                    Some("John Doe".to_string())
880                                )]),
881                                ..SlackReferences::default()
882                            }
883                        ),
884                        "@John Doe".to_string()
885                    );
886                }
887            }
888
889            mod usergroup_element {
890                use super::*;
891
892                #[test]
893                fn test_with_usergroup_id() {
894                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
895                        "type": "rich_text",
896                        "elements": [
897                            {
898                                "type": "rich_text_section",
899                                "elements": [
900                                    {
901                                        "type": "usergroup",
902                                        "usergroup_id": "group1"
903                                    }
904                                ]
905                            }
906                        ]
907                    }))];
908                    assert_eq!(
909                        render_blocks_as_text(blocks, SlackReferences::default()),
910                        "@group1".to_string()
911                    );
912                }
913
914                #[test]
915                fn test_with_usergroup_id_and_reference() {
916                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
917                        "type": "rich_text",
918                        "elements": [
919                            {
920                                "type": "rich_text_section",
921                                "elements": [
922                                    {
923                                        "type": "usergroup",
924                                        "usergroup_id": "group1"
925                                    }
926                                ]
927                            }
928                        ]
929                    }))];
930                    assert_eq!(
931                        render_blocks_as_text(
932                            blocks,
933                            SlackReferences {
934                                usergroups: HashMap::from([(
935                                    SlackUserGroupId("group1".to_string()),
936                                    Some("Admins".to_string())
937                                )]),
938                                ..SlackReferences::default()
939                            }
940                        ),
941                        "@Admins".to_string()
942                    );
943                }
944            }
945
946            mod link_element {
947                use super::*;
948
949                #[test]
950                fn test_with_url() {
951                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
952                        "type": "rich_text",
953                        "elements": [
954                            {
955                                "type": "rich_text_section",
956                                "elements": [
957                                    {
958                                        "type": "link",
959                                        "text": "example",
960                                        "url": "https://example.com"
961                                    }
962                                ]
963                            }
964                        ]
965                    }))];
966                    assert_eq!(
967                        render_blocks_as_text(blocks, SlackReferences::default()),
968                        "example".to_string()
969                    );
970                }
971            }
972
973            mod emoji_element {
974                use super::*;
975
976                #[test]
977                fn test_with_emoji() {
978                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
979                        "type": "rich_text",
980                        "elements": [
981                            {
982                                "type": "rich_text_section",
983                                "elements": [
984                                    {
985                                        "type": "emoji",
986                                        "name": "wave"
987                                    }
988                                ]
989                            }
990                        ]
991                    }))];
992                    assert_eq!(
993                        render_blocks_as_text(blocks, SlackReferences::default()),
994                        "👋".to_string()
995                    );
996                }
997
998                #[test]
999                fn test_with_emoji_with_skin_tone() {
1000                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
1001                        "type": "rich_text",
1002                        "elements": [
1003                            {
1004                                "type": "rich_text_section",
1005                                "elements": [
1006                                    {
1007                                        "type": "emoji",
1008                                        "name": "wave::skin-tone-2"
1009                                    }
1010                                ]
1011                            }
1012                        ]
1013                    }))];
1014                    assert_eq!(
1015                        render_blocks_as_text(blocks, SlackReferences::default()),
1016                        "👋🏻".to_string()
1017                    );
1018                }
1019
1020                #[test]
1021                fn test_with_emoji_with_unknown_skin_tone() {
1022                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
1023                        "type": "rich_text",
1024                        "elements": [
1025                            {
1026                                "type": "rich_text_section",
1027                                "elements": [
1028                                    {
1029                                        "type": "emoji",
1030                                        "name": "wave::skin-tone-42"
1031                                    }
1032                                ]
1033                            }
1034                        ]
1035                    }))];
1036                    assert_eq!(
1037                        render_blocks_as_text(blocks, SlackReferences::default()),
1038                        "👋".to_string()
1039                    );
1040                }
1041
1042                #[test]
1043                fn test_with_unknown_emoji() {
1044                    let blocks = vec![SlackBlock::RichText(serde_json::json!({
1045                        "type": "rich_text",
1046                        "elements": [
1047                            {
1048                                "type": "rich_text_section",
1049                                "elements": [
1050                                    {
1051                                        "type": "emoji",
1052                                        "name": "bbb"
1053                                    }
1054                                ]
1055                            }
1056                        ]
1057                    }))];
1058                    assert_eq!(
1059                        render_blocks_as_text(blocks, SlackReferences::default()),
1060                        "".to_string()
1061                    );
1062                }
1063            }
1064        }
1065
1066        mod rich_text_list {
1067            use super::*;
1068
1069            #[test]
1070            fn test_with_ordered_list() {
1071                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1072                    "type": "rich_text",
1073                    "elements": [
1074                        {
1075                            "type": "rich_text_list",
1076                            "style": "ordered",
1077                            "elements": [
1078                                {
1079                                    "type": "rich_text_section",
1080                                    "elements": [
1081                                        {
1082                                            "type": "text",
1083                                            "text": "Text1"
1084                                        }
1085                                    ]
1086                                },
1087                                {
1088                                    "type": "rich_text_section",
1089                                    "elements": [
1090                                        {
1091                                            "type": "text",
1092                                            "text": "Text2"
1093                                        }
1094                                    ]
1095                                }
1096                            ]
1097                         },
1098                    ]
1099                }))];
1100                assert_eq!(
1101                    render_blocks_as_text(blocks, SlackReferences::default()),
1102                    "1. Text1\n1. Text2".to_string()
1103                );
1104            }
1105
1106            #[test]
1107            fn test_with_bullet_list() {
1108                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1109                    "type": "rich_text",
1110                    "elements": [
1111                        {
1112                            "type": "rich_text_list",
1113                            "style": "bullet",
1114                            "elements": [
1115                                {
1116                                    "type": "rich_text_section",
1117                                    "elements": [
1118                                        {
1119                                            "type": "text",
1120                                            "text": "Text1"
1121                                        }
1122                                    ]
1123                                },
1124                                {
1125                                    "type": "rich_text_section",
1126                                    "elements": [
1127                                        {
1128                                            "type": "text",
1129                                            "text": "Text2"
1130                                        }
1131                                    ]
1132                                }
1133                            ]
1134                        },
1135                    ]
1136                }))];
1137                assert_eq!(
1138                    render_blocks_as_text(blocks, SlackReferences::default()),
1139                    "- Text1\n- Text2".to_string()
1140                );
1141            }
1142        }
1143
1144        mod rich_text_preformatted {
1145            use super::*;
1146
1147            #[test]
1148            fn test_with_text() {
1149                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1150                    "type": "rich_text",
1151                    "elements": [
1152                        {
1153                            "type": "rich_text_preformatted",
1154                            "elements": [
1155                                {
1156                                    "type": "text",
1157                                    "text": "Text1"
1158                                },
1159                                {
1160                                    "type": "text",
1161                                    "text": "Text2"
1162                                }
1163                            ]
1164                        },
1165                    ]
1166                }))];
1167                assert_eq!(
1168                    render_blocks_as_text(blocks, SlackReferences::default()),
1169                    "Text1Text2".to_string()
1170                );
1171            }
1172        }
1173
1174        mod rich_text_quote {
1175            use super::*;
1176
1177            #[test]
1178            fn test_with_text() {
1179                let blocks = vec![SlackBlock::RichText(serde_json::json!({
1180                    "type": "rich_text",
1181                    "elements": [
1182                        {
1183                            "type": "rich_text_quote",
1184                            "elements": [
1185                                {
1186                                    "type": "text",
1187                                    "text": "Text1"
1188                                },
1189                                {
1190                                    "type": "text",
1191                                    "text": "Text2"
1192                                }
1193                            ]
1194                        },
1195                    ]
1196                }))];
1197                assert_eq!(
1198                    render_blocks_as_text(blocks, SlackReferences::default()),
1199                    "Text1Text2".to_string()
1200                );
1201            }
1202        }
1203    }
1204}