1use serde_json::Value;
2use slack_morphism::prelude::*;
3
4use crate::{
5 references::SlackReferences,
6 visitor::{
7 visit_slack_block_image_element, visit_slack_block_mark_down_text,
8 visit_slack_block_plain_text, visit_slack_context_block, visit_slack_divider_block,
9 visit_slack_header_block, visit_slack_image_block, visit_slack_markdown_block,
10 visit_slack_section_block, visit_slack_video_block, SlackRichTextBlock, Visitor,
11 },
12};
13
14pub fn render_blocks_as_markdown(
17 blocks: Vec<SlackBlock>,
18 slack_references: SlackReferences,
19 handle_delimiter: Option<String>,
20) -> String {
21 let mut block_renderer = MarkdownRenderer::new(slack_references, handle_delimiter);
22 for block in blocks {
23 block_renderer.visit_slack_block(&block);
24 }
25 block_renderer.sub_texts.join("\n")
26}
27
28struct MarkdownRenderer {
29 pub sub_texts: Vec<String>,
30 pub slack_references: SlackReferences,
31 pub handle_delimiter: Option<String>,
32}
33
34impl MarkdownRenderer {
35 pub fn new(slack_references: SlackReferences, handle_delimiter: Option<String>) -> Self {
36 MarkdownRenderer {
37 sub_texts: vec![],
38 slack_references,
39 handle_delimiter,
40 }
41 }
42}
43
44fn join(mut texts: Vec<String>, join_str: &str) -> String {
45 for i in 0..texts.len() {
46 if i < texts.len() - 1 {
47 if texts[i].ends_with('`') && texts[i + 1].starts_with('`') {
48 texts[i].pop();
49 texts[i + 1].remove(0);
50 }
51 if texts[i].ends_with('~') && texts[i + 1].starts_with('~') {
52 texts[i].pop();
53 texts[i + 1].remove(0);
54 }
55 if texts[i].ends_with('_') && texts[i + 1].starts_with('_') {
56 texts[i].pop();
57 texts[i + 1].remove(0);
58 }
59 if texts[i].ends_with('*') && texts[i + 1].starts_with('*') {
60 texts[i].pop();
61 texts[i + 1].remove(0);
62 }
63 if texts[i].starts_with("> ") && !texts[i + 1].starts_with("> ") {
64 texts[i].push('\n');
65 }
66 }
67 }
68 texts.join(join_str)
69}
70
71impl Visitor for MarkdownRenderer {
72 fn visit_slack_section_block(&mut self, slack_section_block: &SlackSectionBlock) {
73 let mut section_renderer =
74 MarkdownRenderer::new(self.slack_references.clone(), self.handle_delimiter.clone());
75 visit_slack_section_block(&mut section_renderer, slack_section_block);
76 self.sub_texts.push(join(section_renderer.sub_texts, ""));
77 }
78
79 fn visit_slack_block_plain_text(&mut self, slack_block_plain_text: &SlackBlockPlainText) {
80 self.sub_texts.push(slack_block_plain_text.text.clone());
81 visit_slack_block_plain_text(self, slack_block_plain_text);
82 }
83
84 fn visit_slack_header_block(&mut self, slack_header_block: &SlackHeaderBlock) {
85 let mut header_renderer =
86 MarkdownRenderer::new(self.slack_references.clone(), self.handle_delimiter.clone());
87 visit_slack_header_block(&mut header_renderer, slack_header_block);
88 self.sub_texts
89 .push(format!("## {}", join(header_renderer.sub_texts, "")));
90 }
91
92 fn visit_slack_divider_block(&mut self, slack_divider_block: &SlackDividerBlock) {
93 self.sub_texts.push("---\n".to_string());
94 visit_slack_divider_block(self, slack_divider_block);
95 }
96
97 fn visit_slack_image_block(&mut self, slack_image_block: &SlackImageBlock) {
98 if let Some(image_url) = slack_image_block.image_url_or_file.image_url() {
99 self.sub_texts
100 .push(format!("", slack_image_block.alt_text, image_url));
101 }
102 visit_slack_image_block(self, slack_image_block);
103 }
104
105 fn visit_slack_block_image_element(
106 &mut self,
107 slack_block_image_element: &SlackBlockImageElement,
108 ) {
109 if let Some(image_url) = slack_block_image_element.image_url_or_file.image_url() {
110 self.sub_texts.push(format!(
111 "",
112 slack_block_image_element.alt_text, image_url
113 ));
114 }
115 visit_slack_block_image_element(self, slack_block_image_element);
116 }
117
118 fn visit_slack_block_mark_down_text(
119 &mut self,
120 slack_block_mark_down_text: &SlackBlockMarkDownText,
121 ) {
122 self.sub_texts.push(slack_block_mark_down_text.text.clone());
123 visit_slack_block_mark_down_text(self, slack_block_mark_down_text);
124 }
125
126 fn visit_slack_context_block(&mut self, slack_context_block: &SlackContextBlock) {
127 let mut section_renderer =
128 MarkdownRenderer::new(self.slack_references.clone(), self.handle_delimiter.clone());
129 visit_slack_context_block(&mut section_renderer, slack_context_block);
130 self.sub_texts.push(section_renderer.sub_texts.join(""));
131 }
132
133 fn visit_slack_rich_text_block(&mut self, slack_rich_text_block: &SlackRichTextBlock) {
134 self.sub_texts.push(render_rich_text_block_as_markdown(
135 slack_rich_text_block.json_value.clone(),
136 self,
137 ));
138 }
139
140 fn visit_slack_video_block(&mut self, slack_video_block: &SlackVideoBlock) {
141 let title: SlackBlockText = slack_video_block.title.clone().into();
142 let title = match title {
143 SlackBlockText::Plain(plain_text) => plain_text.text,
144 SlackBlockText::MarkDown(md_text) => md_text.text,
145 };
146 if let Some(ref title_url) = slack_video_block.title_url {
147 self.sub_texts
148 .push(format!("*[{}]({})*\n", title, title_url));
149 } else {
150 self.sub_texts.push(format!("*{}*\n", title));
151 }
152
153 if let Some(description) = slack_video_block.description.clone() {
154 let description: SlackBlockText = description.into();
155 let description = match description {
156 SlackBlockText::Plain(plain_text) => plain_text.text,
157 SlackBlockText::MarkDown(md_text) => md_text.text,
158 };
159 self.sub_texts.push(format!("{}\n", description));
160 }
161
162 self.sub_texts.push(format!(
163 "",
164 slack_video_block.alt_text, slack_video_block.thumbnail_url
165 ));
166
167 visit_slack_video_block(self, slack_video_block);
168 }
169
170 fn visit_slack_markdown_block(&mut self, slack_markdown_block: &SlackMarkdownBlock) {
171 self.sub_texts.push(slack_markdown_block.text.clone());
172 visit_slack_markdown_block(self, slack_markdown_block);
173 }
174}
175
176fn render_rich_text_block_as_markdown(
177 json_value: serde_json::Value,
178 renderer: &MarkdownRenderer,
179) -> String {
180 match json_value.get("elements") {
181 Some(serde_json::Value::Array(elements)) => join(
182 elements
183 .iter()
184 .map(|element| {
185 match (
186 element.get("type").map(|t| t.as_str()),
187 element.get("style"),
188 element.get("elements"),
189 element.get("indent"),
190 ) {
191 (
192 Some(Some("rich_text_section")),
193 _,
194 Some(serde_json::Value::Array(elements)),
195 _,
196 ) => render_rich_text_section_elements(elements, renderer, true),
197 (
198 Some(Some("rich_text_list")),
199 Some(serde_json::Value::String(style)),
200 Some(serde_json::Value::Array(elements)),
201 Some(serde_json::Value::Number(indent)),
202 ) => render_rich_text_list_elements(
203 elements,
204 style,
205 indent
206 .as_u64()
207 .unwrap_or_default()
208 .try_into()
209 .unwrap_or_default(),
210 renderer,
211 ),
212 (
213 Some(Some("rich_text_list")),
214 Some(serde_json::Value::String(style)),
215 Some(serde_json::Value::Array(elements)),
216 _,
217 ) => render_rich_text_list_elements(elements, style, 0, renderer),
218 (
219 Some(Some("rich_text_preformatted")),
220 _,
221 Some(serde_json::Value::Array(elements)),
222 _,
223 ) => render_rich_text_preformatted_elements(elements, renderer),
224
225 (
226 Some(Some("rich_text_quote")),
227 _,
228 Some(serde_json::Value::Array(elements)),
229 _,
230 ) => render_rich_text_quote_elements(elements, renderer),
231
232 _ => "".to_string(),
233 }
234 })
235 .collect::<Vec<String>>(),
236 "\n",
237 ),
238 _ => "".to_string(),
239 }
240}
241
242fn render_rich_text_section_elements(
243 elements: &[serde_json::Value],
244 renderer: &MarkdownRenderer,
245 fix_newlines_in_text: bool,
246) -> String {
247 let result = join(
248 elements
249 .iter()
250 .map(|e| render_rich_text_section_element(e, renderer))
251 .collect::<Vec<String>>(),
252 "",
253 );
254 if fix_newlines_in_text {
255 fix_newlines(result)
256 } else {
257 result
258 }
259}
260
261fn render_rich_text_list_elements(
262 elements: &[serde_json::Value],
263 style: &str,
264 indent: usize,
265 renderer: &MarkdownRenderer,
266) -> String {
267 let list_style = if style == "ordered" { "1." } else { "-" };
268 let space_per_level = if style == "ordered" { 3 } else { 2 };
269 let indent_prefix = " ".repeat(space_per_level * indent);
270 elements
271 .iter()
272 .filter_map(|element| {
273 if let Some(serde_json::Value::Array(elements)) = element.get("elements") {
274 Some(render_rich_text_section_elements(elements, renderer, true))
275 } else {
276 None
277 }
278 })
279 .map(|element| format!("{indent_prefix}{list_style} {element}"))
280 .collect::<Vec<String>>()
281 .join("\n")
282}
283
284fn render_rich_text_preformatted_elements(
285 elements: &[serde_json::Value],
286 renderer: &MarkdownRenderer,
287) -> String {
288 format!(
289 "```\n{}\n```",
290 render_rich_text_section_elements(elements, renderer, false)
291 )
292}
293
294fn render_rich_text_quote_elements(
295 elements: &[serde_json::Value],
296 renderer: &MarkdownRenderer,
297) -> String {
298 format!(
299 "> {}",
300 render_rich_text_section_elements(elements, renderer, true)
301 )
302}
303
304fn render_rich_text_section_element(
305 element: &serde_json::Value,
306 renderer: &MarkdownRenderer,
307) -> String {
308 let handle_delimiter = renderer.handle_delimiter.clone().unwrap_or_default();
309 match element.get("type").map(|t| t.as_str()) {
310 Some(Some("text")) => {
311 let Some(serde_json::Value::String(text)) = element.get("text") else {
312 return "".to_string();
313 };
314 let style = element.get("style");
315 apply_all_styles(text.to_string(), style)
316 }
317 Some(Some("channel")) => {
318 let Some(serde_json::Value::String(channel_id)) = element.get("channel_id") else {
319 return "".to_string();
320 };
321 let channel_rendered = if let Some(Some(channel_name)) = renderer
322 .slack_references
323 .channels
324 .get(&SlackChannelId(channel_id.clone()))
325 {
326 channel_name
327 } else {
328 channel_id
329 };
330 let style = element.get("style");
331 apply_all_styles(format!("#{channel_rendered}"), style)
332 }
333 Some(Some("user")) => {
334 let Some(serde_json::Value::String(user_id)) = element.get("user_id") else {
335 return "".to_string();
336 };
337 let user_rendered = if let Some(Some(user_name)) = renderer
338 .slack_references
339 .users
340 .get(&SlackUserId(user_id.clone()))
341 {
342 user_name
343 } else {
344 user_id
345 };
346 let style = element.get("style");
347 apply_all_styles(
348 format!("{handle_delimiter}@{user_rendered}{handle_delimiter}"),
349 style,
350 )
351 }
352 Some(Some("usergroup")) => {
353 let Some(serde_json::Value::String(usergroup_id)) = element.get("usergroup_id") else {
354 return "".to_string();
355 };
356 let usergroup_rendered = if let Some(Some(usergroup_name)) = renderer
357 .slack_references
358 .usergroups
359 .get(&SlackUserGroupId(usergroup_id.clone()))
360 {
361 usergroup_name
362 } else {
363 usergroup_id
364 };
365 let style = element.get("style");
366 apply_all_styles(
367 format!("{handle_delimiter}@{usergroup_rendered}{handle_delimiter}"),
368 style,
369 )
370 }
371 Some(Some("emoji")) => {
372 let Some(serde_json::Value::String(name)) = element.get("name") else {
373 return "".to_string();
374 };
375 let style = element.get("style");
376 render_emoji(
377 &SlackEmojiName(name.to_string()),
378 &renderer.slack_references,
379 style,
380 )
381 }
382 Some(Some("link")) | Some(Some("attachment_mention")) | Some(Some("message_mention")) => {
386 let Some(serde_json::Value::String(url)) = element.get("url") else {
387 return "".to_string();
388 };
389 let Some(serde_json::Value::String(text)) = element.get("text") else {
390 return render_url_as_markdown(url, url);
391 };
392 let style = element.get("style");
393 apply_all_styles(render_url_as_markdown(url, text), style)
394 }
395 _ => "".to_string(),
396 }
397}
398
399fn render_url_as_markdown(url: &str, text: &str) -> String {
400 format!("[{}]({})", text, url)
401}
402
403fn render_emoji(
404 emoji_name: &SlackEmojiName,
405 slack_references: &SlackReferences,
406 style: Option<&Value>,
407) -> String {
408 if let Some(Some(emoji)) = slack_references.emojis.get(emoji_name) {
409 match emoji {
410 SlackEmojiRef::Alias(alias) => {
411 return render_emoji(alias, slack_references, style);
412 }
413 SlackEmojiRef::Url(url) => {
414 return apply_all_styles(format!("", emoji_name.0, url), style);
415 }
416 }
417 }
418 let name = &emoji_name.0;
419
420 let splitted = name.split("::skin-tone-").collect::<Vec<&str>>();
421 let Some(first) = splitted.first() else {
422 return apply_all_styles(format!(":{}:", name), style);
423 };
424 let Some(emoji) = emojis::get_by_shortcode(first) else {
425 return apply_all_styles(format!(":{}:", name), style);
426 };
427 let Some(skin_tone) = splitted.get(1).and_then(|s| s.parse::<usize>().ok()) else {
428 return apply_all_styles(emoji.to_string(), style);
429 };
430 let Some(mut skin_tones) = emoji.skin_tones() else {
431 return apply_all_styles(emoji.to_string(), style);
432 };
433 let Some(skinned_emoji) = skin_tones.nth(skin_tone - 1) else {
434 return apply_all_styles(emoji.to_string(), style);
435 };
436 apply_all_styles(skinned_emoji.to_string(), style)
437}
438
439fn apply_all_styles(text: String, style: Option<&serde_json::Value>) -> String {
440 let text = apply_bold_style(text, style);
441 let text = apply_italic_style(text, style);
442 let text = apply_strike_style(text, style);
443 apply_code_style(text, style)
444}
445
446fn apply_bold_style(text: String, style: Option<&serde_json::Value>) -> String {
447 if is_styled(style, "bold") {
448 format!("*{}*", text)
449 } else {
450 text
451 }
452}
453
454fn apply_italic_style(text: String, style: Option<&serde_json::Value>) -> String {
455 if is_styled(style, "italic") {
456 format!("_{}_", text)
457 } else {
458 text
459 }
460}
461
462fn apply_strike_style(text: String, style: Option<&serde_json::Value>) -> String {
463 if is_styled(style, "strike") {
464 format!("~{}~", text)
465 } else {
466 text
467 }
468}
469
470fn apply_code_style(text: String, style: Option<&serde_json::Value>) -> String {
471 if is_styled(style, "code") {
472 format!("`{}`", text)
473 } else {
474 text
475 }
476}
477
478fn is_styled(style: Option<&serde_json::Value>, style_name: &str) -> bool {
479 style
480 .and_then(|s| s.get(style_name).map(|b| b.as_bool()))
481 .flatten()
482 .unwrap_or_default()
483}
484
485fn fix_newlines(text: String) -> String {
486 text.replace("\n", "\\\n")
487 .trim_end_matches("\\\n")
488 .to_string()
489}
490
491#[cfg(test)]
492mod tests {
493 use std::collections::HashMap;
494
495 use url::Url;
496
497 use super::*;
498 use crate::test_utils::rich_text_block;
499
500 #[test]
501 fn test_empty_input() {
502 assert_eq!(
503 render_blocks_as_markdown(vec![], SlackReferences::default(), None),
504 "".to_string()
505 );
506 }
507
508 #[test]
509 fn test_with_image() {
510 let blocks = vec![
511 SlackBlock::Image(SlackImageBlock::new(
512 SlackImageUrlOrFile::ImageUrl {
513 image_url: Url::parse("https://example.com/image.png").unwrap(),
514 },
515 "Image".to_string(),
516 )),
517 SlackBlock::Image(SlackImageBlock::new(
518 SlackImageUrlOrFile::ImageUrl {
519 image_url: Url::parse("https://example.com/image2.png").unwrap(),
520 },
521 "Image2".to_string(),
522 )),
523 ];
524 assert_eq!(
525 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
526 "\n"
527 .to_string()
528 );
529 }
530
531 #[test]
532 fn test_with_divider() {
533 let blocks = vec![
534 SlackBlock::Divider(SlackDividerBlock::new()),
535 SlackBlock::Divider(SlackDividerBlock::new()),
536 ];
537 assert_eq!(
538 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
539 "---\n\n---\n".to_string()
540 );
541 }
542
543 #[test]
544 fn test_with_input() {
545 let blocks = vec![SlackBlock::Input(SlackInputBlock::new(
547 "label".into(),
548 SlackInputBlockElement::PlainTextInput(SlackBlockPlainTextInputElement::new(
549 "id".into(),
550 )),
551 ))];
552 assert_eq!(
553 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
554 "".to_string()
555 );
556 }
557
558 #[test]
559 fn test_with_action() {
560 let blocks = vec![SlackBlock::Actions(SlackActionsBlock::new(vec![]))];
562 assert_eq!(
563 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
564 "".to_string()
565 );
566 }
567
568 #[test]
569 fn test_with_file() {
570 let blocks = vec![SlackBlock::File(SlackFileBlock::new("external_id".into()))];
572 assert_eq!(
573 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
574 "".to_string()
575 );
576 }
577
578 #[test]
579 fn test_with_video() {
580 let blocks = vec![SlackBlock::Video(
581 SlackVideoBlock::new(
582 "alt text".into(),
583 "Video title".into(),
584 "https://example.com/thumbnail.jpg".parse().unwrap(),
585 "https://example.com/video_embed.avi".parse().unwrap(),
586 )
587 .with_description("Video description".into())
588 .with_title_url("https://example.com/video".parse().unwrap()),
589 )];
590 assert_eq!(
591 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
592 r#"*[Video title](https://example.com/video)*
593
594Video description
595
596"#
597 .to_string()
598 );
599 }
600
601 #[test]
602 fn test_with_video_minimal() {
603 let blocks = vec![SlackBlock::Video(SlackVideoBlock::new(
604 "alt text".into(),
605 "Video title".into(),
606 "https://example.com/thumbnail.jpg".parse().unwrap(),
607 "https://example.com/video_embed.avi".parse().unwrap(),
608 ))];
609 assert_eq!(
610 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
611 r#"*Video title*
612
613"#
614 .to_string()
615 );
616 }
617
618 #[test]
619 fn test_with_event() {
620 let blocks = vec![SlackBlock::Event(serde_json::json!({}))];
622 assert_eq!(
623 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
624 "".to_string()
625 );
626 }
627
628 #[test]
629 fn test_header() {
630 let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
631 assert_eq!(
632 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
633 "## Text".to_string()
634 );
635 }
636
637 mod section {
638 use super::*;
639
640 #[test]
641 fn test_with_plain_text() {
642 let blocks = vec![
643 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
644 SlackBlockPlainText::new("Text".to_string()),
645 ))),
646 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
647 SlackBlockPlainText::new("Text2".to_string()),
648 ))),
649 ];
650 assert_eq!(
651 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
652 "Text\nText2".to_string()
653 );
654 }
655
656 #[test]
657 fn test_with_markdown() {
658 let blocks = vec![
659 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
660 SlackBlockMarkDownText::new("Text".to_string()),
661 ))),
662 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
663 SlackBlockMarkDownText::new("Text2".to_string()),
664 ))),
665 ];
666 assert_eq!(
667 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
668 "Text\nText2".to_string()
669 );
670 }
671
672 #[test]
673 fn test_with_fields() {
674 let blocks = vec![
675 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
676 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
677 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
678 ])),
679 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
680 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
681 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
682 ])),
683 ];
684 assert_eq!(
685 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
686 "Text11Text12\nText21Text22".to_string()
687 );
688 }
689
690 #[test]
691 fn test_with_fields_and_text() {
692 let blocks = vec![
693 SlackBlock::Section(
694 SlackSectionBlock::new()
695 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
696 "Text1".to_string(),
697 )))
698 .with_fields(vec![
699 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
700 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
701 ]),
702 ),
703 SlackBlock::Section(
704 SlackSectionBlock::new()
705 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
706 "Text2".to_string(),
707 )))
708 .with_fields(vec![
709 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
710 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
711 ]),
712 ),
713 ];
714 assert_eq!(
715 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
716 "Text1Text11Text12\nText2Text21Text22".to_string()
717 );
718 }
719 }
720
721 mod context {
722 use super::*;
723
724 #[test]
725 fn test_with_image() {
726 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
727 SlackContextBlockElement::Image(SlackBlockImageElement::new(
728 SlackImageUrlOrFile::ImageUrl {
729 image_url: Url::parse("https://example.com/image.png").unwrap(),
730 },
731 "Image".to_string(),
732 )),
733 SlackContextBlockElement::Image(SlackBlockImageElement::new(
734 SlackImageUrlOrFile::ImageUrl {
735 image_url: Url::parse("https://example.com/image2.png").unwrap(),
736 },
737 "Image2".to_string(),
738 )),
739 ]))];
740 assert_eq!(
741 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
742 ""
743 .to_string()
744 );
745 }
746
747 #[test]
748 fn test_with_plain_text() {
749 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
750 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
751 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
752 ]))];
753 assert_eq!(
754 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
755 "TextText2".to_string()
756 );
757 }
758
759 #[test]
760 fn test_with_markdown() {
761 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
762 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
763 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
764 "Text2".to_string(),
765 )),
766 ]))];
767 assert_eq!(
768 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
769 "TextText2".to_string()
770 );
771 }
772 }
773
774 mod rich_text {
775 use super::*;
776
777 #[test]
778 fn test_with_empty_json() {
779 let blocks = vec![
780 rich_text_block(serde_json::json!({})),
781 rich_text_block(serde_json::json!({})),
782 ];
783 assert_eq!(
784 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
785 "\n".to_string()
786 );
787 }
788
789 mod rich_text_section {
790 use super::*;
791
792 mod text_element {
793 use super::*;
794
795 #[test]
796 fn test_with_text() {
797 let blocks = vec![
798 rich_text_block(serde_json::json!({
799 "type": "rich_text",
800 "elements": [
801 {
802 "type": "rich_text_section",
803 "elements": [
804 {
805 "type": "text",
806 "text": "Text111"
807 },
808 {
809 "type": "text",
810 "text": "Text112"
811 }
812 ]
813 },
814 {
815 "type": "rich_text_section",
816 "elements": [
817 {
818 "type": "text",
819 "text": "Text121"
820 },
821 {
822 "type": "text",
823 "text": "Text122"
824 }
825 ]
826 }
827 ]
828 })),
829 rich_text_block(serde_json::json!({
830 "type": "rich_text",
831 "elements": [
832 {
833 "type": "rich_text_section",
834 "elements": [
835 {
836 "type": "text",
837 "text": "Text211"
838 },
839 {
840 "type": "text",
841 "text": "Text212"
842 }
843 ]
844 },
845 {
846 "type": "rich_text_section",
847 "elements": [
848 {
849 "type": "text",
850 "text": "Text221"
851 },
852 {
853 "type": "text",
854 "text": "Text222"
855 }
856 ]
857 }
858 ]
859 })),
860 ];
861 assert_eq!(
862 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
863 "Text111Text112\nText121Text122\nText211Text212\nText221Text222"
864 .to_string()
865 );
866 }
867
868 #[test]
869 fn test_with_text_with_newline() {
870 let blocks = vec![rich_text_block(serde_json::json!({
871 "type": "rich_text",
872 "elements": [
873 {
874 "type": "rich_text_section",
875 "elements": [
876 {
877 "type": "text",
878 "text": "Text111\nText112\n"
879 },
880 {
881 "type": "text",
882 "text": "Text211\nText212\n"
883 }
884 ]
885 }
886 ]
887 }))];
888 assert_eq!(
889 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
890 "Text111\\\nText112\\\nText211\\\nText212".to_string()
891 );
892 }
893
894 #[test]
895 fn test_with_text_with_newline_char() {
896 let blocks = vec![rich_text_block(serde_json::json!({
897 "type": "rich_text",
898 "elements": [
899 {
900 "type": "rich_text_section",
901 "elements": [
902 {
903 "type": "text",
904 "text": "Text111\\nText112"
905 }
906 ]
907 }
908 ]
909 }))];
910 assert_eq!(
911 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
912 "Text111\\nText112".to_string()
913 );
914 }
915
916 #[test]
917 fn test_with_text_with_only_newline() {
918 let blocks = vec![rich_text_block(serde_json::json!({
919 "type": "rich_text",
920 "elements": [
921 {
922 "type": "rich_text_section",
923 "elements": [
924 {
925 "type": "text",
926 "text": "\n"
927 }
928 ]
929 }
930 ]
931 }))];
932 assert_eq!(
933 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
934 "".to_string()
935 );
936 }
937
938 #[test]
939 fn test_with_bold_text() {
940 let blocks = vec![rich_text_block(serde_json::json!({
941 "type": "rich_text",
942 "elements": [
943 {
944 "type": "rich_text_section",
945 "elements": [
946 {
947 "type": "text",
948 "text": "Text",
949 "style": {
950 "bold": true
951 }
952 }
953 ]
954 }
955 ]
956 }))];
957 assert_eq!(
958 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
959 "*Text*".to_string()
960 );
961 }
962
963 #[test]
964 fn test_with_consecutive_bold_text() {
965 let blocks = vec![rich_text_block(serde_json::json!({
966 "type": "rich_text",
967 "elements": [
968 {
969 "type": "rich_text_section",
970 "elements": [
971 {
972 "type": "text",
973 "text": "Hello",
974 "style": {
975 "bold": true
976 }
977 },
978 {
979 "type": "text",
980 "text": " ",
981 "style": {
982 "bold": true
983 }
984 },
985 {
986 "type": "text",
987 "text": "World!",
988 "style": {
989 "bold": true
990 }
991 }
992 ]
993 }
994 ]
995 }))];
996 assert_eq!(
997 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
998 "*Hello World!*".to_string()
999 );
1000 }
1001
1002 #[test]
1003 fn test_with_italic_text() {
1004 let blocks = vec![rich_text_block(serde_json::json!({
1005 "type": "rich_text",
1006 "elements": [
1007 {
1008 "type": "rich_text_section",
1009 "elements": [
1010 {
1011 "type": "text",
1012 "text": "Text",
1013 "style": {
1014 "italic": true
1015 }
1016 }
1017 ]
1018 }
1019 ]
1020 }))];
1021 assert_eq!(
1022 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1023 "_Text_".to_string()
1024 );
1025 }
1026
1027 #[test]
1028 fn test_with_consecutive_italic_text() {
1029 let blocks = vec![rich_text_block(serde_json::json!({
1030 "type": "rich_text",
1031 "elements": [
1032 {
1033 "type": "rich_text_section",
1034 "elements": [
1035 {
1036 "type": "text",
1037 "text": "Hello",
1038 "style": {
1039 "italic": true
1040 }
1041 },
1042 {
1043 "type": "text",
1044 "text": " ",
1045 "style": {
1046 "italic": true
1047 }
1048 },
1049 {
1050 "type": "text",
1051 "text": "World!",
1052 "style": {
1053 "italic": true
1054 }
1055 }
1056 ]
1057 }
1058 ]
1059 }))];
1060 assert_eq!(
1061 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1062 "_Hello World!_".to_string()
1063 );
1064 }
1065
1066 #[test]
1067 fn test_with_strike_text() {
1068 let blocks = vec![rich_text_block(serde_json::json!({
1069 "type": "rich_text",
1070 "elements": [
1071 {
1072 "type": "rich_text_section",
1073 "elements": [
1074 {
1075 "type": "text",
1076 "text": "Text",
1077 "style": {
1078 "strike": true
1079 }
1080 }
1081 ]
1082 }
1083 ]
1084 }))];
1085 assert_eq!(
1086 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1087 "~Text~".to_string()
1088 );
1089 }
1090
1091 #[test]
1092 fn test_with_consecutive_strike_text() {
1093 let blocks = vec![rich_text_block(serde_json::json!({
1094 "type": "rich_text",
1095 "elements": [
1096 {
1097 "type": "rich_text_section",
1098 "elements": [
1099 {
1100 "type": "text",
1101 "text": "Hello",
1102 "style": {
1103 "strike": true
1104 }
1105 },
1106 {
1107 "type": "text",
1108 "text": " ",
1109 "style": {
1110 "strike": true
1111 }
1112 },
1113 {
1114 "type": "text",
1115 "text": "World!",
1116 "style": {
1117 "strike": true
1118 }
1119 }
1120 ]
1121 }
1122 ]
1123 }))];
1124 assert_eq!(
1125 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1126 "~Hello World!~".to_string()
1127 );
1128 }
1129
1130 #[test]
1131 fn test_with_code_text() {
1132 let blocks = vec![rich_text_block(serde_json::json!({
1133 "type": "rich_text",
1134 "elements": [
1135 {
1136 "type": "rich_text_section",
1137 "elements": [
1138 {
1139 "type": "text",
1140 "text": "Text",
1141 "style": {
1142 "code": true
1143 }
1144 }
1145 ]
1146 }
1147 ]
1148 }))];
1149 assert_eq!(
1150 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1151 "`Text`".to_string()
1152 );
1153 }
1154
1155 #[test]
1156 fn test_with_consecutive_code_text() {
1157 let blocks = vec![rich_text_block(serde_json::json!({
1158 "type": "rich_text",
1159 "elements": [
1160 {
1161 "type": "rich_text_section",
1162 "elements": [
1163 {
1164 "type": "text",
1165 "text": "Text1",
1166 "style": {
1167 "code": true
1168 }
1169 },
1170 {
1171 "type": "text",
1172 "text": "Text2",
1173 "style": {
1174 "code": true
1175 }
1176 }
1177 ]
1178 }
1179 ]
1180 }))];
1181 assert_eq!(
1182 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1183 "`Text1Text2`".to_string()
1184 );
1185 }
1186
1187 #[test]
1188 fn test_with_styled_text() {
1189 let blocks = vec![rich_text_block(serde_json::json!({
1190 "type": "rich_text",
1191 "elements": [
1192 {
1193 "type": "rich_text_section",
1194 "elements": [
1195 {
1196 "type": "text",
1197 "text": "Text",
1198 "style": {
1199 "bold": true,
1200 "italic": true,
1201 "strike": true
1202 }
1203 }
1204 ]
1205 }
1206 ]
1207 }))];
1208 assert_eq!(
1209 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1210 "~_*Text*_~".to_string()
1211 );
1212 }
1213
1214 #[test]
1215 fn test_with_consecutive_styled_text() {
1216 let blocks = vec![rich_text_block(serde_json::json!({
1217 "type": "rich_text",
1218 "elements": [
1219 {
1220 "type": "rich_text_section",
1221 "elements": [
1222 {
1223 "name": "chart_with_upwards_trend",
1224 "type": "emoji",
1225 "style": {
1226 "bold": true,
1227 "italic": true,
1228 "strike": true
1229 },
1230 },
1231 {
1232 "type": "text",
1233 "text": "Hello",
1234 "style": {
1235 "bold": true,
1236 "italic": true,
1237 "strike": true
1238 }
1239 },
1240 {
1241 "type": "text",
1242 "text": " ",
1243 "style": {
1244 "bold": true,
1245 "italic": true,
1246 "strike": true
1247 }
1248 },
1249 {
1250 "type": "text",
1251 "text": "World!",
1252 "style": {
1253 "bold": true,
1254 "italic": true,
1255 "strike": true
1256 }
1257 }
1258 ]
1259 }
1260 ]
1261 }))];
1262 assert_eq!(
1263 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1264 "📈~_*Hello World!*_~".to_string()
1265 );
1266 }
1267 }
1268
1269 mod channel_element {
1270 use super::*;
1271
1272 #[test]
1273 fn test_with_channel_id() {
1274 let blocks = vec![rich_text_block(serde_json::json!({
1275 "type": "rich_text",
1276 "elements": [
1277 {
1278 "type": "rich_text_section",
1279 "elements": [
1280 {
1281 "type": "channel",
1282 "channel_id": "C0123456"
1283 }
1284 ]
1285 }
1286 ]
1287 }))];
1288 assert_eq!(
1289 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1290 "#C0123456".to_string()
1291 );
1292 }
1293
1294 #[test]
1295 fn test_with_channel_id_and_reference() {
1296 let blocks = vec![rich_text_block(serde_json::json!({
1297 "type": "rich_text",
1298 "elements": [
1299 {
1300 "type": "rich_text_section",
1301 "elements": [
1302 {
1303 "type": "channel",
1304 "channel_id": "C0123456"
1305 }
1306 ]
1307 }
1308 ]
1309 }))];
1310 assert_eq!(
1311 render_blocks_as_markdown(
1312 blocks,
1313 SlackReferences {
1314 channels: HashMap::from([(
1315 SlackChannelId("C0123456".to_string()),
1316 Some("general".to_string())
1317 )]),
1318 ..SlackReferences::default()
1319 },
1320 None
1321 ),
1322 "#general".to_string()
1323 );
1324 }
1325 }
1326
1327 mod user_element {
1328 use super::*;
1329
1330 #[test]
1331 fn test_with_user_id() {
1332 let blocks = vec![rich_text_block(serde_json::json!({
1333 "type": "rich_text",
1334 "elements": [
1335 {
1336 "type": "rich_text_section",
1337 "elements": [
1338 {
1339 "type": "user",
1340 "user_id": "user1"
1341 }
1342 ]
1343 }
1344 ]
1345 }))];
1346 assert_eq!(
1347 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1348 "@user1".to_string()
1349 );
1350 }
1351
1352 #[test]
1353 fn test_with_user_id_and_custom_delimiter() {
1354 let blocks = vec![rich_text_block(serde_json::json!({
1355 "type": "rich_text",
1356 "elements": [
1357 {
1358 "type": "rich_text_section",
1359 "elements": [
1360 {
1361 "type": "user",
1362 "user_id": "user1"
1363 }
1364 ]
1365 }
1366 ]
1367 }))];
1368 assert_eq!(
1369 render_blocks_as_markdown(
1370 blocks,
1371 SlackReferences::default(),
1372 Some("@".to_string())
1373 ),
1374 "@@user1@".to_string()
1375 );
1376 }
1377
1378 #[test]
1379 fn test_with_user_id_and_reference() {
1380 let blocks = vec![rich_text_block(serde_json::json!({
1381 "type": "rich_text",
1382 "elements": [
1383 {
1384 "type": "rich_text_section",
1385 "elements": [
1386 {
1387 "type": "user",
1388 "user_id": "user1"
1389 }
1390 ]
1391 }
1392 ]
1393 }))];
1394 assert_eq!(
1395 render_blocks_as_markdown(
1396 blocks,
1397 SlackReferences {
1398 users: HashMap::from([(
1399 SlackUserId("user1".to_string()),
1400 Some("John Doe".to_string())
1401 )]),
1402 ..SlackReferences::default()
1403 },
1404 None
1405 ),
1406 "@John Doe".to_string()
1407 );
1408 }
1409
1410 #[test]
1411 fn test_with_user_id_and_reference_and_custom_delimiter() {
1412 let blocks = vec![rich_text_block(serde_json::json!({
1413 "type": "rich_text",
1414 "elements": [
1415 {
1416 "type": "rich_text_section",
1417 "elements": [
1418 {
1419 "type": "user",
1420 "user_id": "user1"
1421 }
1422 ]
1423 }
1424 ]
1425 }))];
1426 assert_eq!(
1427 render_blocks_as_markdown(
1428 blocks,
1429 SlackReferences {
1430 users: HashMap::from([(
1431 SlackUserId("user1".to_string()),
1432 Some("John Doe".to_string())
1433 )]),
1434 ..SlackReferences::default()
1435 },
1436 Some("@".to_string())
1437 ),
1438 "@@John Doe@".to_string()
1439 );
1440 }
1441 }
1442
1443 mod usergroup_element {
1444 use super::*;
1445
1446 #[test]
1447 fn test_with_usergroup_id() {
1448 let blocks = vec![rich_text_block(serde_json::json!({
1449 "type": "rich_text",
1450 "elements": [
1451 {
1452 "type": "rich_text_section",
1453 "elements": [
1454 {
1455 "type": "usergroup",
1456 "usergroup_id": "group1"
1457 }
1458 ]
1459 }
1460 ]
1461 }))];
1462 assert_eq!(
1463 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1464 "@group1".to_string()
1465 );
1466 }
1467
1468 #[test]
1469 fn test_with_usergroup_id_and_custom_delimiter() {
1470 let blocks = vec![rich_text_block(serde_json::json!({
1471 "type": "rich_text",
1472 "elements": [
1473 {
1474 "type": "rich_text_section",
1475 "elements": [
1476 {
1477 "type": "usergroup",
1478 "usergroup_id": "group1"
1479 }
1480 ]
1481 }
1482 ]
1483 }))];
1484 assert_eq!(
1485 render_blocks_as_markdown(
1486 blocks,
1487 SlackReferences::default(),
1488 Some("@".to_string())
1489 ),
1490 "@@group1@".to_string()
1491 );
1492 }
1493
1494 #[test]
1495 fn test_with_usergroup_id_and_reference() {
1496 let blocks = vec![rich_text_block(serde_json::json!({
1497 "type": "rich_text",
1498 "elements": [
1499 {
1500 "type": "rich_text_section",
1501 "elements": [
1502 {
1503 "type": "usergroup",
1504 "usergroup_id": "group1"
1505 }
1506 ]
1507 }
1508 ]
1509 }))];
1510 assert_eq!(
1511 render_blocks_as_markdown(
1512 blocks,
1513 SlackReferences {
1514 usergroups: HashMap::from([(
1515 SlackUserGroupId("group1".to_string()),
1516 Some("Admins".to_string())
1517 )]),
1518 ..SlackReferences::default()
1519 },
1520 None
1521 ),
1522 "@Admins".to_string()
1523 );
1524 }
1525
1526 #[test]
1527 fn test_with_usergroup_id_and_reference_and_custom_delimiter() {
1528 let blocks = vec![rich_text_block(serde_json::json!({
1529 "type": "rich_text",
1530 "elements": [
1531 {
1532 "type": "rich_text_section",
1533 "elements": [
1534 {
1535 "type": "usergroup",
1536 "usergroup_id": "group1"
1537 }
1538 ]
1539 }
1540 ]
1541 }))];
1542 assert_eq!(
1543 render_blocks_as_markdown(
1544 blocks,
1545 SlackReferences {
1546 usergroups: HashMap::from([(
1547 SlackUserGroupId("group1".to_string()),
1548 Some("Admins".to_string())
1549 )]),
1550 ..SlackReferences::default()
1551 },
1552 Some("@".to_string())
1553 ),
1554 "@@Admins@".to_string()
1555 );
1556 }
1557 }
1558
1559 mod link_element {
1560 use super::*;
1561
1562 #[test]
1563 fn test_with_url() {
1564 let blocks = vec![rich_text_block(serde_json::json!({
1565 "type": "rich_text",
1566 "elements": [
1567 {
1568 "type": "rich_text_section",
1569 "elements": [
1570 {
1571 "type": "link",
1572 "text": "example",
1573 "url": "https://example.com"
1574 }
1575 ]
1576 }
1577 ]
1578 }))];
1579 assert_eq!(
1580 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1581 "[example](https://example.com)".to_string()
1582 );
1583 }
1584
1585 #[test]
1586 fn test_with_url_without_text() {
1587 let blocks = vec![rich_text_block(serde_json::json!({
1588 "type": "rich_text",
1589 "elements": [
1590 {
1591 "type": "rich_text_section",
1592 "elements": [
1593 {
1594 "type": "link",
1595 "url": "https://example.com"
1596 }
1597 ]
1598 }
1599 ]
1600 }))];
1601 assert_eq!(
1602 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1603 "[https://example.com](https://example.com)".to_string()
1604 );
1605 }
1606 }
1607
1608 mod attachment_mention_element {
1609 use super::*;
1610
1611 #[test]
1612 fn test_with_text() {
1613 let blocks = vec![rich_text_block(serde_json::json!({
1614 "type": "rich_text",
1615 "elements": [
1616 {
1617 "type": "rich_text_section",
1618 "elements": [
1619 {
1620 "type": "attachment_mention",
1621 "url": "https://docs.google.com/document/d/1abc/edit?tab=t.0",
1622 "text": "Project plan",
1623 "app_id": "A6NL8MJ6Q",
1624 "entity_id": "W:A6NL8MJ6Q:1abc",
1625 "icon_url": "https://a.slack-edge.com/img/app-gdrive/icon_document.png",
1626 "product_name": "Google Docs",
1627 "channel_id": "C08GE9NPGFK",
1628 "ts": "1788850799.491249"
1629 }
1630 ]
1631 }
1632 ]
1633 }))];
1634 assert_eq!(
1635 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1636 "[Project plan](https://docs.google.com/document/d/1abc/edit?tab=t.0)"
1637 .to_string()
1638 );
1639 }
1640
1641 #[test]
1642 fn test_without_text() {
1643 let blocks = vec![rich_text_block(serde_json::json!({
1644 "type": "rich_text",
1645 "elements": [
1646 {
1647 "type": "rich_text_section",
1648 "elements": [
1649 {
1650 "type": "attachment_mention",
1651 "url": "https://example.com/doc"
1652 }
1653 ]
1654 }
1655 ]
1656 }))];
1657 assert_eq!(
1658 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1659 "[https://example.com/doc](https://example.com/doc)".to_string()
1660 );
1661 }
1662 }
1663
1664 mod message_mention_element {
1665 use super::*;
1666
1667 #[test]
1668 fn test_with_text() {
1669 let blocks = vec![rich_text_block(serde_json::json!({
1670 "type": "rich_text",
1671 "elements": [
1672 {
1673 "type": "rich_text_section",
1674 "elements": [
1675 {
1676 "type": "message_mention",
1677 "url": "https://slack.com/archives/C123/p1788850799491249",
1678 "text": "a previous message",
1679 "channel_id": "C123",
1680 "author_id": "U123",
1681 "message_ts": "1788850799.491249"
1682 }
1683 ]
1684 }
1685 ]
1686 }))];
1687 assert_eq!(
1688 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1689 "[a previous message](https://slack.com/archives/C123/p1788850799491249)"
1690 .to_string()
1691 );
1692 }
1693
1694 #[test]
1695 fn test_without_text() {
1696 let blocks = vec![rich_text_block(serde_json::json!({
1697 "type": "rich_text",
1698 "elements": [
1699 {
1700 "type": "rich_text_section",
1701 "elements": [
1702 {
1703 "type": "message_mention",
1704 "url": "https://slack.com/archives/C123/p1788850799491249"
1705 }
1706 ]
1707 }
1708 ]
1709 }))];
1710 assert_eq!(
1711 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1712 "[https://slack.com/archives/C123/p1788850799491249](https://slack.com/archives/C123/p1788850799491249)"
1713 .to_string()
1714 );
1715 }
1716 }
1717
1718 mod emoji_element {
1719 use super::*;
1720
1721 #[test]
1722 fn test_with_emoji() {
1723 let blocks = vec![rich_text_block(serde_json::json!({
1724 "type": "rich_text",
1725 "elements": [
1726 {
1727 "type": "rich_text_section",
1728 "elements": [
1729 {
1730 "type": "emoji",
1731 "name": "wave"
1732 }
1733 ]
1734 }
1735 ]
1736 }))];
1737 assert_eq!(
1738 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1739 "👋".to_string()
1740 );
1741 }
1742
1743 #[test]
1744 fn test_with_emoji_with_skin_tone() {
1745 let blocks = vec![rich_text_block(serde_json::json!({
1746 "type": "rich_text",
1747 "elements": [
1748 {
1749 "type": "rich_text_section",
1750 "elements": [
1751 {
1752 "type": "emoji",
1753 "name": "wave::skin-tone-2"
1754 }
1755 ]
1756 }
1757 ]
1758 }))];
1759 assert_eq!(
1760 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1761 "👋🏻".to_string()
1762 );
1763 }
1764
1765 #[test]
1766 fn test_with_emoji_with_unknown_skin_tone() {
1767 let blocks = vec![rich_text_block(serde_json::json!({
1768 "type": "rich_text",
1769 "elements": [
1770 {
1771 "type": "rich_text_section",
1772 "elements": [
1773 {
1774 "type": "emoji",
1775 "name": "wave::skin-tone-42"
1776 }
1777 ]
1778 }
1779 ]
1780 }))];
1781 assert_eq!(
1782 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1783 "👋".to_string()
1784 );
1785 }
1786
1787 #[test]
1788 fn test_with_unknown_emoji() {
1789 let blocks = vec![rich_text_block(serde_json::json!({
1790 "type": "rich_text",
1791 "elements": [
1792 {
1793 "type": "rich_text_section",
1794 "elements": [
1795 {
1796 "type": "emoji",
1797 "name": "unknown1"
1798 }
1799 ]
1800 }
1801 ]
1802 }))];
1803 assert_eq!(
1804 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1805 ":unknown1:".to_string()
1806 );
1807 }
1808
1809 #[test]
1810 fn test_with_unknown_emoji_with_slack_reference_alias() {
1811 let blocks = vec![rich_text_block(serde_json::json!({
1812 "type": "rich_text",
1813 "elements": [
1814 {
1815 "type": "rich_text_section",
1816 "elements": [
1817 {
1818 "type": "emoji",
1819 "name": "unknown1"
1820 }
1821 ]
1822 }
1823 ]
1824 }))];
1825 assert_eq!(
1826 render_blocks_as_markdown(
1827 blocks,
1828 SlackReferences {
1829 emojis: HashMap::from([(
1830 SlackEmojiName("unknown1".to_string()),
1831 Some(SlackEmojiRef::Alias(SlackEmojiName("wave".to_string())))
1832 )]),
1833 ..SlackReferences::default()
1834 },
1835 None
1836 ),
1837 "👋".to_string()
1838 );
1839 }
1840
1841 #[test]
1842 fn test_with_unknown_emoji_with_slack_reference_alias_to_custom_emoji() {
1843 let blocks = vec![rich_text_block(serde_json::json!({
1844 "type": "rich_text",
1845 "elements": [
1846 {
1847 "type": "rich_text_section",
1848 "elements": [
1849 {
1850 "type": "emoji",
1851 "name": "unknown1"
1852 }
1853 ]
1854 }
1855 ]
1856 }))];
1857 assert_eq!(
1858 render_blocks_as_markdown(
1859 blocks,
1860 SlackReferences {
1861 emojis: HashMap::from([
1862 (
1863 SlackEmojiName("unknown1".to_string()),
1864 Some(SlackEmojiRef::Alias(SlackEmojiName(
1865 "unknown2".to_string()
1866 )))
1867 ),
1868 (
1869 SlackEmojiName("unknown2".to_string()),
1870 Some(SlackEmojiRef::Url(
1871 "https://emoji.com/unknown2.png".parse().unwrap()
1872 ))
1873 )
1874 ]),
1875 ..SlackReferences::default()
1876 },
1877 None
1878 ),
1879 "".to_string()
1880 );
1881 }
1882
1883 #[test]
1884 fn test_with_unknown_emoji_with_slack_reference_image_url() {
1885 let blocks = vec![rich_text_block(serde_json::json!({
1886 "type": "rich_text",
1887 "elements": [
1888 {
1889 "type": "rich_text_section",
1890 "elements": [
1891 {
1892 "type": "emoji",
1893 "name": "unknown1"
1894 }
1895 ]
1896 }
1897 ]
1898 }))];
1899 assert_eq!(
1900 render_blocks_as_markdown(
1901 blocks,
1902 SlackReferences {
1903 emojis: HashMap::from([(
1904 SlackEmojiName("unknown1".to_string()),
1905 Some(SlackEmojiRef::Url(
1906 "https://emoji.com/unknown1.png".parse().unwrap()
1907 ))
1908 )]),
1909 ..SlackReferences::default()
1910 },
1911 None
1912 ),
1913 "".to_string()
1914 );
1915 }
1916 }
1917 }
1918
1919 mod rich_text_list {
1920 use super::*;
1921
1922 #[test]
1923 fn test_with_ordered_list() {
1924 let blocks = vec![rich_text_block(serde_json::json!({
1925 "type": "rich_text",
1926 "elements": [
1927 {
1928 "type": "rich_text_list",
1929 "style": "ordered",
1930 "elements": [
1931 {
1932 "type": "rich_text_section",
1933 "elements": [
1934 {
1935 "type": "text",
1936 "text": "Text1"
1937 }
1938 ]
1939 },
1940 {
1941 "type": "rich_text_section",
1942 "elements": [
1943 {
1944 "type": "text",
1945 "text": "Text2"
1946 }
1947 ]
1948 }
1949 ]
1950 },
1951 ]
1952 }))];
1953 assert_eq!(
1954 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1955 "1. Text1\n1. Text2".to_string()
1956 );
1957 }
1958
1959 #[test]
1960 fn test_with_nested_ordered_list() {
1961 let blocks = vec![rich_text_block(serde_json::json!({
1962 "type": "rich_text",
1963 "elements": [
1964 {
1965 "type": "rich_text_list",
1966 "style": "ordered",
1967 "elements": [
1968 {
1969 "type": "rich_text_section",
1970 "elements": [
1971 {
1972 "type": "text",
1973 "text": "Text1"
1974 }
1975 ]
1976 },
1977 ]
1978 },
1979 {
1980 "type": "rich_text_list",
1981 "style": "ordered",
1982 "elements": [
1983 {
1984 "type": "rich_text_section",
1985 "elements": [
1986 {
1987 "type": "text",
1988 "text": "Text2"
1989 }
1990 ]
1991 },
1992 ]
1993 },
1994 {
1995 "type": "rich_text_list",
1996 "style": "ordered",
1997 "indent": 1,
1998 "elements": [
1999 {
2000 "type": "rich_text_section",
2001 "elements": [
2002 {
2003 "type": "text",
2004 "text": "Text2.1"
2005 }
2006 ]
2007 },
2008 ]
2009 },
2010 {
2011 "type": "rich_text_list",
2012 "style": "ordered",
2013 "indent": 2,
2014 "elements": [
2015 {
2016 "type": "rich_text_section",
2017 "elements": [
2018 {
2019 "type": "text",
2020 "text": "Text2.1.1"
2021 }
2022 ]
2023 },
2024 ]
2025 }
2026 ]
2027 }))];
2028 assert_eq!(
2029 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2030 "1. Text1\n1. Text2\n 1. Text2.1\n 1. Text2.1.1".to_string()
2031 );
2032 }
2033
2034 #[test]
2035 fn test_with_bullet_list() {
2036 let blocks = vec![rich_text_block(serde_json::json!({
2037 "type": "rich_text",
2038 "elements": [
2039 {
2040 "type": "rich_text_list",
2041 "style": "bullet",
2042 "elements": [
2043 {
2044 "type": "rich_text_section",
2045 "elements": [
2046 {
2047 "type": "text",
2048 "text": "Text1"
2049 }
2050 ]
2051 },
2052 {
2053 "type": "rich_text_section",
2054 "elements": [
2055 {
2056 "type": "text",
2057 "text": "Text2"
2058 }
2059 ]
2060 }
2061 ]
2062 },
2063 ]
2064 }))];
2065 assert_eq!(
2066 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2067 "- Text1\n- Text2".to_string()
2068 );
2069 }
2070 }
2071
2072 #[test]
2073 fn test_with_nested_bullet_list() {
2074 let blocks = vec![rich_text_block(serde_json::json!({
2075 "type": "rich_text",
2076 "elements": [
2077 {
2078 "type": "rich_text_list",
2079 "style": "bullet",
2080 "elements": [
2081 {
2082 "type": "rich_text_section",
2083 "elements": [
2084 {
2085 "type": "text",
2086 "text": "Text1"
2087 }
2088 ]
2089 },
2090 ]
2091 },
2092 {
2093 "type": "rich_text_list",
2094 "style": "bullet",
2095 "elements": [
2096 {
2097 "type": "rich_text_section",
2098 "elements": [
2099 {
2100 "type": "text",
2101 "text": "Text2"
2102 }
2103 ]
2104 },
2105 ]
2106 },
2107 {
2108 "type": "rich_text_list",
2109 "style": "bullet",
2110 "indent": 1,
2111 "elements": [
2112 {
2113 "type": "rich_text_section",
2114 "elements": [
2115 {
2116 "type": "text",
2117 "text": "Text2.1"
2118 }
2119 ]
2120 },
2121 ]
2122 },
2123 {
2124 "type": "rich_text_list",
2125 "style": "bullet",
2126 "indent": 2,
2127 "elements": [
2128 {
2129 "type": "rich_text_section",
2130 "elements": [
2131 {
2132 "type": "text",
2133 "text": "Text2.1.1"
2134 }
2135 ]
2136 },
2137 ]
2138 }
2139 ]
2140 }))];
2141 assert_eq!(
2142 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2143 "- Text1\n- Text2\n - Text2.1\n - Text2.1.1".to_string()
2144 );
2145 }
2146
2147 mod rich_text_preformatted {
2148 use super::*;
2149
2150 #[test]
2151 fn test_with_text() {
2152 let blocks = vec![rich_text_block(serde_json::json!({
2153 "type": "rich_text",
2154 "elements": [
2155 {
2156 "type": "rich_text_preformatted",
2157 "elements": [
2158 {
2159 "type": "text",
2160 "text": "Text1"
2161 },
2162 {
2163 "type": "text",
2164 "text": "Text2"
2165 }
2166 ]
2167 },
2168 ]
2169 }))];
2170 assert_eq!(
2171 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2172 "```\nText1Text2\n```".to_string()
2173 );
2174 }
2175
2176 #[test]
2177 fn test_with_text_and_newline() {
2178 let blocks = vec![rich_text_block(serde_json::json!({
2179 "type": "rich_text",
2180 "elements": [
2181 {
2182 "type": "rich_text_preformatted",
2183 "elements": [
2184 {
2185 "type": "text",
2186 "text": "test:\n sub: value"
2187 }
2188 ]
2189 }
2190 ]
2191 }))];
2192 assert_eq!(
2193 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2194 "```\ntest:\n sub: value\n```".to_string()
2195 );
2196 }
2197
2198 #[test]
2199 fn test_with_preformatted_text_followed_by_text() {
2200 let blocks = vec![rich_text_block(serde_json::json!({
2201 "type": "rich_text",
2202 "elements": [
2203 {
2204 "type": "rich_text_preformatted",
2205 "elements": [
2206 {
2207 "type": "text",
2208 "text": "Text1"
2209 }
2210 ]
2211 },
2212 {
2213 "type": "rich_text_section",
2214 "elements": [
2215 {
2216 "type": "text",
2217 "text": "Text2"
2218 }
2219 ]
2220 },
2221 ]
2222 }))];
2223 assert_eq!(
2224 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2225 "```\nText1\n```\nText2".to_string()
2226 );
2227 }
2228 }
2229
2230 mod rich_text_quote {
2231 use super::*;
2232
2233 #[test]
2234 fn test_with_text() {
2235 let blocks = vec![rich_text_block(serde_json::json!({
2236 "type": "rich_text",
2237 "elements": [
2238 {
2239 "type": "rich_text_quote",
2240 "elements": [
2241 {
2242 "type": "text",
2243 "text": "Text1"
2244 },
2245 {
2246 "type": "text",
2247 "text": "Text2"
2248 }
2249 ]
2250 },
2251 ]
2252 }))];
2253 assert_eq!(
2254 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2255 "> Text1Text2".to_string()
2256 );
2257 }
2258
2259 #[test]
2260 fn test_with_quoted_text_followed_by_quoted_text() {
2261 let blocks = vec![rich_text_block(serde_json::json!({
2262 "type": "rich_text",
2263 "elements": [
2264 {
2265 "type": "rich_text_quote",
2266 "elements": [
2267 {
2268 "type": "text",
2269 "text": "Text1"
2270 },
2271 ]
2272 },
2273 {
2274 "type": "rich_text_quote",
2275 "elements": [
2276 {
2277 "type": "text",
2278 "text": "Text2"
2279 }
2280 ]
2281 },
2282 ]
2283 }))];
2284 assert_eq!(
2285 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2286 "> Text1\n> Text2".to_string()
2287 );
2288 }
2289
2290 #[test]
2291 fn test_with_quoted_text_followed_by_non_quoted_text() {
2292 let blocks = vec![rich_text_block(serde_json::json!({
2293 "type": "rich_text",
2294 "elements": [
2295 {
2296 "type": "rich_text_quote",
2297 "elements": [
2298 {
2299 "text": "Text1",
2300 "type": "text"
2301 }
2302 ]
2303 },
2304 {
2305 "type": "rich_text_section",
2306 "elements": [
2307 {
2308 "text": "Text2",
2309 "type": "text"
2310 },
2311 ]
2312 }
2313 ]
2314 }))];
2315
2316 assert_eq!(
2317 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2318 "> Text1\n\nText2".to_string()
2319 );
2320 }
2321 }
2322 }
2323}