mirror of
https://github.com/jimeh/go-render.git
synced 2026-02-19 03:16:39 +00:00
Replace yaml.v3 with goccy/go-yaml, as the former is now unmaintained. Also upgrade minimum Go version to 1.21.0 and update testify to v1.10.0. Add support for configuring encoder options in YAML renderer to provide more flexibility in YAML output formatting. Include new options for sequence indentation and automatic integer conversion. Implement support for both yaml.InterfaceMarshaler and yaml.BytesMarshaler interfaces with appropriate test cases. Rename mock implementation to clarify interface implementation.
619 lines
15 KiB
Go
619 lines
15 KiB
Go
package render
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
handlers map[string]Handler
|
|
want *Renderer
|
|
}{
|
|
{
|
|
name: "nil handlers map",
|
|
want: &Renderer{Handlers: map[string]Handler{}},
|
|
},
|
|
{
|
|
name: "empty handlers map",
|
|
handlers: map[string]Handler{},
|
|
want: &Renderer{Handlers: map[string]Handler{}},
|
|
},
|
|
{
|
|
name: "single handler",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{},
|
|
},
|
|
want: &Renderer{Handlers: map[string]Handler{
|
|
"mock": &mockHandler{},
|
|
}},
|
|
},
|
|
{
|
|
name: "multiple handlers",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{},
|
|
"other": &mockHandler{output: "other output"},
|
|
},
|
|
want: &Renderer{Handlers: map[string]Handler{
|
|
"mock": &mockHandler{},
|
|
"other": &mockHandler{output: "other output"},
|
|
}},
|
|
},
|
|
{
|
|
name: "multiple handlers with alias formats",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"other": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
},
|
|
want: &Renderer{Handlers: map[string]Handler{
|
|
"mock": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"m": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"other": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
"o": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
}},
|
|
},
|
|
{
|
|
name: "multiple handlers with custom formats",
|
|
handlers: map[string]Handler{
|
|
"foo": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"bar": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
},
|
|
want: &Renderer{Handlers: map[string]Handler{
|
|
"foo": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"mock": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"m": &mockFormatsHandler{formats: []string{"mock", "m"}},
|
|
"bar": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
"other": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
"o": &mockFormatsHandler{formats: []string{"other", "o"}},
|
|
}},
|
|
},
|
|
{
|
|
name: "multiple handlers with capitalized formats",
|
|
handlers: map[string]Handler{
|
|
"Foo": &mockFormatsHandler{formats: []string{"MOCK", "m"}},
|
|
"Bar": &mockFormatsHandler{formats: []string{"OTHER", "o"}},
|
|
},
|
|
want: &Renderer{Handlers: map[string]Handler{
|
|
"foo": &mockFormatsHandler{formats: []string{"MOCK", "m"}},
|
|
"mock": &mockFormatsHandler{formats: []string{"MOCK", "m"}},
|
|
"m": &mockFormatsHandler{formats: []string{"MOCK", "m"}},
|
|
"bar": &mockFormatsHandler{formats: []string{"OTHER", "o"}},
|
|
"other": &mockFormatsHandler{formats: []string{"OTHER", "o"}},
|
|
"o": &mockFormatsHandler{formats: []string{"OTHER", "o"}},
|
|
}},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := New(tt.handlers)
|
|
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderer_Add(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
format string
|
|
handler Handler
|
|
want []string
|
|
}{
|
|
{
|
|
name: "handler without Formats",
|
|
format: "tackle",
|
|
handler: &mockHandler{},
|
|
want: []string{"tackle"},
|
|
},
|
|
{
|
|
name: "handler with Formats",
|
|
format: "hackle",
|
|
handler: &mockFormatsHandler{formats: []string{"hackle"}},
|
|
want: []string{"hackle"},
|
|
},
|
|
{
|
|
name: "handler with alias formats",
|
|
format: "hackle",
|
|
handler: &mockFormatsHandler{formats: []string{"hackle", "hack"}},
|
|
want: []string{"hackle", "hack"},
|
|
},
|
|
{
|
|
name: "given format differs from Formats",
|
|
format: "foobar",
|
|
handler: &mockFormatsHandler{formats: []string{"hackle", "hack"}},
|
|
want: []string{"foobar", "hackle", "hack"},
|
|
},
|
|
{
|
|
name: "lowercases capitalized formats",
|
|
format: "FooBar",
|
|
handler: &mockFormatsHandler{formats: []string{"HACKLE", "Hack"}},
|
|
want: []string{"foobar", "hackle", "hack"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := &Renderer{Handlers: map[string]Handler{}}
|
|
|
|
r.Add(tt.format, tt.handler)
|
|
|
|
for _, f := range tt.want {
|
|
got, ok := r.Handlers[f]
|
|
assert.Truef(t, ok, "not added as %q format", f)
|
|
assert.Equal(t, tt.handler, got)
|
|
}
|
|
|
|
gotFormats := []string{}
|
|
for f := range r.Handlers {
|
|
gotFormats = append(gotFormats, f)
|
|
}
|
|
assert.ElementsMatch(t, tt.want, gotFormats)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderer_Render(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
handlers map[string]Handler
|
|
format string
|
|
pretty bool
|
|
value any
|
|
want string
|
|
wantErr string
|
|
wantErrIs []error
|
|
}{
|
|
{
|
|
name: "no pretty with handler that supports pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockPrettyHandler{
|
|
output: "plain output",
|
|
prettyOutput: "pretty output",
|
|
},
|
|
},
|
|
format: "mock",
|
|
pretty: false,
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "no pretty with handler that does not support pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{output: "plain output"},
|
|
},
|
|
format: "mock",
|
|
pretty: false,
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "pretty with handler that supports pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockPrettyHandler{
|
|
output: "plain output",
|
|
prettyOutput: "pretty output",
|
|
},
|
|
},
|
|
format: "mock",
|
|
pretty: true,
|
|
value: struct{}{},
|
|
want: "pretty output",
|
|
},
|
|
{
|
|
name: "pretty with handler that does not support pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{
|
|
output: "plain output",
|
|
},
|
|
},
|
|
format: "mock",
|
|
pretty: true,
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "normalizes given format to lowercase",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{
|
|
output: "plain output",
|
|
},
|
|
},
|
|
format: "MOCK",
|
|
pretty: true,
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "handler returns error",
|
|
handlers: map[string]Handler{
|
|
"other": &mockHandler{
|
|
output: "mock output",
|
|
err: errors.New("mock error"),
|
|
},
|
|
},
|
|
format: "other",
|
|
value: struct{}{},
|
|
wantErr: "render: failed: mock error",
|
|
},
|
|
{
|
|
name: "handler returns ErrCannotRender",
|
|
handlers: map[string]Handler{
|
|
"other": &mockHandler{
|
|
output: "mock output",
|
|
err: fmt.Errorf("%w: mock", ErrCannotRender),
|
|
},
|
|
},
|
|
format: "other",
|
|
value: struct{}{},
|
|
wantErr: "render: unsupported format: other",
|
|
wantErrIs: []error{Err, ErrUnsupportedFormat},
|
|
},
|
|
{
|
|
name: "non-existing handler",
|
|
handlers: map[string]Handler{},
|
|
format: "unknown",
|
|
value: struct{}{},
|
|
wantErr: "render: unsupported format: unknown",
|
|
wantErrIs: []error{Err, ErrUnsupportedFormat},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := &Renderer{
|
|
Handlers: tt.handlers,
|
|
}
|
|
var buf bytes.Buffer
|
|
|
|
err := r.Render(&buf, tt.format, tt.pretty, tt.value)
|
|
got := buf.String()
|
|
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
}
|
|
for _, e := range tt.wantErrIs {
|
|
assert.ErrorIs(t, err, e)
|
|
}
|
|
|
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderer_Compact(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
handlers map[string]Handler
|
|
format string
|
|
value any
|
|
want string
|
|
wantErr string
|
|
wantErrIs []error
|
|
}{
|
|
{
|
|
name: "handler supports pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockPrettyHandler{
|
|
output: "plain output",
|
|
prettyOutput: "pretty output",
|
|
},
|
|
},
|
|
format: "mock",
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "handler does not support pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{output: "plain output"},
|
|
},
|
|
format: "mock",
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "handler returns error",
|
|
handlers: map[string]Handler{
|
|
"other": &mockHandler{
|
|
output: "mock output",
|
|
err: errors.New("mock error"),
|
|
},
|
|
},
|
|
format: "other",
|
|
value: struct{}{},
|
|
wantErr: "render: failed: mock error",
|
|
},
|
|
{
|
|
name: "handler returns ErrCannotRender",
|
|
handlers: map[string]Handler{
|
|
"other": &mockHandler{
|
|
output: "mock output",
|
|
err: fmt.Errorf("%w: mock", ErrCannotRender),
|
|
},
|
|
},
|
|
format: "other",
|
|
value: struct{}{},
|
|
wantErr: "render: unsupported format: other",
|
|
wantErrIs: []error{Err, ErrUnsupportedFormat},
|
|
},
|
|
{
|
|
name: "non-existing handler",
|
|
handlers: map[string]Handler{},
|
|
format: "unknown",
|
|
value: struct{}{},
|
|
wantErr: "render: unsupported format: unknown",
|
|
wantErrIs: []error{Err, ErrUnsupportedFormat},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := &Renderer{
|
|
Handlers: tt.handlers,
|
|
}
|
|
var buf bytes.Buffer
|
|
|
|
err := r.Compact(&buf, tt.format, tt.value)
|
|
got := buf.String()
|
|
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
}
|
|
for _, e := range tt.wantErrIs {
|
|
assert.ErrorIs(t, err, e)
|
|
}
|
|
|
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderer_Pretty(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
handlers map[string]Handler
|
|
format string
|
|
value any
|
|
want string
|
|
wantErr string
|
|
wantErrIs []error
|
|
}{
|
|
{
|
|
name: "handler supports pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockPrettyHandler{
|
|
output: "plain output",
|
|
prettyOutput: "pretty output",
|
|
},
|
|
},
|
|
format: "mock",
|
|
value: struct{}{},
|
|
want: "pretty output",
|
|
},
|
|
{
|
|
name: "handler does not support pretty",
|
|
handlers: map[string]Handler{
|
|
"mock": &mockHandler{
|
|
output: "plain output",
|
|
},
|
|
},
|
|
format: "mock",
|
|
value: struct{}{},
|
|
want: "plain output",
|
|
},
|
|
{
|
|
name: "handler returns error",
|
|
handlers: map[string]Handler{
|
|
"other": &mockHandler{
|
|
output: "mock output",
|
|
err: errors.New("mock error"),
|
|
},
|
|
},
|
|
format: "other",
|
|
value: struct{}{},
|
|
wantErr: "render: failed: mock error",
|
|
},
|
|
{
|
|
name: "handler returns ErrCannotRender",
|
|
handlers: map[string]Handler{
|
|
"other": &mockHandler{
|
|
output: "mock output",
|
|
err: fmt.Errorf("%w: mock", ErrCannotRender),
|
|
},
|
|
},
|
|
format: "other",
|
|
value: struct{}{},
|
|
wantErr: "render: unsupported format: other",
|
|
wantErrIs: []error{Err, ErrUnsupportedFormat},
|
|
},
|
|
{
|
|
name: "non-existing handler",
|
|
handlers: map[string]Handler{},
|
|
format: "unknown",
|
|
value: struct{}{},
|
|
wantErr: "render: unsupported format: unknown",
|
|
wantErrIs: []error{Err, ErrUnsupportedFormat},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := &Renderer{
|
|
Handlers: tt.handlers,
|
|
}
|
|
var buf bytes.Buffer
|
|
|
|
err := r.Pretty(&buf, tt.format, tt.value)
|
|
got := buf.String()
|
|
|
|
if tt.wantErr != "" {
|
|
assert.EqualError(t, err, tt.wantErr)
|
|
}
|
|
for _, e := range tt.wantErrIs {
|
|
assert.ErrorIs(t, err, e)
|
|
}
|
|
|
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderer_RenderAllFormats(t *testing.T) {
|
|
tests := []renderFormatTestCase{}
|
|
tests = append(tests, binaryFormatTestCases...)
|
|
tests = append(tests, jsonFormatTestCases...)
|
|
tests = append(tests, textFormatTestCases...)
|
|
tests = append(tests, xmlFormatTestCases...)
|
|
tests = append(tests, yamlFormatTestCases...)
|
|
|
|
for _, tt := range tests {
|
|
for _, pretty := range []bool{false, true} {
|
|
for _, format := range tt.formats {
|
|
name := format + " format " + tt.name
|
|
if pretty {
|
|
name = "pretty " + name
|
|
}
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
w := &mockWriter{WriteErr: tt.writeErr}
|
|
|
|
value := tt.value
|
|
if tt.valueFunc != nil {
|
|
value = tt.valueFunc()
|
|
}
|
|
|
|
want := tt.want
|
|
if pretty && tt.wantPretty != "" {
|
|
want = tt.wantPretty
|
|
} else if tt.wantCompact != "" {
|
|
want = tt.wantCompact
|
|
}
|
|
|
|
err := Base.Render(w, format, pretty, value)
|
|
got := w.String()
|
|
|
|
if tt.wantErr != "" {
|
|
wantErr := strings.ReplaceAll(
|
|
tt.wantErr, "{{format}}", format,
|
|
)
|
|
assert.EqualError(t, err, wantErr)
|
|
}
|
|
for _, e := range tt.wantErrIs {
|
|
assert.ErrorIs(t, err, e)
|
|
}
|
|
|
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderer_CompactAllFormats(t *testing.T) {
|
|
tests := []renderFormatTestCase{}
|
|
tests = append(tests, binaryFormatTestCases...)
|
|
tests = append(tests, jsonFormatTestCases...)
|
|
tests = append(tests, textFormatTestCases...)
|
|
tests = append(tests, xmlFormatTestCases...)
|
|
tests = append(tests, yamlFormatTestCases...)
|
|
|
|
for _, tt := range tests {
|
|
for _, format := range tt.formats {
|
|
t.Run(format+" format "+tt.name, func(t *testing.T) {
|
|
w := &mockWriter{WriteErr: tt.writeErr}
|
|
|
|
value := tt.value
|
|
if tt.valueFunc != nil {
|
|
value = tt.valueFunc()
|
|
}
|
|
|
|
var want string
|
|
if tt.wantPretty == "" && tt.wantCompact == "" {
|
|
want = tt.want
|
|
} else {
|
|
want = tt.wantCompact
|
|
}
|
|
|
|
err := Base.Compact(w, format, value)
|
|
got := w.String()
|
|
|
|
if tt.wantErr != "" {
|
|
wantErr := strings.ReplaceAll(
|
|
tt.wantErr, "{{format}}", format,
|
|
)
|
|
assert.EqualError(t, err, wantErr)
|
|
}
|
|
for _, e := range tt.wantErrIs {
|
|
assert.ErrorIs(t, err, e)
|
|
}
|
|
|
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderer_PrettyAllFormats(t *testing.T) {
|
|
tests := []renderFormatTestCase{}
|
|
tests = append(tests, binaryFormatTestCases...)
|
|
tests = append(tests, jsonFormatTestCases...)
|
|
tests = append(tests, textFormatTestCases...)
|
|
tests = append(tests, xmlFormatTestCases...)
|
|
tests = append(tests, yamlFormatTestCases...)
|
|
|
|
for _, tt := range tests {
|
|
for _, format := range tt.formats {
|
|
t.Run(format+" format "+tt.name, func(t *testing.T) {
|
|
w := &mockWriter{WriteErr: tt.writeErr}
|
|
|
|
value := tt.value
|
|
if tt.valueFunc != nil {
|
|
value = tt.valueFunc()
|
|
}
|
|
|
|
var want string
|
|
if tt.wantPretty == "" && tt.wantCompact == "" {
|
|
want = tt.want
|
|
} else {
|
|
want = tt.wantPretty
|
|
}
|
|
|
|
err := Base.Pretty(w, format, value)
|
|
got := w.String()
|
|
|
|
if tt.wantErr != "" {
|
|
wantErr := strings.ReplaceAll(
|
|
tt.wantErr, "{{format}}", format,
|
|
)
|
|
assert.EqualError(t, err, wantErr)
|
|
}
|
|
for _, e := range tt.wantErrIs {
|
|
assert.ErrorIs(t, err, e)
|
|
}
|
|
|
|
if tt.wantErr == "" && len(tt.wantErrIs) == 0 {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|