Skip to content

Commit

Permalink
test: add basic renderer tests
Browse files Browse the repository at this point in the history
  • Loading branch information
piksel committed Aug 10, 2021
1 parent 265789c commit 9c1cbc3
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 23 deletions.
5 changes: 4 additions & 1 deletion cli/cmd/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ func printDocs(format string, services []string) cli.Result {
case "console":
renderer = f.ConsoleTreeRenderer{WithValues: false}
case "markdown":
renderer = f.MarkdownTreeRenderer{HeaderPrefix: "### "}
renderer = f.MarkdownTreeRenderer{
HeaderPrefix: "### ",
PropsDescription: "Props can be either supplied using the params argument, or through the URL using \n`?key=value&key=value` etc.\n",
}
default:
return cli.InvalidUsage("invalid format")
}
Expand Down
11 changes: 8 additions & 3 deletions pkg/format/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ func getNode(fieldVal r.Value, fieldInfo *FieldInfo) Node {
}
}

func getRootNode(config types.ServiceConfig) *ContainerNode {
structValue := r.ValueOf(config)
func getRootNode(v interface{}) *ContainerNode {
structValue := r.ValueOf(v)
if structValue.Kind() == r.Ptr {
structValue = structValue.Elem()
}
Expand All @@ -186,7 +186,12 @@ func getRootNode(config types.ServiceConfig) *ContainerNode {
Type: structValue.Type(),
}

infoFields := getStructFieldInfo(fieldInfo.Type, config.Enums())
enums := map[string]types.EnumFormatter{}
if enummer, isEnummer := v.(types.Enummer); isEnummer {
enums = enummer.Enums()
}

infoFields := getStructFieldInfo(fieldInfo.Type, enums)

numFields := len(infoFields)
nodeItems := make([]Node, numFields)
Expand Down
56 changes: 56 additions & 0 deletions pkg/format/render_console_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package format

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
gformat "github.com/onsi/gomega/format"
)

var _ = Describe("RenderConsole", func() {
gformat.CharactersAroundMismatchToInclude = 30
renderer := ConsoleTreeRenderer{WithValues: false}

It("should render the expected output based on config reflection/tags", func() {
actual := testRenderTee(renderer, &struct {
Name string `default:"notempty"`
Host string `url:"host"`
}{})

expected := `
Host string <URL: Host> <Required>
Name string <Default: notempty>
`[1:]
println()
println(actual)

Expect(actual).To(Equal(expected))
})

It("should render url paths in sorted order", func() {
actual := testRenderTee(renderer, &struct {
Host string `url:"host"`
Path1 string `url:"path1"`
Path3 string `url:"path3"`
Path2 string `url:"path2"`
}{})

expected := `
Host string <URL: Host> <Required>
Path1 string <URL: Path> <Required>
Path2 string <URL: Path> <Required>
Path3 string <URL: Path> <Required>
`[1:]

println()
println(actual)

Expect(actual).To(Equal(expected))
})
})

/*
* __TestEnum__
Default: `+"`None`"+`
Possible values: `+"`None`, `Foo`, `Bar`"+`
*/
43 changes: 25 additions & 18 deletions pkg/format/render_markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (

// MarkdownTreeRenderer renders a ContainerNode tree into a markdown documentation string
type MarkdownTreeRenderer struct {
HeaderPrefix string
HeaderPrefix string
PropsDescription string
}

// RenderTree renders a ContainerNode tree into a markdown documentation string
Expand All @@ -20,7 +21,6 @@ func (r MarkdownTreeRenderer) RenderTree(root *ContainerNode, scheme string) str

queryFields := make([]*FieldInfo, 0, len(root.Items))
urlFields := make([]*FieldInfo, URLPath+1)
fieldsPrinted := make(map[string]bool)

for _, node := range root.Items {
field := node.Field()
Expand All @@ -38,6 +38,27 @@ func (r MarkdownTreeRenderer) RenderTree(root *ContainerNode, scheme string) str
}
}

r.writeURLFields(&sb, urlFields, scheme)

sort.SliceStable(queryFields, func(i, j int) bool {
return queryFields[i].Required && !queryFields[j].Required
})

r.writeHeader(&sb, "Query/Param Props")
sb.WriteString(r.PropsDescription)
sb.WriteRune('\n')
for _, field := range queryFields {
r.writeFieldPrimary(&sb, field)
r.writeFieldExtras(&sb, field)
sb.WriteRune('\n')
}

return sb.String()
}

func (r MarkdownTreeRenderer) writeURLFields(sb *strings.Builder, urlFields []*FieldInfo, scheme string) {
fieldsPrinted := make(map[string]bool)

sort.SliceStable(urlFields, func(i, j int) bool {
if urlFields[i] == nil || urlFields[j] == nil {
return false
Expand All @@ -56,12 +77,12 @@ func (r MarkdownTreeRenderer) RenderTree(root *ContainerNode, scheme string) str
return urlPartA < urlPartB
})

r.writeHeader(&sb, "URL Fields")
r.writeHeader(sb, "URL Fields")
for _, field := range urlFields {
if field == nil || fieldsPrinted[field.Name] {
continue
}
r.writeFieldPrimary(&sb, field)
r.writeFieldPrimary(sb, field)

sb.WriteString(" URL part: <code class=\"service-url\">")

Expand Down Expand Up @@ -109,20 +130,6 @@ func (r MarkdownTreeRenderer) RenderTree(root *ContainerNode, scheme string) str

fieldsPrinted[field.Name] = true
}

sort.SliceStable(queryFields, func(i, j int) bool {
return queryFields[i].Required && !queryFields[j].Required
})

r.writeHeader(&sb, "Query/Param Props")
sb.WriteString("Props can be either supplied using the params argument, or through the URL using \n`?key=value&key=value` etc.\n\n")
for _, field := range queryFields {
r.writeFieldPrimary(&sb, field)
r.writeFieldExtras(&sb, field)
sb.WriteRune('\n')
}

return sb.String()
}

func (MarkdownTreeRenderer) writeFieldExtras(sb *strings.Builder, field *FieldInfo) {
Expand Down
67 changes: 67 additions & 0 deletions pkg/format/render_markdown_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package format

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
gformat "github.com/onsi/gomega/format"
)

var _ = Describe("RenderMarkdown", func() {
gformat.CharactersAroundMismatchToInclude = 30

It("should render the expected output based on config reflection/tags", func() {
actual := testRenderTee(MarkdownTreeRenderer{HeaderPrefix: `### `}, &struct {
Name string `default:"notempty"`
Host string `url:"host"`
}{})

expected := `
### URL Fields
* __Host__ (**Required**)
URL part: <code class="service-url">mock://<strong>host</strong>/</code>
### Query/Param Props
* __Name__
Default: `[1:] + "`notempty`" + `
`

Expect(actual).To(Equal(expected))
})

It("should render url paths in sorted order", func() {
actual := testRenderTee(MarkdownTreeRenderer{HeaderPrefix: `### `}, &struct {
Host string `url:"host"`
Path1 string `url:"path1"`
Path3 string `url:"path3"`
Path2 string `url:"path2"`
}{})

expected := `
### URL Fields
* __Host__ (**Required**)
URL part: <code class="service-url">mock://<strong>host</strong>/path1/path2/path3</code>
* __Path1__ (**Required**)
URL part: <code class="service-url">mock://host/<strong>path1</strong>/path2/path3</code>
* __Path2__ (**Required**)
URL part: <code class="service-url">mock://host/path1/<strong>path2</strong>/path3</code>
* __Path3__ (**Required**)
URL part: <code class="service-url">mock://host/path1/path2/<strong>path3</strong></code>
### Query/Param Props
`[1:] // Remove initial newline

Expect(actual).To(Equal(expected))
})
})

/*
* __TestEnum__
Default: `+"`None`"+`
Possible values: `+"`None`, `Foo`, `Bar`"+`
*/
11 changes: 11 additions & 0 deletions pkg/format/renderer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package format

// Renderer renders a supplied node tree to a string
type Renderer interface {
// RenderTree renders a ContainerNode tree into a ansi-colored console string
RenderTree(root *ContainerNode, scheme string) string
}

func testRenderTee(r Renderer, v interface{}) string {
return r.RenderTree(getRootNode(v), "mock")
}
7 changes: 6 additions & 1 deletion pkg/types/service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ package types

import "net/url"

// Enummer contains fields that have associated EnumFormatter instances
type Enummer interface {
Enums() map[string]EnumFormatter
}

// ServiceConfig is the common interface for all types of service configurations
type ServiceConfig interface {
Enummer
GetURL() *url.URL
SetURL(*url.URL) error
Enums() map[string]EnumFormatter
}

// ConfigQueryResolver is the interface used to get/set and list service config query fields
Expand Down

0 comments on commit 9c1cbc3

Please sign in to comment.