Skip to content

Commit f438257

Browse files
committed
Keep field values when register a field continuously, append more unit tests for value change
1 parent 663f748 commit f438257

File tree

3 files changed

+118
-6
lines changed

3 files changed

+118
-6
lines changed

Example/FormHookExample/ContentView.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ struct ContentView: HookView {
164164
var dobView: some View {
165165
Controller(
166166
name: FormFieldName.dob,
167-
defaultValue: Date()
167+
defaultValue: Calendar.current.startOfDay(for: Date())
168168
) { field, fieldState, _ in
169169
let picker = DatePicker(
170170
selection: field.value,
@@ -231,7 +231,8 @@ struct ContentView: HookView {
231231
Controller(
232232
name: FormFieldName.email,
233233
defaultValue: "",
234-
rules: emailPatternValidator
234+
rules: NotEmptyValidator(FormFieldName.email.messages(for:))
235+
.and(validator: emailPatternValidator)
235236
) { field, fieldState, _ in
236237
let textField = TextField(field.name.rawValue, text: field.value)
237238
.focused($focusField, equals: field.name)

Sources/FormHook/Form.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ public class FormControl<FieldName> where FieldName: Hashable {
2626
}
2727

2828
public func register<Value>(name: FieldName, options: RegisterOption<Value>) -> FieldRegistration<Value> {
29-
self.instantFormState.formValues[name] = options.defaultValue
3029
self.instantFormState.defaultValues[name] = options.defaultValue
3130
let field: Field<Value>
3231
if let f = fields[name] as? Field<Value> {
3332
field = f
33+
if !areEqual(first: options.defaultValue, second: field.options.defaultValue) && !instantFormState.dirtyFields.contains(name) {
34+
instantFormState.formValues[name] = options.defaultValue
35+
}
3436
field.options = options
3537
} else {
3638
field = Field(name: name, options: options, control: self)
3739
fields[name] = field
40+
instantFormState.formValues[name] = options.defaultValue
3841
}
3942
return field.value
4043
}
@@ -164,6 +167,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
164167
instantFormState.isValidating = false
165168
await syncFormState()
166169
} else {
170+
await syncFormState()
167171
isOveralValid = instantFormState.isValid
168172
errors = instantFormState.errors
169173
}
@@ -438,6 +442,9 @@ private extension FormControl {
438442
let name: FieldName
439443
var options: RegisterOption<Value> {
440444
didSet {
445+
if areEqual(first: oldValue.defaultValue, second: options.defaultValue) {
446+
return
447+
}
441448
value = control.computeValueBinding(name: name, defaultValue: options.defaultValue)
442449
}
443450
}
@@ -468,9 +475,14 @@ private extension FormControl {
468475
.init { [weak self] in
469476
self?.instantFormState.formValues[name] as? Value ?? defaultValue
470477
} set: { [weak self] value in
471-
self?.instantFormState.formValues[name] = value
472-
self?.instantFormState.dirtyFields.insert(name)
473-
guard let self = self, self.shouldReValidateOnChange(name: name) else {
478+
guard let self = self else {
479+
return
480+
}
481+
self.instantFormState.formValues[name] = value
482+
if !self.instantFormState.dirtyFields.contains(name) && !areEqual(first: self.instantFormState.defaultValues[name], second: value) {
483+
self.instantFormState.dirtyFields.insert(name)
484+
}
485+
guard self.shouldReValidateOnChange(name: name) else {
474486
return
475487
}
476488
Task {

Tests/FormHookTests/FormHookTests.swift

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ final class FormHookTests: QuickSpec {
2020
handleSubmitSpecs()
2121
triggerSpecs()
2222
resolverSpecs()
23+
changeFieldValueSpecs()
2324
}
2425

2526
func registerSpecs() {
@@ -59,6 +60,32 @@ final class FormHookTests: QuickSpec {
5960
await formControl.syncFormState()
6061
}
6162

63+
context("registers field \"a\" with the same options") {
64+
beforeEach {
65+
aBinder = formControl.register(name: .a, options: .init(rules: aValidator!, defaultValue: testDefaultValue))
66+
await formControl.syncFormState()
67+
}
68+
69+
it("field \"a\" changes its default value, and doesn't change its value") {
70+
let formState = await formControl.formState
71+
expect(areEqual(first: formState.defaultValues[.a], second: testDefaultValue)) == true
72+
expect(areEqual(first: formState.formValues[.a], second: "a")) == true
73+
}
74+
}
75+
76+
context("registers field \"a\" with other options") {
77+
beforeEach {
78+
aBinder = formControl.register(name: .a, options: .init(rules: aValidator!, defaultValue: testDefaultValue2))
79+
await formControl.syncFormState()
80+
}
81+
82+
it("field \"a\" changes its default value, and doesn't change its value") {
83+
let formState = await formControl.formState
84+
expect(areEqual(first: formState.defaultValues[.a], second: testDefaultValue2)) == true
85+
expect(areEqual(first: formState.formValues[.a], second: "a")) == true
86+
}
87+
}
88+
6289
it("key \"a\" and formState are dirty") {
6390
let fieldState = await formControl.getFieldState(name: .a)
6491
expect(fieldState.isDirty) == true
@@ -1812,6 +1839,78 @@ final class FormHookTests: QuickSpec {
18121839
}
18131840
}
18141841
}
1842+
1843+
func changeFieldValueSpecs() {
1844+
describe("Form Control with shouldUnregister equals false registers a field \"a\" with a default value") {
1845+
var formControl: FormControl<TestFieldName>!
1846+
var aValidator: MockValidator<String, Bool>!
1847+
let testDefaultValue = "%^$#"
1848+
var aBinder: FieldRegistration<String>!
1849+
1850+
beforeEach {
1851+
var formState: FormState<TestFieldName> = .init()
1852+
let options = FormOption<TestFieldName>(
1853+
mode: .onSubmit,
1854+
reValidateMode: .onChange,
1855+
resolver: nil,
1856+
context: nil,
1857+
shouldUnregister: false,
1858+
delayErrorInNanoseconds: 0
1859+
)
1860+
formControl = .init(options: options, formState: .init(
1861+
get: { formState },
1862+
set: { formState = $0 }
1863+
))
1864+
1865+
aValidator = MockValidator<String, Bool>(result: true)
1866+
aBinder = formControl.register(name: .a, options: .init(rules: aValidator!, defaultValue: testDefaultValue))
1867+
}
1868+
1869+
context("changes value for \"a\" to the original default value") {
1870+
beforeEach {
1871+
aBinder.wrappedValue = testDefaultValue
1872+
}
1873+
1874+
it("key \"a\" is still not dirty") {
1875+
let fieldState = formControl.getFieldState(name: .a)
1876+
expect(fieldState.isDirty) == false
1877+
}
1878+
1879+
context("changes value for \"a\" to another value") {
1880+
beforeEach {
1881+
aBinder.wrappedValue = "a"
1882+
}
1883+
1884+
it("key \"a\" is dirty") {
1885+
let fieldState = formControl.getFieldState(name: .a)
1886+
expect(fieldState.isDirty) == true
1887+
}
1888+
}
1889+
}
1890+
1891+
context("changes value for \"a\" to another value") {
1892+
beforeEach {
1893+
aBinder.wrappedValue = "a"
1894+
}
1895+
1896+
it("key \"a\" is dirty") {
1897+
let fieldState = formControl.getFieldState(name: .a)
1898+
expect(fieldState.isDirty) == true
1899+
}
1900+
1901+
context("changes value for \"a\" to the original default value") {
1902+
beforeEach {
1903+
aBinder.wrappedValue = testDefaultValue
1904+
}
1905+
1906+
it("key \"a\" is still dirty") {
1907+
let fieldState = formControl.getFieldState(name: .a)
1908+
expect(fieldState.isDirty) == true
1909+
}
1910+
}
1911+
}
1912+
}
1913+
}
18151914
}
18161915

18171916
private class ResolverProxy<FieldName> where FieldName: Hashable {

0 commit comments

Comments
 (0)