From 9c1cbc363a00243abc60771dc941d83fd58da429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Tue, 10 Aug 2021 19:45:35 +0200 Subject: [PATCH] test: add basic renderer tests --- cli/cmd/docs/docs.go | 5 ++- pkg/format/node.go | 11 +++-- pkg/format/render_console_test.go | 56 +++++++++++++++++++++++++ pkg/format/render_markdown.go | 43 +++++++++++-------- pkg/format/render_markdown_test.go | 67 ++++++++++++++++++++++++++++++ pkg/format/renderer.go | 11 +++++ pkg/types/service_config.go | 7 +++- 7 files changed, 177 insertions(+), 23 deletions(-) create mode 100644 pkg/format/render_console_test.go create mode 100644 pkg/format/render_markdown_test.go create mode 100644 pkg/format/renderer.go diff --git a/cli/cmd/docs/docs.go b/cli/cmd/docs/docs.go index 60135e34..0a19b0ca 100644 --- a/cli/cmd/docs/docs.go +++ b/cli/cmd/docs/docs.go @@ -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") } diff --git a/pkg/format/node.go b/pkg/format/node.go index 033b0933..de0bcd75 100644 --- a/pkg/format/node.go +++ b/pkg/format/node.go @@ -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() } @@ -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) diff --git a/pkg/format/render_console_test.go b/pkg/format/render_console_test.go new file mode 100644 index 00000000..5587bd9f --- /dev/null +++ b/pkg/format/render_console_test.go @@ -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 +Name string +`[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 +Path1 string +Path2 string +Path3 string +`[1:] + + println() + println(actual) + + Expect(actual).To(Equal(expected)) + }) +}) + +/* + +* __TestEnum__ + Default: `+"`None`"+` + Possible values: `+"`None`, `Foo`, `Bar`"+` +*/ diff --git a/pkg/format/render_markdown.go b/pkg/format/render_markdown.go index 623033ab..07f420c0 100644 --- a/pkg/format/render_markdown.go +++ b/pkg/format/render_markdown.go @@ -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 @@ -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() @@ -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 @@ -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: ") @@ -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) { diff --git a/pkg/format/render_markdown_test.go b/pkg/format/render_markdown_test.go new file mode 100644 index 00000000..adc8ac33 --- /dev/null +++ b/pkg/format/render_markdown_test.go @@ -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: mock://host/ +### 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: mock://host/path1/path2/path3 +* __Path1__ (**Required**) + URL part: mock://host/path1/path2/path3 +* __Path2__ (**Required**) + URL part: mock://host/path1/path2/path3 +* __Path3__ (**Required**) + URL part: mock://host/path1/path2/path3 +### Query/Param Props + + +`[1:] // Remove initial newline + + Expect(actual).To(Equal(expected)) + }) +}) + +/* + +* __TestEnum__ + Default: `+"`None`"+` + Possible values: `+"`None`, `Foo`, `Bar`"+` +*/ diff --git a/pkg/format/renderer.go b/pkg/format/renderer.go new file mode 100644 index 00000000..369c7ff9 --- /dev/null +++ b/pkg/format/renderer.go @@ -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") +} diff --git a/pkg/types/service_config.go b/pkg/types/service_config.go index eb7273d7..80c6d68b 100644 --- a/pkg/types/service_config.go +++ b/pkg/types/service_config.go @@ -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