diff --git a/Alicerce.xcodeproj/project.pbxproj b/Alicerce.xcodeproj/project.pbxproj index ad75531f..948a63de 100644 --- a/Alicerce.xcodeproj/project.pbxproj +++ b/Alicerce.xcodeproj/project.pbxproj @@ -307,6 +307,7 @@ 1B7166BC216BE424008A8A46 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1B7166B9216BE402008A8A46 /* Localizable.strings */; }; 1BBEB6091F333E5600D06526 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBEB6081F333E5600D06526 /* UIImage.swift */; }; 1BBEB60C1F333E6E00D06526 /* UIImageTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BBEB60A1F333E6600D06526 /* UIImageTestCase.swift */; }; + 3E8D61952546F90400C08EA2 /* ConstraintGroupToggleTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8D61932546F8AF00C08EA2 /* ConstraintGroupToggleTestCase.swift */; }; 4833D5B423D0D2CE00EBD925 /* WidthConstrainableProxyTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4833D5B223D0D28C00EBD925 /* WidthConstrainableProxyTestCase.swift */; }; 4838FE3023A94CE0007311F0 /* ConstrainableProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4838FE2823A94CE0007311F0 /* ConstrainableProxy.swift */; }; 4838FE3123A94CE0007311F0 /* Array+ConstrainableProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4838FE2923A94CE0007311F0 /* Array+ConstrainableProxy.swift */; }; @@ -655,6 +656,7 @@ 1B7166CD216C231E008A8A46 /* StringTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringTestCase.swift; sourceTree = ""; }; 1BBEB6081F333E5600D06526 /* UIImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; 1BBEB60A1F333E6600D06526 /* UIImageTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageTestCase.swift; sourceTree = ""; }; + 3E8D61932546F8AF00C08EA2 /* ConstraintGroupToggleTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstraintGroupToggleTestCase.swift; sourceTree = ""; }; 4833D5B223D0D28C00EBD925 /* WidthConstrainableProxyTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidthConstrainableProxyTestCase.swift; sourceTree = ""; }; 4838FE2823A94CE0007311F0 /* ConstrainableProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstrainableProxy.swift; sourceTree = ""; }; 4838FE2923A94CE0007311F0 /* Array+ConstrainableProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+ConstrainableProxy.swift"; sourceTree = ""; }; @@ -1530,6 +1532,7 @@ 4833D5B223D0D28C00EBD925 /* WidthConstrainableProxyTestCase.swift */, 4838FE3C23A950CA007311F0 /* XCTAssertConstraint.swift */, 48A5ECCF23D5B9F70014B2B7 /* TopBottomConstrainableProxyTestCase.swift */, + 3E8D61932546F8AF00C08EA2 /* ConstraintGroupToggleTestCase.swift */, ); path = AutoLayout; sourceTree = ""; @@ -1862,6 +1865,7 @@ 0A266FBF1ED59FCD009CD0D7 /* EmptyCoreDataStackModel.xcdatamodeld in Sources */, 4838FE5723A951E6007311F0 /* TopConstrainableProxyTestCase.swift in Sources */, 0A708F6E20E99D9F001784DA /* MockAnalyticsTracker.swift in Sources */, + 3E8D61952546F90400C08EA2 /* ConstraintGroupToggleTestCase.swift in Sources */, 0A266F8C1ED59FB6009CD0D7 /* MultiTrackerTestCase.swift in Sources */, 0A85F0E720B3177E0095AFFB /* PublicKeyAlgorithmTestCase.swift in Sources */, 0A266F901ED59FB6009CD0D7 /* Route+ComponentTests.swift in Sources */, diff --git a/Sources/AutoLayout/Constrain.swift b/Sources/AutoLayout/Constrain.swift index bc5d3a95..8926e053 100644 --- a/Sources/AutoLayout/Constrain.swift +++ b/Sources/AutoLayout/Constrain.swift @@ -12,10 +12,15 @@ public final class LayoutContext { } } -public final class ConstraintGroup { +public class ConstraintGroup { public init() { } + internal init(constraints: [NSLayoutConstraint] = []) { + + self.constraints = constraints + } + fileprivate var constraints: [NSLayoutConstraint] = [] { willSet { if isActive { deactivate() } @@ -41,6 +46,36 @@ public final class ConstraintGroup { } } +public final class ConstraintGroupToggle { + + private var constraintGroups: [T: ConstraintGroup] = [:] + + public init(initial: T? = nil, constraintGroups: [T: ConstraintGroup]) { + + self.constraintGroups = constraintGroups + + if let initial = initial { + activate(initial) + } else { + deactivate() + } + } + + public func activate(_ key: T) { + + constraintGroups.lazy.filter { $0 != key && $1.isActive }.forEach { $1.isActive = false } + + if let constraintGroup = constraintGroups[key] { + constraintGroup.isActive = true + } + } + + public func deactivate() { + + constraintGroups.forEach { $1.isActive = false } + } +} + @discardableResult public func constrain( _ a: A, diff --git a/Sources/AutoLayout/ConstrainableProxy.swift b/Sources/AutoLayout/ConstrainableProxy.swift index 4da6b9c2..8e212916 100644 --- a/Sources/AutoLayout/ConstrainableProxy.swift +++ b/Sources/AutoLayout/ConstrainableProxy.swift @@ -50,6 +50,40 @@ public extension TopConstrainableProxy { priority: priority ) } + + @discardableResult + func topToFirstBaseline( + of anotherProxy: BaselineConstrainableProxy, + offset: CGFloat = 0, + relation: ConstraintRelation = .equal, + priority: UILayoutPriority = .required + ) -> NSLayoutConstraint { + + constrain( + from: top, + to: anotherProxy.firstBaseline, + offset: offset, + relation: relation, + priority: priority + ) + } + + @discardableResult + func topToLastBaseline( + of anotherProxy: BaselineConstrainableProxy, + offset: CGFloat = 0, + relation: ConstraintRelation = .equal, + priority: UILayoutPriority = .required + ) -> NSLayoutConstraint { + + constrain( + from: top, + to: anotherProxy.lastBaseline, + offset: offset, + relation: relation, + priority: priority + ) + } } public protocol BottomConstrainableProxy: ConstrainableProxy { @@ -92,6 +126,40 @@ public extension BottomConstrainableProxy { priority: priority ) } + + @discardableResult + func bottomToFirstBaseline( + of anotherProxy: BaselineConstrainableProxy, + offset: CGFloat = 0, + relation: ConstraintRelation = .equal, + priority: UILayoutPriority = .required + ) -> NSLayoutConstraint { + + constrain( + from: bottom, + to: anotherProxy.firstBaseline, + offset: offset, + relation: relation, + priority: priority + ) + } + + @discardableResult + func bottomToLastBaseline( + of anotherProxy: BaselineConstrainableProxy, + offset: CGFloat = 0, + relation: ConstraintRelation = .equal, + priority: UILayoutPriority = .required + ) -> NSLayoutConstraint { + + constrain( + from: bottom, + to: anotherProxy.lastBaseline, + offset: offset, + relation: relation, + priority: priority + ) + } } public protocol LeadingConstrainableProxy: ConstrainableProxy { @@ -389,6 +457,40 @@ public extension CenterYConstrainableProxy { priority: priority ) } + + @discardableResult + func centerYToFirstBaseline( + of anotherProxy: BaselineConstrainableProxy, + offset: CGFloat = 0, + relation: ConstraintRelation = .equal, + priority: UILayoutPriority = .required + ) -> NSLayoutConstraint { + + constrain( + from: centerY, + to: anotherProxy.firstBaseline, + offset: offset, + relation: relation, + priority: priority + ) + } + + @discardableResult + func centerYToLastBaseline( + of anotherProxy: BaselineConstrainableProxy, + offset: CGFloat = 0, + relation: ConstraintRelation = .equal, + priority: UILayoutPriority = .required + ) -> NSLayoutConstraint { + + constrain( + from: centerY, + to: anotherProxy.lastBaseline, + offset: offset, + relation: relation, + priority: priority + ) + } } public protocol CenterXConstrainableProxy: ConstrainableProxy { diff --git a/Sources/DeepLinking/Route+TrieRouter.swift b/Sources/DeepLinking/Route+TrieRouter.swift index 8c1a1a81..9efa80a6 100644 --- a/Sources/DeepLinking/Route+TrieRouter.swift +++ b/Sources/DeepLinking/Route+TrieRouter.swift @@ -178,8 +178,9 @@ extension Route { private func parseAnnotatedRoute(_ route: URL) throws -> [Route.Component] { // use a wildcard for empty schemes/hosts, to match any scheme/host - let schemeComponent = route.scheme.constantOrWildcardComponent - let hostComponent = route.host.constantOrWildcardComponent + // URL scheme and host comparison should be case insensitive in conformance to RFC-3986 + let schemeComponent = (route.scheme?.lowercased()).constantOrWildcardComponent + let hostComponent = (route.host?.lowercased()).constantOrWildcardComponent do { let pathComponents = try route.pathComponents.filter { $0 != "/" }.map(Route.Component.init(component:)) @@ -204,8 +205,9 @@ extension Route { private func parseMatchRoute(_ route: URL) throws -> MatchRoute { // use an empty string for empty scheme/host, to match wildcard scheme/host - let schemeComponent = route.scheme ?? "" - let hostComponent = route.host ?? "" + // URL scheme and host comparison should be case insensitive in conformance to RFC-3986 + let schemeComponent = route.scheme?.lowercased() ?? "" + let hostComponent = route.host?.lowercased() ?? "" let pathComponents = route.pathComponents.filter { $0 != "/" } let routeComponents = [schemeComponent, hostComponent] + pathComponents diff --git a/Tests/AlicerceTests/AutoLayout/BottomConstrainableProxyTestCase.swift b/Tests/AlicerceTests/AutoLayout/BottomConstrainableProxyTestCase.swift index aa238630..f4618c90 100644 --- a/Tests/AlicerceTests/AutoLayout/BottomConstrainableProxyTestCase.swift +++ b/Tests/AlicerceTests/AutoLayout/BottomConstrainableProxyTestCase.swift @@ -317,4 +317,48 @@ final class BottomConstrainableProxyTestCase: BaseConstrainableProxyTestCase { XCTAssert(constraintGroup1.isActive) XCTAssertEqual(view0.frame.maxY, 400) } + + func testConstrain_WithBottomConstraint_ShouldSupportFirstBaselineAttribute() { + + var constraint: NSLayoutConstraint! + constrain(host, view0) { host, view0 in + constraint = view0.bottomToFirstBaseline(of: host) + } + + let expected = NSLayoutConstraint( + item: view0!, + attribute: .bottom, + relatedBy: .equal, + toItem: host, + attribute: .firstBaseline, + multiplier: 1, + constant: 0, + priority: .required, + active: true + ) + + XCTAssertConstraint(constraint, expected) + } + + func testConstrain_WithBottomConstraint_ShouldSupportLastBaselineAttribute() { + + var constraint: NSLayoutConstraint! + constrain(host, view0) { host, view0 in + constraint = view0.bottomToLastBaseline(of: host) + } + + let expected = NSLayoutConstraint( + item: view0!, + attribute: .bottom, + relatedBy: .equal, + toItem: host, + attribute: .lastBaseline, + multiplier: 1, + constant: 0, + priority: .required, + active: true + ) + + XCTAssertConstraint(constraint, expected) + } } diff --git a/Tests/AlicerceTests/AutoLayout/CenterYConstrainableProxyTestCase.swift b/Tests/AlicerceTests/AutoLayout/CenterYConstrainableProxyTestCase.swift index 768913b1..28af4803 100644 --- a/Tests/AlicerceTests/AutoLayout/CenterYConstrainableProxyTestCase.swift +++ b/Tests/AlicerceTests/AutoLayout/CenterYConstrainableProxyTestCase.swift @@ -256,6 +256,50 @@ class CenterYConstrainableProxyTestCase: BaseConstrainableProxyTestCase { XCTAssert(constraintGroup1.isActive) XCTAssertEqual(view0.center.y, host.center.y + 100) } + + func testConstrain_WithCenterY_ShouldSupportFirstBaselineAttribute() { + + var constraint: NSLayoutConstraint! + constrain(host, view0) { host, view0 in + constraint = view0.centerYToFirstBaseline(of: host) + } + + let expected = NSLayoutConstraint( + item: view0!, + attribute: .centerY, + relatedBy: .equal, + toItem: host, + attribute: .firstBaseline, + multiplier: 1, + constant: 0, + priority: .required, + active: true + ) + + XCTAssertConstraint(constraint, expected) + } + + func testConstrain_WithCenterYConstraint_ShouldSupportLastBaselineAttribute() { + + var constraint: NSLayoutConstraint! + constrain(host, view0) { host, view0 in + constraint = view0.centerYToLastBaseline(of: host) + } + + let expected = NSLayoutConstraint( + item: view0!, + attribute: .centerY, + relatedBy: .equal, + toItem: host, + attribute: .lastBaseline, + multiplier: 1, + constant: 0, + priority: .required, + active: true + ) + + XCTAssertConstraint(constraint, expected) + } } private extension CenterYConstrainableProxyTestCase { diff --git a/Tests/AlicerceTests/AutoLayout/ConstraintGroupToggleTestCase.swift b/Tests/AlicerceTests/AutoLayout/ConstraintGroupToggleTestCase.swift new file mode 100644 index 00000000..9387548e --- /dev/null +++ b/Tests/AlicerceTests/AutoLayout/ConstraintGroupToggleTestCase.swift @@ -0,0 +1,210 @@ +import XCTest +@testable import Alicerce + +class ConstraintGroupToggleTestCase: BaseConstrainableProxyTestCase { + + private var constraintGroupToggle: ConstraintGroupToggle! + + private var constraintGroup1: ConstraintGroup! + private var constraintGroup2: ConstraintGroup! + private var constraintGroup3: ConstraintGroup! + + private var constraint1: NSLayoutConstraint! + private var constraint2: NSLayoutConstraint! + private var constraint3: NSLayoutConstraint! + + override func setUp() { + super.setUp() + + constraintGroup1 = constrain(self.view0, self.view1, activate: false) { view0, view1 in + self.constraint1 = view0.bottom(to: view1) + } + + constraintGroup2 = constrain(self.view0, self.view2, activate: false) { view0, view2 in + self.constraint2 = view0.bottom(to: view2) + } + + constraintGroup3 = constrain(self.view0, self.view3, activate: false) { view0, view3 in + self.constraint3 = view0.bottom(to: view3) + } + + constraintGroupToggle = ConstraintGroupToggle( + initial: .first, + constraintGroups: [.first: constraintGroup1, .second: constraintGroup2, .third: constraintGroup3] + ) + } + + override func tearDown() { + + constraint1 = nil + constraint2 = nil + constraint3 = nil + + constraintGroup1 = nil + constraintGroup2 = nil + constraintGroup3 = nil + + super.tearDown() + } + + func testInit_WithNoInitialConstraint_ShouldNotActivateConstraints() { + + constraintGroupToggle = ConstraintGroupToggle( + constraintGroups: [.first: constraintGroup1, .second: constraintGroup2, .third: constraintGroup3] + ) + + XCTAssertFalse(constraint1.isActive) + XCTAssertFalse(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } + + func testInit_WithInitialConstraint_ShouldActivateFirstConstraint() { + + XCTAssert(constraint1.isActive) + XCTAssertFalse(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } + + func testInit_WithNoInitialConstraintGroupsActive_ShouldDeactivateConstraintGroups() { + + constraintGroup1 = constrain(self.view0, self.view1, activate: true) { view0, view1 in + self.constraint1 = view0.bottom(to: view1) + } + + constraintGroup2 = constrain(self.view0, self.view2, activate: true) { view0, view2 in + self.constraint2 = view0.bottom(to: view2) + } + + constraintGroup3 = constrain(self.view0, self.view3, activate: true) { view0, view3 in + self.constraint3 = view0.bottom(to: view3) + } + + constraintGroupToggle = ConstraintGroupToggle( + constraintGroups: [.first: constraintGroup1, .second: constraintGroup2, .third: constraintGroup3] + ) + + XCTAssertFalse(constraint1.isActive) + XCTAssertFalse(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } + + func testActivate_WithSecondConstraint_ShouldDeactivateFirstConstraintAndActivateSecondConstraint() { + + constraintGroupToggle.activate(.second) + + XCTAssertFalse(constraint1.isActive) + XCTAssert(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } + + func testActivate_WithSecondAndThirdConstraint_ShouldDeactivateFirstAndSecondConstraintAndActivateThirdConstraint() { + + constraintGroupToggle.activate(.second) + + XCTAssertFalse(constraint1.isActive) + XCTAssert(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + + constraintGroupToggle.activate(.third) + + XCTAssertFalse(constraint1.isActive) + XCTAssertFalse(constraint2.isActive) + XCTAssert(constraint3.isActive) + } + + func testActivate_WithSecondAndFirstConstraint_ShouldDeactivateSecondAndActivateFirstConstraint() { + + constraintGroupToggle.activate(.second) + + XCTAssertFalse(constraint1.isActive) + XCTAssert(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + + constraintGroupToggle.activate(.first) + + XCTAssert(constraint1.isActive) + XCTAssertFalse(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } + + func testActivate_WithUnhandledKey_ShouldDeactivateAllConstraints() { + + let nonFiniteConstraintGroupToggle = ConstraintGroupToggle( + initial: "constraint1", + constraintGroups: ["constraint1": constraintGroup2, "constraint2": constraintGroup3] + ) + + nonFiniteConstraintGroupToggle.activate("constraint3") + + XCTAssertFalse(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } + + func testActivate_WithSecondConstraint_ShouldDeactivatePreviouConstraintGroupBeforeActivatingSecond() { + + let activateClosureOne = self.expectation(description: "ConstraintGroup one activated") + let deactivateClosureOne = self.expectation(description: "ConstraintGroup one deactivated") + let activateClosureTwo = self.expectation(description: "ConstraintGroup two activated") + + let constraint1Group = MockContraintGroup(constraints: [constraint1]) + constraint1Group.didSetIsActive = { isActive in + + if isActive { + activateClosureOne.fulfill() + } else { + deactivateClosureOne.fulfill() + } + } + + let constraint2Group = MockContraintGroup(constraints: [constraint2]) + constraint2Group.didSetIsActive = { isActive in + + if isActive { + activateClosureTwo.fulfill() + } else { + XCTFail() + } + } + + let nonFiniteConstraintGroupToggle = ConstraintGroupToggle( + initial: "constraint1", + constraintGroups: ["constraint1": constraint1Group, "constraint2": constraint2Group] + ) + + nonFiniteConstraintGroupToggle.activate("constraint2") + + wait( + for: [activateClosureOne, deactivateClosureOne, activateClosureTwo], + timeout: 1, + enforceOrder: true + ) + } + + func testDeactivate_ShouldDeactivateFirstConstraint() { + + constraintGroupToggle.deactivate() + + XCTAssertFalse(constraint1.isActive) + XCTAssertFalse(constraint2.isActive) + XCTAssertFalse(constraint3.isActive) + } +} + +private enum TestConstraintGroupKey: Hashable { + case first + case second + case third +} + +private final class MockContraintGroup: ConstraintGroup { + + var didSetIsActive: ((Bool) -> Void)? + + public override init(constraints: [NSLayoutConstraint]) { + super.init(constraints: constraints) + } + + public override var isActive: Bool { + didSet { didSetIsActive?(isActive) } + } +} diff --git a/Tests/AlicerceTests/AutoLayout/TopConstrainableProxyTestCase.swift b/Tests/AlicerceTests/AutoLayout/TopConstrainableProxyTestCase.swift index 315c1be1..f16cece6 100644 --- a/Tests/AlicerceTests/AutoLayout/TopConstrainableProxyTestCase.swift +++ b/Tests/AlicerceTests/AutoLayout/TopConstrainableProxyTestCase.swift @@ -350,4 +350,48 @@ final class TopConstrainableProxyTestCase: BaseConstrainableProxyTestCase { XCTAssert(constraintGroup1.isActive) XCTAssertEqual(view0.frame.minY, host.frame.minY + 100) } + + func testConstrain_WithTopConstraint_ShouldSupportFirstBaselineAttribute() { + + var constraint: NSLayoutConstraint! + constrain(host, view0) { host, view0 in + constraint = view0.topToFirstBaseline(of: host) + } + + let expected = NSLayoutConstraint( + item: view0!, + attribute: .top, + relatedBy: .equal, + toItem: host, + attribute: .firstBaseline, + multiplier: 1, + constant: 0, + priority: .required, + active: true + ) + + XCTAssertConstraint(constraint, expected) + } + + func testConstrain_WithTopConstraint_ShouldSupportLastBaselineAttribute() { + + var constraint: NSLayoutConstraint! + constrain(host, view0) { host, view0 in + constraint = view0.topToLastBaseline(of: host) + } + + let expected = NSLayoutConstraint( + item: view0!, + attribute: .top, + relatedBy: .equal, + toItem: host, + attribute: .lastBaseline, + multiplier: 1, + constant: 0, + priority: .required, + active: true + ) + + XCTAssertConstraint(constraint, expected) + } } diff --git a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift index c16e9c17..b35cf081 100644 --- a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift +++ b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_DescriptionTests.swift @@ -57,22 +57,22 @@ class Route_TrieRouter_DescriptionTests: XCTestCase { XCTAssertEqual( router.description, """ - ├──┬ schemeB - │ ├──┬ hostB + ├──┬ schemeb + │ ├──┬ hosta + │ │ └──● AnyRouteHandler(P) + │ │ + │ ├──┬ hostb │ │ └──┬ path │ │ ├──┬ * │ │ │ └──● AnyRouteHandler(R) │ │ │ │ │ └──● AnyRouteHandler(Q) │ │ - │ ├──┬ hostA - │ │ └──● AnyRouteHandler(P) - │ │ │ └──┬ * │ └──● AnyRouteHandler(O) │ - ├──┬ schemeA - │ ├──┬ hostC + ├──┬ schemea + │ ├──┬ hostc │ │ └──┬ * │ │ └──┬ yet │ │ └──┬ another diff --git a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift index 2162450e..fe970054 100644 --- a/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift +++ b/Tests/AlicerceTests/DeepLinking/Route+TrieRouter_RouteTests.swift @@ -130,6 +130,18 @@ class Route_TrieRouter_RouteTests: XCTestCase { XCTAssertRouteSucceeds(initial: [("/", testHandler)], route: "://") } + func testRoute_WithCaseInsensitiveMatchingScheme_ShouldSucceed() { + XCTAssertRouteSucceeds(initial: [("HTTP://", testHandler)], route: "http://") + XCTAssertRouteSucceeds(initial: [("HTTP://host/", testHandler)], route: "http://host/") + XCTAssertRouteSucceeds(initial: [("HTTP://host/path", testHandler)], route: "http://host/path") + XCTAssertRouteSucceeds(initial: [("HtTp://host/path", testHandler)], route: "http://host/path") + + XCTAssertRouteSucceeds(initial: [("http://", testHandler)], route: "HTTP://") + XCTAssertRouteSucceeds(initial: [("http://host/", testHandler)], route: "HTTP://host/") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "HTTP://host/path") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "httP://host/path") + } + // MARK: host func testRoute_WithMatchingHost_ShouldSucceed() { @@ -163,6 +175,16 @@ class Route_TrieRouter_RouteTests: XCTestCase { XCTAssertRouteSucceeds(initial: [("/", testHandler)], route: ":///") } + func testRoute_WithCaseInsensitiveMatchingHost_ShouldSucceed() { + XCTAssertRouteSucceeds(initial: [("http://HOST/", testHandler)], route: "http://host/") + XCTAssertRouteSucceeds(initial: [("http://HOST/path", testHandler)], route: "http://host/path") + XCTAssertRouteSucceeds(initial: [("http://HosT/path", testHandler)], route: "http://host/path") + + XCTAssertRouteSucceeds(initial: [("http://host/", testHandler)], route: "http://HOST/") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "http://HOST/path") + XCTAssertRouteSucceeds(initial: [("http://host/path", testHandler)], route: "http://hOSt/path") + } + // MARK: single level path func testRoute_WithMatchingSingleLevelPath_ShouldSucceed() {