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