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