Skip to main content

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