From 82b8d4c08291e573c02975d4a52425f246d75096 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Sun, 2 Nov 2025 22:15:31 +0100 Subject: [PATCH] testing --- escape.go | 2 +- html.go | 6 ++++ html_test.go | 87 +++++++++++++++++++++++++++++++++++++++++++----- lib.go | 1 + markdown.go | 35 ++++++++++--------- markdown_test.go | 61 +++++++++++++++++++++++++++++++++ runoff_test.go | 49 +++++++++++++++++++++------ teletype_test.go | 28 ++++++++++++---- 8 files changed, 227 insertions(+), 42 deletions(-) diff --git a/escape.go b/escape.go index ec5ea82..6a96947 100644 --- a/escape.go +++ b/escape.go @@ -45,7 +45,7 @@ func escapeRoffEdit(additional ...string) func(rune, bool) ([]rune, bool) { '\u00a0': []rune("\\~"), } - for i := 0; i > len(additional); i += 2 { + for i := 0; i < len(additional); i += 2 { r := []rune(additional[i]) if len(r) != 1 { panic(errors.New(invalidAdditional)) diff --git a/html.go b/html.go index c3e8dda..dadd4a3 100644 --- a/html.go +++ b/html.go @@ -266,6 +266,12 @@ func renderHTMLFragment(out io.Writer, doc Document) error { } for i, tag := range tags { + if i > 0 { + if _, err := fmt.Fprintln(out); err != nil { + return err + } + } + indent := html.Indentation{ Indent: "\t", PWidth: 120, diff --git a/html_test.go b/html_test.go index d324c64..e78ab81 100644 --- a/html_test.go +++ b/html_test.go @@ -321,15 +321,46 @@ lines.

}) 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) - } + t.Run("basic", 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()) - } + if b.String() != "

This is a title

\n" { + t.Fatal(b.String()) + } + }) + + t.Run("every level", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc( + textfmt.Title(0, "H1"), + textfmt.Title(1, "H2"), + textfmt.Title(2, "H3"), + textfmt.Title(3, "H4"), + textfmt.Title(4, "H5"), + textfmt.Title(5, "H6"), + ) + + if err := textfmt.HTMLFragment(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` +

H1

+

H2

+

H3

+

H4

+
H5
+
H6
+` + + if "\n" + b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) }) t.Run("paragraph", func(t *testing.T) { @@ -673,6 +704,46 @@ lines.

} }) + t.Run("wrapped and different indents", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Wrap( + textfmt.Indent( + textfmt.List(textfmt.Item(textfmt.Text("foo bar baz"))), + 4, + 0, + ), + 24, + ), + textfmt.Wrap( + textfmt.Indent( + textfmt.List(textfmt.Item(textfmt.Text("foo bar baz"))), + 8, + 0, + ), + 24, + ), + ) + + 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("long numbered definition list", func(t *testing.T) { doc := textfmt.Doc( textfmt.NumberedDefinitionList( diff --git a/lib.go b/lib.go index 88c94f5..4b4fded 100644 --- a/lib.go +++ b/lib.go @@ -241,6 +241,7 @@ func Sequence(items ...SyntaxItem) SyntaxItem { return SyntaxItem{sequence: items} } +// top level on separate lines without delimiter func Choice(items ...SyntaxItem) SyntaxItem { return SyntaxItem{choice: items} } diff --git a/markdown.go b/markdown.go index 4b3fa8a..3b79d31 100644 --- a/markdown.go +++ b/markdown.go @@ -54,10 +54,6 @@ func renderMDText(w io.Writer, text Txt) { return } - text.text = editString(text.text, singleLine()) - text.text = editString(text.text, escapeMarkdown()) - text.link = editString(text.link, singleLine()) - text.link = editString(text.link, escapeMarkdown()) if text.bold { write(w, "**") } @@ -76,21 +72,30 @@ func renderMDText(w io.Writer, text Txt) { } }() - if text.link != "" { - if text.text != "" { - write(w, "[") - write(w, text.text) - write(w, "](") - write(w, text.link) - write(w, ")") - return - } - - write(w, text.link) + if text.link == "" { + w, f := writeWith(w, singleLine(), escapeMarkdown()) + write(w, text.text) + w, _ = f() return } + if text.text == "" { + w, f := writeWith(w, singleLine(), escapeMarkdown()) + write(w, text.link) + w, _ = f() + return + } + + write(w, "[") + w, f := writeWith(w, singleLine(), escapeMarkdown()) write(w, text.text) + w, _ = f() + write(w, "](") + w, f = writeWith(w, singleLine(), escapeMarkdown()) + write(w, text.link) + w, _ = f() + write(w, ")") + w, _ = f() } func renderMDTitle(w io.Writer, e Entry) { diff --git a/markdown_test.go b/markdown_test.go index 36def59..c92ab77 100644 --- a/markdown_test.go +++ b/markdown_test.go @@ -394,6 +394,50 @@ textfmt supports the following entries: } }) + t.Run("link", func(t *testing.T) { + t.Run("normal", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Paragraph( + textfmt.Cat( + textfmt.Text("This is a "), + textfmt.Link("link", "https://foo.bar"), + textfmt.Text("alright."), + ), + ), + ) + + var b bytes.Buffer + if err := textfmt.Markdown(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "This is a [link](https://foo.bar) alright.\n" { + t.Fatal(b.String()) + } + }) + + t.Run("without label", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Paragraph( + textfmt.Cat( + textfmt.Text("This is a "), + textfmt.Link("", "https://foo.bar"), + textfmt.Text("alright."), + ), + ), + ) + + var b bytes.Buffer + if err := textfmt.Markdown(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "This is a https://foo.bar alright.\n" { + t.Fatal(b.String()) + } + }) + }) + t.Run("escape", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( @@ -428,6 +472,23 @@ textfmt supports the following entries: } }) + t.Run("escape non link", func(t *testing.T) { + doc := textfmt.Doc( + textfmt.Paragraph( + textfmt.Text("[looks-like-a-link] but it's not"), + ), + ) + + var b bytes.Buffer + if err := textfmt.Markdown(&b, doc); err != nil { + t.Fatal(err) + } + + if b.String() != "\\[looks-like-a-link\\] but it's not\n" { + t.Fatal(b.String()) + } + }) + t.Run("escape negative number on line start", func(t *testing.T) { doc := textfmt.Doc( textfmt.Paragraph( diff --git a/runoff_test.go b/runoff_test.go index 6485eb7..d13bd1c 100644 --- a/runoff_test.go +++ b/runoff_test.go @@ -4,6 +4,7 @@ import ( "bytes" "code.squareroundforest.org/arpio/textfmt" "testing" + "time" ) func TestRoff(t *testing.T) { @@ -230,8 +231,16 @@ textfmt supports the following entries: }) t.Run("example man", func(t *testing.T) { + releaseDate := time.Date(2025, 11, 2, 15, 36, 18, 0, time.FixedZone("CET", 3600)) doc := textfmt.Doc( - textfmt.Title(0, "Example Text", textfmt.ManSection(1)), + textfmt.Title( + 0, + "Example Text", + textfmt.ManSection(1), + textfmt.ManCategory("User Command"), + textfmt.ReleaseDate(releaseDate), + textfmt.ReleaseVersion("v1"), + ), textfmt.Indent( textfmt.Paragraph(textfmt.Text("Below you can find some test text, with various text items.")), @@ -315,7 +324,7 @@ textfmt supports the following entries: t.Fatal(err) } - const expect = `.TH "Example Text" 1 "" "" "" + const expect = `.TH "Example Text" 1 "November 2025" "v1" "User Command" .br .sp 1v .in 0 @@ -634,20 +643,38 @@ Some sample text... on multiple lines. }) t.Run("title", func(t *testing.T) { - var b bytes.Buffer - doc := textfmt.Doc(textfmt.Title(0, "This is a title")) - if err := textfmt.Runoff(&b, doc); err != nil { - t.Fatal(err) - } + t.Run("basic", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Title(0, "This is a title")) + if err := textfmt.Runoff(&b, doc); err != nil { + t.Fatal(err) + } - const expect = `.in 0 + const expect = `.in 0 .ti 0 \fBThis is a title\fR ` - if b.String() != expect { - t.Fatal(b.String()) - } + if b.String() != expect { + t.Fatal(b.String()) + } + }) + + t.Run("escaped man", func(t *testing.T) { + var b bytes.Buffer + doc := textfmt.Doc(textfmt.Title(0, "This is a title \"\\\"", textfmt.ManSection(1))) + if err := textfmt.Runoff(&b, doc); err != nil { + t.Fatal(err) + } + + const expect = ` +.TH "This is a title \(dq\\\(dq" 1 "" "" "" +` + + if "\n" + b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) }) t.Run("paragraph", func(t *testing.T) { diff --git a/teletype_test.go b/teletype_test.go index 9d7a17d..e282788 100644 --- a/teletype_test.go +++ b/teletype_test.go @@ -316,14 +316,28 @@ Entry explanations: }) 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.")), - ) + t.Run("once", func(t *testing.T) { + w := &failingWriter{failAfter: 15} + doc := textfmt.Doc( + textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), + ) - if err := textfmt.Teletype(w, doc); err == nil { - t.Fatal("failed to fail") - } + if err := textfmt.Teletype(w, doc); err == nil { + t.Fatal("failed to fail") + } + }) + + t.Run("multple times", func(t *testing.T) { + w := &failingWriter{failAfter: 15} + doc := textfmt.Doc( + textfmt.Paragraph(textfmt.Text("Some sample text...\non multiple lines.")), + textfmt.Paragraph(textfmt.Text("Some more sample text...\non multiple lines.")), + ) + + if err := textfmt.Teletype(w, doc); err == nil { + t.Fatal("failed to fail") + } + }) }) t.Run("concatenate", func(t *testing.T) {