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
12pub 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")) | 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 let blocks = vec![SlackBlock::Input(SlackInputBlock::new(
338 "label".into(),
339 SlackInputBlockElement::PlainTextInput(
340 SlackBlockPlainTextInputElement::new().with_action_id("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 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 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 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_with_unrendered_blocks() {
415 let blocks: Vec<SlackBlock> = serde_json::from_value(serde_json::json!([
417 { "type": "plan", "title": "Plan", "tasks": [] },
418 { "type": "data_table", "caption": "Caption", "rows": [] },
419 { "type": "some_future_block", "text": "Text" }
420 ]))
421 .unwrap();
422 assert!(matches!(blocks[0], SlackBlock::Plan(_)));
423 assert!(matches!(blocks[2], SlackBlock::Unknown(_)));
424 assert_eq!(
425 render_blocks_as_text(blocks, SlackReferences::default()),
426 "".to_string()
427 );
428 }
429
430 #[test]
431 fn test_with_container() {
432 let blocks = vec![SlackBlock::Container(SlackContainerBlock::new(vec![
433 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
434 SlackBlockPlainText::new("Text".to_string()),
435 ))),
436 ]))];
437 assert_eq!(
438 render_blocks_as_text(blocks, SlackReferences::default()),
439 "Text".to_string()
440 );
441 }
442
443 #[test]
444 fn test_header() {
445 let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
446 assert_eq!(
447 render_blocks_as_text(blocks, SlackReferences::default()),
448 "Text".to_string()
449 );
450 }
451
452 mod section {
453 use super::*;
454
455 #[test]
456 fn test_with_plain_text() {
457 let blocks = vec![
458 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
459 SlackBlockPlainText::new("Text".to_string()),
460 ))),
461 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
462 SlackBlockPlainText::new("Text2".to_string()),
463 ))),
464 ];
465 assert_eq!(
466 render_blocks_as_text(blocks, SlackReferences::default()),
467 "TextText2".to_string()
468 );
469 }
470
471 #[test]
472 fn test_with_markdown() {
473 let blocks = vec![
474 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
475 SlackBlockMarkDownText::new("Text".to_string()),
476 ))),
477 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
478 SlackBlockMarkDownText::new("Text2".to_string()),
479 ))),
480 ];
481 assert_eq!(
482 render_blocks_as_text(blocks, SlackReferences::default()),
483 "TextText2".to_string()
484 );
485 }
486
487 #[test]
488 fn test_with_fields() {
489 let blocks = vec![
490 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
491 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
492 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
493 ])),
494 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
495 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
496 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
497 ])),
498 ];
499 assert_eq!(
500 render_blocks_as_text(blocks, SlackReferences::default()),
501 "Text11Text12Text21Text22".to_string()
502 );
503 }
504
505 #[test]
506 fn test_with_fields_and_text() {
507 let blocks = vec![
508 SlackBlock::Section(
509 SlackSectionBlock::new()
510 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
511 "Text1".to_string(),
512 )))
513 .with_fields(vec![
514 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
515 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
516 ]),
517 ),
518 SlackBlock::Section(
519 SlackSectionBlock::new()
520 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
521 "Text2".to_string(),
522 )))
523 .with_fields(vec![
524 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
525 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
526 ]),
527 ),
528 ];
529 assert_eq!(
530 render_blocks_as_text(blocks, SlackReferences::default()),
531 "Text1Text11Text12Text2Text21Text22".to_string()
532 );
533 }
534 }
535
536 mod context {
537 use super::*;
538
539 #[test]
540 fn test_with_image() {
541 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
542 SlackContextBlockElement::Image(SlackBlockImageElement::new(
543 SlackImageUrlOrFile::ImageUrl {
544 image_url: Url::parse("https://example.com/image.png").unwrap(),
545 },
546 "Image".to_string(),
547 )),
548 SlackContextBlockElement::Image(SlackBlockImageElement::new(
549 SlackImageUrlOrFile::ImageUrl {
550 image_url: Url::parse("https://example.com/image2.png").unwrap(),
551 },
552 "Image2".to_string(),
553 )),
554 ]))];
555 assert_eq!(
556 render_blocks_as_text(blocks, SlackReferences::default()),
557 "".to_string()
558 );
559 }
560
561 #[test]
562 fn test_with_plain_text() {
563 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
564 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
565 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
566 ]))];
567 assert_eq!(
568 render_blocks_as_text(blocks, SlackReferences::default()),
569 "TextText2".to_string()
570 );
571 }
572
573 #[test]
574 fn test_with_markdown() {
575 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
576 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
577 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
578 "Text2".to_string(),
579 )),
580 ]))];
581 assert_eq!(
582 render_blocks_as_text(blocks, SlackReferences::default()),
583 "TextText2".to_string()
584 );
585 }
586 }
587
588 mod rich_text {
589 use super::*;
590
591 #[test]
592 fn test_with_empty_json() {
593 let blocks = vec![
594 rich_text_block(serde_json::json!({})),
595 rich_text_block(serde_json::json!({})),
596 ];
597 assert_eq!(
598 render_blocks_as_text(blocks, SlackReferences::default()),
599 "".to_string()
600 );
601 }
602
603 mod rich_text_section {
604 use super::*;
605
606 mod text_element {
607 use super::*;
608
609 #[test]
610 fn test_with_text() {
611 let blocks = vec![
612 rich_text_block(serde_json::json!({
613 "type": "rich_text",
614 "elements": [
615 {
616 "type": "rich_text_section",
617 "elements": [
618 {
619 "type": "text",
620 "text": "Text111"
621 },
622 {
623 "type": "text",
624 "text": "Text112"
625 }
626 ]
627 },
628 {
629 "type": "rich_text_section",
630 "elements": [
631 {
632 "type": "text",
633 "text": "Text121"
634 },
635 {
636 "type": "text",
637 "text": "Text122"
638 }
639 ]
640 }
641 ]
642 })),
643 rich_text_block(serde_json::json!({
644 "type": "rich_text",
645 "elements": [
646 {
647 "type": "rich_text_section",
648 "elements": [
649 {
650 "type": "text",
651 "text": "Text211"
652 },
653 {
654 "type": "text",
655 "text": "Text212"
656 }
657 ]
658 },
659 {
660 "type": "rich_text_section",
661 "elements": [
662 {
663 "type": "text",
664 "text": "Text221"
665 },
666 {
667 "type": "text",
668 "text": "Text222"
669 }
670 ]
671 }
672 ]
673 })),
674 ];
675 assert_eq!(
676 render_blocks_as_text(blocks, SlackReferences::default()),
677 "Text111Text112Text121Text122Text211Text212Text221Text222".to_string()
678 );
679 }
680
681 #[test]
682 fn test_with_bold_text() {
683 let blocks = vec![rich_text_block(serde_json::json!({
684 "type": "rich_text",
685 "elements": [
686 {
687 "type": "rich_text_section",
688 "elements": [
689 {
690 "type": "text",
691 "text": "Text",
692 "style": {
693 "bold": true
694 }
695 }
696 ]
697 }
698 ]
699 }))];
700 assert_eq!(
701 render_blocks_as_text(blocks, SlackReferences::default()),
702 "Text".to_string()
703 );
704 }
705
706 #[test]
707 fn test_with_italic_text() {
708 let blocks = vec![rich_text_block(serde_json::json!({
709 "type": "rich_text",
710 "elements": [
711 {
712 "type": "rich_text_section",
713 "elements": [
714 {
715 "type": "text",
716 "text": "Text",
717 "style": {
718 "italic": true
719 }
720 }
721 ]
722 }
723 ]
724 }))];
725 assert_eq!(
726 render_blocks_as_text(blocks, SlackReferences::default()),
727 "Text".to_string()
728 );
729 }
730
731 #[test]
732 fn test_with_strike_text() {
733 let blocks = vec![rich_text_block(serde_json::json!({
734 "type": "rich_text",
735 "elements": [
736 {
737 "type": "rich_text_section",
738 "elements": [
739 {
740 "type": "text",
741 "text": "Text",
742 "style": {
743 "strike": true
744 }
745 }
746 ]
747 }
748 ]
749 }))];
750 assert_eq!(
751 render_blocks_as_text(blocks, SlackReferences::default()),
752 "Text".to_string()
753 );
754 }
755
756 #[test]
757 fn test_with_code_text() {
758 let blocks = vec![rich_text_block(serde_json::json!({
759 "type": "rich_text",
760 "elements": [
761 {
762 "type": "rich_text_section",
763 "elements": [
764 {
765 "type": "text",
766 "text": "Text",
767 "style": {
768 "code": true
769 }
770 }
771 ]
772 }
773 ]
774 }))];
775 assert_eq!(
776 render_blocks_as_text(blocks, SlackReferences::default()),
777 "Text".to_string()
778 );
779 }
780
781 #[test]
782 fn test_with_styled_text() {
783 let blocks = vec![rich_text_block(serde_json::json!({
784 "type": "rich_text",
785 "elements": [
786 {
787 "type": "rich_text_section",
788 "elements": [
789 {
790 "type": "text",
791 "text": "Text",
792 "style": {
793 "bold": true,
794 "italic": true,
795 "strike": true
796 }
797 }
798 ]
799 }
800 ]
801 }))];
802 assert_eq!(
803 render_blocks_as_text(blocks, SlackReferences::default()),
804 "Text".to_string()
805 );
806 }
807 }
808
809 mod channel_element {
810 use super::*;
811
812 #[test]
813 fn test_with_channel_id() {
814 let blocks = vec![rich_text_block(serde_json::json!({
815 "type": "rich_text",
816 "elements": [
817 {
818 "type": "rich_text_section",
819 "elements": [
820 {
821 "type": "channel",
822 "channel_id": "C0123456"
823 }
824 ]
825 }
826 ]
827 }))];
828 assert_eq!(
829 render_blocks_as_text(blocks, SlackReferences::default()),
830 "#C0123456".to_string()
831 );
832 }
833
834 #[test]
835 fn test_with_channel_id_and_reference() {
836 let blocks = vec![rich_text_block(serde_json::json!({
837 "type": "rich_text",
838 "elements": [
839 {
840 "type": "rich_text_section",
841 "elements": [
842 {
843 "type": "channel",
844 "channel_id": "C0123456"
845 }
846 ]
847 }
848 ]
849 }))];
850 assert_eq!(
851 render_blocks_as_text(
852 blocks,
853 SlackReferences {
854 channels: HashMap::from([(
855 SlackChannelId("C0123456".to_string()),
856 Some("general".to_string())
857 )]),
858 ..SlackReferences::default()
859 }
860 ),
861 "#general".to_string()
862 );
863 }
864 }
865
866 mod user_element {
867 use super::*;
868
869 #[test]
870 fn test_with_user_id() {
871 let blocks = vec![rich_text_block(serde_json::json!({
872 "type": "rich_text",
873 "elements": [
874 {
875 "type": "rich_text_section",
876 "elements": [
877 {
878 "type": "user",
879 "user_id": "user1"
880 }
881 ]
882 }
883 ]
884 }))];
885 assert_eq!(
886 render_blocks_as_text(blocks, SlackReferences::default()),
887 "@user1".to_string()
888 );
889 }
890
891 #[test]
892 fn test_with_user_id_and_reference() {
893 let blocks = vec![rich_text_block(serde_json::json!({
894 "type": "rich_text",
895 "elements": [
896 {
897 "type": "rich_text_section",
898 "elements": [
899 {
900 "type": "user",
901 "user_id": "user1"
902 }
903 ]
904 }
905 ]
906 }))];
907 assert_eq!(
908 render_blocks_as_text(
909 blocks,
910 SlackReferences {
911 users: HashMap::from([(
912 SlackUserId("user1".to_string()),
913 Some("John Doe".to_string())
914 )]),
915 ..SlackReferences::default()
916 }
917 ),
918 "@John Doe".to_string()
919 );
920 }
921 }
922
923 mod usergroup_element {
924 use super::*;
925
926 #[test]
927 fn test_with_usergroup_id() {
928 let blocks = vec![rich_text_block(serde_json::json!({
929 "type": "rich_text",
930 "elements": [
931 {
932 "type": "rich_text_section",
933 "elements": [
934 {
935 "type": "usergroup",
936 "usergroup_id": "group1"
937 }
938 ]
939 }
940 ]
941 }))];
942 assert_eq!(
943 render_blocks_as_text(blocks, SlackReferences::default()),
944 "@group1".to_string()
945 );
946 }
947
948 #[test]
949 fn test_with_usergroup_id_and_reference() {
950 let blocks = vec![rich_text_block(serde_json::json!({
951 "type": "rich_text",
952 "elements": [
953 {
954 "type": "rich_text_section",
955 "elements": [
956 {
957 "type": "usergroup",
958 "usergroup_id": "group1"
959 }
960 ]
961 }
962 ]
963 }))];
964 assert_eq!(
965 render_blocks_as_text(
966 blocks,
967 SlackReferences {
968 usergroups: HashMap::from([(
969 SlackUserGroupId("group1".to_string()),
970 Some("Admins".to_string())
971 )]),
972 ..SlackReferences::default()
973 }
974 ),
975 "@Admins".to_string()
976 );
977 }
978 }
979
980 mod link_element {
981 use super::*;
982
983 #[test]
984 fn test_with_url() {
985 let blocks = vec![rich_text_block(serde_json::json!({
986 "type": "rich_text",
987 "elements": [
988 {
989 "type": "rich_text_section",
990 "elements": [
991 {
992 "type": "link",
993 "text": "example",
994 "url": "https://example.com"
995 }
996 ]
997 }
998 ]
999 }))];
1000 assert_eq!(
1001 render_blocks_as_text(blocks, SlackReferences::default()),
1002 "example".to_string()
1003 );
1004 }
1005 }
1006
1007 mod attachment_mention_element {
1008 use super::*;
1009
1010 #[test]
1011 fn test_with_text() {
1012 let blocks = vec![rich_text_block(serde_json::json!({
1013 "type": "rich_text",
1014 "elements": [
1015 {
1016 "type": "rich_text_section",
1017 "elements": [
1018 {
1019 "type": "attachment_mention",
1020 "url": "https://docs.google.com/document/d/1abc/edit?tab=t.0",
1021 "text": "Project plan",
1022 "product_name": "Google Docs"
1023 }
1024 ]
1025 }
1026 ]
1027 }))];
1028 assert_eq!(
1029 render_blocks_as_text(blocks, SlackReferences::default()),
1030 "Project plan".to_string()
1031 );
1032 }
1033 }
1034
1035 mod message_mention_element {
1036 use super::*;
1037
1038 #[test]
1039 fn test_with_text() {
1040 let blocks = vec![rich_text_block(serde_json::json!({
1041 "type": "rich_text",
1042 "elements": [
1043 {
1044 "type": "rich_text_section",
1045 "elements": [
1046 {
1047 "type": "message_mention",
1048 "url": "https://slack.com/archives/C123/p1788850799491249",
1049 "text": "a previous message"
1050 }
1051 ]
1052 }
1053 ]
1054 }))];
1055 assert_eq!(
1056 render_blocks_as_text(blocks, SlackReferences::default()),
1057 "a previous message".to_string()
1058 );
1059 }
1060 }
1061
1062 mod emoji_element {
1063 use super::*;
1064
1065 #[test]
1066 fn test_with_emoji() {
1067 let blocks = vec![rich_text_block(serde_json::json!({
1068 "type": "rich_text",
1069 "elements": [
1070 {
1071 "type": "rich_text_section",
1072 "elements": [
1073 {
1074 "type": "emoji",
1075 "name": "wave"
1076 }
1077 ]
1078 }
1079 ]
1080 }))];
1081 assert_eq!(
1082 render_blocks_as_text(blocks, SlackReferences::default()),
1083 "👋".to_string()
1084 );
1085 }
1086
1087 #[test]
1088 fn test_with_emoji_with_skin_tone() {
1089 let blocks = vec![rich_text_block(serde_json::json!({
1090 "type": "rich_text",
1091 "elements": [
1092 {
1093 "type": "rich_text_section",
1094 "elements": [
1095 {
1096 "type": "emoji",
1097 "name": "wave::skin-tone-2"
1098 }
1099 ]
1100 }
1101 ]
1102 }))];
1103 assert_eq!(
1104 render_blocks_as_text(blocks, SlackReferences::default()),
1105 "👋🏻".to_string()
1106 );
1107 }
1108
1109 #[test]
1110 fn test_with_emoji_with_unknown_skin_tone() {
1111 let blocks = vec![rich_text_block(serde_json::json!({
1112 "type": "rich_text",
1113 "elements": [
1114 {
1115 "type": "rich_text_section",
1116 "elements": [
1117 {
1118 "type": "emoji",
1119 "name": "wave::skin-tone-42"
1120 }
1121 ]
1122 }
1123 ]
1124 }))];
1125 assert_eq!(
1126 render_blocks_as_text(blocks, SlackReferences::default()),
1127 "👋".to_string()
1128 );
1129 }
1130
1131 #[test]
1132 fn test_with_unknown_emoji() {
1133 let blocks = vec![rich_text_block(serde_json::json!({
1134 "type": "rich_text",
1135 "elements": [
1136 {
1137 "type": "rich_text_section",
1138 "elements": [
1139 {
1140 "type": "emoji",
1141 "name": "bbb"
1142 }
1143 ]
1144 }
1145 ]
1146 }))];
1147 assert_eq!(
1148 render_blocks_as_text(blocks, SlackReferences::default()),
1149 "".to_string()
1150 );
1151 }
1152 }
1153 }
1154
1155 mod rich_text_list {
1156 use super::*;
1157
1158 #[test]
1159 fn test_with_ordered_list() {
1160 let blocks = vec![rich_text_block(serde_json::json!({
1161 "type": "rich_text",
1162 "elements": [
1163 {
1164 "type": "rich_text_list",
1165 "style": "ordered",
1166 "elements": [
1167 {
1168 "type": "rich_text_section",
1169 "elements": [
1170 {
1171 "type": "text",
1172 "text": "Text1"
1173 }
1174 ]
1175 },
1176 {
1177 "type": "rich_text_section",
1178 "elements": [
1179 {
1180 "type": "text",
1181 "text": "Text2"
1182 }
1183 ]
1184 }
1185 ]
1186 },
1187 ]
1188 }))];
1189 assert_eq!(
1190 render_blocks_as_text(blocks, SlackReferences::default()),
1191 "1. Text1\n1. Text2".to_string()
1192 );
1193 }
1194
1195 #[test]
1196 fn test_with_bullet_list() {
1197 let blocks = vec![rich_text_block(serde_json::json!({
1198 "type": "rich_text",
1199 "elements": [
1200 {
1201 "type": "rich_text_list",
1202 "style": "bullet",
1203 "elements": [
1204 {
1205 "type": "rich_text_section",
1206 "elements": [
1207 {
1208 "type": "text",
1209 "text": "Text1"
1210 }
1211 ]
1212 },
1213 {
1214 "type": "rich_text_section",
1215 "elements": [
1216 {
1217 "type": "text",
1218 "text": "Text2"
1219 }
1220 ]
1221 }
1222 ]
1223 },
1224 ]
1225 }))];
1226 assert_eq!(
1227 render_blocks_as_text(blocks, SlackReferences::default()),
1228 "- Text1\n- Text2".to_string()
1229 );
1230 }
1231 }
1232
1233 mod rich_text_preformatted {
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_preformatted",
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 mod rich_text_quote {
1264 use super::*;
1265
1266 #[test]
1267 fn test_with_text() {
1268 let blocks = vec![rich_text_block(serde_json::json!({
1269 "type": "rich_text",
1270 "elements": [
1271 {
1272 "type": "rich_text_quote",
1273 "elements": [
1274 {
1275 "type": "text",
1276 "text": "Text1"
1277 },
1278 {
1279 "type": "text",
1280 "text": "Text2"
1281 }
1282 ]
1283 },
1284 ]
1285 }))];
1286 assert_eq!(
1287 render_blocks_as_text(blocks, SlackReferences::default()),
1288 "Text1Text2".to_string()
1289 );
1290 }
1291 }
1292 }
1293}