discord_md/
ast.rs

1//! Markdown AST structure
2//!
3//! [`ast`](crate::ast) module provides syntax tree components.
4//!
5//! Note: [`builder`](crate::builder) module provides helper functions to build AST in less lines of code.
6//!
7//! # AST structure
8//!
9//! An AST consists of [`MarkdownDocument`], [`MarkdownElementCollection`], and [`MarkdownElement`].
10//!
11//! - [`MarkdownDocument`] is the root of AST. It contains a [`MarkdownElementCollection`].
12//! - [`MarkdownElementCollection`] is a collection of markdown elements. It consists of one or more [`MarkdownElement`].
13//! - [`MarkdownElement`] is a markdown element, such as `plain text` and `*italics text*`. The content of [`MarkdownElement`] can be nested. For instance, `__*nested* styles__` is also valid markdown element.
14//!
15//! # Generating markdown text
16//!
17//! `MarkdownDocument::to_string()` generates markdown text from the AST.
18//!
19//! ## Example
20//!
21//! ```
22//! use discord_md::ast::*;
23//!
24//! let ast = MarkdownDocument::new(vec![
25//!     MarkdownElement::Bold(Box::new(Bold::new("bold"))),
26//!     MarkdownElement::Plain(Box::new(Plain::new(" text")))
27//! ]);
28//!
29//! assert_eq!(ast.to_string(), "**bold** text");
30//! ```
31
32use crate::generate::{ToMarkdownString, ToMarkdownStringOption};
33use derive_more::{Display, From, Into, IntoIterator};
34
35/// A markdown document. The root of AST.
36///
37/// # Generating markdown text
38///
39/// `to_string()` generates markdown text from the AST.
40///
41/// ## Example
42///
43/// ```
44/// use discord_md::ast::*;
45///
46/// let ast = MarkdownDocument::new(vec![
47///     MarkdownElement::Bold(Box::new(Bold::new("bold text")))
48/// ]);
49///
50/// assert_eq!(ast.to_string(), "**bold text**");
51/// ```
52#[derive(Debug, Eq, PartialEq, Hash, Default, Display)]
53#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
54pub struct MarkdownDocument {
55    content: MarkdownElementCollection,
56}
57
58impl MarkdownDocument {
59    /// Creates a markdown document.
60    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
61        Self {
62            content: content.into(),
63        }
64    }
65
66    /// Returns the content of the markdown document.
67    pub fn content(&self) -> &MarkdownElementCollection {
68        &self.content
69    }
70}
71
72/// A collection of [`MarkdownElement`].
73#[derive(Debug, Eq, PartialEq, Hash, Default, From, Into, IntoIterator, Display)]
74#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
75pub struct MarkdownElementCollection(Vec<MarkdownElement>);
76
77impl MarkdownElementCollection {
78    /// Creates a collection of markdown element.
79    pub fn new(value: Vec<MarkdownElement>) -> Self {
80        Self(value)
81    }
82
83    /// Returns the collection of markdown element in [`Vec`].
84    pub fn get(&self) -> &Vec<MarkdownElement> {
85        &self.0
86    }
87}
88
89impl From<MarkdownElement> for MarkdownElementCollection {
90    fn from(value: MarkdownElement) -> Self {
91        MarkdownElementCollection::new(vec![value])
92    }
93}
94
95impl From<&str> for MarkdownElementCollection {
96    fn from(value: &str) -> Self {
97        MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(value)))])
98    }
99}
100
101impl From<String> for MarkdownElementCollection {
102    fn from(value: String) -> Self {
103        MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(value)))])
104    }
105}
106
107impl From<&String> for MarkdownElementCollection {
108    fn from(value: &String) -> Self {
109        MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(value)))])
110    }
111}
112
113/// A markdown element.
114#[derive(Debug, Eq, PartialEq, Hash, Display)]
115pub enum MarkdownElement {
116    /// Plain text.
117    Plain(Box<Plain>),
118
119    /// Italics text, wrapped in `*`.
120    ItalicsStar(Box<ItalicsStar>),
121
122    /// Italics text, wrapped in `_`.
123    ItalicsUnderscore(Box<ItalicsUnderscore>),
124
125    /// Bold text, wrapped in `**`.
126    Bold(Box<Bold>),
127
128    /// Underline text, wrapped in `__`.
129    Underline(Box<Underline>),
130
131    /// Strikethrough text, wrapped in `~~`.
132    Strikethrough(Box<Strikethrough>),
133
134    /// Spoiler text, wrapped in `||`.
135    Spoiler(Box<Spoiler>),
136
137    /// Inline code, wrapped in `` ` ``.
138    OneLineCode(Box<OneLineCode>),
139
140    /// Multiline code block, wrapped in ```` ``` ````.
141    MultiLineCode(Box<MultiLineCode>),
142
143    /// Block quote, preceded by `> `.
144    BlockQuote(Box<BlockQuote>),
145}
146
147/// Plain text.
148///
149/// # Example markdown text
150///
151/// `plain text` (plain text)
152#[derive(Debug, Eq, PartialEq, Hash, Display)]
153#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
154pub struct Plain {
155    content: String,
156}
157
158impl Plain {
159    /// Creates plain text.
160    pub fn new(content: impl Into<String>) -> Self {
161        Self {
162            content: content.into(),
163        }
164    }
165
166    /// Returns the content of plain text.
167    pub fn content(&self) -> &str {
168        &self.content
169    }
170}
171
172/// Italics text, wrapped in `*`.
173///
174/// # Example markdown text
175///
176/// `*italics text*` (*italics text*)
177#[derive(Debug, Eq, PartialEq, Hash, Display)]
178#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
179pub struct ItalicsStar {
180    content: MarkdownElementCollection,
181}
182
183impl ItalicsStar {
184    /// Creates italics text wrapped in `*`.
185    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
186        Self {
187            content: content.into(),
188        }
189    }
190
191    /// Returns the content of italics text.
192    pub fn content(&self) -> &MarkdownElementCollection {
193        &self.content
194    }
195}
196
197/// Italics text, wrapped in `_`.
198///
199/// # Example markdown text
200///
201/// `_italics text_` (_italics text_)
202#[derive(Debug, Eq, PartialEq, Hash, Display)]
203#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
204pub struct ItalicsUnderscore {
205    content: MarkdownElementCollection,
206}
207
208impl ItalicsUnderscore {
209    /// Creates italics text wrapped in `_`.
210    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
211        Self {
212            content: content.into(),
213        }
214    }
215
216    /// Returns the content of italics text.
217    pub fn content(&self) -> &MarkdownElementCollection {
218        &self.content
219    }
220}
221
222/// Bold text, wrapped in `**`.
223///
224/// # Example markdown text
225///
226/// `**bold text**` (**bold text**)
227#[derive(Debug, Eq, PartialEq, Hash, Display)]
228#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
229pub struct Bold {
230    content: MarkdownElementCollection,
231}
232
233impl Bold {
234    /// Creates bold text.
235    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
236        Self {
237            content: content.into(),
238        }
239    }
240
241    /// Returns the content of bold text.
242    pub fn content(&self) -> &MarkdownElementCollection {
243        &self.content
244    }
245}
246
247/// Underline text, wrapped in `__`.
248///
249/// # Example markdown text
250///
251/// `__underline text__`
252#[derive(Debug, Eq, PartialEq, Hash, Display)]
253#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
254pub struct Underline {
255    content: MarkdownElementCollection,
256}
257
258impl Underline {
259    /// Creates underline text.
260    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
261        Self {
262            content: content.into(),
263        }
264    }
265
266    /// Returns the content of underline text.
267    pub fn content(&self) -> &MarkdownElementCollection {
268        &self.content
269    }
270}
271
272/// Strikethrough text, wrapped in `~~`.
273///
274/// # Example markdown text
275///
276/// `~~strikethrough text~~` (~~strikethrough text~~)
277#[derive(Debug, Eq, PartialEq, Hash, Display)]
278#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
279pub struct Strikethrough {
280    content: MarkdownElementCollection,
281}
282
283impl Strikethrough {
284    /// Creates strikethrough text.
285    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
286        Self {
287            content: content.into(),
288        }
289    }
290
291    /// Returns the content of strikethrough text.
292    pub fn content(&self) -> &MarkdownElementCollection {
293        &self.content
294    }
295}
296
297/// Spoiler text, wrapped in `||`.
298///
299/// # Example markdown text
300///
301/// `||spoiler text||`
302#[derive(Debug, Eq, PartialEq, Hash, Display)]
303#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
304pub struct Spoiler {
305    content: MarkdownElementCollection,
306}
307
308impl Spoiler {
309    /// Creates spoiler text.
310    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
311        Self {
312            content: content.into(),
313        }
314    }
315
316    /// Returns the content of spoiler text.
317    pub fn content(&self) -> &MarkdownElementCollection {
318        &self.content
319    }
320}
321
322/// Inline code, wrapped in `` ` ``.
323///
324/// # Example markdown text
325///
326/// `` `let foo = "bar";` `` (`let foo = "bar";`)
327#[derive(Debug, Eq, PartialEq, Hash, Display)]
328#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
329pub struct OneLineCode {
330    content: String,
331}
332
333impl OneLineCode {
334    /// Creates an inline code.
335    pub fn new(content: impl Into<String>) -> Self {
336        Self {
337            content: content.into(),
338        }
339    }
340
341    /// Returns the content of the code block.
342    pub fn content(&self) -> &str {
343        &self.content
344    }
345}
346
347/// Multiline code block, wrapped in ```` ``` ````.
348///
349/// # Example markdown text
350///
351/// ````text
352/// ```html
353/// <p>
354///   code block
355/// </p>
356/// ```
357/// ````
358#[derive(Debug, Eq, PartialEq, Hash, Display)]
359#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
360pub struct MultiLineCode {
361    content: String,
362    language: Option<String>,
363}
364
365impl MultiLineCode {
366    /// Creates a multiline code block.
367    pub fn new(content: impl Into<String>, language: Option<String>) -> Self {
368        // language の型を Option<impl Into<String>> にしたいが、そうすると None を渡せなくなる
369        // never type の実装を待つ必要がありそう
370        // https://stackoverflow.com/q/42141129
371        Self {
372            content: content.into(),
373            language,
374        }
375    }
376
377    /// Returns the content of the code block.
378    pub fn content(&self) -> &str {
379        &self.content
380    }
381
382    /// Returns the language of the code block.
383    pub fn language(&self) -> Option<&str> {
384        self.language.as_deref()
385    }
386}
387
388/// Block quote, preceded by `> `.
389///
390/// # Example markdown text
391///
392/// ```text
393/// > this is
394/// > block quote
395/// ```
396#[derive(Debug, Eq, PartialEq, Hash, Display)]
397#[display(fmt = "{}", "self.to_markdown_string(&ToMarkdownStringOption::new())")]
398pub struct BlockQuote {
399    content: MarkdownElementCollection,
400}
401
402impl BlockQuote {
403    /// Creates a block quote text.
404    pub fn new(content: impl Into<MarkdownElementCollection>) -> Self {
405        Self {
406            content: content.into(),
407        }
408    }
409
410    /// Returns the content of the block quote text.
411    pub fn content(&self) -> &MarkdownElementCollection {
412        &self.content
413    }
414}
415
416impl From<Plain> for MarkdownElement {
417    fn from(value: Plain) -> Self {
418        MarkdownElement::Plain(Box::new(value))
419    }
420}
421
422impl From<ItalicsStar> for MarkdownElement {
423    fn from(value: ItalicsStar) -> Self {
424        MarkdownElement::ItalicsStar(Box::new(value))
425    }
426}
427
428impl From<ItalicsUnderscore> for MarkdownElement {
429    fn from(value: ItalicsUnderscore) -> Self {
430        MarkdownElement::ItalicsUnderscore(Box::new(value))
431    }
432}
433
434impl From<Bold> for MarkdownElement {
435    fn from(value: Bold) -> Self {
436        MarkdownElement::Bold(Box::new(value))
437    }
438}
439
440impl From<Underline> for MarkdownElement {
441    fn from(value: Underline) -> Self {
442        MarkdownElement::Underline(Box::new(value))
443    }
444}
445
446impl From<Strikethrough> for MarkdownElement {
447    fn from(value: Strikethrough) -> Self {
448        MarkdownElement::Strikethrough(Box::new(value))
449    }
450}
451
452impl From<Spoiler> for MarkdownElement {
453    fn from(value: Spoiler) -> Self {
454        MarkdownElement::Spoiler(Box::new(value))
455    }
456}
457
458impl From<OneLineCode> for MarkdownElement {
459    fn from(value: OneLineCode) -> Self {
460        MarkdownElement::OneLineCode(Box::new(value))
461    }
462}
463
464impl From<MultiLineCode> for MarkdownElement {
465    fn from(value: MultiLineCode) -> Self {
466        MarkdownElement::MultiLineCode(Box::new(value))
467    }
468}
469
470impl From<BlockQuote> for MarkdownElement {
471    fn from(value: BlockQuote) -> Self {
472        MarkdownElement::BlockQuote(Box::new(value))
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479
480    fn example_text() -> MarkdownElementCollection {
481        MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new("text")))])
482    }
483
484    #[test]
485    fn test_document_content() {
486        assert_eq!(
487            MarkdownDocument::new(example_text()).content(),
488            &example_text()
489        );
490    }
491
492    #[test]
493    fn test_element_collection_get() {
494        let test_case = || vec![MarkdownElement::Plain(Box::new(Plain::new("plain")))];
495        assert_eq!(
496            MarkdownElementCollection::new(test_case()).get(),
497            &test_case()
498        );
499    }
500
501    #[test]
502    fn test_element_collection_from_element() {
503        assert_eq!(
504            MarkdownElementCollection::from(MarkdownElement::Plain(Box::new(Plain::new(
505                "plain text"
506            )))),
507            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
508                "plain text"
509            )))]),
510        );
511    }
512
513    #[test]
514    fn test_element_collection_from_str() {
515        assert_eq!(
516            MarkdownElementCollection::from("plain text"),
517            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
518                "plain text"
519            )))]),
520        );
521    }
522
523    #[test]
524    fn test_element_collection_from_string() {
525        let test_case = "plain text".to_string();
526        assert_eq!(
527            MarkdownElementCollection::from(&test_case),
528            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
529                "plain text"
530            )))]),
531        );
532        assert_eq!(
533            MarkdownElementCollection::from(test_case),
534            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
535                "plain text"
536            )))]),
537        );
538    }
539
540    #[test]
541    fn test_plain_content() {
542        assert_eq!(Plain::new("plain text").content(), "plain text");
543    }
544
545    #[test]
546    fn test_italics_star_content() {
547        assert_eq!(ItalicsStar::new(example_text()).content(), &example_text());
548    }
549
550    #[test]
551    fn test_italics_underscore_content() {
552        assert_eq!(
553            ItalicsUnderscore::new(example_text()).content(),
554            &example_text()
555        );
556    }
557
558    #[test]
559    fn test_bold_content() {
560        assert_eq!(Bold::new(example_text()).content(), &example_text());
561    }
562
563    #[test]
564    fn test_underline_content() {
565        assert_eq!(Underline::new(example_text()).content(), &example_text());
566    }
567
568    #[test]
569    fn test_strikethrough_content() {
570        assert_eq!(
571            Strikethrough::new(example_text()).content(),
572            &example_text()
573        );
574    }
575
576    #[test]
577    fn test_spoiler_content() {
578        assert_eq!(Spoiler::new(example_text()).content(), &example_text());
579    }
580
581    #[test]
582    fn test_one_line_code_content() {
583        assert_eq!(OneLineCode::new("one line code").content(), "one line code");
584    }
585
586    #[test]
587    fn test_multi_line_code_content() {
588        assert_eq!(
589            MultiLineCode::new("multi\nline\ncode\n", None).content(),
590            "multi\nline\ncode\n"
591        );
592    }
593
594    #[test]
595    fn test_multi_line_code_language() {
596        assert_eq!(
597            MultiLineCode::new("multi\nline\ncode\n", Some("js".to_string())).language(),
598            Some("js")
599        );
600        assert_eq!(
601            MultiLineCode::new("multi\nline\ncode\n", None).language(),
602            None
603        );
604    }
605
606    #[test]
607    fn test_block_quote_content() {
608        let test_case = || {
609            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
610                "block quote\ntext",
611            )))])
612        };
613
614        assert_eq!(BlockQuote::new(test_case()).content(), &test_case());
615    }
616
617    #[test]
618    fn test_document_to_string() {
619        let ast = MarkdownDocument::new(MarkdownElementCollection::new(vec![
620            MarkdownElement::Bold(Box::new(Bold::new(MarkdownElementCollection::new(vec![
621                MarkdownElement::Plain(Box::new(Plain::new("bold"))),
622            ])))),
623            MarkdownElement::Plain(Box::new(Plain::new(" plain"))),
624        ]));
625
626        assert_eq!(ast.to_string(), "**bold** plain");
627    }
628
629    #[test]
630    fn test_element_collection_to_string() {
631        let ast = MarkdownElementCollection::new(vec![
632            MarkdownElement::Bold(Box::new(Bold::new(MarkdownElementCollection::new(vec![
633                MarkdownElement::Plain(Box::new(Plain::new("bold"))),
634            ])))),
635            MarkdownElement::Plain(Box::new(Plain::new(" plain "))),
636            MarkdownElement::Underline(Box::new(Underline::new(MarkdownElementCollection::new(
637                vec![MarkdownElement::Bold(Box::new(Bold::new(
638                    MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(
639                        Plain::new("underline bold"),
640                    ))]),
641                )))],
642            )))),
643        ]);
644
645        assert_eq!(ast.to_string(), "**bold** plain __**underline bold**__");
646    }
647
648    #[test]
649    fn test_plain_to_string() {
650        assert_eq!(Plain::new("plain text").to_string(), "plain text");
651    }
652
653    #[test]
654    fn test_italics_star_to_string() {
655        assert_eq!(ItalicsStar::new(example_text()).to_string(), "*text*");
656    }
657
658    #[test]
659    fn test_italics_underscore_to_string() {
660        assert_eq!(ItalicsUnderscore::new(example_text()).to_string(), "_text_");
661    }
662
663    #[test]
664    fn test_bold_to_string() {
665        assert_eq!(Bold::new(example_text()).to_string(), "**text**");
666    }
667
668    #[test]
669    fn test_underline_to_string() {
670        assert_eq!(Underline::new(example_text()).to_string(), "__text__");
671    }
672
673    #[test]
674    fn test_strikethrough_to_string() {
675        assert_eq!(Strikethrough::new(example_text()).to_string(), "~~text~~");
676    }
677
678    #[test]
679    fn test_spoiler_to_string() {
680        assert_eq!(Spoiler::new(example_text()).to_string(), "||text||");
681    }
682
683    #[test]
684    fn test_one_line_code_to_string() {
685        assert_eq!(
686            OneLineCode::new("one line code").to_string(),
687            "`one line code`"
688        );
689    }
690
691    #[test]
692    fn test_multi_line_code_to_string() {
693        assert_eq!(
694            MultiLineCode::new("\nmulti\nline\ncode\n", None).to_string(),
695            "```\nmulti\nline\ncode\n```"
696        );
697        assert_eq!(
698            MultiLineCode::new(" multi\nline\ncode\n", None).to_string(),
699            "``` multi\nline\ncode\n```"
700        );
701        assert_eq!(
702            MultiLineCode::new("multi line code", None).to_string(),
703            "```multi line code```"
704        );
705        assert_eq!(
706            MultiLineCode::new("\nmulti\nline\ncode\n", Some("js".to_string())).to_string(),
707            "```js\nmulti\nline\ncode\n```"
708        );
709    }
710
711    #[test]
712    fn test_block_quote_to_string() {
713        let test_case = || {
714            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
715                "block quote\ntext",
716            )))])
717        };
718
719        assert_eq!(
720            BlockQuote::new(test_case()).to_string(),
721            "> block quote\n> text"
722        );
723    }
724
725    #[test]
726    fn test_element_from_plain() {
727        assert_eq!(
728            MarkdownElement::from(Plain::new("plain text")),
729            MarkdownElement::Plain(Box::new(Plain::new("plain text")))
730        );
731    }
732
733    #[test]
734    fn test_element_from_italics_star() {
735        assert_eq!(
736            MarkdownElement::from(ItalicsStar::new(example_text())),
737            MarkdownElement::ItalicsStar(Box::new(ItalicsStar::new(example_text())))
738        );
739    }
740
741    #[test]
742    fn test_element_from_italics_underscore() {
743        assert_eq!(
744            MarkdownElement::from(ItalicsUnderscore::new(example_text())),
745            MarkdownElement::ItalicsUnderscore(Box::new(ItalicsUnderscore::new(example_text())))
746        );
747    }
748
749    #[test]
750    fn test_element_from_bold() {
751        assert_eq!(
752            MarkdownElement::from(Bold::new(example_text())),
753            MarkdownElement::Bold(Box::new(Bold::new(example_text())))
754        );
755    }
756
757    #[test]
758    fn test_element_from_underline() {
759        assert_eq!(
760            MarkdownElement::from(Underline::new(example_text())),
761            MarkdownElement::Underline(Box::new(Underline::new(example_text())))
762        );
763    }
764
765    #[test]
766    fn test_element_from_strikethrough() {
767        assert_eq!(
768            MarkdownElement::from(Strikethrough::new(example_text())),
769            MarkdownElement::Strikethrough(Box::new(Strikethrough::new(example_text())))
770        );
771    }
772
773    #[test]
774    fn test_element_from_spoiler() {
775        assert_eq!(
776            MarkdownElement::from(Spoiler::new(example_text())),
777            MarkdownElement::Spoiler(Box::new(Spoiler::new(example_text())))
778        );
779    }
780
781    #[test]
782    fn test_element_from_one_line_code() {
783        assert_eq!(
784            MarkdownElement::from(OneLineCode::new("one line code")),
785            MarkdownElement::OneLineCode(Box::new(OneLineCode::new("one line code")))
786        );
787    }
788
789    #[test]
790    fn test_element_from_multi_line_code() {
791        assert_eq!(
792            MarkdownElement::from(MultiLineCode::new(
793                "multi\nline\ncode\n",
794                Some("js".to_string())
795            )),
796            MarkdownElement::MultiLineCode(Box::new(MultiLineCode::new(
797                "multi\nline\ncode\n",
798                Some("js".to_string())
799            )))
800        );
801    }
802
803    #[test]
804    fn test_element_from_block_quote() {
805        let test_case = || {
806            MarkdownElementCollection::new(vec![MarkdownElement::Plain(Box::new(Plain::new(
807                "block quote\ntext",
808            )))])
809        };
810
811        assert_eq!(
812            MarkdownElement::from(BlockQuote::new(test_case())),
813            MarkdownElement::BlockQuote(Box::new(BlockQuote::new(test_case())))
814        );
815    }
816}