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