Compare commits
1 Commits
5362614c3e
...
v0.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10b4838f8d |
23
context.go
23
context.go
@@ -32,6 +32,7 @@ type Context struct {
|
||||
mu sync.RWMutex
|
||||
ctxDisposedChan chan struct{}
|
||||
reqCtx context.Context
|
||||
fields []*Field
|
||||
subscriptions []Subscription
|
||||
subsMu sync.Mutex
|
||||
disposeOnce sync.Once
|
||||
@@ -482,16 +483,28 @@ func (c *Context) unsubscribeAll() {
|
||||
|
||||
// Field creates a signal with validation rules attached.
|
||||
// The initial value seeds both the signal and the reset target.
|
||||
// The field is tracked on the context so ValidateAll/ResetFields
|
||||
// can operate on all fields by default.
|
||||
func (c *Context) Field(initial any, rules ...Rule) *Field {
|
||||
return &Field{
|
||||
f := &Field{
|
||||
signal: c.Signal(initial),
|
||||
rules: rules,
|
||||
initialVal: initial,
|
||||
}
|
||||
target := c
|
||||
if c.isComponent() {
|
||||
target = c.parentPageCtx
|
||||
}
|
||||
target.fields = append(target.fields, f)
|
||||
return f
|
||||
}
|
||||
|
||||
// ValidateAll runs Validate on every field, returning true only if all pass.
|
||||
// ValidateAll runs Validate on each field, returning true only if all pass.
|
||||
// With no arguments it validates every field tracked on this context.
|
||||
func (c *Context) ValidateAll(fields ...*Field) bool {
|
||||
if len(fields) == 0 {
|
||||
fields = c.fields
|
||||
}
|
||||
ok := true
|
||||
for _, f := range fields {
|
||||
if !f.Validate() {
|
||||
@@ -501,8 +514,12 @@ func (c *Context) ValidateAll(fields ...*Field) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetFields resets every field to its initial value and clears errors.
|
||||
// ResetFields resets each field to its initial value and clears errors.
|
||||
// With no arguments it resets every field tracked on this context.
|
||||
func (c *Context) ResetFields(fields ...*Field) {
|
||||
if len(fields) == 0 {
|
||||
fields = c.fields
|
||||
}
|
||||
for _, f := range fields {
|
||||
f.Reset()
|
||||
}
|
||||
|
||||
@@ -96,27 +96,22 @@ func TestFieldReset(t *testing.T) {
|
||||
|
||||
func TestValidateAll(t *testing.T) {
|
||||
v := New()
|
||||
var username, email *Field
|
||||
v.Page("/", func(c *Context) {
|
||||
username = c.Field("", Required(), MinLen(3))
|
||||
email = c.Field("", Required(), Email())
|
||||
c.Field("", Required(), MinLen(3))
|
||||
c.Field("", Required(), Email())
|
||||
c.View(func() h.H { return h.Div() })
|
||||
|
||||
// both empty → both fail
|
||||
assert.False(t, c.ValidateAll())
|
||||
})
|
||||
|
||||
// both empty → both fail
|
||||
assert.False(t, false) // sanity
|
||||
ok := username.Validate() && email.Validate()
|
||||
assert.False(t, ok)
|
||||
|
||||
// simulate ValidateAll via context
|
||||
v2 := New()
|
||||
var u2, e2 *Field
|
||||
v2.Page("/", func(c *Context) {
|
||||
u2 = c.Field("joe", Required(), MinLen(3))
|
||||
e2 = c.Field("joe@x.com", Required(), Email())
|
||||
c.Field("joe", Required(), MinLen(3))
|
||||
c.Field("joe@x.com", Required(), Email())
|
||||
c.View(func() h.H { return h.Div() })
|
||||
|
||||
assert.True(t, c.ValidateAll(u2, e2))
|
||||
assert.True(t, c.ValidateAll())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,14 +122,30 @@ func TestValidateAllPartialFailure(t *testing.T) {
|
||||
bad := c.Field("", Required())
|
||||
c.View(func() h.H { return h.Div() })
|
||||
|
||||
// ValidateAll must run ALL fields even if first passes
|
||||
ok := c.ValidateAll(good, bad)
|
||||
ok := c.ValidateAll()
|
||||
assert.False(t, ok)
|
||||
assert.False(t, good.HasError())
|
||||
assert.True(t, bad.HasError())
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateAllSelectiveArgs(t *testing.T) {
|
||||
v := New()
|
||||
v.Page("/", func(c *Context) {
|
||||
a := c.Field("", Required())
|
||||
b := c.Field("ok", Required())
|
||||
cField := c.Field("", Required())
|
||||
c.View(func() h.H { return h.Div() })
|
||||
|
||||
// only validate a and b — cField should be untouched
|
||||
ok := c.ValidateAll(a, b)
|
||||
assert.False(t, ok)
|
||||
assert.True(t, a.HasError())
|
||||
assert.False(t, b.HasError())
|
||||
assert.False(t, cField.HasError(), "unselected field should not be validated")
|
||||
})
|
||||
}
|
||||
|
||||
func TestResetFields(t *testing.T) {
|
||||
v := New()
|
||||
v.Page("/", func(c *Context) {
|
||||
@@ -146,13 +157,30 @@ func TestResetFields(t *testing.T) {
|
||||
b.SetValue("changed-b")
|
||||
a.AddError("err")
|
||||
|
||||
c.ResetFields(a, b)
|
||||
c.ResetFields()
|
||||
assert.Equal(t, "a", a.String())
|
||||
assert.Equal(t, "b", b.String())
|
||||
assert.False(t, a.HasError())
|
||||
})
|
||||
}
|
||||
|
||||
func TestResetFieldsSelectiveArgs(t *testing.T) {
|
||||
v := New()
|
||||
v.Page("/", func(c *Context) {
|
||||
a := c.Field("a")
|
||||
b := c.Field("b")
|
||||
c.View(func() h.H { return h.Div() })
|
||||
|
||||
a.SetValue("changed-a")
|
||||
b.SetValue("changed-b")
|
||||
|
||||
// only reset a
|
||||
c.ResetFields(a)
|
||||
assert.Equal(t, "a", a.String())
|
||||
assert.Equal(t, "changed-b", b.String(), "unselected field should not be reset")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFieldValidateClearsPreviousErrors(t *testing.T) {
|
||||
f := newTestField("", Required())
|
||||
f.Validate()
|
||||
|
||||
@@ -26,13 +26,13 @@ func main() {
|
||||
email := c.Field("", via.Required(), via.Email())
|
||||
age := c.Field("", via.Required(), via.Min(13), via.Max(120))
|
||||
// Optional field — only validated when non-empty
|
||||
website := c.Field("", via.Custom(func(val string) error { return nil }), via.Pattern(`^$|^https?://\S+$`, "Must be a valid URL"))
|
||||
website := c.Field("", via.Pattern(`^$|^https?://\S+$`, "Must be a valid URL"))
|
||||
|
||||
var success string
|
||||
|
||||
signup := c.Action(func() {
|
||||
success = ""
|
||||
if !c.ValidateAll(username, email, age, website) {
|
||||
if !c.ValidateAll() {
|
||||
c.Sync()
|
||||
return
|
||||
}
|
||||
@@ -43,13 +43,13 @@ func main() {
|
||||
return
|
||||
}
|
||||
success = "Account created for " + username.String() + "!"
|
||||
c.ResetFields(username, email, age, website)
|
||||
c.ResetFields()
|
||||
c.Sync()
|
||||
})
|
||||
|
||||
reset := c.Action(func() {
|
||||
success = ""
|
||||
c.ResetFields(username, email, age, website)
|
||||
c.ResetFields()
|
||||
c.Sync()
|
||||
})
|
||||
|
||||
|
||||
24
rule.go
24
rule.go
@@ -1,10 +1,12 @@
|
||||
package via
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Rule defines a single validation check for a Field.
|
||||
@@ -20,7 +22,7 @@ func Required(msg ...string) Rule {
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if strings.TrimSpace(val) == "" {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
@@ -33,8 +35,8 @@ func MinLen(n int, msg ...string) Rule {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if len(val) < n {
|
||||
return fmt.Errorf("%s", m)
|
||||
if utf8.RuneCountInString(val) < n {
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
@@ -47,8 +49,8 @@ func MaxLen(n int, msg ...string) Rule {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if len(val) > n {
|
||||
return fmt.Errorf("%s", m)
|
||||
if utf8.RuneCountInString(val) > n {
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
@@ -63,10 +65,10 @@ func Min(n int, msg ...string) Rule {
|
||||
return Rule{func(val string) error {
|
||||
v, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New("Must be a valid number")
|
||||
}
|
||||
if v < n {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
@@ -81,10 +83,10 @@ func Max(n int, msg ...string) Rule {
|
||||
return Rule{func(val string) error {
|
||||
v, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New("Must be a valid number")
|
||||
}
|
||||
if v > n {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
@@ -99,7 +101,7 @@ func Pattern(re string, msg ...string) Rule {
|
||||
compiled := regexp.MustCompile(re)
|
||||
return Rule{func(val string) error {
|
||||
if !compiled.MatchString(val) {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
@@ -115,7 +117,7 @@ func Email(msg ...string) Rule {
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if !emailRegexp.MatchString(val) {
|
||||
return fmt.Errorf("%s", m)
|
||||
return errors.New(m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user