From e4dd3ca0dfeaf93a5e9d72f9adcabe0dbfe0403e Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Wed, 29 Oct 2025 22:48:38 +0100 Subject: [PATCH] test html --- go.mod | 2 +- go.sum | 2 + html.go | 36 +- html_test.go | 1540 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1574 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 45b0bdc..49150c3 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ go 1.25.0 require code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 -require code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f // indirect +require code.squareroundforest.org/arpio/html v0.0.0-20251029200407-effffeadf9f8 // indirect diff --git a/go.sum b/go.sum index 017d044..70c8d6a 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f h1:Ep/POhkmvOfSkQklPIpeA4n2FTD2SoFxthjF0SJbsCU= code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f/go.mod h1:LX+Fwqu/a7nDayuDNhXA56cVb+BNrkz4M/WCqvw9YFQ= +code.squareroundforest.org/arpio/html v0.0.0-20251029200407-effffeadf9f8 h1:6OwHDturRjOeIxoc2Zlfkhf4InnMnNKKDb3LtrbIJjg= +code.squareroundforest.org/arpio/html v0.0.0-20251029200407-effffeadf9f8/go.mod h1:LX+Fwqu/a7nDayuDNhXA56cVb+BNrkz4M/WCqvw9YFQ= code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I= code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY= diff --git a/html.go b/html.go index 13dd097..8d5d131 100644 --- a/html.go +++ b/html.go @@ -19,11 +19,20 @@ func htmlText(t Txt) []any { return c } + var text any = t.text if t.link != "" { - return []any{tag.A(html.Attr("href", t.link), t.text)} + text = tag.A(html.Attr("href", t.link), text) } - return []any{t.text} + if t.bold { + text = tag.B(text) + } + + if t.italic { + text = tag.I(text) + } + + return []any{text} } func htmlTitle(e Entry) html.Tag { @@ -92,6 +101,7 @@ func htmlNumberedDefinitions(e Entry) html.Tag { func htmlTable(e Entry) html.Tag { table := tag.Table + e.rows = normalizeTable(e.rows) for _, r := range e.rows { row := tag.Tr cell := tag.Td @@ -143,7 +153,7 @@ func htmlSequence(s SyntaxItem) string { } func htmlChoice(s SyntaxItem) string { - ss := htmlSyntaxItems(s.sequence) + ss := htmlSyntaxItems(s.choice) if s.topLevel { return strings.Join(ss, "\n") } @@ -251,6 +261,10 @@ func renderHTMLFragment(out io.Writer, doc Document) error { return err } + if len(tags) == 0 { + return nil + } + for i, tag := range tags { indent := html.Indentation{ Indent: "\t", @@ -267,11 +281,15 @@ func renderHTMLFragment(out io.Writer, doc Document) error { indent.Indent = timesn(" ", doc.entries[i].indent) } - if err := html.RenderIndent(out, indent, tag); err != nil { + if err := html.WriteIndent(out, indent, tag); err != nil { return err } } + if _, err := fmt.Fprintln(out); err != nil { + return err + } + return nil } @@ -302,5 +320,13 @@ func renderHTML(out io.Writer, doc Document, lang string) error { MinPWidth: 60, } - return html.RenderIndent(out, indent, tag.Doctype("html"), htmlDoc) + if err := html.WriteIndent(out, indent, tag.Doctype("html"), htmlDoc); err != nil { + return err + } + + if _, err := fmt.Fprintln(out); err != nil { + return err + } + + return nil } diff --git a/html_test.go b/html_test.go index 49b8ef8..d5284cc 100644 --- a/html_test.go +++ b/html_test.go @@ -1 +1,1541 @@ package textfmt_test + +import ( + "bytes" + "code.squareroundforest.org/arpio/textfmt" + "testing" +) + +func TestHTML(t *testing.T) { + t.Run("invalid", func(t *testing.T) { + var b bytes.Buffer + if err := textfmt.HTMLFragment(&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.HTMLFragment(&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.HTML(&b, doc, "en"); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + Example Text + + +

Example Text

+

Below you can find some test text, with various text items.

+

Document syntax:

+
+textfmt.Doc ( [Entry]... )
+		
+

Entries:

+

textfmt supports the following entries:

+ +

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("escape", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Paragraph(textfmt.Text("Some sample text... with invalid chars."))) + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = "

Some sample text... with invalid chars.

\n" + if b.String() != expect { + logBytes(t, b.String()) + logBytes(t, expect) + t.Log(b.String()) + t.Log(expect) + t.Fatal() + } + }) + + t.Run("styling", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Paragraph( + textfmt.Cat( + textfmt.Italic(textfmt.Text("Some sample text... with")), + textfmt.Bold(textfmt.Text(" some ")), + textfmt.Italic(textfmt.Text("styling.")), + ), + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

Some sample text... with some styling.

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("single line by default", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Paragraph(textfmt.Text("Some sample text...\n\non multiple lines."))) + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

Some sample text... on multiple lines.

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("wrap", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc( + textfmt.Wrap(textfmt.Paragraph(textfmt.Text("Some sample text...\n on multiple lines.")), 15), + ) + + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` +

Some sample +text... +on  multiple +lines.

+` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("failing writer", func(t *testing.T) { + w := &failingWriter{failAfter: 15} + doc := textfmt.Doc( + textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), + ) + + if err := textfmt.HTMLFragment(w, doc); err == nil { + t.Fatal("failed to fail") + } + }) + + t.Run("concatenate", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Paragraph( + textfmt.Cat( + textfmt.Text("Text from"), + textfmt.Text(" multiple"), + textfmt.Text(" pieces."), + ), + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

Text from multiple pieces.

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("link", func(t *testing.T) { + t.Run("without label", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Paragraph(textfmt.Link("", "https://sqrndfst.org"))) + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("with label", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Paragraph(textfmt.Link("a link", "https://sqrndfst.org"))) + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

a link

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("newline in link", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Paragraph(textfmt.Link("a\nlink", "https://sqrndfst.org\n/foo"))) + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

a link

\n" { + t.Fatal(b.String()) + } + }) + }) + + t.Run("title", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Title(0, "This is a title")) + if err := textfmt.HTMLFragment(&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("unwrapped", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Paragraph(textfmt.Text("This is a paragraph."))) + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

This is a paragraph.

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("wrapped", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc( + textfmt.Wrap(textfmt.Paragraph(textfmt.Text("This is a paragraph.")), 12), + ) + + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "

This is a\nparagraph.\n

\n" { + t.Fatal(b.String()) + } + }) + }) + + t.Run("list", func(t *testing.T) { + t.Run("unwrapped", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.List( + textfmt.Item(textfmt.Text("this is an item")), + textfmt.Item(textfmt.Text("this is another item")), + textfmt.Item(textfmt.Text("this is a third item")), + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + +` + + 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("this is an item")), + textfmt.Item(textfmt.Text("this is another item")), + textfmt.Item(textfmt.Text("this is a third item")), + ), + 18, + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + +` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + }) + + t.Run("numbered list", func(t *testing.T) { + t.Run("unwrapped", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.NumberedList( + textfmt.Item(textfmt.Text("this is an item")), + textfmt.Item(textfmt.Text("this is another item")), + textfmt.Item(textfmt.Text("this is a third item")), + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` +
    +
  1. this is an item
  2. +
  3. this is another item
  4. +
  5. this is a third item
  6. +
+` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("wrapped", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Wrap( + textfmt.NumberedList( + textfmt.Item(textfmt.Text("this is an item")), + textfmt.Item(textfmt.Text("this is another item")), + textfmt.Item(textfmt.Text("this is a third item")), + ), + 18, + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` +
    +
  1. this + is an item +
  2. +
  3. this + is another + item
  4. +
  5. this + is a third + item
  6. +
+` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("long numbered list", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.NumberedList( + textfmt.Item(textfmt.Text("this is an item")), + textfmt.Item(textfmt.Text("this is another item")), + textfmt.Item(textfmt.Text("this is the third item")), + textfmt.Item(textfmt.Text("this is the fourth item")), + textfmt.Item(textfmt.Text("this is the fifth item")), + textfmt.Item(textfmt.Text("this is the sixth item")), + textfmt.Item(textfmt.Text("this is the seventh item")), + textfmt.Item(textfmt.Text("this is the eighth item")), + textfmt.Item(textfmt.Text("this is the nineth item")), + textfmt.Item(textfmt.Text("this is the tenth item")), + textfmt.Item(textfmt.Text("this is the eleventh item")), + textfmt.Item(textfmt.Text("this is the twelfth item")), + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` +
    +
  1. this is an item
  2. +
  3. this is another item
  4. +
  5. this is the third item
  6. +
  7. this is the fourth item
  8. +
  9. this is the fifth item
  10. +
  11. this is the sixth item
  12. +
  13. this is the seventh item
  14. +
  15. this is the eighth item
  16. +
  17. this is the nineth item
  18. +
  19. this is the tenth item
  20. +
  21. this is the eleventh item
  22. +
  23. this is the twelfth item
  24. +
+` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + }) + + t.Run("definition list", func(t *testing.T) { + t.Run("unwrapped", 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.HTMLFragment(&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("wrapped", 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")), + ), + 24, + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&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 definition list", func(t *testing.T) { + t.Run("unwrapped", 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.HTMLFragment(&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("wrapped", 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")), + ), + 24, + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&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 numbered definition list", func(t *testing.T) { + doc := textfmt.Doc( + 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")), + ), + ) + + var b bytes.Buffer + if err := textfmt.HTMLFragment(&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("now rows", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Table()) + if err := textfmt.HTMLFragment(&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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "\n\t\n\t\n\t\n
\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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + + + + + + +
123
456
789
+` + + 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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + + + + + + + + + + + +
1-10
123
456
789
+` + + 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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + +
foobarbaz
+` + + 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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + +
123
+` + + 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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + +
foobarbaz
123
+` + + 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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + + + + + + +
12
4
789
+` + + if "\n"+b.String() != expect { + t.Fatal("\n" + 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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + + + + + + + + + + + +
1-1
123
4
89
+` + + 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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + + + +
+` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` + + + + + + + + + + + + + + + + +
onetwothree
Walking through the mixed forests of Brandenburg in + early autumnone 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 lightand shadow on the forest floor
+` + + if "\n"+b.String() != expect { + logBytes(t, expect) + logBytes(t, "\n"+b.String()) + t.Log("\n" + expect) + t.Fatal("\n" + "\n" + b.String()) + } + }) + }) + + t.Run("code", func(t *testing.T) { + t.Run("simple", 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.HTMLFragment(&b, textfmt.Doc(textfmt.CodeBlock(code))); 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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "
\n<foo>...\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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "
\n<foo>\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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&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.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "
\nfoo [options]... <filename> [string|number]...\n
\n" { + t.Fatal(b.String()) + } + }) + }) +}