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