Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`Last`]: https://go-testdeep.zetta.rocks/operators/last/
[`Lax`]: https://go-testdeep.zetta.rocks/operators/lax/
[`Len`]: https://go-testdeep.zetta.rocks/operators/len/
[`List`]: https://go-testdeep.zetta.rocks/operators/list/
[`Lt`]: https://go-testdeep.zetta.rocks/operators/lt/
[`Lte`]: https://go-testdeep.zetta.rocks/operators/lte/
[`Map`]: https://go-testdeep.zetta.rocks/operators/map/
Expand Down Expand Up @@ -396,6 +397,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`CmpLast`]: https://go-testdeep.zetta.rocks/operators/last/#cmplast-shortcut
[`CmpLax`]: https://go-testdeep.zetta.rocks/operators/lax/#cmplax-shortcut
[`CmpLen`]: https://go-testdeep.zetta.rocks/operators/len/#cmplen-shortcut
[`CmpList`]: https://go-testdeep.zetta.rocks/operators/list/#cmplist-shortcut
[`CmpLt`]: https://go-testdeep.zetta.rocks/operators/lt/#cmplt-shortcut
[`CmpLte`]: https://go-testdeep.zetta.rocks/operators/lte/#cmplte-shortcut
[`CmpMap`]: https://go-testdeep.zetta.rocks/operators/map/#cmpmap-shortcut
Expand Down Expand Up @@ -462,6 +464,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`T.Last`]: https://go-testdeep.zetta.rocks/operators/last/#tlast-shortcut
[`T.CmpLax`]: https://go-testdeep.zetta.rocks/operators/lax/#tcmplax-shortcut
[`T.Len`]: https://go-testdeep.zetta.rocks/operators/len/#tlen-shortcut
[`T.List`]: https://go-testdeep.zetta.rocks/operators/list/#tlist-shortcut
[`T.Lt`]: https://go-testdeep.zetta.rocks/operators/lt/#tlt-shortcut
[`T.Lte`]: https://go-testdeep.zetta.rocks/operators/lte/#tlte-shortcut
[`T.Map`]: https://go-testdeep.zetta.rocks/operators/map/#tmap-shortcut
Expand Down
24 changes: 23 additions & 1 deletion td/cmp_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"time"
)

// allOperators lists the 69 operators.
// allOperators lists the 70 operators.
// nil means not usable in JSON().
var allOperators = map[string]any{
"All": All,
Expand Down Expand Up @@ -43,6 +43,7 @@ var allOperators = map[string]any{
"Last": Last,
"Lax": nil,
"Len": Len,
"List": nil,
"Lt": Lt,
"Lte": Lte,
"Map": nil,
Expand Down Expand Up @@ -615,6 +616,27 @@ func CmpLen(t TestingT, got, expectedLen any, args ...any) bool {
return Cmp(t, got, Len(expectedLen), args...)
}

// CmpList is a shortcut for:
//
// td.Cmp(t, got, td.List(expectedValues...), args...)
//
// See [List] for details.
//
// Returns true if the test is OK, false if it fails.
//
// If t is a [*T] then its Config field is inherited.
//
// args... are optional and allow to name the test. This name is
// used in case of failure to qualify the test. If len(args) > 1 and
// the first item of args is a string and contains a '%' rune then
// [fmt.Fprintf] is used to compose the name, else args are passed to
// [fmt.Fprint]. Do not forget it is the name of the test, not the
// reason of a potential failure.
func CmpList(t TestingT, got any, expectedValues []any, args ...any) bool {
t.Helper()
return Cmp(t, got, List(expectedValues...), args...)
}

// CmpLt is a shortcut for:
//
// td.Cmp(t, got, td.Lt(maxExpectedValue), args...)
Expand Down
41 changes: 20 additions & 21 deletions td/equal.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,31 +314,30 @@ func deepValueEqual(ctx ctxerr.Context, got, expected reflect.Value) (err *ctxer
return
}
}
if gotLen == expectedLen {
return
}

if gotLen != expectedLen {
res := tdSetResult{
Kind: itemsSetResult,
// do not sort Extra/Mising here
}
res := tdSetResult{
Kind: itemsSetResult,
// do not sort Extra/Mising here
}

if gotLen > expectedLen {
res.Extra = make([]reflect.Value, gotLen-expectedLen)
for i := expectedLen; i < gotLen; i++ {
res.Extra[i-expectedLen] = got.Index(i)
}
} else {
res.Missing = make([]reflect.Value, expectedLen-gotLen)
for i := gotLen; i < expectedLen; i++ {
res.Missing[i-gotLen] = expected.Index(i)
}
if gotLen > expectedLen {
res.Extra = make([]reflect.Value, gotLen-expectedLen)
for i := expectedLen; i < gotLen; i++ {
res.Extra[i-expectedLen] = got.Index(i)
}
} else {
res.Missing = make([]reflect.Value, expectedLen-gotLen)
for i := gotLen; i < expectedLen; i++ {
res.Missing[i-gotLen] = expected.Index(i)
}

return ctx.CollectError(&ctxerr.Error{
Message: fmt.Sprintf("comparing slices, from index #%d", maxLen),
Summary: res.Summary(),
})
}
return
return ctx.CollectError(&ctxerr.Error{
Message: fmt.Sprintf("comparing slices, from index #%d", maxLen),
Summary: res.Summary(),
})

case reflect.Interface:
return deepValueEqual(ctx, got.Elem(), expected.Elem())
Expand Down
26 changes: 26 additions & 0 deletions td/example_cmp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,32 @@ func ExampleCmpLen_operatorMap() {
// true
}

func ExampleCmpList() {
t := &testing.T{}

got := []int{1, 33, 8, 2}

// Matches as all items are present
ok := td.CmpList(t, got, []any{1, td.Between(32, 34), td.Gt(7), 2})
fmt.Println("checks all items match, in this order:", ok)

// Does not match as got does not use the same order as expected
ok = td.CmpList(t, got, []any{1, td.Gt(7), 2, td.Between(32, 34)})
fmt.Println("checks all items match, in wrong order:", ok)

// When expected is already a non-[]any slice, it cannot be
// flattened directly using expected... without copying it to a new
// []any slice, then use td.Flatten!
expected := []int{1, 33, 8}
ok = td.CmpList(t, got, []any{td.Flatten(expected), td.Lte(2)})
fmt.Println("checks all expected items are present + last one ≤ 2:", ok)

// Output:
// checks all items match, in this order: true
// checks all items match, in wrong order: false
// checks all expected items are present + last one ≤ 2: true
}

func ExampleCmpLt_int() {
t := &testing.T{}

Expand Down
26 changes: 26 additions & 0 deletions td/example_t_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,32 @@ func ExampleT_Len_operatorMap() {
// true
}

func ExampleT_List() {
t := td.NewT(&testing.T{})

got := []int{1, 33, 8, 2}

// Matches as all items are present
ok := t.List(got, []any{1, td.Between(32, 34), td.Gt(7), 2})
fmt.Println("checks all items match, in this order:", ok)

// Does not match as got does not use the same order as expected
ok = t.List(got, []any{1, td.Gt(7), 2, td.Between(32, 34)})
fmt.Println("checks all items match, in wrong order:", ok)

// When expected is already a non-[]any slice, it cannot be
// flattened directly using expected... without copying it to a new
// []any slice, then use td.Flatten!
expected := []int{1, 33, 8}
ok = t.List(got, []any{td.Flatten(expected), td.Lte(2)})
fmt.Println("checks all expected items are present + last one ≤ 2:", ok)

// Output:
// checks all items match, in this order: true
// checks all items match, in wrong order: false
// checks all expected items are present + last one ≤ 2: true
}

func ExampleT_Lt_int() {
t := td.NewT(&testing.T{})

Expand Down
26 changes: 26 additions & 0 deletions td/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,32 @@ func ExampleJSONPointer_has_hasnt() {
// Britt hasn't children: false
}

func ExampleList() {
t := &testing.T{}

got := []int{1, 33, 8, 2}

// Matches as all items are present
ok := td.Cmp(t, got, td.List(1, td.Between(32, 34), td.Gt(7), 2))
fmt.Println("checks all items match, in this order:", ok)

// Does not match as got does not use the same order as expected
ok = td.Cmp(t, got, td.List(1, td.Gt(7), 2, td.Between(32, 34)))
fmt.Println("checks all items match, in wrong order:", ok)

// When expected is already a non-[]any slice, it cannot be
// flattened directly using expected... without copying it to a new
// []any slice, then use td.Flatten!
expected := []int{1, 33, 8}
ok = td.Cmp(t, got, td.List(td.Flatten(expected), td.Lte(2)))
fmt.Println("checks all expected items are present + last one ≤ 2:", ok)

// Output:
// checks all items match, in this order: true
// checks all items match, in wrong order: false
// checks all expected items are present + last one ≤ 2: true
}

func ExampleKeys() {
t := &testing.T{}

Expand Down
87 changes: 3 additions & 84 deletions td/flatten.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ import (
// and "/PATH" can really be.
//
// Flatten with an fn can be useful when testing some fields of
// structs in a slice with [Set] or [Bag] operators families. As an
// example, here we test only "Name" field for each item of a person
// slice:
// structs in a slice with [Bag] or [Set] operators families as well
// as [List]. As an example, here we test only "Name" field for each
// item of a person slice:
//
// type person struct {
// Name string `json:"name"`
Expand Down Expand Up @@ -249,84 +249,3 @@ func Flatten(sliceOrMap any, fn ...any) flat.Slice {

return flat.Slice{Slice: final}
}

// Flatten allows to flatten any slice, array or map in
// parameters of operators expecting ...any after applying a function
// on each item to exclude or transform it.
//
// fn must be a non-nil function with a signature like:
//
// func(T) V
// func(T) (V, bool)
//
// T can be the same as V but it is not mandatory. The (V, bool)
// returned case allows to exclude some items when returning false.
//
// If fn signature does not match these cases, Flatten panics.
//
// If the type of an item of sliceOrMap is not convertible to T, the
// item is dropped silently, as if fn returned false.
//
// fn can also be a string among:
//
// "Smuggle:FIELD"
// "JSONPointer:/PATH"
//
// that are shortcuts for respectively:
//
// func(in any) any { return td.Smuggle("FIELD", in) }
// func(in any) any { return td.JSONPointer("/PATH", in) }
//
// See [Smuggle] and [JSONPointer] for a description of what "FIELD"
// and "/PATH" can really be.
//
// Flatten can be useful when testing some fields of structs in
// a slice with [Set] or [Bag] operators families. As an example, here
// we test only "Name" field for each item of a person slice:
//
// type person struct {
// Name string `json:"name"`
// Age int `json:"age"`
// }
// got := []person{{"alice", 22}, {"bob", 18}, {"brian", 34}, {"britt", 32}}
//
// td.Cmp(t, got,
// td.Bag(td.Flatten(
// func(name string) any { return td.Smuggle("Name", name) },
// []string{"alice", "britt", "brian", "bob"})))
// // distributes td.Smuggle for each Name, so is equivalent of:
// td.Cmp(t, got, td.Bag(
// td.Smuggle("Name", "alice"),
// td.Smuggle("Name", "britt"),
// td.Smuggle("Name", "brian"),
// td.Smuggle("Name", "bob")))
//
// // Same here using Smuggle string shortcut
// td.Cmp(t, got,
// td.Bag(td.Flatten(
// "Smuggle:Name", []string{"alice", "britt", "brian", "bob"})))
//
// // Same here, but using JSONPointer operator
// td.Cmp(t, got,
// td.Bag(td.Flatten(
// func(name string) any { return td.JSONPointer("/name", name) },
// []string{"alice", "britt", "brian", "bob"})))
//
// // Same here using JSONPointer string shortcut
// td.Cmp(t, got,
// td.Bag(td.Flatten(
// "JSONPointer:/name", []string{"alice", "britt", "brian", "bob"})))
//
// // Same here, but using SuperJSONOf operator
// td.Cmp(t, got,
// td.Bag(td.Flatten(
// func(name string) any { return td.SuperJSONOf(`{"name":$1}`, name) },
// []string{"alice", "britt", "brian", "bob"})))
//
// // Same here, but using Struct operator
// td.Cmp(t, got,
// td.Bag(td.Flatten(
// func(name string) any { return td.Struct(person{Name: name}) },
// []string{"alice", "britt", "brian", "bob"})))
//
// See also [Flatten] and [Grep].
19 changes: 19 additions & 0 deletions td/t.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,25 @@ func (t *T) Len(got, expectedLen any, args ...any) bool {
return t.Cmp(got, Len(expectedLen), args...)
}

// List is a shortcut for:
//
// t.Cmp(got, td.List(expectedValues...), args...)
//
// See [List] for details.
//
// Returns true if the test is OK, false if it fails.
//
// args... are optional and allow to name the test. This name is
// used in case of failure to qualify the test. If len(args) > 1 and
// the first item of args is a string and contains a '%' rune then
// [fmt.Fprintf] is used to compose the name, else args are passed to
// [fmt.Fprint]. Do not forget it is the name of the test, not the
// reason of a potential failure.
func (t *T) List(got any, expectedValues []any, args ...any) bool {
t.Helper()
return t.Cmp(got, List(expectedValues...), args...)
}

// Lt is a shortcut for:
//
// t.Cmp(got, td.Lt(maxExpectedValue), args...)
Expand Down
8 changes: 4 additions & 4 deletions td/td_all.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018, Maxime Soulé
// Copyright (c) 2018-2025, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
Expand All @@ -14,7 +14,7 @@ import (
)

type tdAll struct {
tdList
tdListBase
}

var _ TestDeep = &tdAll{}
Expand Down Expand Up @@ -60,11 +60,11 @@ var _ TestDeep = &tdAll{}
// See also [Any] and [None].
func All(expectedValues ...any) TestDeep {
return &tdAll{
tdList: newList(expectedValues...),
tdListBase: newListBase(expectedValues...),
}
}

func (a *tdAll) Match(ctx ctxerr.Context, got reflect.Value) (err *ctxerr.Error) {
func (a *tdAll) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {
var origErr *ctxerr.Error
for idx, item := range a.items {
// Use deepValueEqualFinal here instead of deepValueEqual as we
Expand Down
6 changes: 3 additions & 3 deletions td/td_any.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018, Maxime Soulé
// Copyright (c) 2018-2025, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
Expand All @@ -13,7 +13,7 @@ import (
)

type tdAny struct {
tdList
tdListBase
}

var _ TestDeep = &tdAny{}
Expand Down Expand Up @@ -48,7 +48,7 @@ var _ TestDeep = &tdAny{}
// See also [All] and [None].
func Any(expectedValues ...any) TestDeep {
return &tdAny{
tdList: newList(expectedValues...),
tdListBase: newListBase(expectedValues...),
}
}

Expand Down
Loading