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
12 changes: 8 additions & 4 deletions td/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,19 @@
// &Record{
// Name: "Bob",
// Age: 23,
// ID: t.A(td.NotZero(), uint64(0)).(uint64),
// CreatedAt: t.A(td.Between(before, time.Now())).(time.Time),
// ID: t.AT[uint64](td.NotZero()),
// CreatedAt: t.AT[time.Time](td.Between(before, time.Now())),
// // or if using go<1.27
// ID: td.A[uint64](t, td.NotZero()),
// CreatedAt: td.A[time.Time](t, td.Between(before, time.Now())),
// },
// "Newly created record")
// }
// }
//
// See the [T.A] method (or its full name alias [T.Anchor])
// documentation for details.
// See the [T.AT] method (or its full name alias [T.AnchorT])
// documentation for details (if go<1.27, see [A] & [Anchor] functions
// instead.)
//
// [go-testdeep]: https://go-testdeep.zetta.rocks/
// [Test::Deep]: https://metacpan.org/pod/Test::Deep
Expand Down
9 changes: 8 additions & 1 deletion td/t_anchor.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ func AddAnchorableStructType(fn any) {
// operator operator in a go classic literal like a struct, slice,
// array or map value.
//
// Starting go1.27, you should prefer to use [T.AT].
//
// If the TypeBehind method of operator returns non-nil, model can be
// omitted (like with [Between] operator in the example
// below). Otherwise, model should contain only one value
Expand Down Expand Up @@ -130,7 +132,8 @@ func AddAnchorableStructType(fn any) {
// next [T.Cmp] or [T.CmpLax] call. To make it persistent across calls,
// see [T.SetAnchorsPersist] and [T.AnchorsPersistTemporarily] methods.
//
// See [T.A] method for a shorter synonym of Anchor.
// See [T.AT] & [T.AnchorT] methods for generic variants (go1.27
// required) and [T.A] method for a shorter synonym of Anchor.
//
// See also [T.AnchorsPersistTemporarily], [T.DoAnchorsPersist],
// [T.ResetAnchors], [T.SetAnchorsPersist] and [AddAnchorableStructType].
Expand Down Expand Up @@ -182,6 +185,8 @@ func (t *T) Anchor(operator TestDeep, model ...any) any {

// A is a synonym for [T.Anchor].
//
// Starting go1.27, you should prefer to use [T.AT].
//
// import (
// "testing"
//
Expand All @@ -201,6 +206,8 @@ func (t *T) Anchor(operator TestDeep, model ...any) any {
// })
// }
//
// See [T.AT] & [T.AnchorT] methods for generic variants (go1.27 required).
//
// See also [T.AnchorsPersistTemporarily], [T.DoAnchorsPersist],
// [T.ResetAnchors], [T.SetAnchorsPersist] and [AddAnchorableStructType].
func (t *T) A(operator TestDeep, model ...any) any {
Expand Down
6 changes: 6 additions & 0 deletions td/t_anchor_118.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
package td

// Anchor is a generic shortcut to [T.Anchor].
//
// See also [T.AnchorT] method for a generic variant (go1.27 required).
func Anchor[X any](t *T, operator TestDeep) X {
t.Helper()
var model X
return t.Anchor(operator, model).(X)
}

// A is a generic shortcut to [T.A].
//
// See also [T.AT] method for a generic variant (go1.27 required).
func A[X any](t *T, operator TestDeep) X {
t.Helper()
var model X
return t.A(operator, model).(X)
}
147 changes: 147 additions & 0 deletions td/t_anchor_127.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) 2026, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

//go:build go1.27
// +build go1.27

package td

// AnchorT returns a typed value allowing to anchor the TestDeep
// operator operator in a go classic literal like a struct, slice,
// array or map value.
//
// X type must be compatible with operator, so if the TypeBehind
// method of operator returns a non-nil type, it has to match X.
//
// AnchorT returns a typed value ready to be embed in a go data
// structure to be compared using [T.Cmp] or [T.CmpLax]:
//
// import (
// "testing"
//
// "github.com/maxatome/go-testdeep/td"
// )
//
// func TestFunc(tt *testing.T) {
// got := Func()
//
// t := td.NewT(tt)
// t.Cmp(got, &MyStruct{
// Name: "Bob",
// Details: &MyDetails{
// Nick: t.AnchorT[string](td.HasPrefix("Bobby")),
// Age: t.AnchorT[int](td.Between(40, 50)),
// },
// })
// }
//
// Without operator anchoring feature, the previous example would have
// been:
//
// import (
// "testing"
//
// "github.com/maxatome/go-testdeep/td"
// )
//
// func TestFunc(tt *testing.T) {
// got := Func()
//
// t := td.NewT(tt)
// t.Cmp(got, td.Struct(&MyStruct{Name: "Bob"},
// td.StructFields{
// "Details": td.Struct(&MyDetails{},
// td.StructFields{
// "Nick": td.HasPrefix("Bobby"),
// "Age": td.Between(40, 50),
// }),
// }))
// }
//
// using two times the [Struct] operator to work around the strict type
// checking of golang.
//
// AnchorT is a synonym of [T.AT].
//
// By default, the value returned by AnchorT can only be used in the
// next [T.Cmp] or [T.CmpLax] call. To make it persistent across calls,
// see [T.SetAnchorsPersist] and [T.AnchorsPersistTemporarily] methods.
//
// See also [T.AnchorsPersistTemporarily], [T.DoAnchorsPersist],
// [T.ResetAnchors], [T.SetAnchorsPersist] and [AddAnchorableStructType].
func (t *T) AnchorT[X any](operator TestDeep) X {
t.Helper()
return Anchor[X](t, operator)
}

// AT returns a typed value allowing to anchor the TestDeep
// operator operator in a go classic literal like a struct, slice,
// array or map value.
//
// X type must be compatible with operator, so if the TypeBehind
// method of operator returns a non-nil type, it has to match X.
//
// AT returns a typed value ready to be embed in a go data
// structure to be compared using [T.Cmp] or [T.CmpLax]:
//
// import (
// "testing"
//
// "github.com/maxatome/go-testdeep/td"
// )
//
// func TestFunc(tt *testing.T) {
// got := Func()
//
// t := td.NewT(tt)
// t.Cmp(got, &MyStruct{
// Name: "Bob",
// Details: &MyDetails{
// Nick: t.AT[string](td.HasPrefix("Bobby")),
// Age: t.AT[int](td.Between(40, 50)),
// },
// })
// }
//
// Without operator anchoring feature, the previous example would have
// been:
//
// import (
// "testing"
//
// "github.com/maxatome/go-testdeep/td"
// )
//
// func TestFunc(tt *testing.T) {
// got := Func()
//
// t := td.NewT(tt)
// t.Cmp(got, td.Struct(&MyStruct{Name: "Bob"},
// td.StructFields{
// "Details": td.Struct(&MyDetails{},
// td.StructFields{
// "Nick": td.HasPrefix("Bobby"),
// "Age": td.Between(40, 50),
// }),
// }))
// }
//
// using two times the [Struct] operator to work around the strict type
// checking of golang.
//
// AT is a shorter synonym of [T.AnchorT].
//
// By default, the value returned by AT can only be used in the
// next [T.Cmp] or [T.CmpLax] call. To make it persistent across calls,
// see [T.SetAnchorsPersist] and [T.AnchorsPersistTemporarily] methods.
//
// See also [T.AnchorsPersistTemporarily], [T.DoAnchorsPersist],
// [T.ResetAnchors], [T.SetAnchorsPersist] and [AddAnchorableStructType].
func (t *T) AT[X any](operator TestDeep) X {
t.Helper()
return A[X](t, operator)
}

54 changes: 54 additions & 0 deletions td/t_anchor_127_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2026, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

//go:build go1.27
// +build go1.27

package td_test

import (
"testing"
"time"

"github.com/maxatome/go-testdeep/internal/test"
"github.com/maxatome/go-testdeep/td"
)

func TestT_AnchorT(tt *testing.T) {
ttt := test.NewTestingTB(tt.Name())
t := td.NewT(ttt)
type MyStruct struct {
PNum *int
Num int64
Str string
Slice []int
Map map[string]bool
Time time.Time
}
n := 42
got := MyStruct{
PNum: &n,
Num: 136,
Str: "Pipo bingo",
Time: timeParse(tt, "2019-01-02T11:22:33.123456Z"),
}

td.CmpTrue(tt,
t.Cmp(got, MyStruct{
PNum: t.AnchorT[*int](td.Ptr(td.Between(40, 45))),
Num: t.AnchorT[int64](td.Between(int64(135), int64(137))),
Str: t.AnchorT[string](td.HasPrefix("Pipo")),
Time: t.AnchorT[time.Time](td.TruncTime(timeParse(tt, "2019-01-02T11:22:00Z"), time.Minute)),
}))

td.CmpTrue(tt,
t.Cmp(got, MyStruct{
PNum: t.AT[*int](td.Ptr(td.Between(40, 45))),
Num: t.AT[int64](td.Between(int64(135), int64(137))),
Str: t.AT[string](td.HasPrefix("Pipo")),
Time: t.AT[time.Time](td.TruncTime(timeParse(tt, "2019-01-02T11:22:00Z"), time.Minute)),
}))
}
Loading