From 912e24f6dd06d12a3b9de1345c334ef3709e7046 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Tue, 18 Apr 2023 18:47:18 +0300 Subject: [PATCH 1/5] Adding struct form --- widget/struct_form.go | 146 +++++++++++++++++++++++++++ widget/struct_form_test.go | 195 +++++++++++++++++++++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 widget/struct_form.go create mode 100644 widget/struct_form_test.go diff --git a/widget/struct_form.go b/widget/struct_form.go new file mode 100644 index 00000000..1538c1e4 --- /dev/null +++ b/widget/struct_form.go @@ -0,0 +1,146 @@ +package widget + +import ( + "reflect" + "regexp" + "strings" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/widget" +) + +type StructForm struct { + widget.BaseWidget + structType reflect.Type + modal *widget.PopUp + canvas fyne.Canvas + fields []reflect.StructField + widgets []*widget.FormItem + bindings []binding.DataItem + submit func(s interface{}) +} + +func NewStructForm(c fyne.Canvas, s interface{}, submit func(s interface{})) *StructForm { + sf := &StructForm{} + sf.ExtendBaseWidget(sf) + + sf.canvas = c + sf.structType = reflect.TypeOf(s) + sf.fields = parseFields(sf.structType) + sf.widgets = createWidgets(sf.fields) + sf.submit = submit + + form := &widget.Form{ + OnCancel: func() { + sf.modal.Hide() + }, + OnSubmit: func() { + sf.Submit() + sf.modal.Hide() + }, + Items: sf.widgets, + } + sf.modal = widget.NewModalPopUp( + container.NewVBox(form), + sf.canvas, + ) + + return sf +} + +func buildValue(t reflect.Type, fields []reflect.StructField, bindings []binding.DataItem) reflect.Value { + val := reflect.New(t) + + for i, f := range fields { + var v interface{} + var err error + + switch bindings[i].(type) { + case binding.Int: + v, err = bindings[i].(binding.Int).Get() + case binding.String: + v, err = bindings[i].(binding.String).Get() + case binding.Float: + v, err = bindings[i].(binding.Float).Get() + } + + if err != nil { + panic(err) + } + val.FieldByName(f.Name).Set( + reflect.ValueOf(v), + ) + } + + return val +} + +func (sf *StructForm) Submit() { + sf.submit(buildValue( + sf.structType, sf.fields, sf.bindings, + ).Interface()) +} + +func parseFields(t reflect.Type) []reflect.StructField { + fields := make([]reflect.StructField, 0) + for i := 0; i < t.NumField(); i += 1 { + f := t.Field(i) + if strings.Contains(f.Tag.Get("fyne"), "field") { + fields = append(fields, f) + } + } + return fields +} + +func createWidgets(fs []reflect.StructField) []*widget.FormItem { + widgets := make([]*widget.FormItem, len(fs)) + binding := make([]*binding.DataItem, len(fs)) + matchFirstCap := regexp.MustCompile("(.)([A-Z][a-z]+)") + for i, f := range fs { + w, b := fieldToWidget(f) + widgets[i] = &widget.FormItem{ + Text: matchFirstCap.ReplaceAllString(f.Name, "${1} ${2}"), + Widget: w, + } + binding[i] = &b + } + return widgets +} + +func fieldToWidget(f reflect.StructField) (fyne.CanvasObject, binding.DataItem) { + switch f.Type.Kind() { + case reflect.Float32, reflect.Float64: + w := NewNumericalEntry() + w.AllowFloat = true + b := binding.NewString() + w.Bind(b) + return w, binding.StringToFloat(b) + case reflect.Int: + w := NewNumericalEntry() + b := binding.NewString() + w.Bind(b) + return w, binding.StringToInt(b) + case reflect.TypeOf(time.Time{}).Kind(): + b := binding.NewUntyped() + w := NewCalendar(time.Now(), func(t time.Time) { + b.Set(&t) + }) + return w, b + default: + w := widget.NewEntry() + b := binding.NewString() + w.Bind(b) + return w, b + } +} + +func (sf *StructForm) Show() { + sf.modal.Show() +} + +func (sf *StructForm) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(sf.modal) +} diff --git a/widget/struct_form_test.go b/widget/struct_form_test.go new file mode 100644 index 00000000..ca8e6b31 --- /dev/null +++ b/widget/struct_form_test.go @@ -0,0 +1,195 @@ +package widget + +import ( + "reflect" + "testing" + "time" + + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/widget" +) + +func Test_parseFields(t *testing.T) { + type Foo struct { + Field1 string `fyne:"field"` + Field2 int + Field3 bool `fyne:"field"` + } + + type Bar struct { + Field1 float64 `fyne:"field"` + Field2 time.Time + } + + tests := []struct { + name string + args reflect.Type + want []reflect.StructField + }{ + { + name: "parses fields with fyne tag", + args: reflect.TypeOf(Foo{}), + want: []reflect.StructField{ + { + Name: "Field1", + Type: reflect.TypeOf(""), + Offset: 0, + Index: []int{0}, + Tag: reflect.StructTag(`fyne:"field"`), + }, + { + Name: "Field3", + Type: reflect.TypeOf(true), + Offset: 24, + Index: []int{2}, + Tag: reflect.StructTag(`fyne:"field"`), + }, + }, + }, + { + name: "returns empty slice when there are no fields with fyne tag", + args: reflect.TypeOf(Bar{}), + want: []reflect.StructField{ + { + Name: "Field1", + Type: reflect.TypeOf(float64(1.0)), + Offset: 0, + Index: []int{0}, + Tag: reflect.StructTag(`fyne:"field"`), + }}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseFields(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseFields() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_createWidgets(t *testing.T) { + type Foo struct { + Field1 string `fyne:"field"` + Field2 int `fyne:"field"` + Field3 bool + } + + type Bar struct { + Field1 float64 `fyne:"field"` + Field2 time.Time + } + + tests := []struct { + name string + args []reflect.StructField + want []*widget.FormItem + }{ + { + name: "creates widgets for fields with fyne tag", + args: []reflect.StructField{ + { + Name: "Field1", + Type: reflect.TypeOf(""), + Tag: reflect.StructTag(`fyne:"field"`), + }, + { + Name: "Field2", + Type: reflect.TypeOf(0), + Tag: reflect.StructTag(`fyne:"field"`), + }, + }, + want: []*widget.FormItem{ + { + Text: "Field1", + Widget: widget.NewEntry(), + }, + { + Text: "Field2", + Widget: NewNumericalEntry(), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := createWidgets(tt.args) + if len(got) != len(tt.want) { + t.Errorf("got length %v, wanted %v", len(got), len(tt.want)) + } + for i := 0; i < len(got) && i < len(tt.want); i += 1 { + if reflect.TypeOf(got[i].Widget) != reflect.TypeOf(tt.want[i].Widget) { + t.Errorf("createWidgets()[%v] = %v, want %v", i, reflect.TypeOf(got[i].Widget), reflect.TypeOf(tt.want[i].Widget)) + } + } + }) + } +} + +type testStruct struct { + Name string `fyne:"field"` + Age int `fyne:"field"` + Address string `fyne:"field"` +} + +func StringBind(s string) binding.DataItem { + return binding.BindString(&s).(binding.DataItem) +} + +func IntBind(i int) binding.DataItem { + return binding.BindInt(&i).(binding.DataItem) +} + +func Test_buildValue(t *testing.T) { + type args struct { + t reflect.Type + fields []reflect.StructField + bindings []binding.DataItem + } + + tests := []struct { + name string + args args + want reflect.Value + wantErr bool + }{ + { + name: "Test Case 1 - Success", + args: args{ + t: reflect.TypeOf(testStruct{}), + fields: []reflect.StructField{{Name: "Name"}, {Name: "Age"}, {Name: "Address"}}, + bindings: []binding.DataItem{StringBind("John Doe"), IntBind(32), StringBind("1234 Main St.")}, + }, + want: reflect.ValueOf(&testStruct{Name: "John Doe", Age: 32, Address: "1234 Main St."}), + }, + { + name: "Test Case 2 - Invalid Binding", + args: args{ + t: reflect.TypeOf(testStruct{}), + fields: []reflect.StructField{{Name: "Name"}, {Name: "Age"}, {Name: "Address"}}, + bindings: []binding.DataItem{StringBind("John Doe"), StringBind("Invalid Age Value"), StringBind("1234 Main St.")}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := reflect.ValueOf(nil) + err := false + defer func() { + if r := recover(); r != nil { + err = true + } + }() + got = buildValue(tt.args.t, tt.args.fields, tt.args.bindings) + if err != tt.wantErr { + t.Errorf("buildValue() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got.Interface(), tt.want.Interface()) { + t.Errorf("buildValue() = %v, want %v", got, tt.want) + } + }) + } +} From cb150a6a8f9a27f51b910249c3b57637e0bc3b96 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Wed, 19 Apr 2023 11:59:41 +0300 Subject: [PATCH 2/5] Restructuring tests --- widget/struct_form.go | 29 +--- widget/struct_form_test.go | 270 +++++++++++++++++-------------------- 2 files changed, 131 insertions(+), 168 deletions(-) diff --git a/widget/struct_form.go b/widget/struct_form.go index 1538c1e4..b88b4fb8 100644 --- a/widget/struct_form.go +++ b/widget/struct_form.go @@ -7,7 +7,6 @@ import ( "time" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/widget" ) @@ -15,7 +14,7 @@ import ( type StructForm struct { widget.BaseWidget structType reflect.Type - modal *widget.PopUp + form *widget.Form canvas fyne.Canvas fields []reflect.StructField widgets []*widget.FormItem @@ -23,30 +22,20 @@ type StructForm struct { submit func(s interface{}) } -func NewStructForm(c fyne.Canvas, s interface{}, submit func(s interface{})) *StructForm { +func NewStructForm(s interface{}, submit func(s interface{}), cancel func()) *StructForm { sf := &StructForm{} sf.ExtendBaseWidget(sf) - sf.canvas = c sf.structType = reflect.TypeOf(s) sf.fields = parseFields(sf.structType) sf.widgets = createWidgets(sf.fields) sf.submit = submit - form := &widget.Form{ - OnCancel: func() { - sf.modal.Hide() - }, - OnSubmit: func() { - sf.Submit() - sf.modal.Hide() - }, - Items: sf.widgets, + sf.form = &widget.Form{ + OnCancel: cancel, + OnSubmit: sf.Submit, + Items: sf.widgets, } - sf.modal = widget.NewModalPopUp( - container.NewVBox(form), - sf.canvas, - ) return sf } @@ -137,10 +126,6 @@ func fieldToWidget(f reflect.StructField) (fyne.CanvasObject, binding.DataItem) } } -func (sf *StructForm) Show() { - sf.modal.Show() -} - func (sf *StructForm) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(sf.modal) + return widget.NewSimpleRenderer(sf.form) } diff --git a/widget/struct_form_test.go b/widget/struct_form_test.go index ca8e6b31..2e5e9798 100644 --- a/widget/struct_form_test.go +++ b/widget/struct_form_test.go @@ -9,122 +9,15 @@ import ( "fyne.io/fyne/v2/widget" ) -func Test_parseFields(t *testing.T) { - type Foo struct { - Field1 string `fyne:"field"` - Field2 int - Field3 bool `fyne:"field"` - } - - type Bar struct { - Field1 float64 `fyne:"field"` - Field2 time.Time - } - - tests := []struct { - name string - args reflect.Type - want []reflect.StructField - }{ - { - name: "parses fields with fyne tag", - args: reflect.TypeOf(Foo{}), - want: []reflect.StructField{ - { - Name: "Field1", - Type: reflect.TypeOf(""), - Offset: 0, - Index: []int{0}, - Tag: reflect.StructTag(`fyne:"field"`), - }, - { - Name: "Field3", - Type: reflect.TypeOf(true), - Offset: 24, - Index: []int{2}, - Tag: reflect.StructTag(`fyne:"field"`), - }, - }, - }, - { - name: "returns empty slice when there are no fields with fyne tag", - args: reflect.TypeOf(Bar{}), - want: []reflect.StructField{ - { - Name: "Field1", - Type: reflect.TypeOf(float64(1.0)), - Offset: 0, - Index: []int{0}, - Tag: reflect.StructTag(`fyne:"field"`), - }}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := parseFields(tt.args); !reflect.DeepEqual(got, tt.want) { - t.Errorf("parseFields() = %v, want %v", got, tt.want) - } - }) - } +type testFoo struct { + Field1 string `fyne:"field"` + Field2 int + Field3 bool `fyne:"field"` } -func Test_createWidgets(t *testing.T) { - type Foo struct { - Field1 string `fyne:"field"` - Field2 int `fyne:"field"` - Field3 bool - } - - type Bar struct { - Field1 float64 `fyne:"field"` - Field2 time.Time - } - - tests := []struct { - name string - args []reflect.StructField - want []*widget.FormItem - }{ - { - name: "creates widgets for fields with fyne tag", - args: []reflect.StructField{ - { - Name: "Field1", - Type: reflect.TypeOf(""), - Tag: reflect.StructTag(`fyne:"field"`), - }, - { - Name: "Field2", - Type: reflect.TypeOf(0), - Tag: reflect.StructTag(`fyne:"field"`), - }, - }, - want: []*widget.FormItem{ - { - Text: "Field1", - Widget: widget.NewEntry(), - }, - { - Text: "Field2", - Widget: NewNumericalEntry(), - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := createWidgets(tt.args) - if len(got) != len(tt.want) { - t.Errorf("got length %v, wanted %v", len(got), len(tt.want)) - } - for i := 0; i < len(got) && i < len(tt.want); i += 1 { - if reflect.TypeOf(got[i].Widget) != reflect.TypeOf(tt.want[i].Widget) { - t.Errorf("createWidgets()[%v] = %v, want %v", i, reflect.TypeOf(got[i].Widget), reflect.TypeOf(tt.want[i].Widget)) - } - } - }) - } +type testBar struct { + Field1 float64 `fyne:"field"` + Field2 time.Time } type testStruct struct { @@ -141,54 +34,139 @@ func IntBind(i int) binding.DataItem { return binding.BindInt(&i).(binding.DataItem) } -func Test_buildValue(t *testing.T) { +func FloatBind(i float64) binding.DataItem { + return binding.BindFloat(&i).(binding.DataItem) +} + +func TestNewStructForm(t *testing.T) { type args struct { - t reflect.Type + s interface{} + submit func(s interface{}) + cancel func() + } + type res struct { fields []reflect.StructField + widgets []*widget.FormItem bindings []binding.DataItem } - tests := []struct { - name string - args args - want reflect.Value - wantErr bool + name string + args args + want res }{ { - name: "Test Case 1 - Success", - args: args{ - t: reflect.TypeOf(testStruct{}), - fields: []reflect.StructField{{Name: "Name"}, {Name: "Age"}, {Name: "Address"}}, - bindings: []binding.DataItem{StringBind("John Doe"), IntBind(32), StringBind("1234 Main St.")}, + "testFoo", + args{ + testFoo{}, + func(s interface{}) { + val, ok := s.(testFoo) + if !ok { + t.Errorf("testFoo struct not created %v from %v", val, s) + } + }, + func() {}, + }, + res{ + fields: []reflect.StructField{ + { + Name: "Field1", + Type: reflect.TypeOf(""), + Offset: 0, + Index: []int{0}, + Tag: reflect.StructTag(`fyne:"field"`), + }, + { + Name: "Field3", + Type: reflect.TypeOf(true), + Offset: 24, + Index: []int{2}, + Tag: reflect.StructTag(`fyne:"field"`), + }, + }, + widgets: []*widget.FormItem{ + { + Text: "Field1", + Widget: widget.NewEntry(), + }, + { + Text: "Field2", + Widget: widget.NewEntry(), + }, + }, + bindings: []binding.DataItem{ + StringBind("John Doe"), + StringBind("1234 Main St."), + }, }, - want: reflect.ValueOf(&testStruct{Name: "John Doe", Age: 32, Address: "1234 Main St."}), }, { - name: "Test Case 2 - Invalid Binding", - args: args{ - t: reflect.TypeOf(testStruct{}), - fields: []reflect.StructField{{Name: "Name"}, {Name: "Age"}, {Name: "Address"}}, - bindings: []binding.DataItem{StringBind("John Doe"), StringBind("Invalid Age Value"), StringBind("1234 Main St.")}, + "testBar", + args{ + testBar{}, + func(s interface{}) { + val, ok := s.(testBar) + if !ok { + t.Errorf("testFoo struct not created %v from %v", val, s) + } + }, + func() {}, + }, + res{ + fields: []reflect.StructField{ + { + Name: "Field1", + Type: reflect.TypeOf(float64(1.0)), + Offset: 0, + Index: []int{0}, + Tag: reflect.StructTag(`fyne:"field"`), + }, + }, + widgets: []*widget.FormItem{ + { + Text: "Field1", + Widget: NewNumericalEntry(), + }, + }, + bindings: []binding.DataItem{ + FloatBind(1.0), + }, }, - wantErr: true, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := reflect.ValueOf(nil) - err := false - defer func() { - if r := recover(); r != nil { - err = true - } - }() - got = buildValue(tt.args.t, tt.args.fields, tt.args.bindings) - if err != tt.wantErr { - t.Errorf("buildValue() error = %v, wantErr %v", err, tt.wantErr) - return + got := NewStructForm(tt.args.s, tt.args.submit, tt.args.cancel) + if len(got.fields) != len(got.widgets) { + t.Errorf("NewStructForm() len(fields) = %v != len(widgets) = %v", len(got.fields), len(got.widgets)) } - if !reflect.DeepEqual(got.Interface(), tt.want.Interface()) { - t.Errorf("buildValue() = %v, want %v", got, tt.want) + if len(got.fields) != len(got.bindings) { + t.Errorf("NewStructForm() len(fields) = %v != len(bindings) = %v", len(got.fields), len(got.bindings)) + } + + if len(got.fields) != len(tt.want.fields) { + t.Errorf("NewStructForm() len(fields) = %v, want %v", len(got.fields), len(tt.want.fields)) + } + if !reflect.DeepEqual(got.fields, tt.want.fields) { + t.Errorf("NewStructForm().fields got %v want %v", got.fields, tt.want.fields) + } + + if len(got.widgets) != len(tt.want.widgets) { + t.Errorf("NewStructForm().widgets got length %v, wanted %v", len(got.widgets), len(tt.want.widgets)) + } + for i := 0; i < len(got.widgets) && i < len(tt.want.widgets); i += 1 { + if reflect.TypeOf(got.widgets[i].Widget) != reflect.TypeOf(tt.want.widgets[i].Widget) { + t.Errorf( + "NewStructForm().widgets[%v] = %v, want %v", i, + reflect.TypeOf(got.widgets[i].Widget), reflect.TypeOf(tt.want.widgets[i].Widget), + ) + } + if reflect.TypeOf(got.bindings[i]) != reflect.TypeOf(tt.want.bindings[i]) { + t.Errorf( + "NewStructForm().bindings[%v] = %v, want %v", i, + reflect.TypeOf(got.bindings[i]), reflect.TypeOf(tt.want.bindings[i]), + ) + } } }) } From 7416f7bad36f0e498a2d24cb6856689e5e10ca24 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Wed, 19 Apr 2023 14:14:23 +0300 Subject: [PATCH 3/5] Better error handling --- widget/struct_form.go | 44 +++++++++++++++++++++++++++----------- widget/struct_form_test.go | 26 +++++++++------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/widget/struct_form.go b/widget/struct_form.go index b88b4fb8..d345ac42 100644 --- a/widget/struct_form.go +++ b/widget/struct_form.go @@ -1,6 +1,8 @@ package widget import ( + "errors" + "fmt" "reflect" "regexp" "strings" @@ -19,16 +21,16 @@ type StructForm struct { fields []reflect.StructField widgets []*widget.FormItem bindings []binding.DataItem - submit func(s interface{}) + submit func(s interface{}, err error) } -func NewStructForm(s interface{}, submit func(s interface{}), cancel func()) *StructForm { +func NewStructForm(s interface{}, submit func(s interface{}, err error), cancel func()) *StructForm { sf := &StructForm{} sf.ExtendBaseWidget(sf) sf.structType = reflect.TypeOf(s) sf.fields = parseFields(sf.structType) - sf.widgets = createWidgets(sf.fields) + sf.widgets, sf.bindings = createWidgets(sf.fields) sf.submit = submit sf.form = &widget.Form{ @@ -40,7 +42,13 @@ func NewStructForm(s interface{}, submit func(s interface{}), cancel func()) *St return sf } -func buildValue(t reflect.Type, fields []reflect.StructField, bindings []binding.DataItem) reflect.Value { +func buildValue( + t reflect.Type, + fields []reflect.StructField, + bindings []binding.DataItem) ( + *reflect.Value, + error, +) { val := reflect.New(t) for i, f := range fields { @@ -57,20 +65,30 @@ func buildValue(t reflect.Type, fields []reflect.StructField, bindings []binding } if err != nil { - panic(err) + return nil, err } - val.FieldByName(f.Name).Set( + + t_s, t_v := val.Elem().FieldByName(f.Name).Type(), reflect.TypeOf(v) + if t_s != t_v { + return nil, errors.New(fmt.Sprintf("cannot bind mismatched types %v and %v", t_s, t_v)) + } + val.Elem().FieldByName(f.Name).Set( reflect.ValueOf(v), ) } - return val + return &val, nil } func (sf *StructForm) Submit() { - sf.submit(buildValue( + s, err := buildValue( sf.structType, sf.fields, sf.bindings, - ).Interface()) + ) + if err != nil { + sf.submit(nil, err) + } else { + sf.submit(s.Interface(), err) + } } func parseFields(t reflect.Type) []reflect.StructField { @@ -84,9 +102,9 @@ func parseFields(t reflect.Type) []reflect.StructField { return fields } -func createWidgets(fs []reflect.StructField) []*widget.FormItem { +func createWidgets(fs []reflect.StructField) ([]*widget.FormItem, []binding.DataItem) { widgets := make([]*widget.FormItem, len(fs)) - binding := make([]*binding.DataItem, len(fs)) + binding := make([]binding.DataItem, len(fs)) matchFirstCap := regexp.MustCompile("(.)([A-Z][a-z]+)") for i, f := range fs { w, b := fieldToWidget(f) @@ -94,9 +112,9 @@ func createWidgets(fs []reflect.StructField) []*widget.FormItem { Text: matchFirstCap.ReplaceAllString(f.Name, "${1} ${2}"), Widget: w, } - binding[i] = &b + binding[i] = b } - return widgets + return widgets, binding } func fieldToWidget(f reflect.StructField) (fyne.CanvasObject, binding.DataItem) { diff --git a/widget/struct_form_test.go b/widget/struct_form_test.go index 2e5e9798..779c03a3 100644 --- a/widget/struct_form_test.go +++ b/widget/struct_form_test.go @@ -41,7 +41,7 @@ func FloatBind(i float64) binding.DataItem { func TestNewStructForm(t *testing.T) { type args struct { s interface{} - submit func(s interface{}) + submit func(s interface{}, err error) cancel func() } type res struct { @@ -58,7 +58,10 @@ func TestNewStructForm(t *testing.T) { "testFoo", args{ testFoo{}, - func(s interface{}) { + func(s interface{}, err error) { + if err != nil { + t.Error(err) + } val, ok := s.(testFoo) if !ok { t.Errorf("testFoo struct not created %v from %v", val, s) @@ -93,17 +96,16 @@ func TestNewStructForm(t *testing.T) { Widget: widget.NewEntry(), }, }, - bindings: []binding.DataItem{ - StringBind("John Doe"), - StringBind("1234 Main St."), - }, }, }, { "testBar", args{ testBar{}, - func(s interface{}) { + func(s interface{}, err error) { + if err != nil { + t.Error(err) + } val, ok := s.(testBar) if !ok { t.Errorf("testFoo struct not created %v from %v", val, s) @@ -127,9 +129,6 @@ func TestNewStructForm(t *testing.T) { Widget: NewNumericalEntry(), }, }, - bindings: []binding.DataItem{ - FloatBind(1.0), - }, }, }, } @@ -161,13 +160,8 @@ func TestNewStructForm(t *testing.T) { reflect.TypeOf(got.widgets[i].Widget), reflect.TypeOf(tt.want.widgets[i].Widget), ) } - if reflect.TypeOf(got.bindings[i]) != reflect.TypeOf(tt.want.bindings[i]) { - t.Errorf( - "NewStructForm().bindings[%v] = %v, want %v", i, - reflect.TypeOf(got.bindings[i]), reflect.TypeOf(tt.want.bindings[i]), - ) - } } + got.Submit() }) } } From 902224bf141ec1c62f8e998725866fcc79fd7165 Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Wed, 19 Apr 2023 14:19:23 +0300 Subject: [PATCH 4/5] Fixed tests --- widget/struct_form.go | 8 ++++++++ widget/struct_form_test.go | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/widget/struct_form.go b/widget/struct_form.go index d345ac42..d89374c3 100644 --- a/widget/struct_form.go +++ b/widget/struct_form.go @@ -62,6 +62,8 @@ func buildValue( v, err = bindings[i].(binding.String).Get() case binding.Float: v, err = bindings[i].(binding.Float).Get() + case binding.Bool: + v, err = bindings[i].(binding.Bool).Get() } if err != nil { @@ -130,6 +132,12 @@ func fieldToWidget(f reflect.StructField) (fyne.CanvasObject, binding.DataItem) b := binding.NewString() w.Bind(b) return w, binding.StringToInt(b) + case reflect.Bool: + b := binding.NewBool() + w := widget.NewCheck("", func(v bool) { + b.Set(v) + }) + return w, b case reflect.TypeOf(time.Time{}).Kind(): b := binding.NewUntyped() w := NewCalendar(time.Now(), func(t time.Time) { diff --git a/widget/struct_form_test.go b/widget/struct_form_test.go index 779c03a3..cca27542 100644 --- a/widget/struct_form_test.go +++ b/widget/struct_form_test.go @@ -62,7 +62,7 @@ func TestNewStructForm(t *testing.T) { if err != nil { t.Error(err) } - val, ok := s.(testFoo) + val, ok := s.(*testFoo) if !ok { t.Errorf("testFoo struct not created %v from %v", val, s) } @@ -92,8 +92,8 @@ func TestNewStructForm(t *testing.T) { Widget: widget.NewEntry(), }, { - Text: "Field2", - Widget: widget.NewEntry(), + Text: "Field3", + Widget: widget.NewCheck("", func(b bool) {}), }, }, }, @@ -106,7 +106,7 @@ func TestNewStructForm(t *testing.T) { if err != nil { t.Error(err) } - val, ok := s.(testBar) + val, ok := s.(*testBar) if !ok { t.Errorf("testFoo struct not created %v from %v", val, s) } From 95acfc7ad73c1482d4d8b95e6d44e7a44747de0d Mon Sep 17 00:00:00 2001 From: Aidan Macdonald Date: Fri, 21 Apr 2023 10:02:46 +0300 Subject: [PATCH 5/5] Fixed Calendar binding --- widget/struct_form.go | 7 ++++++- widget/struct_form_test.go | 14 +++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/widget/struct_form.go b/widget/struct_form.go index d89374c3..73076d0b 100644 --- a/widget/struct_form.go +++ b/widget/struct_form.go @@ -64,6 +64,8 @@ func buildValue( v, err = bindings[i].(binding.Float).Get() case binding.Bool: v, err = bindings[i].(binding.Bool).Get() + default: + v, err = bindings[i].(binding.Untyped).Get() } if err != nil { @@ -140,8 +142,11 @@ func fieldToWidget(f reflect.StructField) (fyne.CanvasObject, binding.DataItem) return w, b case reflect.TypeOf(time.Time{}).Kind(): b := binding.NewUntyped() + if err := b.Set(time.Now()); err != nil { + panic(err) + } w := NewCalendar(time.Now(), func(t time.Time) { - b.Set(&t) + b.Set(t) }) return w, b default: diff --git a/widget/struct_form_test.go b/widget/struct_form_test.go index cca27542..ae4255c8 100644 --- a/widget/struct_form_test.go +++ b/widget/struct_form_test.go @@ -12,7 +12,8 @@ import ( type testFoo struct { Field1 string `fyne:"field"` Field2 int - Field3 bool `fyne:"field"` + Field3 bool `fyne:"field"` + Field4 time.Time `fyne:"field"` } type testBar struct { @@ -85,6 +86,13 @@ func TestNewStructForm(t *testing.T) { Index: []int{2}, Tag: reflect.StructTag(`fyne:"field"`), }, + { + Name: "Field4", + Type: reflect.TypeOf(time.Time{}), + Offset: 32, + Index: []int{3}, + Tag: reflect.StructTag(`fyne:"field"`), + }, }, widgets: []*widget.FormItem{ { @@ -95,6 +103,10 @@ func TestNewStructForm(t *testing.T) { Text: "Field3", Widget: widget.NewCheck("", func(b bool) {}), }, + { + Text: "Field4", + Widget: NewCalendar(time.Now(), func(t time.Time) {}), + }, }, }, },