diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e78e62..8cfb772 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,6 @@ jobs: fail-fast: false matrix: go_version: - - "1.20" - "1.21" - "1.22" - "1.23" diff --git a/go.mod b/go.mod index d1dc7b3..88c1ccd 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,14 @@ module github.com/jimeh/go-render -go 1.20 +go 1.21.0 require ( - github.com/stretchr/testify v1.9.0 - gopkg.in/yaml.v3 v3.0.1 + github.com/goccy/go-yaml v1.18.0 + github.com/stretchr/testify v1.10.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 60ce688..2428dd7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/mise.toml b/mise.toml deleted file mode 100644 index 946c73e..0000000 --- a/mise.toml +++ /dev/null @@ -1,3 +0,0 @@ -[tools] -go = "1.23" -golangci-lint = "2.1" diff --git a/render_test.go b/render_test.go index 96d179c..3779325 100644 --- a/render_test.go +++ b/render_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) +// mockWriter is a mock implementation of io.Writer. type mockWriter struct { WriteErr error buf bytes.Buffer @@ -30,6 +31,7 @@ func (mw *mockWriter) String() string { return mw.buf.String() } +// mockHandler is a mock implementation of Handler. type mockHandler struct { output string formats []string @@ -55,6 +57,7 @@ func (mh *mockHandler) Formats() []string { return mh.formats } +// mockPrettyHandler is a mock implementation of PrettyHandler. type mockPrettyHandler struct { output string prettyOutput string @@ -92,6 +95,7 @@ func (mph *mockPrettyHandler) Formats() []string { return mph.formats } +// mockFormatsHandler is a mock implementation of FormatsHandler. type mockFormatsHandler struct { output string formats []string @@ -128,7 +132,6 @@ type renderFormatTestCase struct { wantCompact string wantErr string wantErrIs []error - wantPanic string } // "binary" format. @@ -492,15 +495,38 @@ var yamlFormatTestCases = []renderFormatTestCase{ want: "user:\n age: 30\n name: John Doe\n", }, { - name: "yaml format with yaml.Marshaler", + name: "yaml format with sequences", + value: map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + want: "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "yaml format with yaml.InterfaceMarshaler", formats: []string{"yaml", "yml"}, - value: &mockYAMLMarshaler{val: map[string]int{"age": 30}}, + value: &mockYAMLInterfaceMarshaler{val: map[string]int{"age": 30}}, want: "age: 30\n", }, { - name: "yaml format with error from yaml.Marshaler", + name: "yaml format with error from yaml.InterfaceMarshaler", formats: []string{"yaml", "yml"}, - value: &mockYAMLMarshaler{err: errors.New("mock error")}, + value: &mockYAMLInterfaceMarshaler{err: errors.New("mock error")}, + wantErr: "render: failed: mock error", + wantErrIs: []error{Err, ErrFailed}, + }, + { + name: "yaml format with yaml.BytesMarshaler", + formats: []string{"yaml", "yml"}, + value: &mockYAMLBytesMarshaler{val: []byte("age: 30\n")}, + want: "age: 30\n", + }, + { + name: "yaml format with error from yaml.BytesMarshaler", + formats: []string{"yaml", "yml"}, + value: &mockYAMLBytesMarshaler{err: errors.New("mock error")}, wantErr: "render: failed: mock error", wantErrIs: []error{Err, ErrFailed}, }, @@ -516,7 +542,8 @@ var yamlFormatTestCases = []renderFormatTestCase{ name: "yaml format with invalid type", formats: []string{"yaml", "yml"}, value: make(chan int), - wantPanic: "cannot marshal type: chan int", + wantErr: "render: failed: unknown value type chan int", + wantErrIs: []error{Err, ErrFailed}, }, } @@ -542,18 +569,6 @@ func TestRender(t *testing.T) { value = tt.valueFunc() } - var err error - var panicRes any - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = Render(w, format, pretty, value) - }() - - got := w.String() want := tt.want if pretty && tt.wantPretty != "" { want = tt.wantPretty @@ -561,9 +576,8 @@ func TestRender(t *testing.T) { want = tt.wantCompact } - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } + err := Render(w, format, pretty, value) + got := w.String() if tt.wantErr != "" { wantErr := strings.ReplaceAll( @@ -575,8 +589,7 @@ func TestRender(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, want, got) } @@ -602,28 +615,15 @@ func TestPretty(t *testing.T) { value = tt.valueFunc() } - var err error - var panicRes any - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = Pretty(w, format, value) - }() - - got := w.String() - var want string - if tt.wantPretty == "" && tt.wantCompact == "" { - want = tt.want - } else { + want := tt.want + if tt.wantPretty != "" { want = tt.wantPretty + } else if tt.wantCompact != "" { + want = tt.wantCompact } - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } + err := Pretty(w, format, value) + got := w.String() if tt.wantErr != "" { wantErr := strings.ReplaceAll( @@ -635,8 +635,7 @@ func TestPretty(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, want, got) } @@ -661,28 +660,15 @@ func TestCompact(t *testing.T) { value = tt.valueFunc() } - var err error - var panicRes any - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = Compact(w, format, value) - }() - - got := w.String() - var want string - if tt.wantPretty == "" && tt.wantCompact == "" { - want = tt.want - } else { + want := tt.want + if tt.wantPretty != "" { + want = tt.wantPretty + } else if tt.wantCompact != "" { want = tt.wantCompact } - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } + err := Compact(w, format, value) + got := w.String() if tt.wantErr != "" { wantErr := strings.ReplaceAll( @@ -694,8 +680,7 @@ func TestCompact(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, want, got) } diff --git a/renderer_test.go b/renderer_test.go index e253617..ab87cb6 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -493,19 +493,6 @@ func TestRenderer_RenderAllFormats(t *testing.T) { value = tt.valueFunc() } - var err error - var panicRes any - - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = Base.Render(w, format, pretty, value) - }() - - got := w.String() want := tt.want if pretty && tt.wantPretty != "" { want = tt.wantPretty @@ -513,9 +500,8 @@ func TestRenderer_RenderAllFormats(t *testing.T) { want = tt.wantCompact } - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } + err := Base.Render(w, format, pretty, value) + got := w.String() if tt.wantErr != "" { wantErr := strings.ReplaceAll( @@ -527,8 +513,7 @@ func TestRenderer_RenderAllFormats(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, want, got) } @@ -556,19 +541,6 @@ func TestRenderer_CompactAllFormats(t *testing.T) { value = tt.valueFunc() } - var err error - var panicRes any - - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = Base.Compact(w, format, value) - }() - - got := w.String() var want string if tt.wantPretty == "" && tt.wantCompact == "" { want = tt.want @@ -576,9 +548,8 @@ func TestRenderer_CompactAllFormats(t *testing.T) { want = tt.wantCompact } - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } + err := Base.Compact(w, format, value) + got := w.String() if tt.wantErr != "" { wantErr := strings.ReplaceAll( @@ -590,8 +561,7 @@ func TestRenderer_CompactAllFormats(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, want, got) } @@ -618,19 +588,6 @@ func TestRenderer_PrettyAllFormats(t *testing.T) { value = tt.valueFunc() } - var err error - var panicRes any - - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = Base.Pretty(w, format, value) - }() - - got := w.String() var want string if tt.wantPretty == "" && tt.wantCompact == "" { want = tt.want @@ -638,9 +595,8 @@ func TestRenderer_PrettyAllFormats(t *testing.T) { want = tt.wantPretty } - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } + err := Base.Pretty(w, format, value) + got := w.String() if tt.wantErr != "" { wantErr := strings.ReplaceAll( @@ -652,8 +608,7 @@ func TestRenderer_PrettyAllFormats(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, want, got) } diff --git a/yaml.go b/yaml.go index e2bd7bb..3fdaf2d 100644 --- a/yaml.go +++ b/yaml.go @@ -4,16 +4,31 @@ import ( "fmt" "io" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) -var YAMLDefaultIndent = 2 +var ( + // YAMLDefaultIndent is the default number of spaces to use for indenting + // nested blocks in the output YAML. + YAMLDefaultIndent = 2 + + // YAMLDefaultIndentSequence is the default value for the IndentSequence + // option passed to yaml.NewEncoder(). When true, sequences will be + // indented by default. + YAMLDefaultIndentSequence = true +) // YAML is a Handler that marshals the given value to YAML. type YAML struct { - // Indent controls how many spaces will be used for indenting nested blocks - // in the output YAML. When Indent is zero, YAMLDefaultIndent will be used. + // Indent is the number of spaces to use for indenting nested blocks in the + // output YAML. When empty/zero, YAMLDefaultIndent will be used. Indent int + + // EncodeOptions is a list of options to pass to yaml.NewEncoder(). + // + // These options will be appended to the default indent and sequence indent + // options. With duplicate options, the last one will take precedence. + EncodeOptions []yaml.EncodeOption } var ( @@ -21,21 +36,38 @@ var ( _ FormatsHandler = (*YAML)(nil) ) -// Render marshals the given value to YAML. -func (y *YAML) Render(w io.Writer, v any) error { - indent := y.Indent - if indent == 0 { - indent = YAMLDefaultIndent +func (y *YAML) buildEncodeOptions() []yaml.EncodeOption { + indent := YAMLDefaultIndent + if y.Indent > 0 { + indent = y.Indent } - enc := yaml.NewEncoder(w) - enc.SetIndent(indent) + opts := []yaml.EncodeOption{ + yaml.Indent(indent), + yaml.IndentSequence(YAMLDefaultIndentSequence), + } + + if len(y.EncodeOptions) > 0 { + opts = append(opts, y.EncodeOptions...) + } + + return opts +} + +// Render marshals the given value to YAML. +func (y *YAML) Render(w io.Writer, v any) error { + enc := yaml.NewEncoder(w, y.buildEncodeOptions()...) err := enc.Encode(v) if err != nil { return fmt.Errorf("%w: %w", ErrFailed, err) } + err = enc.Close() + if err != nil { + return fmt.Errorf("%w: %w", ErrFailed, err) + } + return nil } diff --git a/yaml_test.go b/yaml_test.go index 3565659..a1f9b8b 100644 --- a/yaml_test.go +++ b/yaml_test.go @@ -1,43 +1,58 @@ package render import ( - "bytes" "errors" "testing" + "github.com/goccy/go-yaml" "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" + "github.com/stretchr/testify/require" ) -type mockYAMLMarshaler struct { +func ptr[T any](v T) *T { + return &v +} + +type mockYAMLInterfaceMarshaler struct { val any err error } -var _ yaml.Marshaler = (*mockYAMLMarshaler)(nil) +var _ yaml.InterfaceMarshaler = (*mockYAMLInterfaceMarshaler)(nil) -func (m *mockYAMLMarshaler) MarshalYAML() (any, error) { +func (m *mockYAMLInterfaceMarshaler) MarshalYAML() (any, error) { + return m.val, m.err +} + +type mockYAMLBytesMarshaler struct { + val []byte + err error +} + +var _ yaml.BytesMarshaler = (*mockYAMLBytesMarshaler)(nil) + +func (m *mockYAMLBytesMarshaler) MarshalYAML() ([]byte, error) { return m.val, m.err } func TestYAML_Render(t *testing.T) { tests := []struct { - name string - indent int - value any - want string - wantErr string - wantErrIs []error - wantPanic string + name string + encoderOptions []yaml.EncodeOption + indent int + value any + want string + writeErr error + wantErr string + wantErrIs []error }{ { - name: "simple object default indent", + name: "simple object", value: map[string]int{"age": 30}, want: "age: 30\n", }, { - name: "nested structure", - indent: 0, // This will use the default indent of 2 spaces + name: "nested object", value: map[string]any{ "user": map[string]any{ "age": 30, @@ -47,7 +62,44 @@ func TestYAML_Render(t *testing.T) { want: "user:\n age: 30\n name: John Doe\n", }, { - name: "simple object custom indent", + name: "sequences", + value: map[string]any{ + "items": map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + }, + want: "items:\n books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "sequences without IndentSequence", + encoderOptions: []yaml.EncodeOption{ + yaml.IndentSequence(false), + }, + value: map[string]any{ + "items": map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + }, + want: "items:\n books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "custom AutoInt encoder option", + encoderOptions: []yaml.EncodeOption{ + yaml.AutoInt(), + }, + value: map[string]any{ + "age": 1.0, + }, + want: "age: 1\n", + }, + { + name: "nested object with custom indent", indent: 4, value: map[string]any{ "user": map[string]any{ @@ -58,46 +110,86 @@ func TestYAML_Render(t *testing.T) { want: "user:\n age: 30\n name: John Doe\n", }, { - name: "implements yaml.Marshaler", - value: &mockYAMLMarshaler{val: map[string]int{"age": 30}}, + name: "sequences with custom indent", + indent: 4, + value: map[string]any{ + "items": map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "sequences with custom indent and without IndentSequence", + indent: 4, + encoderOptions: []yaml.EncodeOption{ + yaml.IndentSequence(false), + }, + value: map[string]any{ + "items": map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "implements yaml.InterfaceMarshaler", + value: &mockYAMLInterfaceMarshaler{val: map[string]int{"age": 30}}, want: "age: 30\n", }, { - name: "error from yaml.Marshaler", - value: &mockYAMLMarshaler{err: errors.New("mock error")}, + name: "error from yaml.InterfaceMarshaler", + value: &mockYAMLInterfaceMarshaler{ + err: errors.New("mock error"), + }, wantErr: "render: failed: mock error", wantErrIs: []error{Err, ErrFailed}, }, + { + name: "implements yaml.BytesMarshaler", + value: &mockYAMLBytesMarshaler{val: []byte("age: 30\n")}, + want: "age: 30\n", + }, + { + name: "error from yaml.BytesMarshaler", + value: &mockYAMLBytesMarshaler{err: errors.New("mock error")}, + wantErr: "render: failed: mock error", + wantErrIs: []error{Err, ErrFailed}, + }, + { + name: "yaml format with error writing to writer", + writeErr: errors.New("write error!!1"), + value: map[string]int{"age": 30}, + wantErr: "render: failed: yaml: write error: write error!!1", + wantErrIs: []error{Err, ErrFailed}, + }, { name: "invalid value", - indent: 0, value: make(chan int), - wantPanic: "cannot marshal type: chan int", + wantErr: "render: failed: unknown value type chan int", + wantErrIs: []error{Err, ErrFailed}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { j := &YAML{ - Indent: tt.indent, + Indent: tt.indent, + EncodeOptions: tt.encoderOptions, } - var buf bytes.Buffer - var err error - var panicRes any - func() { - defer func() { - if r := recover(); r != nil { - panicRes = r - } - }() - err = j.Render(&buf, tt.value) - }() + w := &mockWriter{WriteErr: tt.writeErr} - got := buf.String() + err := j.Render(w, tt.value) + got := w.String() - if tt.wantPanic != "" { - assert.Equal(t, tt.wantPanic, panicRes) - } if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) } @@ -105,8 +197,7 @@ func TestYAML_Render(t *testing.T) { assert.ErrorIs(t, err, e) } - if tt.wantPanic == "" && - tt.wantErr == "" && len(tt.wantErrIs) == 0 { + if tt.wantErr == "" && len(tt.wantErrIs) == 0 { assert.NoError(t, err) assert.Equal(t, tt.want, got) } @@ -119,3 +210,172 @@ func TestYAML_Formats(t *testing.T) { assert.Equal(t, []string{"yaml", "yml"}, h.Formats()) } + +func TestYAML_DefaultIndent(t *testing.T) { + tests := []struct { + name string + defaultVal *int + indent int + encoderOptions []yaml.EncodeOption + want string + }{ + { + name: "do not set default, indent, or encoder options", + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "custom default takes precedence over default", + defaultVal: ptr(4), + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "indent takes precedence over default", + indent: 4, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "encoder option takes precedence over default", + encoderOptions: []yaml.EncodeOption{ + yaml.Indent(4), + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "indent takes precedence over custom default", + defaultVal: ptr(4), + indent: 3, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "encoder option takes precedence over indent", + indent: 4, + encoderOptions: []yaml.EncodeOption{ + yaml.Indent(3), + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "encoder option takes precedence over indent and " + + "custom default", + defaultVal: ptr(5), + indent: 4, + encoderOptions: []yaml.EncodeOption{ + yaml.Indent(3), + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture and restore the original default value. + originalDefault := YAMLDefaultIndent + t.Cleanup(func() { YAMLDefaultIndent = originalDefault }) + + if tt.defaultVal != nil { + YAMLDefaultIndent = *tt.defaultVal + } + + y := &YAML{ + Indent: tt.indent, + EncodeOptions: tt.encoderOptions, + } + w := &mockWriter{} + + value := map[string]any{ + "items": map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + } + + err := y.Render(w, value) + require.NoError(t, err) + + assert.Equal(t, tt.want, w.String()) + }) + } +} + +func TestYAML_DefaultIndentSequence(t *testing.T) { + tests := []struct { + name string + defaultVal *bool + encoderOptions []yaml.EncodeOption + want string + }{ + { + name: "do not set default or encoder options", + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "set default to true", + defaultVal: ptr(true), + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "set default to true and encoder option to false", + defaultVal: ptr(true), + encoderOptions: []yaml.EncodeOption{ + yaml.IndentSequence(false), + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "set default to false", + defaultVal: ptr(false), + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + { + name: "set default to false and encoder option to true", + defaultVal: ptr(false), + encoderOptions: []yaml.EncodeOption{ + yaml.IndentSequence(true), + }, + want: "items:\n " + + "books:\n - The Great Gatsby\n - \"1984\"\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Capture and restore the original default value. + originalDefault := YAMLDefaultIndentSequence + t.Cleanup(func() { YAMLDefaultIndentSequence = originalDefault }) + + if tt.defaultVal != nil { + YAMLDefaultIndentSequence = *tt.defaultVal + } + + y := &YAML{ + EncodeOptions: tt.encoderOptions, + } + w := &mockWriter{} + + value := map[string]any{ + "items": map[string]any{ + "books": []string{ + "The Great Gatsby", + "1984", + }, + }, + } + + err := y.Render(w, value) + require.NoError(t, err) + + assert.Equal(t, tt.want, w.String()) + }) + } +}