package textfmt_test import ( "bytes" "code.squareroundforest.org/arpio/textfmt" "testing" ) func TestMarkdown(t *testing.T) { t.Run("invalid", func(t *testing.T) { var b bytes.Buffer if err := textfmt.Markdown(&b, textfmt.Doc(textfmt.Entry{})); err == nil { t.Fatal("failed to fail") } }) t.Run("empty", func(t *testing.T) { var b bytes.Buffer if err := textfmt.Markdown(&b, textfmt.Doc()); err != nil { t.Fatal(err) } if b.String() != "" { t.Fatal(b.String()) } }) t.Run("example", func(t *testing.T) { doc := textfmt.Doc( textfmt.Title(0, "Example Text"), textfmt.Wrap( textfmt.Indent( textfmt.Paragraph(textfmt.Text("Below you can find some test text, with various text items.")), 0, 8, ), 30, ), textfmt.Title(1, "Document syntax:"), textfmt.Indent( textfmt.Syntax( textfmt.Symbol("textfmt.Doc"), textfmt.Symbol("("), textfmt.ZeroOrMore(textfmt.Symbol("Entry")), textfmt.Symbol(")"), ), 0, 8, ), textfmt.Title(1, "Entries:"), textfmt.Paragraph(textfmt.Text("textfmt supports the following entries:")), textfmt.List( textfmt.Item(textfmt.Text("CodeBlock")), textfmt.Item(textfmt.Text("DefinitionList")), textfmt.Item(textfmt.Text("List")), textfmt.Item(textfmt.Text("NumberedDefinitionList")), textfmt.Item(textfmt.Text("NumberedList")), textfmt.Item(textfmt.Text("Paragraph")), textfmt.Item(textfmt.Text("Syntax")), textfmt.Item(textfmt.Text("Table")), textfmt.Item(textfmt.Text("Title")), ), textfmt.Title(1, "Entry explanations:"), textfmt.Wrap( textfmt.DefinitionList( textfmt.Definition( textfmt.Text("CodeBlock"), textfmt.Text("a multiline block of code"), ), textfmt.Definition( textfmt.Text("DefinitionList"), textfmt.Text("a list of definitions like this one"), ), textfmt.Definition( textfmt.Text("List"), textfmt.Text("a list of items"), ), textfmt.Definition( textfmt.Text("NumberedDefinitionList"), textfmt.Text("numbered definitions"), ), textfmt.Definition( textfmt.Text("NumberedList"), textfmt.Text("numbered list"), ), textfmt.Definition( textfmt.Text("Paragraph"), textfmt.Text("paragraph of text"), ), textfmt.Definition( textfmt.Text("Syntax"), textfmt.Text("a syntax expression"), ), textfmt.Definition( textfmt.Text("Table"), textfmt.Text("a table"), ), textfmt.Definition( textfmt.Text("Title"), textfmt.Text("a title"), ), ), 48, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` # Example Text Below you can find some test text, with various text items. ## Document syntax: ` + "```" + ` textfmt.Doc ( [Entry]... ) ` + "```" + ` ## Entries: textfmt supports the following entries: - CodeBlock - DefinitionList - List - NumberedDefinitionList - NumberedList - Paragraph - Syntax - Table - Title ## Entry explanations: - CodeBlock: a multiline block of code - DefinitionList: a list of definitions like this one - List: a list of items - NumberedDefinitionList: numbered definitions - NumberedList: numbered list - Paragraph: paragraph of text - Syntax: a syntax expression - Table: a table - Title: a title ` if "\n"+b.String() != expect { t.Log("\n" + b.String()) t.Log(expect) logBytes(t, "\n"+b.String()) logBytes(t, expect) t.Fatal() } }) t.Run("write error", func(t *testing.T) { w := &failingWriter{failAfter: 15} doc := textfmt.Doc( textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), ) if err := textfmt.Markdown(w, doc); err == nil { t.Fatal("failed to fail") } }) t.Run("title", func(t *testing.T) { t.Run("top level", func(t *testing.T) { doc := textfmt.Doc(textfmt.Title(0, "This is a title")) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "# This is a title\n" { t.Fatal(b.String()) } }) t.Run("second level", func(t *testing.T) { doc := textfmt.Doc(textfmt.Title(1, "This is a title")) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "## This is a title\n" { t.Fatal(b.String()) } }) t.Run("fourth level", func(t *testing.T) { doc := textfmt.Doc(textfmt.Title(3, "This is a title")) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "#### This is a title\n" { t.Fatal(b.String()) } }) t.Run("sixth level", func(t *testing.T) { doc := textfmt.Doc(textfmt.Title(5, "This is a title")) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "###### This is a title\n" { t.Fatal(b.String()) } }) t.Run("overflow level", func(t *testing.T) { doc := textfmt.Doc(textfmt.Title(6, "This is a title")) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "###### This is a title\n" { t.Fatal(b.String()) } }) t.Run("indent ignored", func(t *testing.T) { doc := textfmt.Doc(textfmt.Indent(textfmt.Title(1, "This is a title"), 4, 0)) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "## This is a title\n" { t.Fatal(b.String()) } }) t.Run("wrap ignored", func(t *testing.T) { doc := textfmt.Doc(textfmt.Wrap(textfmt.Title(1, "This is a title"), 6)) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "## This is a title\n" { t.Fatal(b.String()) } }) }) t.Run("paragraph", func(t *testing.T) { t.Run("simple", func(t *testing.T) { doc := textfmt.Doc(textfmt.Paragraph(textfmt.Text("This is a sample paragraph."))) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "This is a sample paragraph.\n" { t.Fatal(b.String()) } }) t.Run("wrap", func(t *testing.T) { doc := textfmt.Doc(textfmt.Wrap(textfmt.Paragraph(textfmt.Text("This is a sample paragraph.")), 6)) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "This\nis a\nsample\nparagraph.\n" { t.Fatal(b.String()) } }) t.Run("ignore indent", func(t *testing.T) { doc := textfmt.Doc(textfmt.Indent(textfmt.Paragraph(textfmt.Text("This is a sample paragraph.")), 4, 0)) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "This is a sample paragraph.\n" { t.Fatal(b.String()) } }) t.Run("cat", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Cat( textfmt.Text("This is"), textfmt.Text("a sample"), textfmt.Text("paragraph."), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "This is a sample paragraph.\n" { t.Fatal(b.String()) } }) t.Run("bold", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Cat( textfmt.Text("This is"), textfmt.Bold(textfmt.Text("a sample")), textfmt.Text("paragraph."), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "This is **a sample** paragraph.\n" { t.Fatal(b.String()) } }) t.Run("italic", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Cat( textfmt.Text("This is"), textfmt.Italic(textfmt.Text("a sample")), textfmt.Text("paragraph."), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "This is _a sample_ paragraph.\n" { t.Fatal(b.String()) } }) t.Run("bold italic", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Cat( textfmt.Italic(textfmt.Text("This is")), textfmt.Italic(textfmt.Bold(textfmt.Text("a sample"))), textfmt.Italic(textfmt.Text("paragraph.")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "_This is_ **_a sample_** _paragraph._\n" { t.Fatal(b.String()) } }) t.Run("escape", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Text("\\`*_[]#<>"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "\\\\\\`\\*\\_\\[\\]\\#\\<\\>\n" { t.Fatal(b.String()) } }) t.Run("escape link", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Text("[looks-like-a-link](https://foo.bar)"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "\\[looks-like-a-link\\]\\(https://foo.bar\\)\n" { t.Fatal(b.String()) } }) t.Run("escape negative number on line start", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Text("-42 policemen jumped on the bus"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "\\-42 policemen jumped on the bus\n" { t.Fatal(b.String()) } }) t.Run("escape year on line start", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( textfmt.Text("2005. was the year when it started, and not 2002. tbh"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "2005\\. was the year when it started, and not 2002. tbh\n" { t.Fatal(b.String()) } }) }) t.Run("list", func(t *testing.T) { t.Run("simple", func(t *testing.T) { doc := textfmt.Doc( textfmt.List( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` - item one - item two - item three ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("indent ignored", func(t *testing.T) { doc := textfmt.Doc( textfmt.Indent( textfmt.List( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), ), 4, 0, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` - item one - item two - item three ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("wrapped", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.List( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), ), 6, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` - item one - item two - item three ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) }) t.Run("numbered list", func(t *testing.T) { t.Run("simple", func(t *testing.T) { doc := textfmt.Doc( textfmt.NumberedList( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. item one 2. item two 3. item three ` if "\n"+b.String() != expect { logBytes(t, expect) logBytes(t, "\n"+b.String()) t.Log(expect) t.Fatal("\n" + b.String()) } }) t.Run("indent ignored", func(t *testing.T) { doc := textfmt.Doc( textfmt.Indent( textfmt.NumberedList( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), ), 4, 0, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. item one 2. item two 3. item three ` if "\n"+b.String() != expect { logBytes(t, expect) logBytes(t, "\n"+b.String()) t.Log(expect) t.Fatal("\n" + b.String()) } }) t.Run("wrap", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.NumberedList( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), ), 9, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. item one 2. item two 3. item three ` if "\n"+b.String() != expect { logBytes(t, expect) logBytes(t, "\n"+b.String()) t.Log(expect) t.Fatal("\n" + b.String()) } }) t.Run("long list", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.NumberedList( textfmt.Item(textfmt.Text("item one")), textfmt.Item(textfmt.Text("item two")), textfmt.Item(textfmt.Text("item three")), textfmt.Item(textfmt.Text("item four")), textfmt.Item(textfmt.Text("item five")), textfmt.Item(textfmt.Text("item six")), textfmt.Item(textfmt.Text("item seven")), textfmt.Item(textfmt.Text("item eight")), textfmt.Item(textfmt.Text("item nine")), textfmt.Item(textfmt.Text("item ten")), textfmt.Item(textfmt.Text("item eleven")), textfmt.Item(textfmt.Text("item twelve")), ), 9, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. item one 2. item two 3. item three 4. item four 5. item five 6. item six 7. item seven 8. item eight 9. item nine 10. item ten 11. item eleven 12. item twelve ` if "\n"+b.String() != expect { logBytes(t, expect) logBytes(t, "\n"+b.String()) t.Log(expect) t.Fatal("\n" + b.String()) } }) }) t.Run("definitions", func(t *testing.T) { t.Run("simple", func(t *testing.T) { doc := textfmt.Doc( textfmt.DefinitionList( textfmt.Definition(textfmt.Text("red"), textfmt.Text("looks like strawberry")), textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")), textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` - red: looks like strawberry - green: looks like grass - blue: looks like sky ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("indent ignored", func(t *testing.T) { doc := textfmt.Doc( textfmt.Indent( textfmt.DefinitionList( textfmt.Definition(textfmt.Text("red"), textfmt.Text("looks like strawberry")), textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")), textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")), ), 4, 0, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` - red: looks like strawberry - green: looks like grass - blue: looks like sky ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("wrap", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.DefinitionList( textfmt.Definition(textfmt.Text("red"), textfmt.Text("looks like strawberry")), textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")), textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")), ), 15, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` - red: looks like strawberry - green: looks like grass - blue: looks like sky ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) }) t.Run("numbered definitions", func(t *testing.T) { t.Run("simple", func(t *testing.T) { doc := textfmt.Doc( textfmt.NumberedDefinitionList( textfmt.Definition(textfmt.Text("red"), textfmt.Text("looks like strawberry")), textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")), textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. red: looks like strawberry 2. green: looks like grass 3. blue: looks like sky ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("indent ignored", func(t *testing.T) { doc := textfmt.Doc( textfmt.Indent( textfmt.NumberedDefinitionList( textfmt.Definition(textfmt.Text("red"), textfmt.Text("looks like strawberry")), textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")), textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")), ), 4, 0, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. red: looks like strawberry 2. green: looks like grass 3. blue: looks like sky ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("wrap", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.NumberedDefinitionList( textfmt.Definition(textfmt.Text("red"), textfmt.Text("looks like strawberry")), textfmt.Definition(textfmt.Text("green"), textfmt.Text("looks like grass")), textfmt.Definition(textfmt.Text("blue"), textfmt.Text("looks like sky")), ), 16, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. red: looks like strawberry 2. green: looks like grass 3. blue: looks like sky ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("long list", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.NumberedDefinitionList( textfmt.Definition(textfmt.Text("one"), textfmt.Text("this is an item")), textfmt.Definition(textfmt.Text("two"), textfmt.Text("this is another item")), textfmt.Definition(textfmt.Text("three"), textfmt.Text("this is the third item")), textfmt.Definition(textfmt.Text("four"), textfmt.Text("this is the fourth item")), textfmt.Definition(textfmt.Text("five"), textfmt.Text("this is the fifth item")), textfmt.Definition(textfmt.Text("six"), textfmt.Text("this is the sixth item")), textfmt.Definition(textfmt.Text("seven"), textfmt.Text("this is the seventh item")), textfmt.Definition(textfmt.Text("eight"), textfmt.Text("this is the eighth item")), textfmt.Definition(textfmt.Text("nine"), textfmt.Text("this is the nineth item")), textfmt.Definition(textfmt.Text("ten"), textfmt.Text("this is the tenth item")), textfmt.Definition(textfmt.Text("eleven"), textfmt.Text("this is the eleventh item")), textfmt.Definition(textfmt.Text("twelve"), textfmt.Text("this is the twelfth item")), ), 21, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` 1. one: this is an item 2. two: this is another item 3. three: this is the third item 4. four: this is the fourth item 5. five: this is the fifth item 6. six: this is the sixth item 7. seven: this is the seventh item 8. eight: this is the eighth item 9. nine: this is the nineth item 10. ten: this is the tenth item 11. eleven: this is the eleventh item 12. twelve: this is the twelfth item ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) }) t.Run("table", func(t *testing.T) { t.Run("no rows", func(t *testing.T) { var b bytes.Buffer doc := textfmt.Doc(textfmt.Table()) if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "\n" { t.Fatal(b.String()) } }) t.Run("no columns", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Row(), textfmt.Row(), textfmt.Row(), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "\n" { t.Fatal(b.String()) } }) t.Run("basic", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Row( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("2")), textfmt.Cell(textfmt.Text("3")), ), textfmt.Row( textfmt.Cell(textfmt.Text("4")), textfmt.Cell(textfmt.Text("5")), textfmt.Cell(textfmt.Text("6")), ), textfmt.Row( textfmt.Cell(textfmt.Text("7")), textfmt.Cell(textfmt.Text("8")), textfmt.Cell(textfmt.Text("9")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | \- | \- | \- | |----|----|----| | 1 | 2 | 3 | | 4 | 5 | 6 | | 7 | 8 | 9 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("basic with header", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("-1")), textfmt.Cell(textfmt.Text("0")), ), textfmt.Row( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("2")), textfmt.Cell(textfmt.Text("3")), ), textfmt.Row( textfmt.Cell(textfmt.Text("4")), textfmt.Cell(textfmt.Text("5")), textfmt.Cell(textfmt.Text("6")), ), textfmt.Row( textfmt.Cell(textfmt.Text("7")), textfmt.Cell(textfmt.Text("8")), textfmt.Cell(textfmt.Text("9")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | 1 | \-1 | 0 | |---|-----|---| | 1 | 2 | 3 | | 4 | 5 | 6 | | 7 | 8 | 9 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("header without rows", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("foo")), textfmt.Cell(textfmt.Text("bar")), textfmt.Cell(textfmt.Text("baz")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | foo | bar | baz | |-----|-----|-----| ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("single row", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Row( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("2")), textfmt.Cell(textfmt.Text("3")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | \- | \- | \- | |----|----|----| | 1 | 2 | 3 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("single row with header", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("foo")), textfmt.Cell(textfmt.Text("bar")), textfmt.Cell(textfmt.Text("baz")), ), textfmt.Row( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("2")), textfmt.Cell(textfmt.Text("3")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | foo | bar | baz | |-----|-----|-----| | 1 | 2 | 3 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("single column", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Row( textfmt.Cell(textfmt.Text("1")), ), textfmt.Row( textfmt.Cell(textfmt.Text("4")), ), textfmt.Row( textfmt.Cell(textfmt.Text("7")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | \- | |----| | 1 | | 4 | | 7 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("single column with header", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("foo")), ), textfmt.Row( textfmt.Cell(textfmt.Text("1")), ), textfmt.Row( textfmt.Cell(textfmt.Text("4")), ), textfmt.Row( textfmt.Cell(textfmt.Text("7")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | foo | |-----| | 1 | | 4 | | 7 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("single row and single column", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Row( textfmt.Cell(textfmt.Text("1")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | \- | |----| | 1 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("single row and single column with header", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("foo")), ), textfmt.Row( textfmt.Cell(textfmt.Text("1")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | foo | |-----| | 1 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("unequal number of row cells", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Row( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("2")), ), textfmt.Row( textfmt.Cell(textfmt.Text("4")), ), textfmt.Row( textfmt.Cell(textfmt.Text("7")), textfmt.Cell(textfmt.Text("8")), textfmt.Cell(textfmt.Text("9")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | \- | \- | \- | |----|----|----| | 1 | 2 | | | 4 | | | | 7 | 8 | 9 | ` if "\n"+b.String() != expect { t.Fatal(b.String()) } }) t.Run("unequal number of row cells with header", func(t *testing.T) { doc := textfmt.Doc( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("-1")), ), textfmt.Row( textfmt.Cell(textfmt.Text("1")), textfmt.Cell(textfmt.Text("2")), textfmt.Cell(textfmt.Text("3")), ), textfmt.Row( textfmt.Cell(textfmt.Text("4")), ), textfmt.Row( textfmt.Cell(textfmt.Text("")), textfmt.Cell(textfmt.Text("8")), textfmt.Cell(textfmt.Text("9")), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | 1 | \-1 | \- | |---|-----|----| | 1 | 2 | 3 | | 4 | | | | | 8 | 9 | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("all empty cells", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.Table( textfmt.Header(textfmt.Cell(textfmt.Text("")), textfmt.Cell(textfmt.Text(""))), textfmt.Row(textfmt.Cell(textfmt.Text("")), textfmt.Cell(textfmt.Text(""))), textfmt.Row(textfmt.Cell(textfmt.Text("")), textfmt.Cell(textfmt.Text(""))), ), 72, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | \- | \- | |----|----| | | | | | | ` if "\n"+b.String() != expect { t.Fatal("\n" + b.String()) } }) t.Run("ignore indent", func(t *testing.T) { doc := textfmt.Doc( textfmt.Indent( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("one")), textfmt.Cell(textfmt.Text("two")), textfmt.Cell(textfmt.Text("three")), ), textfmt.Row( textfmt.Cell(textfmt.Text("Walking through the mixed forests of Brandenburg in early autumn")), textfmt.Cell(textfmt.Text("one notices the dominant presence of Scots pine (Pinus sylvestris)")), textfmt.Cell(textfmt.Text("interspersed with sessile oak (Quercus petraea)")), ), textfmt.Row( textfmt.Cell(textfmt.Text("and silver birch (Betula pendula)")), textfmt.Cell(textfmt.Text("their canopies creating a mosaic of light")), textfmt.Cell(textfmt.Text("and shadow on the forest floor")), ), ), 4, 0, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | one | two | three | |------------------------------------------------------------------|--------------------------------------------------------------------|-------------------------------------------------| | Walking through the mixed forests of Brandenburg in early autumn | one notices the dominant presence of Scots pine (Pinus sylvestris) | interspersed with sessile oak (Quercus petraea) | | and silver birch (Betula pendula) | their canopies creating a mosaic of light | and shadow on the forest floor | ` if "\n"+b.String() != expect { logBytes(t, expect) logBytes(t, "\n"+b.String()) t.Log(expect) t.Fatal("\n" + b.String()) } }) t.Run("ignore wrap", func(t *testing.T) { doc := textfmt.Doc( textfmt.Wrap( textfmt.Table( textfmt.Header( textfmt.Cell(textfmt.Text("one")), textfmt.Cell(textfmt.Text("two")), textfmt.Cell(textfmt.Text("three")), ), textfmt.Row( textfmt.Cell(textfmt.Text("Walking through the mixed forests of Brandenburg in early autumn")), textfmt.Cell(textfmt.Text("one notices the dominant presence of Scots pine (Pinus sylvestris)")), textfmt.Cell(textfmt.Text("interspersed with sessile oak (Quercus petraea)")), ), textfmt.Row( textfmt.Cell(textfmt.Text("and silver birch (Betula pendula)")), textfmt.Cell(textfmt.Text("their canopies creating a mosaic of light")), textfmt.Cell(textfmt.Text("and shadow on the forest floor")), ), ), 72, ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } const expect = ` | one | two | three | |------------------------------------------------------------------|--------------------------------------------------------------------|-------------------------------------------------| | Walking through the mixed forests of Brandenburg in early autumn | one notices the dominant presence of Scots pine (Pinus sylvestris) | interspersed with sessile oak (Quercus petraea) | | and silver birch (Betula pendula) | their canopies creating a mosaic of light | and shadow on the forest floor | ` if "\n"+b.String() != expect { logBytes(t, expect) logBytes(t, "\n"+b.String()) t.Log(expect) t.Fatal("\n" + b.String()) } }) }) t.Run("code", func(t *testing.T) { t.Run("basic", func(t *testing.T) { const code = `func() textfmt.Document { return textfmt.Document( textfmt.Paragraph(textfmt.Text("Hello, world!")), ) }` var b bytes.Buffer if err := textfmt.Markdown(&b, textfmt.Doc(textfmt.CodeBlock(code))); err != nil { t.Fatal(err) } if b.String() != "```\n"+code+"\n```\n" { t.Fatal(b.String()) } }) t.Run("indent ignored", func(t *testing.T) { const code = `func() textfmt.Document { return textfmt.Document( textfmt.Paragraph(textfmt.Text("Hello, world!")), ) }` var b bytes.Buffer if err := textfmt.Markdown(&b, textfmt.Doc(textfmt.Indent(textfmt.CodeBlock(code), 4, 0))); err != nil { t.Fatal(err) } if b.String() != "```\n"+code+"\n```\n" { t.Fatal(b.String()) } }) t.Run("wrap has no effect", func(t *testing.T) { const code = `func() textfmt.Document { return textfmt.Document( textfmt.Paragraph(textfmt.Text("Hello, world!")), ) }` var b bytes.Buffer if err := textfmt.Markdown(&b, textfmt.Doc(textfmt.Wrap(textfmt.CodeBlock(code), 12))); err != nil { t.Fatal(err) } if b.String() != "```\n"+code+"\n```\n" { t.Fatal(b.String()) } }) }) t.Run("syntax", func(t *testing.T) { t.Run("symbol", func(t *testing.T) { doc := textfmt.Doc(textfmt.Syntax(textfmt.Symbol("foo"))) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\nfoo\n```\n" { t.Fatal(b.String()) } }) t.Run("zero or more symbols", func(t *testing.T) { doc := textfmt.Doc(textfmt.Syntax(textfmt.ZeroOrMore(textfmt.Symbol("foo")))) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\n[foo]...\n```\n" { t.Fatal(b.String()) } }) t.Run("one or more", func(t *testing.T) { doc := textfmt.Doc(textfmt.Syntax(textfmt.OneOrMore(textfmt.Symbol("foo")))) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\n...\n```\n" { t.Fatal(b.String()) } }) t.Run("required symbol", func(t *testing.T) { doc := textfmt.Doc(textfmt.Syntax(textfmt.Required(textfmt.Symbol("foo")))) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\n\n```\n" { t.Fatal(b.String()) } }) t.Run("optional symbol", func(t *testing.T) { doc := textfmt.Doc(textfmt.Syntax(textfmt.Optional(textfmt.Symbol("foo")))) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\n[foo]\n```\n" { t.Fatal(b.String()) } }) t.Run("sequence implicit", func(t *testing.T) { doc := textfmt.Doc( textfmt.Syntax( textfmt.Symbol("foo"), textfmt.Symbol("bar"), textfmt.Symbol("baz"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\nfoo bar baz\n```\n" { t.Fatal(b.String()) } }) t.Run("sequence", func(t *testing.T) { doc := textfmt.Doc( textfmt.Syntax( textfmt.Sequence( textfmt.Symbol("foo"), textfmt.Symbol("bar"), textfmt.Symbol("baz"), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\nfoo bar baz\n```\n" { t.Fatal(b.String()) } }) t.Run("subsequence", func(t *testing.T) { doc := textfmt.Doc( textfmt.Syntax( textfmt.Symbol("corge"), textfmt.Sequence( textfmt.Symbol("foo"), textfmt.Symbol("bar"), textfmt.Symbol("baz"), ), textfmt.Symbol("garply"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\ncorge (foo bar baz) garply\n```\n" { t.Fatal(b.String()) } }) t.Run("top level choice", func(t *testing.T) { doc := textfmt.Doc( textfmt.Syntax( textfmt.Choice( textfmt.Symbol("foo"), textfmt.Symbol("bar"), textfmt.Symbol("baz"), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\nfoo\nbar\nbaz\n```\n" { t.Fatal(b.String()) } }) t.Run("choice", func(t *testing.T) { doc := textfmt.Doc( textfmt.Syntax( textfmt.Symbol("corge"), textfmt.Choice( textfmt.Symbol("foo"), textfmt.Symbol("bar"), textfmt.Symbol("baz"), ), textfmt.Symbol("garply"), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\ncorge (foo|bar|baz) garply\n```\n" { t.Fatal(b.String()) } }) t.Run("example", func(t *testing.T) { doc := textfmt.Doc( textfmt.Syntax( textfmt.Symbol("foo"), textfmt.ZeroOrMore(textfmt.Symbol("options")), textfmt.Required(textfmt.Symbol("filename")), textfmt.ZeroOrMore( textfmt.Choice( textfmt.Symbol("string"), textfmt.Symbol("number"), ), ), ), ) var b bytes.Buffer if err := textfmt.Markdown(&b, doc); err != nil { t.Fatal(err) } if b.String() != "```\nfoo [options]... [string|number]...\n```\n" { t.Fatal(b.String()) } }) }) }