diff --git a/Sources/ValidatorUI/Classes/AppKit/Extensions/NSTextField+Validation.swift b/Sources/ValidatorUI/Classes/AppKit/Extensions/NSTextField+Validation.swift new file mode 100644 index 0000000..aacecf7 --- /dev/null +++ b/Sources/ValidatorUI/Classes/AppKit/Extensions/NSTextField+Validation.swift @@ -0,0 +1,50 @@ +// +// Validator +// Copyright © 2023 Space Code. All rights reserved. +// + +#if os(macOS) + import AppKit + + extension NSTextField: IUIValidatable { + /// The value of the text field to validate. + /// Returns an empty string if `text` is nil. + public var inputValue: String { stringValue } + + /// The type of input for validation. + public typealias Input = String + + /// Enables or disables automatic validation when the text changes. + /// + /// - Parameter isEnabled: If true, validation is triggered on every text change. + /// If false, the target-action is removed. + public func validateOnInputChange(isEnabled: Bool) { + if isEnabled { + NotificationCenter.default.addObserver( + self, + selector: #selector(textFieldDidChange(_:)), + name: NSControl.textDidChangeNotification, + object: self + ) + } else { + NotificationCenter.default.removeObserver( + self, + name: NSControl.textDidChangeNotification, + object: self + ) + } + } + + // MARK: Private + + /// Called automatically when the text field changes (if `validateOnInputChange` is enabled). + /// Performs validation using all rules stored in `validationRules`. + /// + /// - Parameter notification: The notification containing the text field. + @objc + private func textFieldDidChange(_ notification: Notification) { + guard notification.object as? NSTextField === self else { return } + validate(rules: validationRules) + } + } +#endif diff --git a/Tests/ValidatorUITests/UnitTests/AppKit/NSTextFieldTests.swift b/Tests/ValidatorUITests/UnitTests/AppKit/NSTextFieldTests.swift new file mode 100644 index 0000000..fa46faf --- /dev/null +++ b/Tests/ValidatorUITests/UnitTests/AppKit/NSTextFieldTests.swift @@ -0,0 +1,73 @@ +// +// Validator +// Copyright © 2023 Space Code. All rights reserved. +// + +import ValidatorCore +import ValidatorUI +import XCTest + +#if canImport(AppKit) + import AppKit +#endif + +#if os(macOS) + final class NSTextFieldTests: XCTestCase { + // MARK: Tests + + @MainActor + func test_thatTextFieldValidationReturnsValid_whenInputValueIsValid() { + // given + let textField = NSTextField() + + textField.validateOnInputChange(isEnabled: true) + textField.add(rule: LengthValidationRule(max: .max, error: String.error)) + + // when + textField.stringValue = String(String.text.prefix(.max)) + + var result: ValidationResult? + + textField.validationHandler = { result = $0 } + textField.validate(rules: textField.validationRules) + + // when + if case .valid = result {} + else { XCTFail("The result must be equal to the valid value") } + } + + @MainActor + func test_thatTextFieldValidationReturnsInvalid_whenInputValueIsInvalid() { + // given + let textField = NSTextField() + + textField.validateOnInputChange(isEnabled: true) + textField.add(rule: LengthValidationRule(max: .max, error: String.error)) + + // when + textField.stringValue = .text + + var result: ValidationResult? + + textField.validationHandler = { result = $0 } + textField.validate(rules: textField.validationRules) + + // when + if case let .invalid(errors) = result { + XCTAssertEqual(errors.count, 1) + XCTAssertEqual(errors.first?.message, .error) + } else { XCTFail("The result must be equal to the invalid value") } + } + } + + private extension String { + static let text: String = "lorem ipsum lorem ipsum lorem ipsum" + static let error: String = "error" + } + + private extension Int { + static let min = 0 + static let max = 10 + } + +#endif diff --git a/Tests/ValidatorUITests/UnitTests/UITextFieldTests.swift b/Tests/ValidatorUITests/UnitTests/UIKit/UITextFieldTests.swift similarity index 100% rename from Tests/ValidatorUITests/UnitTests/UITextFieldTests.swift rename to Tests/ValidatorUITests/UnitTests/UIKit/UITextFieldTests.swift diff --git a/Tests/ValidatorUITests/UnitTests/UITextViewTests.swift b/Tests/ValidatorUITests/UnitTests/UIKit/UITextViewTests.swift similarity index 97% rename from Tests/ValidatorUITests/UnitTests/UITextViewTests.swift rename to Tests/ValidatorUITests/UnitTests/UIKit/UITextViewTests.swift index b4b90d2..76212d8 100644 --- a/Tests/ValidatorUITests/UnitTests/UITextViewTests.swift +++ b/Tests/ValidatorUITests/UnitTests/UIKit/UITextViewTests.swift @@ -1,6 +1,6 @@ // // Validator -// Copyright © 2025 Space Code. All rights reserved. +// Copyright © 2023 Space Code. All rights reserved. // import ValidatorCore