diff --git a/Sources/CombineValidate/Publishers/Publishers+Validators.swift b/Sources/CombineValidate/Publishers/Publishers+Validators.swift index 06c5a29..2b364eb 100644 --- a/Sources/CombineValidate/Publishers/Publishers+Validators.swift +++ b/Sources/CombineValidate/Publishers/Publishers+Validators.swift @@ -64,7 +64,10 @@ public func MultiRegexValidator( } return validationResults } - .map { $0.allSatisfy { $0.isSuccess } ? .success(.none) : $0.first! } + .map { $0.allSatisfy { $0.isSuccess } + ? .success(.none) + : $0.first { $0 != .untouched && $0 != .success(.none) }! + } .eraseToAnyPublisher() } diff --git a/Sources/CombineValidate/Regex.swift b/Sources/CombineValidate/Regex.swift index e207928..2b3c7e8 100644 --- a/Sources/CombineValidate/Regex.swift +++ b/Sources/CombineValidate/Regex.swift @@ -8,6 +8,10 @@ public protocol RegexProtocol where Self: RawRepresentable, Self.RawValue == Reg var pattern: RegexPattern { get } } +public extension RegexProtocol { + var pattern: RegexPattern { self.rawValue } +} + /// Extensions with predefined popular regular expressions /// /// Conforming to ``RegexPattern`` & ``RegexProtocol`` @@ -23,9 +27,17 @@ public enum RegularPattern: RegexPattern, RegexProtocol { /// - 1 number case strongPassword = #"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$"# + case notEmpty = #"[\S\s]+[\S]+"# + + case mustIncludeCapitalLetters = #"(?=.*[A-Z])"# + + case mustIncludeSpecialSymbols = #"(?=.*[\%\!-\/\:-\@\[-\`\{-\~])"# + + case mustIncludeNumbers = #"(?=.*[0-9])"# + + case mustIncludeSmallLetters = #"(?=.*[a-z])"# + case wordAndDigitsOnly = #"^(\d|\w|\S){1,}$"# case anyInOneLine = #"^.+$"# - - public var pattern: RegexPattern { self.rawValue } } diff --git a/Sources/CombineValidate/Validated.swift b/Sources/CombineValidate/Validated.swift index b1459a3..4843112 100644 --- a/Sources/CombineValidate/Validated.swift +++ b/Sources/CombineValidate/Validated.swift @@ -22,7 +22,9 @@ public enum Validated : Equatable { switch (lhs, rhs) { case (.untouched, .untouched): return true case (.success, .success): return true - case (.failure, .failure): return true + case (.failure(let reasonLhs, let tableNameLhs), .failure(let reasonRhs, let tableNameRhs)): + guard reasonLhs == reasonRhs && tableNameLhs == tableNameRhs else { return false } + return true default: return false } } diff --git a/Sources/CombineValidateExtended/CombineValidateExtended.swift b/Sources/CombineValidateExtended/CombineValidateExtended.swift index 60a5f97..6ac0905 100644 --- a/Sources/CombineValidateExtended/CombineValidateExtended.swift +++ b/Sources/CombineValidateExtended/CombineValidateExtended.swift @@ -40,8 +40,6 @@ public enum CreditCardPattern: RegexPattern, RegexProtocol { /// Allows inserting expiry date as MM/YYYY or MM-YYYY format case cardExpireDate = #"^(0[1-9]|1[0-2])(\/|-)([0-9]{4})$"# - - public var pattern: RegexPattern { self.rawValue } } @@ -65,6 +63,4 @@ public enum URLPatterns: RegexPattern, RegexProtocol { /// Youtube link looks like /// `https://www.youtube.com/watch?v=dQw4w9WgXcQ` case youtubeURL = #"/(?:https?://)?(?:(?:(?:www\.?)?youtube\.com(?:/(?:(?:watch\?.*?(v=[^&\s]+).*)|(?:v(/.*))|(channel/.+)|(?:user/(.+))|(?:results\?(search_query=.+))))?)|(?:youtu\.be(/.*)?))/g"# - - public var pattern: RegexPattern { self.rawValue } } diff --git a/Tests/CombineValidateTests/MultiRegexValidatorTests.swift b/Tests/CombineValidateTests/MultiRegexValidatorTests.swift new file mode 100644 index 0000000..9fddba1 --- /dev/null +++ b/Tests/CombineValidateTests/MultiRegexValidatorTests.swift @@ -0,0 +1,64 @@ +import XCTest +import Combine +@testable import CombineValidate + +class MultiRegexValidatorTests: XCTestCase { + class ViewModel: ObservableObject { + + @Published var email = "" + @Published var validationResult: Validated = .untouched + + public lazy var emailValidator: ValidationPublisher = { + $email.validateWithMultiRegex( + regexs: [RegularPattern.mustIncludeNumbers, RegularPattern.mustIncludeSpecialSymbols, RegularPattern.mustIncludeCapitalLetters], + errors: ["Should be one number at least", "Should be one special symbol at least", "Should be one capital letter at least"], + tableName: nil + ) + }() + + private var subscription = Set() + + init() { + emailValidator + .assign(to: \.validationResult, on: self) + .store(in: &subscription) + } + } + + let viewModel = ViewModel() + + func testIgnoreFirstValue() { + XCTAssertEqual(viewModel.validationResult, .untouched) + } + + func testFullyInvalidValue() { + viewModel.email = "" + + XCTAssertEqual( + viewModel.validationResult, + .failure(reason: "Should be one number at least", tableName: nil) + ) + } + + func testValueWithOneNumber() { + viewModel.email = "1" + + XCTAssertEqual(viewModel.validationResult, .failure(reason: "Should be one special symbol at least", tableName: nil)) + } + + func testValueWithOneNumberAndSpecialSymbol() { + viewModel.email = "1%" + + XCTAssertEqual( + viewModel.validationResult, + .failure(reason: "Should be one capital letter at least", tableName: nil) + ) + } + + func testValueWithOneNumberAndSpecialSymbolAndCapitalLetter() { + viewModel.email = "1%A" + + XCTAssertEqual(viewModel.validationResult, .success(.none)) + } + +} diff --git a/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift b/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift index e81b238..25e7f8c 100644 --- a/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift +++ b/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift @@ -7,10 +7,6 @@ final class OneOfRegexValidatorTests: XCTestCase { case facebook = #"(?:https?:\/\/)?(?:www\.)?facebook\.com\/(?:(?:\w)*#!\/)?(?:pages\/)?(?:[\w\-]*\/)*?(\/)?([\w\-\.]{5,})"# case linkedIn = #"^(http(s)?:\/\/)?([\w]+\.)?linkedin\.com\/(pub|in|profile)"# case instagram = #"(?:(?:http|https):\/\/)?(?:www.)?(?:instagram.com|instagr.am|instagr.com)\/(\w+)"# - - var pattern: RegexPattern { - self.rawValue - } } class ViewModel: ObservableObject { diff --git a/Tests/CombineValidateTests/RegexValidatorTests.swift b/Tests/CombineValidateTests/RegexValidatorTests.swift new file mode 100644 index 0000000..1b3630b --- /dev/null +++ b/Tests/CombineValidateTests/RegexValidatorTests.swift @@ -0,0 +1,41 @@ +import XCTest +import Combine +@testable import CombineValidate + +class RegexValidatorTests: XCTestCase { + class ViewModel: ObservableObject { + + @Published var email = "" + @Published var validationResult: Validated = .untouched + + public lazy var emailValidator: ValidationPublisher = { + $email.validateWithRegex(regex: RegularPattern.email, error: "Should be email", tableName: nil) + }() + + private var subscription = Set() + + init() { + emailValidator + .assign(to: \.validationResult, on: self) + .store(in: &subscription) + } + } + + let viewModel = ViewModel() + + func testIgnoreFirstValue() { + XCTAssertEqual(viewModel.validationResult, .untouched) + } + + func testValidEmailValue() { + viewModel.email = "someemail@gmail.com" + + XCTAssertEqual(viewModel.validationResult, .success(.none)) + } + + func testInvalidEmailValue() { + viewModel.email = "someemailgmail.com" + + XCTAssertEqual(viewModel.validationResult, .failure(reason: "Should be email", tableName: nil)) + } +}