diff --git a/pp/Format.go b/pp/Format.go index c5ceae8..7551035 100644 --- a/pp/Format.go +++ b/pp/Format.go @@ -2,6 +2,7 @@ package pp import ( "bytes" + "encoding/json" "fmt" "github.com/adamluzsi/testcase/internal/reflects" "io" @@ -17,6 +18,12 @@ func Format(v any) string { return formatter{}.Format(v) } +var ( + typeByteSlice = reflect.TypeOf((*[]byte)(nil)).Elem() + typeTimeDuration = reflect.TypeOf((*time.Duration)(nil)).Elem() + typeTimeTime = reflect.TypeOf((*time.Time)(nil)).Elem() +) + type formatter struct{} func (f formatter) Format(v any) string { @@ -36,11 +43,6 @@ type visitor struct { stack int } -var ( - typeTimeDuration = reflect.TypeOf((*time.Duration)(nil)).Elem() - typeTimeTime = reflect.TypeOf((*time.Time)(nil)).Elem() -) - func (v *visitor) Visit(w io.Writer, rv reflect.Value, depth int) { defer debugRecover() td, ok := v.recursionGuard(w, rv) @@ -88,10 +90,10 @@ func (v *visitor) Visit(w io.Writer, rv reflect.Value, depth int) { switch rv.Kind() { case reflect.Array, reflect.Slice: - if v.tryByteSlice(w, rv) { + if v.tryNilSlice(w, rv) { return } - if v.tryNilSlice(w, rv) { + if v.tryByteSlice(w, rv, depth) { return } @@ -259,12 +261,12 @@ func (v *visitor) visitStructure(w io.Writer, rv reflect.Value, depth int) { func (v *visitor) newLine(w io.Writer, depth int) { _, _ = w.Write([]byte("\n")) - v.indent(w, depth) + _, _ = w.Write([]byte(v.indent(depth))) } -func (v *visitor) indent(w io.Writer, depth int) { +func (v *visitor) indent(depth int) string { const defaultIndent = "\t" - _, _ = w.Write([]byte(strings.Repeat(defaultIndent, depth))) + return strings.Repeat(defaultIndent, depth) } func (v *visitor) sortMapKeys(keys []reflect.Value) { @@ -288,9 +290,7 @@ func (v *visitor) sortMapKeys(keys []reflect.Value) { }) } -var typeByteSlice = reflect.TypeOf([]byte{}) - -func (v *visitor) tryByteSlice(w io.Writer, rv reflect.Value) bool { +func (v *visitor) tryByteSlice(w io.Writer, rv reflect.Value, depth int) bool { if !rv.Type().ConvertibleTo(typeByteSlice) { return false } @@ -300,6 +300,13 @@ func (v *visitor) tryByteSlice(w io.Writer, rv reflect.Value) bool { return false } + if json.Valid(data) { + var buf bytes.Buffer + if err := json.Indent(&buf, data, v.indent(depth), "\t"); err == nil { + data = buf.Bytes() + } + } + var ( typeName = v.getTypeName(rv) quoteChar = "`" diff --git a/pp/Format_test.go b/pp/Format_test.go index 72a3dd1..2eb6fb3 100644 --- a/pp/Format_test.go +++ b/pp/Format_test.go @@ -105,6 +105,29 @@ func TestFormat(t *testing.T) { t.Must.Equal(expected, act(t)) }) }) + + s.And("it has a field which contains a valid JSON", func(s *testcase.Spec) { + type T struct { + V json.RawMessage + } + type C struct { + Foo string `json:"foo"` + } + ent := testcase.Let(s, func(t *testcase.T) T { + bs, err := json.Marshal(C{Foo: "Charlotte"}) + t.Must.NoError(err) + return T{V: bs} + }) + + v.Let(s, func(t *testcase.T) any { + return ent.Get(t) + }) + + s.Then("it will print an indentet version of it", func(t *testcase.T) { + exp := "pp_test.T{\n\tV: json.RawMessage(`{\n\t\t\"foo\": \"Charlotte\"\n\t}`),\n}" + t.Must.Equal(exp, act(t)) + }) + }) }) s.When("v is a slice", func(s *testcase.Spec) { @@ -201,11 +224,31 @@ func TestFormat(t *testing.T) { }) s.Then("it will print out as a UTF-8 string", func(t *testcase.T) { - expected := "json.RawMessage(`{\"foo\":\"bar\"}`)" + expected := "json.RawMessage(`{\n\t\"foo\": \"bar\"\n}`)" t.Log(expected) t.Must.Equal(expected, act(t)) }) }) + + s.And("it is a valid JSON", func(s *testcase.Spec) { + type T struct { + Foo string `json:"foo"` + } + ent := testcase.Let(s, func(t *testcase.T) T { + return T{Foo: t.Random.StringN(8)} + }) + v.Let(s, func(t *testcase.T) any { + bs, err := json.Marshal(ent.Get(t)) + t.Must.NoError(err) + return bs + }) + + s.Then("it will print an indentet version of it", func(t *testcase.T) { + exp, err := json.MarshalIndent(ent.Get(t), "", "\t") + t.Must.NoError(err) + t.Must.Equal(fmt.Sprintf("[]byte(`%s`)", string(exp)), act(t)) + }) + }) }) s.And("it implements fmt.Stringer", func(s *testcase.Spec) {