Skip to content

Commit 952034f

Browse files
committed
feat: add UITextView validation support
1 parent d4df5bd commit 952034f

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// Validator
3+
// Copyright © 2025 Space Code. All rights reserved.
4+
//
5+
6+
#if os(iOS)
7+
import UIKit
8+
9+
extension UITextView: IUIValidatable {
10+
/// The value of the text view to validate.
11+
/// Returns an empty string if `text` is nil.
12+
public var inputValue: String { text ?? "" }
13+
14+
/// The type of input for validation.
15+
public typealias Input = String
16+
17+
/// Enables or disables automatic validation when the text changes.
18+
///
19+
/// - Parameter isEnabled: If true, adds an observer for text changes.
20+
/// If false, removes the observer.
21+
public func validateOnInputChange(isEnabled: Bool) {
22+
if isEnabled {
23+
NotificationCenter.default.addObserver(
24+
self,
25+
selector: #selector(textViewDidChangeNotification(_:)),
26+
name: UITextView.textDidChangeNotification,
27+
object: self
28+
)
29+
} else {
30+
NotificationCenter.default.removeObserver(
31+
self,
32+
name: UITextView.textDidChangeNotification,
33+
object: self
34+
)
35+
}
36+
}
37+
38+
// MARK: Private
39+
40+
/// Called automatically when the text view changes via NotificationCenter.
41+
@objc
42+
private func textViewDidChangeNotification(_ notification: Notification) {
43+
guard let textView = notification.object as? UITextView, textView === self else { return }
44+
45+
validate(rules: validationRules)
46+
}
47+
}
48+
#endif
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//
2+
// Validator
3+
// Copyright © 2025 Space Code. All rights reserved.
4+
//
5+
6+
import ValidatorCore
7+
import ValidatorUI
8+
import XCTest
9+
10+
#if canImport(UIKit)
11+
import UIKit
12+
#endif
13+
14+
#if os(iOS)
15+
final class UITextViewTests: XCTestCase {
16+
// MARK: Tests
17+
18+
@MainActor
19+
func test_thatTextViewValidationReturnsValid_whenInputValueIsValid() {
20+
// given
21+
let textView = UITextView()
22+
23+
textView.validateOnInputChange(isEnabled: true)
24+
textView.add(rule: LengthValidationRule(max: .max, error: String.error))
25+
26+
// when
27+
textView.text = String(String.text.prefix(.max))
28+
29+
var result: ValidationResult?
30+
31+
textView.validationHandler = { result = $0 }
32+
textView.validate(rules: textView.validationRules)
33+
34+
// when
35+
if case .valid = result {}
36+
else { XCTFail("The result must be equal to the valid value") }
37+
}
38+
39+
@MainActor
40+
func test_thatTextViewValidationReturnsInvalid_whenInputValueIsInvalid() {
41+
// given
42+
let textView = UITextView()
43+
44+
textView.validateOnInputChange(isEnabled: true)
45+
textView.add(rule: LengthValidationRule(max: .max, error: String.error))
46+
47+
// when
48+
textView.text = .text
49+
50+
var result: ValidationResult?
51+
52+
textView.validationHandler = { result = $0 }
53+
textView.validate(rules: textView.validationRules)
54+
55+
// when
56+
if case let .invalid(errors) = result {
57+
XCTAssertEqual(errors.count, 1)
58+
XCTAssertEqual(errors.first?.message, .error)
59+
} else { XCTFail("The result must be equal to the invalid value") }
60+
}
61+
}
62+
#endif
63+
64+
private extension String {
65+
static let text: String = "lorem ipsum lorem ipsum lorem ipsum"
66+
static let error: String = "error"
67+
}
68+
69+
private extension Int {
70+
static let min = 0
71+
static let max = 10
72+
}

0 commit comments

Comments
 (0)