Skip to content

Commit 7020b8e

Browse files
committed
Test trigger method
1 parent c5e200e commit 7020b8e

File tree

4 files changed

+166
-19
lines changed

4 files changed

+166
-19
lines changed

Sources/FormHook/Form.swift

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ public class FormControl<FieldName> where FieldName: Hashable {
4141
if self.options.shouldUnregister {
4242
names.forEach { fields[$0] = nil }
4343
}
44+
names.forEach {
45+
if let field = fields[$0] as? Field<FieldName>, field.options.shouldUnregister {
46+
fields[$0] = nil
47+
}
48+
}
4449
if !options.contains(.keepValue) {
4550
names.forEach { instantFormState.formValues[$0] = nil }
4651
}
@@ -277,35 +282,55 @@ public class FormControl<FieldName> where FieldName: Hashable {
277282
await formState.getFieldState(name: name)
278283
}
279284

285+
@discardableResult
280286
public func trigger(names: [FieldName]) async -> Bool {
287+
let validationNames = names.isEmpty ? fields.map { $0.key } : names
281288
instantFormState.isValidating = true
282289
await syncFormState()
283-
let (isValid, errors) = await withTaskGroup(of: KeyValidationResult.self) { group in
284-
var errors = instantFormState.errors
285-
for name in names {
286-
guard let field = fields[name] else {
287-
continue
288-
}
289-
group.addTask {
290-
let (isValid, messages) = await field.computeMessages()
291-
return KeyValidationResult(key: name, isValid: isValid, messages: messages)
292-
}
290+
let isValid: Bool
291+
let errors: FormError<FieldName>
292+
if let resolver = options.resolver {
293+
let result = await resolver(instantFormState.formValues, options.context, validationNames)
294+
switch result {
295+
case .success:
296+
isValid = true
297+
errors = instantFormState.errors
298+
case .failure(let e):
299+
isValid = false
300+
errors = instantFormState.errors.rewrite(from: e)
293301
}
294-
var isValid = true
295-
for await keyResult in group {
296-
errors.setMessages(name: keyResult.key, messages: keyResult.messages, isValid: keyResult.isValid)
297-
if !keyResult.isValid {
298-
isValid = false
302+
} else {
303+
(isValid, errors) = await withTaskGroup(of: KeyValidationResult.self) { group in
304+
var errors = instantFormState.errors
305+
for name in validationNames {
306+
guard let field = fields[name] else {
307+
continue
308+
}
309+
group.addTask {
310+
let (isValid, messages) = await field.computeMessages()
311+
return KeyValidationResult(key: name, isValid: isValid, messages: messages)
312+
}
299313
}
314+
var isValid = true
315+
for await keyResult in group {
316+
errors.setMessages(name: keyResult.key, messages: keyResult.messages, isValid: keyResult.isValid)
317+
if !keyResult.isValid {
318+
isValid = false
319+
}
320+
}
321+
return (isValid, errors)
300322
}
301-
return (isValid, errors)
323+
}
324+
if !isValid {
325+
instantFormState.isValid = false
302326
}
303327
instantFormState.isValidating = false
304328
instantFormState.errors = errors
305329
await syncFormState()
306330
return isValid
307331
}
308332

333+
@discardableResult
309334
public func trigger(name: FieldName...) async -> Bool {
310335
await trigger(names: name)
311336
}
@@ -314,7 +339,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
314339
extension FormControl {
315340
func updateValid() async {
316341
guard instantFormState.isValid else {
317-
return await syncFormState()
342+
return
318343
}
319344
if let resolver = options.resolver {
320345
let isValid: Bool

Sources/FormHook/Hook/UseController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public func useController<FieldName, Value>(
1616
unregisterOption: UnregisterOption = []
1717
) -> ControllerRenderOption<FieldName, Value> where FieldName: Hashable {
1818
let form = useContext(Context<FormControl<FieldName>>.self)
19-
let registration = form.register(name: name, options: RegisterOption(rules: rules, defaultValue: defaultValue))
19+
let registration = form.register(name: name, options: RegisterOption(rules: rules, defaultValue: defaultValue, shouldUnregister: shouldUnregister))
2020

2121
useEffect {{
2222
guard shouldUnregister else { return }

Sources/FormHook/Types.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import SwiftUI
1111
public struct RegisterOption<Value> {
1212
let rules: any Validator<Value>
1313
let defaultValue: Value
14+
let shouldUnregister: Bool
1415

15-
public init(rules: any Validator<Value>, defaultValue: Value) {
16+
public init(rules: any Validator<Value>, defaultValue: Value, shouldUnregister: Bool = true) {
1617
self.rules = rules
1718
self.defaultValue = defaultValue
19+
self.shouldUnregister = shouldUnregister
1820
}
1921
}
2022

@@ -111,6 +113,15 @@ public struct FormError<FieldName>: Equatable where FieldName: Hashable {
111113
mutating func removeValidityOnly(name: FieldName) {
112114
self.errorFields.remove(name)
113115
}
116+
117+
func rewrite(from other: Self) -> Self {
118+
let errorFields = errorFields.union(other.errorFields)
119+
var newMessages = messages
120+
for (key, newValue) in other.messages {
121+
newMessages[key] = newValue
122+
}
123+
return Self(errorFields: errorFields, messages: newMessages)
124+
}
114125
}
115126

116127
extension FormError: Error {}

Tests/FormHookTests/FormHookTests.swift

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class FormHookTests: QuickSpec {
1717
clearErrorsSpecs()
1818
setValueSpecs()
1919
handleSubmitSpecs()
20+
triggerSpecs()
2021
}
2122

2223
func unregisterSpecs() {
@@ -1237,4 +1238,114 @@ final class FormHookTests: QuickSpec {
12371238
}
12381239
}
12391240
}
1241+
1242+
func triggerSpecs() {
1243+
describe("Form Control registered field \"a\" and \"b\"") {
1244+
var formControl: FormControl<TestFieldName>!
1245+
var aValidator: MockValidator<String, Bool>!
1246+
var bValidator: MockValidator<String, Bool>!
1247+
let aDefaultValue = "%^$#"
1248+
let bDefaultValue = "%^$#*("
1249+
1250+
beforeEach {
1251+
var formState: FormState<TestFieldName> = .init()
1252+
let options = FormOption<TestFieldName>(
1253+
mode: .onSubmit,
1254+
reValidateMode: .onChange,
1255+
resolver: nil,
1256+
context: nil,
1257+
shouldUnregister: true,
1258+
delayError: true
1259+
)
1260+
formControl = .init(options: options, formState: .init(
1261+
get: { formState },
1262+
set: { formState = $0 }
1263+
))
1264+
1265+
aValidator = MockValidator<String, Bool>(result: true)
1266+
_ = formControl.register(name: .a, options: .init(rules: aValidator!, defaultValue: aDefaultValue))
1267+
1268+
bValidator = MockValidator<String, Bool>(result: true)
1269+
_ = formControl.register(name: .b, options: .init(rules: bValidator!, defaultValue: bDefaultValue))
1270+
}
1271+
1272+
context("trigger field \"a\" and validation of \"a\" returns false") {
1273+
beforeEach {
1274+
aValidator.result = false
1275+
aValidator.messages = ["Failed to validate a"]
1276+
}
1277+
1278+
it("result of validation \"a\" is false, and errors of \"a\" equals [\"Failed to validate a\"]") {
1279+
let result = await formControl.trigger(name: .a)
1280+
expect(result) == false
1281+
1282+
let fieldState = await formControl.getFieldState(name: .a)
1283+
expect(fieldState.isInvalid) == true
1284+
expect(fieldState.error) == ["Failed to validate a"]
1285+
}
1286+
1287+
it("key \"b\" is still valid") {
1288+
await formControl.trigger(name: .a)
1289+
1290+
let fieldState = await formControl.getFieldState(name: .b)
1291+
expect(fieldState.isInvalid) == false
1292+
expect(fieldState.error).to(beEmpty())
1293+
}
1294+
}
1295+
1296+
context("trigger by default and validation of \"a\" returns false") {
1297+
beforeEach {
1298+
aValidator.result = false
1299+
aValidator.messages = ["Failed to validate a"]
1300+
}
1301+
1302+
it("result of validation \"a\" is false, and errors of \"a\" equals [\"Failed to validate a\"]") {
1303+
let result = await formControl.trigger()
1304+
expect(result) == false
1305+
1306+
let fieldState = await formControl.getFieldState(name: .a)
1307+
expect(fieldState.isInvalid) == true
1308+
expect(fieldState.error) == ["Failed to validate a"]
1309+
}
1310+
1311+
it("key \"b\" is still valid") {
1312+
await formControl.trigger()
1313+
1314+
let fieldState = await formControl.getFieldState(name: .b)
1315+
expect(fieldState.isInvalid) == false
1316+
expect(fieldState.error).to(beEmpty())
1317+
}
1318+
}
1319+
1320+
context("unregister \"b\"") {
1321+
beforeEach {
1322+
await formControl.unregister(name: .b)
1323+
}
1324+
1325+
context("trigger both key \"a\" and \"b\", and validation of \"a\" returns false") {
1326+
beforeEach {
1327+
aValidator.result = false
1328+
aValidator.messages = ["Failed to validate a"]
1329+
}
1330+
1331+
it("result of validation \"a\" is false, and errors of \"a\" equals [\"Failed to validate a\"]") {
1332+
let result = await formControl.trigger(name: .a, .b)
1333+
expect(result) == false
1334+
1335+
let fieldState = await formControl.getFieldState(name: .a)
1336+
expect(fieldState.isInvalid) == true
1337+
expect(fieldState.error) == ["Failed to validate a"]
1338+
}
1339+
1340+
it("key \"b\" is undefined") {
1341+
await formControl.trigger(name: .a, .b)
1342+
1343+
let formState = await formControl.formState
1344+
expect(formState.defaultValues[.b]).to(beNil())
1345+
expect(formState.formValues[.b]).to(beNil())
1346+
}
1347+
}
1348+
}
1349+
}
1350+
}
12401351
}

0 commit comments

Comments
 (0)