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(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 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_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}