From afa6fad16cedb4470157470b9aa5ccc1c810a9a0 Mon Sep 17 00:00:00 2001 From: Alexo Date: Sat, 13 Nov 2021 23:37:18 +0300 Subject: [PATCH] Develop (#1) * Add more regexps * Fix MultiRegex right error return * Fix validated failure case comparison * Clean up extended regexps enums * Add remained tests * Update README.md * Make regex protocol api more concise * Complete test cover for regex cases --- .../Publishers/Publishers+Validators.swift | 11 +- Sources/CombineValidate/Regex.swift | 39 ++- .../CombineValidateExtended.swift | 3 +- .../CreditCardPatternsTests.swift | 92 ++++--- .../RegexPatternsTests.swift | 8 +- .../MultiRegexValidatorTests.swift | 27 +- .../NotEmpyValidatorTests.swift | 10 +- .../OneOfRegexValidatorTests.swift | 19 +- Tests/CombineValidateTests/RegexTests.swift | 242 ++++++++++++++++-- .../RegexValidatorTests.swift | 9 +- .../ToggleValidatorTests.swift | 2 +- 11 files changed, 357 insertions(+), 105 deletions(-) diff --git a/Sources/CombineValidate/Publishers/Publishers+Validators.swift b/Sources/CombineValidate/Publishers/Publishers+Validators.swift index 2b364eb..da5f5f2 100644 --- a/Sources/CombineValidate/Publishers/Publishers+Validators.swift +++ b/Sources/CombineValidate/Publishers/Publishers+Validators.swift @@ -1,3 +1,4 @@ +import Foundation import Combine public func NotEmptyValidator( @@ -7,6 +8,7 @@ public func NotEmptyValidator( ) -> ValidationPublisher { publisher .dropFirst() + .debounce(for: .seconds(0.5), scheduler: RunLoop.main) .map(\.isEmpty) .map { !$0 ? .success(.none) : .failure(reason: message, tableName: tableName) } .eraseToAnyPublisher() @@ -20,7 +22,8 @@ public func RegexValidator( ) -> ValidationPublisher where Pattern: RegexProtocol { publisher .dropFirst() - .map { $0.range(of: pattern.pattern, options: .regularExpression) != nil } + .debounce(for: .seconds(0.5), scheduler: RunLoop.main) + .map { pattern.test($0) } .map { $0 ? .success(.none) : .failure(reason: message, tableName: tableName) } .eraseToAnyPublisher() } @@ -33,9 +36,10 @@ public func OneOfRegexValidator( ) -> RichValidationPublisher where Pattern: RegexProtocol { publisher .dropFirst() + .debounce(for: .seconds(0.5), scheduler: RunLoop.main) .map { value in for pattern in patterns { - if value.range(of: pattern.pattern, options: .regularExpression) != nil { + if pattern.test(value) { return .success(pattern) } } @@ -52,11 +56,12 @@ public func MultiRegexValidator( ) -> ValidationPublisher where Pattern: RegexProtocol { publisher .dropFirst() + .debounce(for: .seconds(0.5), scheduler: RunLoop.main) .map { (value: String) -> [Validated] in var validationResults: [Validated] = .init(repeating: .untouched, count: patterns.count) for (index, pattern) in patterns.enumerated() { - if value.range(of: pattern.pattern, options: .regularExpression) != nil { + if pattern.test(value) { validationResults[index] = .success(.none) } else { validationResults[index] = .failure(reason: messages[index], tableName: tableName) diff --git a/Sources/CombineValidate/Regex.swift b/Sources/CombineValidate/Regex.swift index 2b3c7e8..8f6e466 100644 --- a/Sources/CombineValidate/Regex.swift +++ b/Sources/CombineValidate/Regex.swift @@ -4,14 +4,33 @@ import Combine public typealias RegexPattern = String /// Describes enum instances when RawValues is ``RegexPattern`` -public protocol RegexProtocol where Self: RawRepresentable, Self.RawValue == RegexPattern { +public protocol RegexProtocol +where + Self: RawRepresentable, + Self.RawValue == RegexPattern +{ + var pattern: RegexPattern { get } + + /// Test provided string by regex within (it has default implementation) + /// - Parameters: + /// - string: StringProtocol + /// - Returns: + /// Boolean value with True if testing finished successfully + func test(_ string: T) -> Bool } +/// Default implementations for protocol API public extension RegexProtocol { + + /// Returns the RawValue var pattern: RegexPattern { self.rawValue } + + + func test(_ string: T) -> Bool { + string.range(of: self.pattern, options: .regularExpression) != nil + } } - /// Extensions with predefined popular regular expressions /// /// Conforming to ``RegexPattern`` & ``RegexProtocol`` @@ -25,19 +44,27 @@ public enum RegularPattern: RegexPattern, RegexProtocol { /// - 1 special symbol /// - 1 Uppercase /// - 1 number - case strongPassword = #"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$"# + case strongPassword = #"^(?=.*?[0-9])(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[^\w\s]).{8,}$"# - case notEmpty = #"[\S\s]+[\S]+"# + case notEmpty = #"([[:graph:]])+"# + /// String must include either 1 or more capital letter. + /// Cannot be empty. case mustIncludeCapitalLetters = #"(?=.*[A-Z])"# + /// String must include either 1 or more spectial symbol. % ! \ : @ [ { ` ~ + /// Cannot be empty. case mustIncludeSpecialSymbols = #"(?=.*[\%\!-\/\:-\@\[-\`\{-\~])"# + /// String must include either 1 or more digit symbol. + /// Cannot be empty. case mustIncludeNumbers = #"(?=.*[0-9])"# + /// String must include either 1 or more lowercased letter. + /// Cannot be empty. case mustIncludeSmallLetters = #"(?=.*[a-z])"# + /// String must be consist from words or digits without whitespaces and new lines + /// Cannot be empty. case wordAndDigitsOnly = #"^(\d|\w|\S){1,}$"# - - case anyInOneLine = #"^.+$"# } diff --git a/Sources/CombineValidateExtended/CombineValidateExtended.swift b/Sources/CombineValidateExtended/CombineValidateExtended.swift index 6ac0905..84a0dab 100644 --- a/Sources/CombineValidateExtended/CombineValidateExtended.swift +++ b/Sources/CombineValidateExtended/CombineValidateExtended.swift @@ -42,8 +42,9 @@ public enum CreditCardPattern: RegexPattern, RegexProtocol { case cardExpireDate = #"^(0[1-9]|1[0-2])(\/|-)([0-9]{4})$"# } - +/// Extended number validation patterns public enum NumberPattern: RegexPattern, RegexProtocol { + /// any numer case anyNumber = #"^(-)?[0-9]{1,18}$"# case positiveNumber = #"^\d+$"# diff --git a/Tests/CombineValidateExtendedTests/CreditCardPatternsTests.swift b/Tests/CombineValidateExtendedTests/CreditCardPatternsTests.swift index 8e729d9..d11a533 100644 --- a/Tests/CombineValidateExtendedTests/CreditCardPatternsTests.swift +++ b/Tests/CombineValidateExtendedTests/CreditCardPatternsTests.swift @@ -4,16 +4,14 @@ import XCTest class CreditCardPatternTests: XCTestCase { func testAmericanExpressPattern() { let americanExpressCard = cleanCardNumber("3700 0000 0000 002") - - XCTAssertNotNil( - americanExpressCard.range(of: CreditCardPattern.Amex.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.Amex.test(americanExpressCard), "AmericanExpress: \(americanExpressCard). Positive case failed" ) let unExistedNumber = cleanCardNumber("0000 0000 0000 000") - - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.Amex.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.Amex.test(unExistedNumber), "AmericanExpress: \(unExistedNumber). Negative case failed" ) } @@ -23,16 +21,16 @@ class CreditCardPatternTests: XCTestCase { for IIR in mastercardIIRRange { let cardNumber = cleanCardNumber("\(IIR)00 0000 0000 4321") - XCTAssertNotNil( - cardNumber.range(of: CreditCardPattern.Mastercard.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.Mastercard.test(cardNumber), "Mastercard: \(cardNumber). Positive case failed" ) } let unExistedNumber = cleanCardNumber("5600 0000 0000 0000") - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.Mastercard.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.Mastercard.test(unExistedNumber), "Mastercard: \(unExistedNumber). Negative case failed" ) } @@ -40,15 +38,15 @@ class CreditCardPatternTests: XCTestCase { func testVisaPattern() { let visaCardNumber = cleanCardNumber("4377 0000 0000 0000") - XCTAssertNotNil( - visaCardNumber.range(of: CreditCardPattern.Visa.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.Visa.test(visaCardNumber), "Mastercard: \(visaCardNumber). Positive case failed" ) let unExistedNumber = cleanCardNumber("0000 0000 0000 0000") - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.Visa.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.Visa.test(unExistedNumber), "Mastercard: \(unExistedNumber). Negative case failed" ) } @@ -58,38 +56,36 @@ class CreditCardPatternTests: XCTestCase { for IIR in maestroIIRRange { let cardNumber = cleanCardNumber("\(IIR) 0000 0000 4321") - XCTAssertNotNil( - cardNumber.range(of: CreditCardPattern.Maestro.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.Maestro.test(cardNumber), "Maestro: \(cardNumber). Positive case failed" ) } let unExistedNumber = cleanCardNumber("0000 0000 0000 0000") - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.Maestro.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.Maestro.test(unExistedNumber), "Mastercard: \(unExistedNumber). Negative case failed" ) } func testDiscoverPattern() { let discoverUS = cleanCardNumber("6011 0000 0000 0000") - let discoverGB = cleanCardNumber("6445 0000 0000 0000") - - XCTAssertNotNil( - discoverUS.range(of: CreditCardPattern.Discover.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.Discover.test(discoverUS), "Discover US: \(discoverUS). Positive case failed" ) - XCTAssertNotNil( - discoverGB.range(of: CreditCardPattern.Discover.pattern, options: .regularExpression), + let discoverGB = cleanCardNumber("6445 0000 0000 0000") + XCTAssertTrue( + CreditCardPattern.Discover.test(discoverGB), "Discover GB: \(discoverGB). Positive case failed" ) let unExistedNumber = cleanCardNumber("0000 0000 0000 0000") - - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.Discover.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.Discover.test(unExistedNumber), "Discover: \(unExistedNumber). Negative case failed" ) } @@ -98,51 +94,49 @@ class CreditCardPatternTests: XCTestCase { let jsbUsCard = cleanCardNumber("3569 9900 1009 5841") let jsbNativeIRRs = ["2131", "1800"] - XCTAssertNotNil( - jsbUsCard.range(of: CreditCardPattern.JSB.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.JSB.test(jsbUsCard), "JSB GB: \(jsbUsCard). Positive case failed" ) for IIR in jsbNativeIRRs { let cardNumber = cleanCardNumber("\(IIR) 0000 0000 000") - XCTAssertNotNil( - cardNumber.range(of: CreditCardPattern.JSB.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.JSB.test(cardNumber), "JSB GB: \(cardNumber). Positive case failed" ) } let unExistedNumber = cleanCardNumber("0000 0000 0000 0000") - - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.JSB.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.JSB.test(unExistedNumber), "JSB: \(unExistedNumber). Negative case failed" ) } func testUnionPayPattern() { let unionPay = cleanCardNumber("6200 0000 0000 0000") - let unionPayAlt1 = cleanCardNumber("8171 0000 0000 0000") - let unionPayAlt2 = cleanCardNumber("8171 0000 0000 0000 000") - XCTAssertNotNil( - unionPay.range(of: CreditCardPattern.UnionPay.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.UnionPay.test(unionPay), "UnionPay: \(unionPay). Positive case failed" ) - XCTAssertNotNil( - unionPayAlt1.range(of: CreditCardPattern.UnionPay.pattern, options: .regularExpression), + let unionPayAlt1 = cleanCardNumber("8171 0000 0000 0000") + XCTAssertTrue( + CreditCardPattern.UnionPay.test(unionPayAlt1), "UnionPay: \(unionPayAlt1). Positive case failed" ) - XCTAssertNotNil( - unionPayAlt2.range(of: CreditCardPattern.UnionPay.pattern, options: .regularExpression), + let unionPayAlt2 = cleanCardNumber("8171 0000 0000 0000 000") + XCTAssertTrue( + CreditCardPattern.UnionPay.test(unionPayAlt2), "UnionPay: \(unionPayAlt2). Positive case failed" ) let unExistedNumber = cleanCardNumber("0000 0000 0000 0000") - - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.UnionPay.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.UnionPay.test(unExistedNumber), "UnionPay: \(unExistedNumber). Negative case failed" ) } @@ -152,16 +146,16 @@ class CreditCardPatternTests: XCTestCase { for IIR in mirIRRs { let cardNumber = cleanCardNumber("\(IIR) 0000 0000 0000") - XCTAssertNotNil( - cardNumber.range(of: CreditCardPattern.Mir.pattern, options: .regularExpression), + XCTAssertTrue( + CreditCardPattern.Mir.test(cardNumber), "Mir: \(cardNumber). Positive case failed" ) } let unExistedNumber = cleanCardNumber("0000 0000 0000 0000") - XCTAssertNil( - unExistedNumber.range(of: CreditCardPattern.Mir.pattern, options: .regularExpression), + XCTAssertFalse( + CreditCardPattern.Mir.test(unExistedNumber), "Mir: \(unExistedNumber). Negative case failed" ) } diff --git a/Tests/CombineValidateExtendedTests/RegexPatternsTests.swift b/Tests/CombineValidateExtendedTests/RegexPatternsTests.swift index 1011059..464974d 100644 --- a/Tests/CombineValidateExtendedTests/RegexPatternsTests.swift +++ b/Tests/CombineValidateExtendedTests/RegexPatternsTests.swift @@ -2,11 +2,11 @@ import XCTest @testable import CombineValidateExtended final class RegexPatternsTests: XCTestCase { - func testUrlPattern() throws { + func testUrlPattern() { let url = "https://google.com" - let nonUrl = "Some string" + XCTAssertTrue(URLPatterns.url.test(url), "Simple URL. Positive case failed") - XCTAssertNotNil(url.range(of: URLPatterns.url.pattern, options: .regularExpression), "Simple URL. Positive case failed") - XCTAssertNil(nonUrl.range(of: URLPatterns.url.pattern, options: .regularExpression), "Simple URL. Negative case failed") + let nonUrl = "Some string" + XCTAssertFalse(URLPatterns.url.test(nonUrl), "Simple URL. Negative case failed") } } diff --git a/Tests/CombineValidateTests/MultiRegexValidatorTests.swift b/Tests/CombineValidateTests/MultiRegexValidatorTests.swift index 9fddba1..1818143 100644 --- a/Tests/CombineValidateTests/MultiRegexValidatorTests.swift +++ b/Tests/CombineValidateTests/MultiRegexValidatorTests.swift @@ -5,21 +5,20 @@ import Combine class MultiRegexValidatorTests: XCTestCase { class ViewModel: ObservableObject { - @Published var email = "" + @Published var specialText = "" @Published var validationResult: Validated = .untouched - public lazy var emailValidator: ValidationPublisher = { - $email.validateWithMultiRegex( + public lazy var specialTextValidator: ValidationPublisher = { + $specialText.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 + errors: ["Should be one number at least", "Should be one special symbol at least", "Should be one capital letter at least"] ) }() private var subscription = Set() init() { - emailValidator + specialTextValidator .assign(to: \.validationResult, on: self) .store(in: &subscription) } @@ -32,7 +31,9 @@ class MultiRegexValidatorTests: XCTestCase { } func testFullyInvalidValue() { - viewModel.email = "" + viewModel.specialText = "" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) XCTAssertEqual( viewModel.validationResult, @@ -41,13 +42,17 @@ class MultiRegexValidatorTests: XCTestCase { } func testValueWithOneNumber() { - viewModel.email = "1" + viewModel.specialText = "1" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) XCTAssertEqual(viewModel.validationResult, .failure(reason: "Should be one special symbol at least", tableName: nil)) } func testValueWithOneNumberAndSpecialSymbol() { - viewModel.email = "1%" + viewModel.specialText = "1%" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) XCTAssertEqual( viewModel.validationResult, @@ -56,7 +61,9 @@ class MultiRegexValidatorTests: XCTestCase { } func testValueWithOneNumberAndSpecialSymbolAndCapitalLetter() { - viewModel.email = "1%A" + viewModel.specialText = "1%A" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) XCTAssertEqual(viewModel.validationResult, .success(.none)) } diff --git a/Tests/CombineValidateTests/NotEmpyValidatorTests.swift b/Tests/CombineValidateTests/NotEmpyValidatorTests.swift index c37d1a3..b0423d3 100644 --- a/Tests/CombineValidateTests/NotEmpyValidatorTests.swift +++ b/Tests/CombineValidateTests/NotEmpyValidatorTests.swift @@ -1,6 +1,6 @@ +import XCTest import Combine @testable import CombineValidate -import XCTest final class NotEmptyValidatorTests: XCTestCase { class ViewModel: ObservableObject { @@ -11,7 +11,7 @@ final class NotEmptyValidatorTests: XCTestCase { public var subscription: AnyCancellable? = nil public lazy var nameNotEmptyValidator: ValidationPublisher = { - $name.validateNonEmpty(error: "Should not empty", tableName: nil) + $name.validateNonEmpty(error: "Should not empty") }() init() { @@ -28,11 +28,17 @@ final class NotEmptyValidatorTests: XCTestCase { func testInputEmptyValue() { viewModel.name = "" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .failure(reason: "Should not empty", tableName: nil)) } func testInputNonEmptyValue() { viewModel.name = "alex" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .success(.none)) } } diff --git a/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift b/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift index 25e7f8c..a7d1288 100644 --- a/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift +++ b/Tests/CombineValidateTests/OneOfRegexValidatorTests.swift @@ -1,6 +1,6 @@ +import XCTest import Combine @testable import CombineValidate -import XCTest final class OneOfRegexValidatorTests: XCTestCase { enum SocialLinkPattern: RegexPattern, RegexProtocol { @@ -17,8 +17,7 @@ final class OneOfRegexValidatorTests: XCTestCase { public lazy var socialProfileValidator: RichValidationPublisher = { $socialProfileUrl.validateOneOfRegex( regexs: [.facebook, .linkedIn, .instagram], - error: "Type one of social profile link (insta, facebook, linkedIn)", - tableName: nil + error: "Type one of social profile link (insta, facebook, linkedIn)" ) }() @@ -40,26 +39,40 @@ final class OneOfRegexValidatorTests: XCTestCase { func testExpectedInstagramInput() { viewModel.socialProfileUrl = "instagram.com/userprofile" + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .success(.instagram)) } func testExpectedFacebookInput() { viewModel.socialProfileUrl = "facebook.com/userprofile" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .success(.facebook)) } func testExpectedLinkedInInput() { viewModel.socialProfileUrl = "linkedin.com/in/userprofile" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .success(.linkedIn)) } func testUnexpectedValue() { viewModel.socialProfileUrl = "http://youtube.com/userprofile" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .failure(reason: "Type one of social profile link (insta, facebook, linkedIn)", tableName: nil)) } func testEmptyValue() { viewModel.socialProfileUrl = "" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .failure(reason: "Type one of social profile link (insta, facebook, linkedIn)", tableName: nil)) } } diff --git a/Tests/CombineValidateTests/RegexTests.swift b/Tests/CombineValidateTests/RegexTests.swift index 8b020a3..c0cdc7b 100644 --- a/Tests/CombineValidateTests/RegexTests.swift +++ b/Tests/CombineValidateTests/RegexTests.swift @@ -2,34 +2,226 @@ import XCTest @testable import CombineValidate class InternalRegexTests: XCTestCase { - func testRegularPatterns() { - func testEmailPattern() { - let email = "somemail@gmail.com" - let nonEmail = "Some string" - - XCTAssertNotNil(email.range(of: RegularPattern.email.pattern, options: .regularExpression), "Regular email. Positive case failed") - XCTAssertNil(nonEmail.range(of: RegularPattern.email.pattern, options: .regularExpression), "Regular email. Negative case failed") - } + func testEmail() { + let email = "somemail@gmail.com" + XCTAssertTrue(RegularPattern.email.test(email), "Tested \(email) should be email") - func testStrongPattern() { - let strongPassword = "dk%1P2Nd" - let generatedStrongPassword = "t@yd!GANpuJ_dKw2L7AYXQoP" - let weakPassword = "weakPassord" - - XCTAssertNotNil( - strongPassword.range(of: RegularPattern.strongPassword.pattern, options: .regularExpression), - "Strong password. Positive case failed" - ) - - XCTAssertNotNil( - generatedStrongPassword.range(of: RegularPattern.strongPassword.pattern, options: .regularExpression), - "Strong password. Positive case failed" + let nonEmail = "Some string" + XCTAssertFalse(RegularPattern.email.test(nonEmail), "Tested \(nonEmail) as negative case should be passed successfully") + + let nonEmailEmptyString = "" + XCTAssertFalse(RegularPattern.email.test(nonEmailEmptyString), "Tested \(nonEmail) as negative case should be passed successfully") + } + + func testStrongPassword() { + let strongPassword = "dk%1P2Nd" + XCTAssertTrue( + RegularPattern.strongPassword.test(strongPassword), + "Strong password. Positive case failed" + ) + + let generatedStrongPassword = "t@yd!GANpuJ_dKw2L7AYXQoP" + XCTAssertTrue( + RegularPattern.strongPassword.test(generatedStrongPassword), + "Strong password. Positive case failed" + ) + + let weakPassword = "weakPassord" + XCTAssertFalse( + RegularPattern.strongPassword.test(weakPassword), + "Strong password. Negative case. Failed" + ) + } + + func testNonEmpty() { + let notEmpty = "S" + XCTAssertTrue( + RegularPattern.notEmpty.test(notEmpty), + "Not empty. Positive case failed" + ) + + let notEmptyWithWhiteSpace = " 1 " + XCTAssertTrue( + RegularPattern.notEmpty.test(notEmptyWithWhiteSpace), + "Not empty with whitespace. Positive case. Failed" + ) + + let empty = "" + XCTAssertFalse( + RegularPattern.notEmpty.test(empty), + "Not empty. Negative case. Failed" + ) + + let emptyWithWhiteSpace = " " + XCTAssertFalse( + RegularPattern.notEmpty.test(emptyWithWhiteSpace), + "Not empty with whitespace. Negative case. Failed" + ) + } + + func testMustIncludeCapitalLetters() { + let capitalLettersOnly = "ABCD" + XCTAssertTrue( + RegularPattern.mustIncludeCapitalLetters.test(capitalLettersOnly), + "Must include capital letter: \(capitalLettersOnly). Positive case. Failed" + ) + + let mixedLetters = "AbCd" + XCTAssertTrue( + RegularPattern.mustIncludeCapitalLetters.test(mixedLetters), + "Must include capital letter: \(mixedLetters). Positive case. Failed" + ) + + let lowercasedOnly = "abcd" + XCTAssertFalse( + RegularPattern.mustIncludeCapitalLetters.test(lowercasedOnly), + "Must include capital letter: \(lowercasedOnly). Negative case. Failed" + ) + + let whitespaces = " " + XCTAssertFalse( + RegularPattern.mustIncludeCapitalLetters.test(whitespaces), + "Must include capital letter: empty string with whitespace. Negative case. Failed" + ) + + let empty = "" + XCTAssertFalse( + RegularPattern.mustIncludeCapitalLetters.test(empty), + "Must include capital letter: empty string. Negative case. Failed" + ) + } + + func testMustIncludeSpecialSymbols() { + let symbols = ["%", "!", "\\", ":", "@", "[", "{", "`", "~"] + + for symbol in symbols { + XCTAssertTrue( + RegularPattern.mustIncludeSpecialSymbols.test(symbol), + "Must include special symbol: \(symbol). Positive case. Failed" ) - - XCTAssertNil( - weakPassword.range(of: RegularPattern.strongPassword.pattern, options: .regularExpression), - "Strong password. Negative case. Failed" + } + + let anyOtherSymbols = "1a" + XCTAssertFalse( + RegularPattern.mustIncludeSpecialSymbols.test(anyOtherSymbols), + "Must include special symbol: \(anyOtherSymbols). Negative case. Failed" + ) + + let whitespaces = " " + XCTAssertFalse( + RegularPattern.mustIncludeSpecialSymbols.test(whitespaces), + "Must include special symbol: empty string with whitespace. Negative case. Failed" + ) + + let empty = "" + XCTAssertFalse( + RegularPattern.mustIncludeSpecialSymbols.test(empty), + "Must include special symbol: empty string. Negative case. Failed" + ) + } + + func testMustIncludeDigitNumbers() { + let numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + + for number in numbers { + XCTAssertTrue( + RegularPattern.mustIncludeNumbers.test(number), + "Must include digit number: \(number). Positive case. Failed" ) } + + let anyOtherSymbolsWithDigit = "1a" + XCTAssertTrue( + RegularPattern.mustIncludeNumbers.test(anyOtherSymbolsWithDigit), + "Must include digit number: \(anyOtherSymbolsWithDigit). Positive case. Failed" + ) + + let withoudDigit = "av%" + XCTAssertFalse( + RegularPattern.mustIncludeNumbers.test(withoudDigit), + "Must include digit number: \(withoudDigit). Negative case. Failed" + ) + + let whitespaces = " " + XCTAssertFalse( + RegularPattern.mustIncludeNumbers.test(whitespaces), + "Must include digit number: empty string with whitespace. Negative case. Failed" + ) + + let empty = "" + XCTAssertFalse( + RegularPattern.mustIncludeNumbers.test(empty), + "Must include digit number: empty string. Negative case. Failed" + ) + } + + func testMustIncludeSmallLetters() { + let lowercasedLettersOnly = "abcd" + XCTAssertTrue( + RegularPattern.mustIncludeSmallLetters.test(lowercasedLettersOnly), + "Must include lowercased letter: \(lowercasedLettersOnly). Positive case. Failed" + ) + + let mixedLetters = "AbCd" + XCTAssertTrue( + RegularPattern.mustIncludeSmallLetters.test(mixedLetters), + "Must include lowercased letter: \(mixedLetters). Positive case. Failed" + ) + + let uppercasedOnly = "ABCD" + XCTAssertFalse( + RegularPattern.mustIncludeSmallLetters.test(uppercasedOnly), + "Must include lowercased letter: \(uppercasedOnly). Negative case. Failed" + ) + + let whitespaces = " " + XCTAssertFalse( + RegularPattern.mustIncludeCapitalLetters.test(whitespaces), + "Must include lowercased letter: empty string with whitespaces. Negative case. Failed" + ) + + let empty = "" + XCTAssertFalse( + RegularPattern.mustIncludeCapitalLetters.test(empty), + "Must include lowercased letter: empty string. Negative case. Failed" + ) + } + + func testwordAndDigitsOnly() { + let wordsOnly = "AccesS" + XCTAssertTrue( + RegularPattern.wordAndDigitsOnly.test(wordsOnly), + "Words and digits only: \(wordsOnly). Positive case. Failed" + ) + + let digitsOnly = "12345" + XCTAssertTrue( + RegularPattern.wordAndDigitsOnly.test(digitsOnly), + "Words and digits only: \(digitsOnly). Positive case. Failed" + ) + + let wordsAndSpecialSymbols = "Asdb%^" + XCTAssertTrue( + RegularPattern.wordAndDigitsOnly.test(wordsAndSpecialSymbols), + "Words and digits only: \(wordsAndSpecialSymbols). Positive case. Failed" + ) + + let digitsAndSpecialSymbols = "13{24_431" + XCTAssertTrue( + RegularPattern.wordAndDigitsOnly.test(digitsAndSpecialSymbols), + "Words and digits only: \(digitsAndSpecialSymbols). Positive case. Failed" + ) + + let whitespaces = " " + XCTAssertFalse( + RegularPattern.wordAndDigitsOnly.test(whitespaces), + "Words and digits only: empty string with whitespace. Negative case. Failed" + ) + + let empty = "" + XCTAssertFalse( + RegularPattern.wordAndDigitsOnly.test(empty), + "Words and digits only: empty string. Negative case. Failed" + ) } } diff --git a/Tests/CombineValidateTests/RegexValidatorTests.swift b/Tests/CombineValidateTests/RegexValidatorTests.swift index 1b3630b..b0b2702 100644 --- a/Tests/CombineValidateTests/RegexValidatorTests.swift +++ b/Tests/CombineValidateTests/RegexValidatorTests.swift @@ -9,7 +9,10 @@ class RegexValidatorTests: XCTestCase { @Published var validationResult: Validated = .untouched public lazy var emailValidator: ValidationPublisher = { - $email.validateWithRegex(regex: RegularPattern.email, error: "Should be email", tableName: nil) + $email.validateWithRegex( + regex: RegularPattern.email, + error: "Should be email" + ) }() private var subscription = Set() @@ -30,11 +33,15 @@ class RegexValidatorTests: XCTestCase { func testValidEmailValue() { viewModel.email = "someemail@gmail.com" + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) + XCTAssertEqual(viewModel.validationResult, .success(.none)) } func testInvalidEmailValue() { viewModel.email = "someemailgmail.com" + + _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 0.75) XCTAssertEqual(viewModel.validationResult, .failure(reason: "Should be email", tableName: nil)) } diff --git a/Tests/CombineValidateTests/ToggleValidatorTests.swift b/Tests/CombineValidateTests/ToggleValidatorTests.swift index c31deca..0955e69 100644 --- a/Tests/CombineValidateTests/ToggleValidatorTests.swift +++ b/Tests/CombineValidateTests/ToggleValidatorTests.swift @@ -1,6 +1,6 @@ +import XCTest import Combine @testable import CombineValidate -import XCTest final class ToggleValidatorTests: XCTestCase { class ViewModel: ObservableObject {