Skip to content

Commit d232c0e

Browse files
committed
Test option.resolver
1 parent ba3cc81 commit d232c0e

File tree

4 files changed

+237
-8
lines changed

4 files changed

+237
-8
lines changed

Sources/FormHook/Form.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ public class FormControl<FieldName> where FieldName: Hashable {
6969
await unregister(names: name, options: options)
7070
}
7171

72-
public func handleSubmit(onValid: @escaping (FormValue<FieldName>, FormError<FieldName>) async throws -> Void, onInvalid: ((FormValue<FieldName>, FormError<FieldName>) async throws -> Void)? = nil) async throws {
72+
public func handleSubmit(
73+
@_implicitSelfCapture onValid: @escaping (FormValue<FieldName>, FormError<FieldName>) async throws -> Void,
74+
@_implicitSelfCapture onInvalid: ((FormValue<FieldName>, FormError<FieldName>) async throws -> Void)? = nil
75+
) async throws {
7376
let preservedSubmissionState = instantFormState.submissionState
7477
instantFormState.submissionState = .submitting
7578
let errors: FormError<FieldName>
@@ -84,9 +87,10 @@ public class FormControl<FieldName> where FieldName: Hashable {
8487
Array(fields.keys)
8588
)
8689
switch result {
87-
case .success:
90+
case .success(let formValues):
8891
isOveralValid = true
8992
errors = .init()
93+
instantFormState.formValues.update(other: formValues)
9094
case .failure(let e):
9195
isOveralValid = false
9296
errors = e
@@ -123,9 +127,10 @@ public class FormControl<FieldName> where FieldName: Hashable {
123127
let names = fields.keys.filter(instantFormState.errors.errorFields.contains)
124128
let result = await resolver(instantFormState.formValues, options.context, names)
125129
switch result {
126-
case .success:
130+
case .success(let formValues):
127131
isOveralValid = true
128132
errors = .init()
133+
instantFormState.formValues.update(other: formValues)
129134
case .failure(let e):
130135
isOveralValid = false
131136
errors = e
@@ -292,9 +297,10 @@ public class FormControl<FieldName> where FieldName: Hashable {
292297
if let resolver = options.resolver {
293298
let result = await resolver(instantFormState.formValues, options.context, validationNames)
294299
switch result {
295-
case .success:
300+
case .success(let formValues):
296301
isValid = true
297302
errors = instantFormState.errors
303+
instantFormState.formValues.update(other: formValues)
298304
case .failure(let e):
299305
isValid = false
300306
errors = instantFormState.errors.rewrite(from: e)
@@ -345,8 +351,9 @@ extension FormControl {
345351
let isValid: Bool
346352
let result = await resolver(formState.formValues, options.context, Array(formState.defaultValues.keys))
347353
switch result {
348-
case .success:
354+
case .success(let formValues):
349355
isValid = true
356+
instantFormState.formValues.update(other: formValues)
350357
case .failure(let e):
351358
isValid = false
352359
instantFormState.errors = e

Sources/FormHook/Hook/UseForm.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ public func useForm<FieldName>(_ options: FormOption<FieldName>) -> FormControl<
3838
public struct FormOption<FieldName> where FieldName: Hashable {
3939
var mode: Mode
4040
var reValidateMode: ReValidateMode
41-
let resolver: Resolver<FieldName>?
42-
let context: Any?
41+
var resolver: Resolver<FieldName>?
42+
var context: Any?
4343
var shouldUnregister: Bool
4444
var delayError: Bool
4545

4646
init(mode: Mode,
4747
reValidateMode: ReValidateMode,
48-
resolver: Resolver<FieldName>?,
48+
@_implicitSelfCapture resolver: Resolver<FieldName>?,
4949
context: Any?,
5050
shouldUnregister: Bool,
5151
delayError: Bool

Sources/FormHook/Types.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ public typealias FieldRegistration<Value> = Binding<Value>
8080

8181
public typealias FormValue<FieldName> = [FieldName: Any] where FieldName: Hashable
8282

83+
extension FormValue {
84+
mutating func update(other: Self) {
85+
for (key, value) in other {
86+
updateValue(value, forKey: key)
87+
}
88+
}
89+
}
90+
8391
public struct FormError<FieldName>: Equatable where FieldName: Hashable {
8492
public private(set) var errorFields: Set<FieldName>
8593
public private(set) var messages: [FieldName: [String]]

Tests/FormHookTests/FormHookTests.swift

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ final class FormHookTests: QuickSpec {
1919
setValueSpecs()
2020
handleSubmitSpecs()
2121
triggerSpecs()
22+
resolverSpecs()
2223
}
2324

2425
func registerSpecs() {
@@ -292,6 +293,67 @@ final class FormHookTests: QuickSpec {
292293
}
293294

294295
func resetSingleFieldSpecs() {
296+
describe("Form Control registered field \"a\" and \"b\" with non-nil resolver") {
297+
var formControl: FormControl<TestFieldName>!
298+
var aValidator: MockValidator<String, Bool>!
299+
var bValidator: MockValidator<String, Bool>!
300+
let aDefaultValue = "%^$#"
301+
let bDefaultValue = "%^$#*("
302+
303+
let resolverProxy = ResolverProxy<TestFieldName>(value: [
304+
.a: aDefaultValue,
305+
.b: bDefaultValue
306+
])
307+
308+
beforeEach {
309+
var formState: FormState<TestFieldName> = .init()
310+
let options = FormOption<TestFieldName>(
311+
mode: .onSubmit,
312+
reValidateMode: .onChange,
313+
resolver: resolverProxy.resolver(values:context:fieldNames:),
314+
context: nil,
315+
shouldUnregister: true,
316+
delayError: true
317+
)
318+
formControl = .init(options: options, formState: .init(
319+
get: { formState },
320+
set: { formState = $0 }
321+
))
322+
323+
aValidator = MockValidator<String, Bool>(result: true)
324+
_ = formControl.register(name: .a, options: .init(rules: aValidator!, defaultValue: aDefaultValue))
325+
326+
bValidator = MockValidator<String, Bool>(result: true)
327+
_ = formControl.register(name: .b, options: .init(rules: bValidator!, defaultValue: bDefaultValue))
328+
}
329+
330+
context("formState is invalid") {
331+
beforeEach {
332+
formControl.instantFormState.isValid = false
333+
formControl.instantFormState.errors.setMessages(
334+
name: .a,
335+
messages: [
336+
"Failed to validate a"
337+
],
338+
isValid: false
339+
)
340+
await formControl.syncFormState()
341+
}
342+
343+
context("reset field \"b\" with default option") {
344+
beforeEach {
345+
await formControl.reset(name: .b)
346+
}
347+
348+
it("field \"a\" is still invalid") {
349+
let fieldState = await formControl.getFieldState(name: .a)
350+
expect(fieldState.isInvalid) == true
351+
expect(fieldState.error) == ["Failed to validate a"]
352+
}
353+
}
354+
}
355+
}
356+
295357
describe("Form Control registered field \"a\"") {
296358
var formControl: FormControl<TestFieldName>!
297359
var aValidator: MockValidator<String, Bool>!
@@ -1524,4 +1586,156 @@ final class FormHookTests: QuickSpec {
15241586
}
15251587
}
15261588
}
1589+
1590+
func resolverSpecs() {
1591+
describe("Form Control registered field \"a\" and \"b\" with non-nil resolver") {
1592+
var formControl: FormControl<TestFieldName>!
1593+
var aValidator: MockValidator<String, Bool>!
1594+
var bValidator: MockValidator<String, Bool>!
1595+
let aDefaultValue = "%^$#"
1596+
let bDefaultValue = "%^$#*("
1597+
1598+
let resolverProxy = ResolverProxy<TestFieldName>(value: [
1599+
.a: aDefaultValue,
1600+
.b: bDefaultValue
1601+
])
1602+
1603+
beforeEach {
1604+
var formState: FormState<TestFieldName> = .init()
1605+
let options = FormOption<TestFieldName>(
1606+
mode: .onSubmit,
1607+
reValidateMode: .onChange,
1608+
resolver: resolverProxy.resolver(values:context:fieldNames:),
1609+
context: nil,
1610+
shouldUnregister: true,
1611+
delayError: true
1612+
)
1613+
formControl = .init(options: options, formState: .init(
1614+
get: { formState },
1615+
set: { formState = $0 }
1616+
))
1617+
1618+
aValidator = MockValidator<String, Bool>(result: true)
1619+
_ = formControl.register(name: .a, options: .init(rules: aValidator!, defaultValue: aDefaultValue))
1620+
1621+
bValidator = MockValidator<String, Bool>(result: true)
1622+
_ = formControl.register(name: .b, options: .init(rules: bValidator!, defaultValue: bDefaultValue))
1623+
}
1624+
1625+
context("resolver indicates all fields are valid") {
1626+
context("validation of \"a\" returns failure") {
1627+
beforeEach {
1628+
aValidator.result = false
1629+
aValidator.messages = ["Failed to validate a"]
1630+
}
1631+
1632+
context("reset field \"a\" with default options") {
1633+
beforeEach {
1634+
await formControl.reset(name: .a)
1635+
}
1636+
1637+
it("all fields are valid") {
1638+
let formState = await formControl.formState
1639+
expect(formState.isValid) == true
1640+
expect(formState.errors[.a]).to(beNil())
1641+
expect(formState.errors[.b]).to(beNil())
1642+
}
1643+
}
1644+
1645+
context("submit the form") {
1646+
beforeEach {
1647+
try? await formControl.handleSubmit(onValid: { _, _ in })
1648+
}
1649+
1650+
it("all fields are valid") {
1651+
let formState = await formControl.formState
1652+
expect(formState.isValid) == true
1653+
expect(formState.errors[.a]).to(beNil())
1654+
expect(formState.errors[.b]).to(beNil())
1655+
}
1656+
}
1657+
1658+
context("trigger field \"a\"") {
1659+
beforeEach {
1660+
await formControl.trigger(name: .a)
1661+
}
1662+
1663+
it("all fields are valid") {
1664+
let formState = await formControl.formState
1665+
expect(formState.isValid) == true
1666+
expect(formState.errors[.a]).to(beNil())
1667+
expect(formState.errors[.b]).to(beNil())
1668+
}
1669+
}
1670+
}
1671+
}
1672+
1673+
context("resolver indicates field \"a\" is invalid") {
1674+
beforeEach {
1675+
let errors: FormError<TestFieldName> = .init(
1676+
errorFields: [.a],
1677+
messages: [
1678+
.a: ["Failed to validate a"]
1679+
]
1680+
)
1681+
resolverProxy.result = .failure(errors)
1682+
}
1683+
1684+
context("submit the form") {
1685+
beforeEach {
1686+
try? await formControl.handleSubmit(onValid: { _, _ in })
1687+
}
1688+
1689+
it("field \"a\" is invalid and \"b\" isn't") {
1690+
let formState = await formControl.formState
1691+
expect(formState.isValid) == false
1692+
expect(formState.errors[.a]) == ["Failed to validate a"]
1693+
expect(formState.errors[.b]).to(beNil())
1694+
}
1695+
}
1696+
1697+
context("trigger field \"a\"") {
1698+
beforeEach {
1699+
await formControl.trigger(name: .a)
1700+
}
1701+
1702+
it("field \"a\" is invalid and \"b\" isn't") {
1703+
let formState = await formControl.formState
1704+
expect(formState.isValid) == false
1705+
expect(formState.errors[.a]) == ["Failed to validate a"]
1706+
expect(formState.errors[.b]).to(beNil())
1707+
}
1708+
}
1709+
1710+
context("reset field \"a\" with default options") {
1711+
beforeEach {
1712+
await formControl.reset(name: .a)
1713+
}
1714+
1715+
it("field \"a\" is invalid and \"b\" isn't") {
1716+
let formState = await formControl.formState
1717+
expect(formState.isValid) == false
1718+
expect(formState.errors[.a]) == ["Failed to validate a"]
1719+
expect(formState.errors[.b]).to(beNil())
1720+
}
1721+
}
1722+
}
1723+
}
1724+
}
1725+
}
1726+
1727+
private class ResolverProxy<FieldName> where FieldName: Hashable {
1728+
var result: Result<ResolverValue<FieldName>, ResolverError<FieldName>>
1729+
1730+
init(value: ResolverValue<FieldName>) {
1731+
self.result = .success(value)
1732+
}
1733+
1734+
init(error: ResolverError<FieldName>) {
1735+
self.result = .failure(error)
1736+
}
1737+
1738+
func resolver(values: ResolverValue<FieldName>, context: Any?, fieldNames: [FieldName]) async -> Result<ResolverValue<FieldName>, ResolverError<FieldName>> {
1739+
result
1740+
}
15271741
}

0 commit comments

Comments
 (0)