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) {