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