Skip to content

Commit e7e5906

Browse files
committed
test: Refactor fixture component
1 parent dcc903b commit e7e5906

File tree

2 files changed

+114
-87
lines changed

2 files changed

+114
-87
lines changed

html/html_form_element_submit_test.go

+44-29
Original file line numberDiff line numberDiff line change
@@ -43,35 +43,32 @@ func (f AssertFixture) Expect(actual interface{}, extra ...interface{}) types.As
4343
return f.gomega.Expect(actual, extra...)
4444
}
4545

46+
type HTTPHandlerFixture struct {
47+
*http.ServeMux
48+
}
49+
50+
func (f *HTTPHandlerFixture) Setup() {
51+
fmt.Println("Setup handler")
52+
f.ServeMux = http.NewServeMux()
53+
}
54+
4655
type WindowFixture struct {
4756
AssertFixture
4857
BaseLocationFixture
4958
InitialHTMLFixture
59+
*HTTPHandlerFixture
5060
Window htmltest.WindowHelper
5161

5262
requests []*http.Request
5363
form html.HTMLFormElement
5464
actualRequest *http.Request
5565
submittedForm url.Values
56-
handler http.Handler
5766
}
5867

5968
func (f *WindowFixture) Setup() {
60-
handler := f.handler
61-
if handler == nil {
62-
fmt.Println("Overwrite handler")
63-
handler =
64-
http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
65-
if req.ParseForm() != nil {
66-
panic("Error parsing form")
67-
}
68-
f.actualRequest = req
69-
f.submittedForm = req.Form
70-
f.requests = append(f.requests, req)
71-
})
72-
}
69+
fmt.Println("Setup window")
7370
win := html.NewWindow(html.WindowOptions{
74-
HttpClient: gosthttp.NewHttpClientFromHandler(handler),
71+
HttpClient: gosthttp.NewHttpClientFromHandler(f.HTTPHandlerFixture),
7572
BaseLocation: string(f.BaseLocationFixture),
7673
})
7774
f.Window = htmltest.NewWindowHelper(f.TB, win)
@@ -86,6 +83,23 @@ func (f *WindowFixture) Setup() {
8683
))
8784
}
8885

86+
type DefaultWindowFixture struct {
87+
WindowFixture
88+
*HTTPHandlerFixture
89+
}
90+
91+
func (f *DefaultWindowFixture) Setup() {
92+
fmt.Println("Setup default window")
93+
f.HTTPHandlerFixture.ServeMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
94+
if req.ParseForm() != nil {
95+
panic("Error parsing form")
96+
}
97+
f.actualRequest = req
98+
f.submittedForm = req.Form
99+
f.requests = append(f.requests, req)
100+
})
101+
}
102+
89103
func AssertType[T any](t testing.TB, actual any) (res T) {
90104
t.Helper()
91105
var ok bool
@@ -110,7 +124,7 @@ type HTMLFormFixture struct {
110124
}
111125

112126
func TestSubmitForm(t *testing.T) {
113-
w, setup := InitFixture(t, &HTMLFormFixture{}, BaseLocationFixture(
127+
w, setup := InitFixture(t, &DefaultWindowFixture{}, BaseLocationFixture(
114128
"http://example.com/forms/example-form.html?original-query=original-value",
115129
))
116130
setup.Setup()
@@ -134,7 +148,7 @@ func TestSubmitForm(t *testing.T) {
134148
}
135149

136150
func TestHTMLFormElementSubmitPost(t *testing.T) {
137-
w, setup := InitFixture(t, &HTMLFormFixture{}, BaseLocationFixture(
151+
w, setup := InitFixture(t, &DefaultWindowFixture{}, BaseLocationFixture(
138152
"http://example.com/forms/example-form.html?original-query=original-value",
139153
))
140154
setup.Setup()
@@ -151,7 +165,7 @@ func TestHTMLFormElementSubmitPost(t *testing.T) {
151165
}
152166

153167
type HTMLFormSubmitButtonFixture struct {
154-
HTMLFormFixture
168+
DefaultWindowFixture
155169
Submitter html.HTMLButtonElement
156170
}
157171

@@ -270,7 +284,7 @@ func TestHTMLFormElementFormDataEvent(t *testing.T) {
270284
}
271285

272286
type HTMLFormSubmitInputFixture struct {
273-
HTMLFormFixture
287+
DefaultWindowFixture
274288
Submitter html.HTMLInputElement
275289
}
276290

@@ -311,22 +325,23 @@ func TestResubmitFormOn307Redirects(t *testing.T) {
311325
submittedForm url.Values
312326
)
313327

314-
mux := http.NewServeMux()
315-
mux.Handle("POST /form-destination", http.RedirectHandler("/form-redirected", 307))
316-
mux.HandleFunc("POST /form-redirected", func(w http.ResponseWriter, r *http.Request) {
317-
r.ParseForm()
318-
actualRequest = r
319-
submittedForm = r.Form
320-
})
321-
322328
w, setup := InitFixture(
323329
t,
324-
&HTMLFormSubmitInputFixture{},
330+
&struct {
331+
HTMLFormSubmitInputFixture
332+
*HTTPHandlerFixture
333+
}{},
325334
BaseLocationFixture("http://example.com/forms"),
326335
)
336+
fmt.Println("*** SETUP")
327337

328-
w.HTMLFormFixture.handler = mux
329338
setup.Setup()
339+
w.Handle("POST /form-destination", http.RedirectHandler("/form-redirected", 307))
340+
w.HandleFunc("POST /form-redirected", func(w http.ResponseWriter, r *http.Request) {
341+
r.ParseForm()
342+
actualRequest = r
343+
submittedForm = r.Form
344+
})
330345
form := w.Form()
331346
form.SetMethod("post")
332347
form.SetAction("/form-destination")

internal/testing/exp/fixture/fixture.go

+70-58
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ func (s *setups) append(setup Setuper) {
2727
*s = append(*s, setup)
2828
}
2929

30+
func (s *setups) tryAppend(val any) {
31+
if setup, ok := val.(Setuper); ok {
32+
s.append(setup)
33+
}
34+
}
35+
3036
func (s *NullSetup) Setup() {}
3137

3238
type Fixture struct {
@@ -35,26 +41,6 @@ type Fixture struct {
3541

3642
func (f *Fixture) SetTB(tb testing.TB) { f.TB = tb }
3743

38-
func typePkgPath(t reflect.Type) (string, bool) {
39-
k := t.Kind()
40-
if k == reflect.Pointer {
41-
t = t.Elem()
42-
k = t.Kind()
43-
}
44-
if k == reflect.Struct {
45-
return t.PkgPath(), true
46-
}
47-
return "", false
48-
}
49-
50-
// valuePkgPath is specifically for filtering struct types based on their
51-
// package path. The use of an ok return value is solely for this specific
52-
// purpose alone; and the function may not be a good fit in other applications.
53-
func valuePkgPath(val reflect.Value) (string, bool) {
54-
t := val.Type()
55-
return typePkgPath(t)
56-
}
57-
5844
func (f *FixtureSetup[T]) Setup() Setuper {
5945
vType := reflect.TypeFor[T]()
6046
f.TB.Helper()
@@ -63,19 +49,19 @@ func (f *FixtureSetup[T]) Setup() Setuper {
6349
}
6450
vType = vType.Elem()
6551
f.pkgPath = vType.PkgPath()
52+
f.depVals = make([]reflect.Value, len(f.Deps))
53+
for i, d := range f.Deps {
54+
f.depVals[i] = reflect.ValueOf(d)
55+
}
6656
return f.setup(reflect.ValueOf(f.Fixture))
6757
}
6858

6959
func (f FixtureSetup[T]) defaultInclude(val reflect.Value) bool {
70-
var currPath, expPath string
71-
var ok bool
72-
if currPath, ok = valuePkgPath(val); ok {
73-
expPath, ok = typePkgPath(reflect.TypeFor[T]())
74-
}
75-
if !ok {
76-
return false
60+
var underlyingType = val.Type()
61+
if underlyingType.Kind() == reflect.Pointer {
62+
underlyingType = underlyingType.Elem()
7763
}
78-
return strings.HasPrefix(currPath, expPath)
64+
return strings.HasSuffix(underlyingType.Name(), "Fixture")
7965
}
8066

8167
func (f FixtureSetup[T]) include(val reflect.Value) bool {
@@ -91,53 +77,79 @@ type FixtureSetup[T FixtureInit] struct {
9177
Include func(val reflect.Value) bool
9278
Deps []any
9379

80+
depVals []reflect.Value
9481
pkgPath string
9582
}
9683

97-
func (f FixtureSetup[T]) setup(val reflect.Value) Setuper {
84+
func (f *FixtureSetup[T]) setupStruct(val reflect.Value, typ reflect.Type) *setups {
9885
var setups = new(setups)
99-
if !f.include(val) {
100-
return setups
86+
fields:
87+
for _, field := range reflect.VisibleFields(typ) {
88+
if len(field.Index) > 1 || !field.IsExported() {
89+
// Don't set fields of embedded or unexported fields
90+
continue
91+
}
92+
fieldVal := val.FieldByIndex(field.Index)
93+
if !f.include(fieldVal) {
94+
continue
95+
}
96+
for _, depVal := range f.depVals {
97+
if field.Type == depVal.Type() {
98+
fieldVal.Set(depVal)
99+
continue fields
100+
}
101+
}
102+
if field.Type.Kind() == reflect.Pointer && fieldVal.IsNil() {
103+
fieldVal.Set(reflect.New(field.Type.Elem()))
104+
f.depVals = append(f.depVals, fieldVal)
105+
}
106+
setups.append(f.setup(fieldVal))
101107
}
108+
return setups
109+
}
110+
111+
func (f *FixtureSetup[T]) setup(val reflect.Value) Setuper {
112+
var setups = new(setups)
102113
if val.Kind() == reflect.Pointer {
103114
if val.IsNil() {
104115
return &NullSetup{}
105116
}
106117
val = val.Elem()
107118
}
119+
108120
typ := val.Type()
109-
for _, field := range reflect.VisibleFields(typ) {
110-
if len(field.Index) > 1 { // Don't set fields of embedded members
111-
continue
112-
}
113-
for _, dep := range f.Deps {
114-
depVal := reflect.ValueOf(dep)
115-
if field.Type == reflect.TypeOf(dep) {
116-
val.FieldByIndex(field.Index).Set(depVal)
117-
continue
118-
}
119-
}
120-
setups.append(f.setup(val.FieldByIndex(field.Index)))
121+
if typ.Kind() == reflect.Struct {
122+
setups.append(f.setupStruct(val, typ))
121123
}
122-
actualVal := val.Interface()
123-
if setup, ok := actualVal.(Setuper); ok {
124-
setups.append(setup)
125-
} else if val.CanAddr() {
126-
if setup, ok := val.Addr().Interface().(Setuper); ok {
127-
setups.append(setup)
128-
}
129-
}
130-
if init, ok := actualVal.(FixtureInit); ok {
131-
init.SetTB(f.TB)
132-
} else if val.CanAddr() {
133-
if init, ok := val.Addr().Interface().(FixtureInit); ok {
134-
init.SetTB(f.TB)
135-
}
124+
125+
if !val.CanAddr() {
126+
asAny := val.Interface()
127+
setups.tryAppend(asAny)
128+
f.tryInit(asAny)
129+
} else {
130+
// val must be addressable, as both Setup and Init are mutating functions.
131+
//
132+
// Val itself may be a non-pointer field in a pointer struct, which
133+
// means val.Interface() itself is not a Setuper or Initer*, but we can
134+
// still get a pointer using Addr() because it's inside an addressable
135+
// struct.
136+
//
137+
// \* While a non-pointer val may _implement_ the interfaces, the
138+
// implementation would be wrong, as they couldn't mutate.
139+
140+
asAny := val.Addr().Interface()
141+
setups.tryAppend(asAny)
142+
f.tryInit(asAny)
136143
}
137-
// var res *NullSetup
138144
return setups
139145
}
140146

147+
func (s *FixtureSetup[T]) tryInit(val any) {
148+
if init, ok := val.(FixtureInit); ok {
149+
init.SetTB(s.TB)
150+
}
151+
}
152+
141153
func InitFixture[T FixtureInit](
142154
t testing.TB,
143155
fixture T,

0 commit comments

Comments
 (0)