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(
549 SlackBlockPlainTextInputElement::new().with_action_id("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_with_unrendered_blocks() {
630 let blocks: Vec<SlackBlock> = serde_json::from_value(serde_json::json!([
632 { "type": "plan", "title": "Plan", "tasks": [] },
633 { "type": "data_table", "caption": "Caption", "rows": [] },
634 { "type": "some_future_block", "text": "Text" }
635 ]))
636 .unwrap();
637 assert!(matches!(blocks[0], SlackBlock::Plan(_)));
638 assert!(matches!(blocks[2], SlackBlock::Unknown(_)));
639 assert_eq!(
640 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
641 "".to_string()
642 );
643 }
644
645 #[test]
646 fn test_with_container() {
647 let blocks = vec![SlackBlock::Container(SlackContainerBlock::new(vec![
648 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
649 SlackBlockPlainText::new("Text".to_string()),
650 ))),
651 ]))];
652 assert_eq!(
653 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
654 "Text".to_string()
655 );
656 }
657
658 #[test]
659 fn test_header() {
660 let blocks = vec![SlackBlock::Header(SlackHeaderBlock::new("Text".into()))];
661 assert_eq!(
662 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
663 "## Text".to_string()
664 );
665 }
666
667 mod section {
668 use super::*;
669
670 #[test]
671 fn test_with_plain_text() {
672 let blocks = vec![
673 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
674 SlackBlockPlainText::new("Text".to_string()),
675 ))),
676 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::Plain(
677 SlackBlockPlainText::new("Text2".to_string()),
678 ))),
679 ];
680 assert_eq!(
681 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
682 "Text\nText2".to_string()
683 );
684 }
685
686 #[test]
687 fn test_with_markdown() {
688 let blocks = vec![
689 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
690 SlackBlockMarkDownText::new("Text".to_string()),
691 ))),
692 SlackBlock::Section(SlackSectionBlock::new().with_text(SlackBlockText::MarkDown(
693 SlackBlockMarkDownText::new("Text2".to_string()),
694 ))),
695 ];
696 assert_eq!(
697 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
698 "Text\nText2".to_string()
699 );
700 }
701
702 #[test]
703 fn test_with_fields() {
704 let blocks = vec![
705 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
706 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
707 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
708 ])),
709 SlackBlock::Section(SlackSectionBlock::new().with_fields(vec![
710 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
711 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
712 ])),
713 ];
714 assert_eq!(
715 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
716 "Text11Text12\nText21Text22".to_string()
717 );
718 }
719
720 #[test]
721 fn test_with_fields_and_text() {
722 let blocks = vec![
723 SlackBlock::Section(
724 SlackSectionBlock::new()
725 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
726 "Text1".to_string(),
727 )))
728 .with_fields(vec![
729 SlackBlockText::Plain(SlackBlockPlainText::new("Text11".to_string())),
730 SlackBlockText::Plain(SlackBlockPlainText::new("Text12".to_string())),
731 ]),
732 ),
733 SlackBlock::Section(
734 SlackSectionBlock::new()
735 .with_text(SlackBlockText::MarkDown(SlackBlockMarkDownText::new(
736 "Text2".to_string(),
737 )))
738 .with_fields(vec![
739 SlackBlockText::Plain(SlackBlockPlainText::new("Text21".to_string())),
740 SlackBlockText::Plain(SlackBlockPlainText::new("Text22".to_string())),
741 ]),
742 ),
743 ];
744 assert_eq!(
745 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
746 "Text1Text11Text12\nText2Text21Text22".to_string()
747 );
748 }
749 }
750
751 mod context {
752 use super::*;
753
754 #[test]
755 fn test_with_image() {
756 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
757 SlackContextBlockElement::Image(SlackBlockImageElement::new(
758 SlackImageUrlOrFile::ImageUrl {
759 image_url: Url::parse("https://example.com/image.png").unwrap(),
760 },
761 "Image".to_string(),
762 )),
763 SlackContextBlockElement::Image(SlackBlockImageElement::new(
764 SlackImageUrlOrFile::ImageUrl {
765 image_url: Url::parse("https://example.com/image2.png").unwrap(),
766 },
767 "Image2".to_string(),
768 )),
769 ]))];
770 assert_eq!(
771 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
772 ""
773 .to_string()
774 );
775 }
776
777 #[test]
778 fn test_with_plain_text() {
779 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
780 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text".to_string())),
781 SlackContextBlockElement::Plain(SlackBlockPlainText::new("Text2".to_string())),
782 ]))];
783 assert_eq!(
784 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
785 "TextText2".to_string()
786 );
787 }
788
789 #[test]
790 fn test_with_markdown() {
791 let blocks = vec![SlackBlock::Context(SlackContextBlock::new(vec![
792 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new("Text".to_string())),
793 SlackContextBlockElement::MarkDown(SlackBlockMarkDownText::new(
794 "Text2".to_string(),
795 )),
796 ]))];
797 assert_eq!(
798 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
799 "TextText2".to_string()
800 );
801 }
802 }
803
804 mod rich_text {
805 use super::*;
806
807 #[test]
808 fn test_with_empty_json() {
809 let blocks = vec![
810 rich_text_block(serde_json::json!({})),
811 rich_text_block(serde_json::json!({})),
812 ];
813 assert_eq!(
814 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
815 "\n".to_string()
816 );
817 }
818
819 mod rich_text_section {
820 use super::*;
821
822 mod text_element {
823 use super::*;
824
825 #[test]
826 fn test_with_text() {
827 let blocks = vec![
828 rich_text_block(serde_json::json!({
829 "type": "rich_text",
830 "elements": [
831 {
832 "type": "rich_text_section",
833 "elements": [
834 {
835 "type": "text",
836 "text": "Text111"
837 },
838 {
839 "type": "text",
840 "text": "Text112"
841 }
842 ]
843 },
844 {
845 "type": "rich_text_section",
846 "elements": [
847 {
848 "type": "text",
849 "text": "Text121"
850 },
851 {
852 "type": "text",
853 "text": "Text122"
854 }
855 ]
856 }
857 ]
858 })),
859 rich_text_block(serde_json::json!({
860 "type": "rich_text",
861 "elements": [
862 {
863 "type": "rich_text_section",
864 "elements": [
865 {
866 "type": "text",
867 "text": "Text211"
868 },
869 {
870 "type": "text",
871 "text": "Text212"
872 }
873 ]
874 },
875 {
876 "type": "rich_text_section",
877 "elements": [
878 {
879 "type": "text",
880 "text": "Text221"
881 },
882 {
883 "type": "text",
884 "text": "Text222"
885 }
886 ]
887 }
888 ]
889 })),
890 ];
891 assert_eq!(
892 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
893 "Text111Text112\nText121Text122\nText211Text212\nText221Text222"
894 .to_string()
895 );
896 }
897
898 #[test]
899 fn test_with_text_with_newline() {
900 let blocks = vec![rich_text_block(serde_json::json!({
901 "type": "rich_text",
902 "elements": [
903 {
904 "type": "rich_text_section",
905 "elements": [
906 {
907 "type": "text",
908 "text": "Text111\nText112\n"
909 },
910 {
911 "type": "text",
912 "text": "Text211\nText212\n"
913 }
914 ]
915 }
916 ]
917 }))];
918 assert_eq!(
919 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
920 "Text111\\\nText112\\\nText211\\\nText212".to_string()
921 );
922 }
923
924 #[test]
925 fn test_with_text_with_newline_char() {
926 let blocks = vec![rich_text_block(serde_json::json!({
927 "type": "rich_text",
928 "elements": [
929 {
930 "type": "rich_text_section",
931 "elements": [
932 {
933 "type": "text",
934 "text": "Text111\\nText112"
935 }
936 ]
937 }
938 ]
939 }))];
940 assert_eq!(
941 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
942 "Text111\\nText112".to_string()
943 );
944 }
945
946 #[test]
947 fn test_with_text_with_only_newline() {
948 let blocks = vec![rich_text_block(serde_json::json!({
949 "type": "rich_text",
950 "elements": [
951 {
952 "type": "rich_text_section",
953 "elements": [
954 {
955 "type": "text",
956 "text": "\n"
957 }
958 ]
959 }
960 ]
961 }))];
962 assert_eq!(
963 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
964 "".to_string()
965 );
966 }
967
968 #[test]
969 fn test_with_bold_text() {
970 let blocks = vec![rich_text_block(serde_json::json!({
971 "type": "rich_text",
972 "elements": [
973 {
974 "type": "rich_text_section",
975 "elements": [
976 {
977 "type": "text",
978 "text": "Text",
979 "style": {
980 "bold": true
981 }
982 }
983 ]
984 }
985 ]
986 }))];
987 assert_eq!(
988 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
989 "*Text*".to_string()
990 );
991 }
992
993 #[test]
994 fn test_with_consecutive_bold_text() {
995 let blocks = vec![rich_text_block(serde_json::json!({
996 "type": "rich_text",
997 "elements": [
998 {
999 "type": "rich_text_section",
1000 "elements": [
1001 {
1002 "type": "text",
1003 "text": "Hello",
1004 "style": {
1005 "bold": true
1006 }
1007 },
1008 {
1009 "type": "text",
1010 "text": " ",
1011 "style": {
1012 "bold": true
1013 }
1014 },
1015 {
1016 "type": "text",
1017 "text": "World!",
1018 "style": {
1019 "bold": true
1020 }
1021 }
1022 ]
1023 }
1024 ]
1025 }))];
1026 assert_eq!(
1027 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1028 "*Hello World!*".to_string()
1029 );
1030 }
1031
1032 #[test]
1033 fn test_with_italic_text() {
1034 let blocks = vec![rich_text_block(serde_json::json!({
1035 "type": "rich_text",
1036 "elements": [
1037 {
1038 "type": "rich_text_section",
1039 "elements": [
1040 {
1041 "type": "text",
1042 "text": "Text",
1043 "style": {
1044 "italic": true
1045 }
1046 }
1047 ]
1048 }
1049 ]
1050 }))];
1051 assert_eq!(
1052 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1053 "_Text_".to_string()
1054 );
1055 }
1056
1057 #[test]
1058 fn test_with_consecutive_italic_text() {
1059 let blocks = vec![rich_text_block(serde_json::json!({
1060 "type": "rich_text",
1061 "elements": [
1062 {
1063 "type": "rich_text_section",
1064 "elements": [
1065 {
1066 "type": "text",
1067 "text": "Hello",
1068 "style": {
1069 "italic": true
1070 }
1071 },
1072 {
1073 "type": "text",
1074 "text": " ",
1075 "style": {
1076 "italic": true
1077 }
1078 },
1079 {
1080 "type": "text",
1081 "text": "World!",
1082 "style": {
1083 "italic": true
1084 }
1085 }
1086 ]
1087 }
1088 ]
1089 }))];
1090 assert_eq!(
1091 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1092 "_Hello World!_".to_string()
1093 );
1094 }
1095
1096 #[test]
1097 fn test_with_strike_text() {
1098 let blocks = vec![rich_text_block(serde_json::json!({
1099 "type": "rich_text",
1100 "elements": [
1101 {
1102 "type": "rich_text_section",
1103 "elements": [
1104 {
1105 "type": "text",
1106 "text": "Text",
1107 "style": {
1108 "strike": true
1109 }
1110 }
1111 ]
1112 }
1113 ]
1114 }))];
1115 assert_eq!(
1116 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1117 "~Text~".to_string()
1118 );
1119 }
1120
1121 #[test]
1122 fn test_with_consecutive_strike_text() {
1123 let blocks = vec![rich_text_block(serde_json::json!({
1124 "type": "rich_text",
1125 "elements": [
1126 {
1127 "type": "rich_text_section",
1128 "elements": [
1129 {
1130 "type": "text",
1131 "text": "Hello",
1132 "style": {
1133 "strike": true
1134 }
1135 },
1136 {
1137 "type": "text",
1138 "text": " ",
1139 "style": {
1140 "strike": true
1141 }
1142 },
1143 {
1144 "type": "text",
1145 "text": "World!",
1146 "style": {
1147 "strike": true
1148 }
1149 }
1150 ]
1151 }
1152 ]
1153 }))];
1154 assert_eq!(
1155 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1156 "~Hello World!~".to_string()
1157 );
1158 }
1159
1160 #[test]
1161 fn test_with_code_text() {
1162 let blocks = vec![rich_text_block(serde_json::json!({
1163 "type": "rich_text",
1164 "elements": [
1165 {
1166 "type": "rich_text_section",
1167 "elements": [
1168 {
1169 "type": "text",
1170 "text": "Text",
1171 "style": {
1172 "code": true
1173 }
1174 }
1175 ]
1176 }
1177 ]
1178 }))];
1179 assert_eq!(
1180 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1181 "`Text`".to_string()
1182 );
1183 }
1184
1185 #[test]
1186 fn test_with_consecutive_code_text() {
1187 let blocks = vec![rich_text_block(serde_json::json!({
1188 "type": "rich_text",
1189 "elements": [
1190 {
1191 "type": "rich_text_section",
1192 "elements": [
1193 {
1194 "type": "text",
1195 "text": "Text1",
1196 "style": {
1197 "code": true
1198 }
1199 },
1200 {
1201 "type": "text",
1202 "text": "Text2",
1203 "style": {
1204 "code": true
1205 }
1206 }
1207 ]
1208 }
1209 ]
1210 }))];
1211 assert_eq!(
1212 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1213 "`Text1Text2`".to_string()
1214 );
1215 }
1216
1217 #[test]
1218 fn test_with_styled_text() {
1219 let blocks = vec![rich_text_block(serde_json::json!({
1220 "type": "rich_text",
1221 "elements": [
1222 {
1223 "type": "rich_text_section",
1224 "elements": [
1225 {
1226 "type": "text",
1227 "text": "Text",
1228 "style": {
1229 "bold": true,
1230 "italic": true,
1231 "strike": true
1232 }
1233 }
1234 ]
1235 }
1236 ]
1237 }))];
1238 assert_eq!(
1239 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1240 "~_*Text*_~".to_string()
1241 );
1242 }
1243
1244 #[test]
1245 fn test_with_consecutive_styled_text() {
1246 let blocks = vec![rich_text_block(serde_json::json!({
1247 "type": "rich_text",
1248 "elements": [
1249 {
1250 "type": "rich_text_section",
1251 "elements": [
1252 {
1253 "name": "chart_with_upwards_trend",
1254 "type": "emoji",
1255 "style": {
1256 "bold": true,
1257 "italic": true,
1258 "strike": true
1259 },
1260 },
1261 {
1262 "type": "text",
1263 "text": "Hello",
1264 "style": {
1265 "bold": true,
1266 "italic": true,
1267 "strike": true
1268 }
1269 },
1270 {
1271 "type": "text",
1272 "text": " ",
1273 "style": {
1274 "bold": true,
1275 "italic": true,
1276 "strike": true
1277 }
1278 },
1279 {
1280 "type": "text",
1281 "text": "World!",
1282 "style": {
1283 "bold": true,
1284 "italic": true,
1285 "strike": true
1286 }
1287 }
1288 ]
1289 }
1290 ]
1291 }))];
1292 assert_eq!(
1293 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1294 "📈~_*Hello World!*_~".to_string()
1295 );
1296 }
1297 }
1298
1299 mod channel_element {
1300 use super::*;
1301
1302 #[test]
1303 fn test_with_channel_id() {
1304 let blocks = vec![rich_text_block(serde_json::json!({
1305 "type": "rich_text",
1306 "elements": [
1307 {
1308 "type": "rich_text_section",
1309 "elements": [
1310 {
1311 "type": "channel",
1312 "channel_id": "C0123456"
1313 }
1314 ]
1315 }
1316 ]
1317 }))];
1318 assert_eq!(
1319 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1320 "#C0123456".to_string()
1321 );
1322 }
1323
1324 #[test]
1325 fn test_with_channel_id_and_reference() {
1326 let blocks = vec![rich_text_block(serde_json::json!({
1327 "type": "rich_text",
1328 "elements": [
1329 {
1330 "type": "rich_text_section",
1331 "elements": [
1332 {
1333 "type": "channel",
1334 "channel_id": "C0123456"
1335 }
1336 ]
1337 }
1338 ]
1339 }))];
1340 assert_eq!(
1341 render_blocks_as_markdown(
1342 blocks,
1343 SlackReferences {
1344 channels: HashMap::from([(
1345 SlackChannelId("C0123456".to_string()),
1346 Some("general".to_string())
1347 )]),
1348 ..SlackReferences::default()
1349 },
1350 None
1351 ),
1352 "#general".to_string()
1353 );
1354 }
1355 }
1356
1357 mod user_element {
1358 use super::*;
1359
1360 #[test]
1361 fn test_with_user_id() {
1362 let blocks = vec![rich_text_block(serde_json::json!({
1363 "type": "rich_text",
1364 "elements": [
1365 {
1366 "type": "rich_text_section",
1367 "elements": [
1368 {
1369 "type": "user",
1370 "user_id": "user1"
1371 }
1372 ]
1373 }
1374 ]
1375 }))];
1376 assert_eq!(
1377 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1378 "@user1".to_string()
1379 );
1380 }
1381
1382 #[test]
1383 fn test_with_user_id_and_custom_delimiter() {
1384 let blocks = vec![rich_text_block(serde_json::json!({
1385 "type": "rich_text",
1386 "elements": [
1387 {
1388 "type": "rich_text_section",
1389 "elements": [
1390 {
1391 "type": "user",
1392 "user_id": "user1"
1393 }
1394 ]
1395 }
1396 ]
1397 }))];
1398 assert_eq!(
1399 render_blocks_as_markdown(
1400 blocks,
1401 SlackReferences::default(),
1402 Some("@".to_string())
1403 ),
1404 "@@user1@".to_string()
1405 );
1406 }
1407
1408 #[test]
1409 fn test_with_user_id_and_reference() {
1410 let blocks = vec![rich_text_block(serde_json::json!({
1411 "type": "rich_text",
1412 "elements": [
1413 {
1414 "type": "rich_text_section",
1415 "elements": [
1416 {
1417 "type": "user",
1418 "user_id": "user1"
1419 }
1420 ]
1421 }
1422 ]
1423 }))];
1424 assert_eq!(
1425 render_blocks_as_markdown(
1426 blocks,
1427 SlackReferences {
1428 users: HashMap::from([(
1429 SlackUserId("user1".to_string()),
1430 Some("John Doe".to_string())
1431 )]),
1432 ..SlackReferences::default()
1433 },
1434 None
1435 ),
1436 "@John Doe".to_string()
1437 );
1438 }
1439
1440 #[test]
1441 fn test_with_user_id_and_reference_and_custom_delimiter() {
1442 let blocks = vec![rich_text_block(serde_json::json!({
1443 "type": "rich_text",
1444 "elements": [
1445 {
1446 "type": "rich_text_section",
1447 "elements": [
1448 {
1449 "type": "user",
1450 "user_id": "user1"
1451 }
1452 ]
1453 }
1454 ]
1455 }))];
1456 assert_eq!(
1457 render_blocks_as_markdown(
1458 blocks,
1459 SlackReferences {
1460 users: HashMap::from([(
1461 SlackUserId("user1".to_string()),
1462 Some("John Doe".to_string())
1463 )]),
1464 ..SlackReferences::default()
1465 },
1466 Some("@".to_string())
1467 ),
1468 "@@John Doe@".to_string()
1469 );
1470 }
1471 }
1472
1473 mod usergroup_element {
1474 use super::*;
1475
1476 #[test]
1477 fn test_with_usergroup_id() {
1478 let blocks = vec![rich_text_block(serde_json::json!({
1479 "type": "rich_text",
1480 "elements": [
1481 {
1482 "type": "rich_text_section",
1483 "elements": [
1484 {
1485 "type": "usergroup",
1486 "usergroup_id": "group1"
1487 }
1488 ]
1489 }
1490 ]
1491 }))];
1492 assert_eq!(
1493 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1494 "@group1".to_string()
1495 );
1496 }
1497
1498 #[test]
1499 fn test_with_usergroup_id_and_custom_delimiter() {
1500 let blocks = vec![rich_text_block(serde_json::json!({
1501 "type": "rich_text",
1502 "elements": [
1503 {
1504 "type": "rich_text_section",
1505 "elements": [
1506 {
1507 "type": "usergroup",
1508 "usergroup_id": "group1"
1509 }
1510 ]
1511 }
1512 ]
1513 }))];
1514 assert_eq!(
1515 render_blocks_as_markdown(
1516 blocks,
1517 SlackReferences::default(),
1518 Some("@".to_string())
1519 ),
1520 "@@group1@".to_string()
1521 );
1522 }
1523
1524 #[test]
1525 fn test_with_usergroup_id_and_reference() {
1526 let blocks = vec![rich_text_block(serde_json::json!({
1527 "type": "rich_text",
1528 "elements": [
1529 {
1530 "type": "rich_text_section",
1531 "elements": [
1532 {
1533 "type": "usergroup",
1534 "usergroup_id": "group1"
1535 }
1536 ]
1537 }
1538 ]
1539 }))];
1540 assert_eq!(
1541 render_blocks_as_markdown(
1542 blocks,
1543 SlackReferences {
1544 usergroups: HashMap::from([(
1545 SlackUserGroupId("group1".to_string()),
1546 Some("Admins".to_string())
1547 )]),
1548 ..SlackReferences::default()
1549 },
1550 None
1551 ),
1552 "@Admins".to_string()
1553 );
1554 }
1555
1556 #[test]
1557 fn test_with_usergroup_id_and_reference_and_custom_delimiter() {
1558 let blocks = vec![rich_text_block(serde_json::json!({
1559 "type": "rich_text",
1560 "elements": [
1561 {
1562 "type": "rich_text_section",
1563 "elements": [
1564 {
1565 "type": "usergroup",
1566 "usergroup_id": "group1"
1567 }
1568 ]
1569 }
1570 ]
1571 }))];
1572 assert_eq!(
1573 render_blocks_as_markdown(
1574 blocks,
1575 SlackReferences {
1576 usergroups: HashMap::from([(
1577 SlackUserGroupId("group1".to_string()),
1578 Some("Admins".to_string())
1579 )]),
1580 ..SlackReferences::default()
1581 },
1582 Some("@".to_string())
1583 ),
1584 "@@Admins@".to_string()
1585 );
1586 }
1587 }
1588
1589 mod link_element {
1590 use super::*;
1591
1592 #[test]
1593 fn test_with_url() {
1594 let blocks = vec![rich_text_block(serde_json::json!({
1595 "type": "rich_text",
1596 "elements": [
1597 {
1598 "type": "rich_text_section",
1599 "elements": [
1600 {
1601 "type": "link",
1602 "text": "example",
1603 "url": "https://example.com"
1604 }
1605 ]
1606 }
1607 ]
1608 }))];
1609 assert_eq!(
1610 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1611 "[example](https://example.com)".to_string()
1612 );
1613 }
1614
1615 #[test]
1616 fn test_with_url_without_text() {
1617 let blocks = vec![rich_text_block(serde_json::json!({
1618 "type": "rich_text",
1619 "elements": [
1620 {
1621 "type": "rich_text_section",
1622 "elements": [
1623 {
1624 "type": "link",
1625 "url": "https://example.com"
1626 }
1627 ]
1628 }
1629 ]
1630 }))];
1631 assert_eq!(
1632 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1633 "[https://example.com](https://example.com)".to_string()
1634 );
1635 }
1636 }
1637
1638 mod attachment_mention_element {
1639 use super::*;
1640
1641 #[test]
1642 fn test_with_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://docs.google.com/document/d/1abc/edit?tab=t.0",
1652 "text": "Project plan",
1653 "app_id": "A6NL8MJ6Q",
1654 "entity_id": "W:A6NL8MJ6Q:1abc",
1655 "icon_url": "https://a.slack-edge.com/img/app-gdrive/icon_document.png",
1656 "product_name": "Google Docs",
1657 "channel_id": "C08GE9NPGFK",
1658 "ts": "1788850799.491249"
1659 }
1660 ]
1661 }
1662 ]
1663 }))];
1664 assert_eq!(
1665 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1666 "[Project plan](https://docs.google.com/document/d/1abc/edit?tab=t.0)"
1667 .to_string()
1668 );
1669 }
1670
1671 #[test]
1672 fn test_without_text() {
1673 let blocks = vec![rich_text_block(serde_json::json!({
1674 "type": "rich_text",
1675 "elements": [
1676 {
1677 "type": "rich_text_section",
1678 "elements": [
1679 {
1680 "type": "attachment_mention",
1681 "url": "https://example.com/doc"
1682 }
1683 ]
1684 }
1685 ]
1686 }))];
1687 assert_eq!(
1688 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1689 "[https://example.com/doc](https://example.com/doc)".to_string()
1690 );
1691 }
1692 }
1693
1694 mod message_mention_element {
1695 use super::*;
1696
1697 #[test]
1698 fn test_with_text() {
1699 let blocks = vec![rich_text_block(serde_json::json!({
1700 "type": "rich_text",
1701 "elements": [
1702 {
1703 "type": "rich_text_section",
1704 "elements": [
1705 {
1706 "type": "message_mention",
1707 "url": "https://slack.com/archives/C123/p1788850799491249",
1708 "text": "a previous message",
1709 "channel_id": "C123",
1710 "author_id": "U123",
1711 "message_ts": "1788850799.491249"
1712 }
1713 ]
1714 }
1715 ]
1716 }))];
1717 assert_eq!(
1718 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1719 "[a previous message](https://slack.com/archives/C123/p1788850799491249)"
1720 .to_string()
1721 );
1722 }
1723
1724 #[test]
1725 fn test_without_text() {
1726 let blocks = vec![rich_text_block(serde_json::json!({
1727 "type": "rich_text",
1728 "elements": [
1729 {
1730 "type": "rich_text_section",
1731 "elements": [
1732 {
1733 "type": "message_mention",
1734 "url": "https://slack.com/archives/C123/p1788850799491249"
1735 }
1736 ]
1737 }
1738 ]
1739 }))];
1740 assert_eq!(
1741 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1742 "[https://slack.com/archives/C123/p1788850799491249](https://slack.com/archives/C123/p1788850799491249)"
1743 .to_string()
1744 );
1745 }
1746 }
1747
1748 mod emoji_element {
1749 use super::*;
1750
1751 #[test]
1752 fn test_with_emoji() {
1753 let blocks = vec![rich_text_block(serde_json::json!({
1754 "type": "rich_text",
1755 "elements": [
1756 {
1757 "type": "rich_text_section",
1758 "elements": [
1759 {
1760 "type": "emoji",
1761 "name": "wave"
1762 }
1763 ]
1764 }
1765 ]
1766 }))];
1767 assert_eq!(
1768 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1769 "👋".to_string()
1770 );
1771 }
1772
1773 #[test]
1774 fn test_with_emoji_with_skin_tone() {
1775 let blocks = vec![rich_text_block(serde_json::json!({
1776 "type": "rich_text",
1777 "elements": [
1778 {
1779 "type": "rich_text_section",
1780 "elements": [
1781 {
1782 "type": "emoji",
1783 "name": "wave::skin-tone-2"
1784 }
1785 ]
1786 }
1787 ]
1788 }))];
1789 assert_eq!(
1790 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1791 "👋🏻".to_string()
1792 );
1793 }
1794
1795 #[test]
1796 fn test_with_emoji_with_unknown_skin_tone() {
1797 let blocks = vec![rich_text_block(serde_json::json!({
1798 "type": "rich_text",
1799 "elements": [
1800 {
1801 "type": "rich_text_section",
1802 "elements": [
1803 {
1804 "type": "emoji",
1805 "name": "wave::skin-tone-42"
1806 }
1807 ]
1808 }
1809 ]
1810 }))];
1811 assert_eq!(
1812 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1813 "👋".to_string()
1814 );
1815 }
1816
1817 #[test]
1818 fn test_with_unknown_emoji() {
1819 let blocks = vec![rich_text_block(serde_json::json!({
1820 "type": "rich_text",
1821 "elements": [
1822 {
1823 "type": "rich_text_section",
1824 "elements": [
1825 {
1826 "type": "emoji",
1827 "name": "unknown1"
1828 }
1829 ]
1830 }
1831 ]
1832 }))];
1833 assert_eq!(
1834 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1835 ":unknown1:".to_string()
1836 );
1837 }
1838
1839 #[test]
1840 fn test_with_unknown_emoji_with_slack_reference_alias() {
1841 let blocks = vec![rich_text_block(serde_json::json!({
1842 "type": "rich_text",
1843 "elements": [
1844 {
1845 "type": "rich_text_section",
1846 "elements": [
1847 {
1848 "type": "emoji",
1849 "name": "unknown1"
1850 }
1851 ]
1852 }
1853 ]
1854 }))];
1855 assert_eq!(
1856 render_blocks_as_markdown(
1857 blocks,
1858 SlackReferences {
1859 emojis: HashMap::from([(
1860 SlackEmojiName("unknown1".to_string()),
1861 Some(SlackEmojiRef::Alias(SlackEmojiName("wave".to_string())))
1862 )]),
1863 ..SlackReferences::default()
1864 },
1865 None
1866 ),
1867 "👋".to_string()
1868 );
1869 }
1870
1871 #[test]
1872 fn test_with_unknown_emoji_with_slack_reference_alias_to_custom_emoji() {
1873 let blocks = vec![rich_text_block(serde_json::json!({
1874 "type": "rich_text",
1875 "elements": [
1876 {
1877 "type": "rich_text_section",
1878 "elements": [
1879 {
1880 "type": "emoji",
1881 "name": "unknown1"
1882 }
1883 ]
1884 }
1885 ]
1886 }))];
1887 assert_eq!(
1888 render_blocks_as_markdown(
1889 blocks,
1890 SlackReferences {
1891 emojis: HashMap::from([
1892 (
1893 SlackEmojiName("unknown1".to_string()),
1894 Some(SlackEmojiRef::Alias(SlackEmojiName(
1895 "unknown2".to_string()
1896 )))
1897 ),
1898 (
1899 SlackEmojiName("unknown2".to_string()),
1900 Some(SlackEmojiRef::Url(
1901 "https://emoji.com/unknown2.png".parse().unwrap()
1902 ))
1903 )
1904 ]),
1905 ..SlackReferences::default()
1906 },
1907 None
1908 ),
1909 "".to_string()
1910 );
1911 }
1912
1913 #[test]
1914 fn test_with_unknown_emoji_with_slack_reference_image_url() {
1915 let blocks = vec![rich_text_block(serde_json::json!({
1916 "type": "rich_text",
1917 "elements": [
1918 {
1919 "type": "rich_text_section",
1920 "elements": [
1921 {
1922 "type": "emoji",
1923 "name": "unknown1"
1924 }
1925 ]
1926 }
1927 ]
1928 }))];
1929 assert_eq!(
1930 render_blocks_as_markdown(
1931 blocks,
1932 SlackReferences {
1933 emojis: HashMap::from([(
1934 SlackEmojiName("unknown1".to_string()),
1935 Some(SlackEmojiRef::Url(
1936 "https://emoji.com/unknown1.png".parse().unwrap()
1937 ))
1938 )]),
1939 ..SlackReferences::default()
1940 },
1941 None
1942 ),
1943 "".to_string()
1944 );
1945 }
1946 }
1947 }
1948
1949 mod rich_text_list {
1950 use super::*;
1951
1952 #[test]
1953 fn test_with_ordered_list() {
1954 let blocks = vec![rich_text_block(serde_json::json!({
1955 "type": "rich_text",
1956 "elements": [
1957 {
1958 "type": "rich_text_list",
1959 "style": "ordered",
1960 "elements": [
1961 {
1962 "type": "rich_text_section",
1963 "elements": [
1964 {
1965 "type": "text",
1966 "text": "Text1"
1967 }
1968 ]
1969 },
1970 {
1971 "type": "rich_text_section",
1972 "elements": [
1973 {
1974 "type": "text",
1975 "text": "Text2"
1976 }
1977 ]
1978 }
1979 ]
1980 },
1981 ]
1982 }))];
1983 assert_eq!(
1984 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
1985 "1. Text1\n1. Text2".to_string()
1986 );
1987 }
1988
1989 #[test]
1990 fn test_with_nested_ordered_list() {
1991 let blocks = vec![rich_text_block(serde_json::json!({
1992 "type": "rich_text",
1993 "elements": [
1994 {
1995 "type": "rich_text_list",
1996 "style": "ordered",
1997 "elements": [
1998 {
1999 "type": "rich_text_section",
2000 "elements": [
2001 {
2002 "type": "text",
2003 "text": "Text1"
2004 }
2005 ]
2006 },
2007 ]
2008 },
2009 {
2010 "type": "rich_text_list",
2011 "style": "ordered",
2012 "elements": [
2013 {
2014 "type": "rich_text_section",
2015 "elements": [
2016 {
2017 "type": "text",
2018 "text": "Text2"
2019 }
2020 ]
2021 },
2022 ]
2023 },
2024 {
2025 "type": "rich_text_list",
2026 "style": "ordered",
2027 "indent": 1,
2028 "elements": [
2029 {
2030 "type": "rich_text_section",
2031 "elements": [
2032 {
2033 "type": "text",
2034 "text": "Text2.1"
2035 }
2036 ]
2037 },
2038 ]
2039 },
2040 {
2041 "type": "rich_text_list",
2042 "style": "ordered",
2043 "indent": 2,
2044 "elements": [
2045 {
2046 "type": "rich_text_section",
2047 "elements": [
2048 {
2049 "type": "text",
2050 "text": "Text2.1.1"
2051 }
2052 ]
2053 },
2054 ]
2055 }
2056 ]
2057 }))];
2058 assert_eq!(
2059 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2060 "1. Text1\n1. Text2\n 1. Text2.1\n 1. Text2.1.1".to_string()
2061 );
2062 }
2063
2064 #[test]
2065 fn test_with_bullet_list() {
2066 let blocks = vec![rich_text_block(serde_json::json!({
2067 "type": "rich_text",
2068 "elements": [
2069 {
2070 "type": "rich_text_list",
2071 "style": "bullet",
2072 "elements": [
2073 {
2074 "type": "rich_text_section",
2075 "elements": [
2076 {
2077 "type": "text",
2078 "text": "Text1"
2079 }
2080 ]
2081 },
2082 {
2083 "type": "rich_text_section",
2084 "elements": [
2085 {
2086 "type": "text",
2087 "text": "Text2"
2088 }
2089 ]
2090 }
2091 ]
2092 },
2093 ]
2094 }))];
2095 assert_eq!(
2096 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2097 "- Text1\n- Text2".to_string()
2098 );
2099 }
2100 }
2101
2102 #[test]
2103 fn test_with_nested_bullet_list() {
2104 let blocks = vec![rich_text_block(serde_json::json!({
2105 "type": "rich_text",
2106 "elements": [
2107 {
2108 "type": "rich_text_list",
2109 "style": "bullet",
2110 "elements": [
2111 {
2112 "type": "rich_text_section",
2113 "elements": [
2114 {
2115 "type": "text",
2116 "text": "Text1"
2117 }
2118 ]
2119 },
2120 ]
2121 },
2122 {
2123 "type": "rich_text_list",
2124 "style": "bullet",
2125 "elements": [
2126 {
2127 "type": "rich_text_section",
2128 "elements": [
2129 {
2130 "type": "text",
2131 "text": "Text2"
2132 }
2133 ]
2134 },
2135 ]
2136 },
2137 {
2138 "type": "rich_text_list",
2139 "style": "bullet",
2140 "indent": 1,
2141 "elements": [
2142 {
2143 "type": "rich_text_section",
2144 "elements": [
2145 {
2146 "type": "text",
2147 "text": "Text2.1"
2148 }
2149 ]
2150 },
2151 ]
2152 },
2153 {
2154 "type": "rich_text_list",
2155 "style": "bullet",
2156 "indent": 2,
2157 "elements": [
2158 {
2159 "type": "rich_text_section",
2160 "elements": [
2161 {
2162 "type": "text",
2163 "text": "Text2.1.1"
2164 }
2165 ]
2166 },
2167 ]
2168 }
2169 ]
2170 }))];
2171 assert_eq!(
2172 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2173 "- Text1\n- Text2\n - Text2.1\n - Text2.1.1".to_string()
2174 );
2175 }
2176
2177 mod rich_text_preformatted {
2178 use super::*;
2179
2180 #[test]
2181 fn test_with_text() {
2182 let blocks = vec![rich_text_block(serde_json::json!({
2183 "type": "rich_text",
2184 "elements": [
2185 {
2186 "type": "rich_text_preformatted",
2187 "elements": [
2188 {
2189 "type": "text",
2190 "text": "Text1"
2191 },
2192 {
2193 "type": "text",
2194 "text": "Text2"
2195 }
2196 ]
2197 },
2198 ]
2199 }))];
2200 assert_eq!(
2201 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2202 "```\nText1Text2\n```".to_string()
2203 );
2204 }
2205
2206 #[test]
2207 fn test_with_text_and_newline() {
2208 let blocks = vec![rich_text_block(serde_json::json!({
2209 "type": "rich_text",
2210 "elements": [
2211 {
2212 "type": "rich_text_preformatted",
2213 "elements": [
2214 {
2215 "type": "text",
2216 "text": "test:\n sub: value"
2217 }
2218 ]
2219 }
2220 ]
2221 }))];
2222 assert_eq!(
2223 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2224 "```\ntest:\n sub: value\n```".to_string()
2225 );
2226 }
2227
2228 #[test]
2229 fn test_with_preformatted_text_followed_by_text() {
2230 let blocks = vec![rich_text_block(serde_json::json!({
2231 "type": "rich_text",
2232 "elements": [
2233 {
2234 "type": "rich_text_preformatted",
2235 "elements": [
2236 {
2237 "type": "text",
2238 "text": "Text1"
2239 }
2240 ]
2241 },
2242 {
2243 "type": "rich_text_section",
2244 "elements": [
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 "```\nText1\n```\nText2".to_string()
2256 );
2257 }
2258 }
2259
2260 mod rich_text_quote {
2261 use super::*;
2262
2263 #[test]
2264 fn test_with_text() {
2265 let blocks = vec![rich_text_block(serde_json::json!({
2266 "type": "rich_text",
2267 "elements": [
2268 {
2269 "type": "rich_text_quote",
2270 "elements": [
2271 {
2272 "type": "text",
2273 "text": "Text1"
2274 },
2275 {
2276 "type": "text",
2277 "text": "Text2"
2278 }
2279 ]
2280 },
2281 ]
2282 }))];
2283 assert_eq!(
2284 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2285 "> Text1Text2".to_string()
2286 );
2287 }
2288
2289 #[test]
2290 fn test_with_quoted_text_followed_by_quoted_text() {
2291 let blocks = vec![rich_text_block(serde_json::json!({
2292 "type": "rich_text",
2293 "elements": [
2294 {
2295 "type": "rich_text_quote",
2296 "elements": [
2297 {
2298 "type": "text",
2299 "text": "Text1"
2300 },
2301 ]
2302 },
2303 {
2304 "type": "rich_text_quote",
2305 "elements": [
2306 {
2307 "type": "text",
2308 "text": "Text2"
2309 }
2310 ]
2311 },
2312 ]
2313 }))];
2314 assert_eq!(
2315 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2316 "> Text1\n> Text2".to_string()
2317 );
2318 }
2319
2320 #[test]
2321 fn test_with_quoted_text_followed_by_non_quoted_text() {
2322 let blocks = vec![rich_text_block(serde_json::json!({
2323 "type": "rich_text",
2324 "elements": [
2325 {
2326 "type": "rich_text_quote",
2327 "elements": [
2328 {
2329 "text": "Text1",
2330 "type": "text"
2331 }
2332 ]
2333 },
2334 {
2335 "type": "rich_text_section",
2336 "elements": [
2337 {
2338 "text": "Text2",
2339 "type": "text"
2340 },
2341 ]
2342 }
2343 ]
2344 }))];
2345
2346 assert_eq!(
2347 render_blocks_as_markdown(blocks, SlackReferences::default(), None),
2348 "> Text1\n\nText2".to_string()
2349 );
2350 }
2351 }
2352 }
2353}