Skip to content

Commit ceac218

Browse files
committed
Add tests and infra for url opener.
1 parent 5e23804 commit ceac218

File tree

6 files changed

+160
-4
lines changed

6 files changed

+160
-4
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
import UIKit
3+
4+
protocol URLOpener {
5+
func open(_ url: URL)
6+
}
7+
8+
/// Uses the UIApplication API to open the url
9+
///
10+
struct ApplicationURLOpener: URLOpener {
11+
func open(_ url: URL) {
12+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
13+
}
14+
}

WooCommerce/Classes/Universal Links/UniversalLinkRouter.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import UIKit
66
///
77
struct UniversalLinkRouter {
88
private let matcher: RouteMatcher
9+
private let bouncingURLOpener: URLOpener
910

1011
/// The order of the passed Route array matters, as given two routes that handle a path only the first
11-
/// will be called to perform its action
12+
/// will be called to perform its action. If no route matches the path it uses the `bouncingURLOpener` to
13+
/// open it e.g to be opened in web when the app cannot handle the link
1214
///
13-
init(routes: [Route]) {
15+
init(routes: [Route], bouncingURLOpener: URLOpener = ApplicationURLOpener()) {
1416
matcher = RouteMatcher(routes: routes)
17+
self.bouncingURLOpener = bouncingURLOpener
1518
}
1619

1720
static let defaultRoutes: [Route] = [
@@ -20,8 +23,7 @@ struct UniversalLinkRouter {
2023

2124
func handle(url: URL) {
2225
guard let matchedRoute = matcher.firstRouteMatching(url) else {
23-
UIApplication.shared.open(url, options: [:], completionHandler: nil)
24-
return
26+
return bouncingURLOpener.open(url)
2527
}
2628

2729
matchedRoute.performAction()

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,10 @@
12761276
B958A7C928B3D47B00823EEF /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7C828B3D47B00823EEF /* Route.swift */; };
12771277
B958A7CB28B3D4A100823EEF /* RouteMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7CA28B3D4A100823EEF /* RouteMatcher.swift */; };
12781278
B958A7CD28B3DD9100823EEF /* OrderDetailsRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7CC28B3DD9100823EEF /* OrderDetailsRoute.swift */; };
1279+
B958A7D128B5281800823EEF /* UniversalLinkRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7D028B5281800823EEF /* UniversalLinkRouterTests.swift */; };
1280+
B958A7D328B52A2300823EEF /* MockRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7D228B52A2300823EEF /* MockRoute.swift */; };
1281+
B958A7D628B5310100823EEF /* URLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7D428B5302500823EEF /* URLOpener.swift */; };
1282+
B958A7D828B5316A00823EEF /* MockURLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958A7D728B5316A00823EEF /* MockURLOpener.swift */; };
12791283
B96B536B2816ECFC00F753E6 /* CardPresentPluginsDataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96B536A2816ECFC00F753E6 /* CardPresentPluginsDataProviderTests.swift */; };
12801284
B979A9BA282D62A500EBB383 /* InPersonPaymentsDeactivateStripeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B979A9B9282D62A500EBB383 /* InPersonPaymentsDeactivateStripeView.swift */; };
12811285
B9B0391628A6824200DC1C83 /* PermanentNoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B0391528A6824200DC1C83 /* PermanentNoticePresenter.swift */; };
@@ -3121,6 +3125,10 @@
31213125
B958A7C828B3D47B00823EEF /* Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = "<group>"; };
31223126
B958A7CA28B3D4A100823EEF /* RouteMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMatcher.swift; sourceTree = "<group>"; };
31233127
B958A7CC28B3DD9100823EEF /* OrderDetailsRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsRoute.swift; sourceTree = "<group>"; };
3128+
B958A7D028B5281800823EEF /* UniversalLinkRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalLinkRouterTests.swift; sourceTree = "<group>"; };
3129+
B958A7D228B52A2300823EEF /* MockRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoute.swift; sourceTree = "<group>"; };
3130+
B958A7D428B5302500823EEF /* URLOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLOpener.swift; sourceTree = "<group>"; };
3131+
B958A7D728B5316A00823EEF /* MockURLOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLOpener.swift; sourceTree = "<group>"; };
31243132
B96B536A2816ECFC00F753E6 /* CardPresentPluginsDataProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentPluginsDataProviderTests.swift; sourceTree = "<group>"; };
31253133
B979A9B9282D62A500EBB383 /* InPersonPaymentsDeactivateStripeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsDeactivateStripeView.swift; sourceTree = "<group>"; };
31263134
B9B0391528A6824200DC1C83 /* PermanentNoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermanentNoticePresenter.swift; sourceTree = "<group>"; };
@@ -6039,6 +6047,8 @@
60396047
0247F50D286E6CCD009C177E /* MockProductImageActionHandler.swift */,
60406048
0248042C2887C92A00991319 /* MockLoggedOutAppSettings.swift */,
60416049
02829BA9288FA8B300951E1E /* MockUserNotification.swift */,
6050+
B958A7D228B52A2300823EEF /* MockRoute.swift */,
6051+
B958A7D728B5316A00823EEF /* MockURLOpener.swift */,
60426052
);
60436053
path = Mocks;
60446054
sourceTree = "<group>";
@@ -6471,6 +6481,7 @@
64716481
31579027273EE2B1008CA3AF /* VersionHelpers.swift */,
64726482
174CA86D27DBFD2D00126524 /* ShareAppTextItemActivitySource.swift */,
64736483
B9C4AB2427FDE4B6007008B8 /* CardPresentPluginsDataProvider.swift */,
6484+
B958A7D428B5302500823EEF /* URLOpener.swift */,
64746485
);
64756486
path = Tools;
64766487
sourceTree = "<group>";
@@ -6514,6 +6525,7 @@
65146525
B56DB3E02049BFAA00D4AA8E /* WooCommerceTests */ = {
65156526
isa = PBXGroup;
65166527
children = (
6528+
B958A7CF28B527FB00823EEF /* Universal Links */,
65176529
D8F01DD125DEDC0100CE70BE /* Stripe Integration Tests */,
65186530
5791FB4024EC833200117FD6 /* ViewModels */,
65196531
57C2F6E324C27B0C00131012 /* Authentication */,
@@ -6902,6 +6914,14 @@
69026914
path = Routes;
69036915
sourceTree = "<group>";
69046916
};
6917+
B958A7CF28B527FB00823EEF /* Universal Links */ = {
6918+
isa = PBXGroup;
6919+
children = (
6920+
B958A7D028B5281800823EEF /* UniversalLinkRouterTests.swift */,
6921+
);
6922+
path = "Universal Links";
6923+
sourceTree = "<group>";
6924+
};
69056925
B9B0391B28A690DA00DC1C83 /* PermanentNotice */ = {
69066926
isa = PBXGroup;
69076927
children = (
@@ -9777,6 +9797,7 @@
97779797
02396251239948470096F34C /* UIImage+TintColor.swift in Sources */,
97789798
DEE6437826D8DAD900888A75 /* InProgressView.swift in Sources */,
97799799
0290E275238E4F8100B5C466 /* PaginatedListSelectorViewController.swift in Sources */,
9800+
B958A7D628B5310100823EEF /* URLOpener.swift in Sources */,
97809801
020DD48F232392C9005822B1 /* UIViewController+AppReview.swift in Sources */,
97819802
2687165524D21BC80042F6AE /* SurveySubmittedViewController.swift in Sources */,
97829803
CE263DE8206ACE3E0015A693 /* MainTabBarController.swift in Sources */,
@@ -10138,6 +10159,7 @@
1013810159
02CE43092769953D0006EAEF /* MockCaptureDevicePermissionChecker.swift in Sources */,
1013910160
7E6A01A32726C5D3001668D5 /* MockProductCategoryStoresManager.swift in Sources */,
1014010161
45F5A3C323DF31D2007D40E5 /* ShippingInputFormatterTests.swift in Sources */,
10162+
B958A7D328B52A2300823EEF /* MockRoute.swift in Sources */,
1014110163
02153211242376B5003F2BBD /* ProductPriceSettingsViewModelTests.swift in Sources */,
1014210164
45C8B25D231529410002FA77 /* CustomerInfoTableViewCellTests.swift in Sources */,
1014310165
023EC2E624DAB1270021DA91 /* EditableProductVariationModelTests.swift in Sources */,
@@ -10215,6 +10237,7 @@
1021510237
0375799D2822F9040083F2E1 /* MockCardPresentPaymentsOnboardingPresenter.swift in Sources */,
1021610238
455800CC24C6F83F00A8D117 /* ProductSettingsSectionsTests.swift in Sources */,
1021710239
D85B833F2230F268002168F3 /* SummaryTableViewCellTests.swift in Sources */,
10240+
B958A7D828B5316A00823EEF /* MockURLOpener.swift in Sources */,
1021810241
02645D8227BA20A30065DC68 /* InboxViewModelTests.swift in Sources */,
1021910242
57ABE36824EB048A00A64F49 /* MockSwitchStoreUseCase.swift in Sources */,
1022010243
311F827626CD8AB100DF5BAD /* MockCardReaderSettingsAlerts.swift in Sources */,
@@ -10459,6 +10482,7 @@
1045910482
571FDDAE24C768DC00D486A5 /* MockZendeskManager.swift in Sources */,
1046010483
45FBDF3C238D4EA800127F77 /* ExtendedAddProductImageCollectionViewCellTests.swift in Sources */,
1046110484
02279590237A5DC900787C63 /* AztecUnorderedListFormatBarCommandTests.swift in Sources */,
10485+
B958A7D128B5281800823EEF /* UniversalLinkRouterTests.swift in Sources */,
1046210486
B5F571AB21BEECB60010D1B8 /* NoteWooTests.swift in Sources */,
1046310487
D802546B2655180A001B2CC1 /* CardPresentModalReaderIsReadyTests.swift in Sources */,
1046410488
45DB706C26161F970064A6CF /* DecimalWooTests.swift in Sources */,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@testable import WooCommerce
2+
3+
final class MockRoute: Route {
4+
let path: String
5+
let performAction: ([String: String]) -> ()
6+
7+
init(path: String, performAction: @escaping ([String: String]) -> ()) {
8+
self.path = path
9+
self.performAction = performAction
10+
}
11+
12+
func perform(with parameters: [String: String]) {
13+
performAction(parameters)
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Foundation
2+
@testable import WooCommerce
3+
4+
struct MockURLOpener: URLOpener {
5+
let open: (URL) -> Void
6+
7+
func open(_ url: URL) {
8+
open(url)
9+
}
10+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
@testable import WooCommerce
2+
import XCTest
3+
4+
final class UniversalLinkRouterTests: XCTestCase {
5+
func test_handle_when_there_is_a_route_matching_then_calls_to_perform_action_with_right_actions() {
6+
// Given
7+
let path = "/test/path"
8+
let queryItem = URLQueryItem(name: "name", value: "value")
9+
var components = URLComponents()
10+
components.scheme = "https"
11+
components.host = "woocommerce.com"
12+
components.path = path
13+
components.queryItems = [
14+
queryItem
15+
]
16+
17+
var retrievedParameters: [String: String]?
18+
let route = MockRoute(path: path, performAction: { parameters in
19+
retrievedParameters = parameters
20+
})
21+
let sut = UniversalLinkRouter(routes: [route])
22+
23+
guard let url = components.url else {
24+
XCTFail()
25+
return
26+
}
27+
28+
// When
29+
sut.handle(url: url)
30+
31+
// Then
32+
XCTAssertEqual(retrievedParameters?[queryItem.name], queryItem.value)
33+
}
34+
35+
func test_handle_when_there_are_routes_matching_then_calls_to_perform_action_to_the_first_one() {
36+
// Given
37+
let path = "/test/path"
38+
var components = URLComponents()
39+
components.scheme = "https"
40+
components.host = "woocommerce.com"
41+
components.path = path
42+
43+
var routeOneWasCalled = false
44+
let routeOne = MockRoute(path: path, performAction: { _ in
45+
routeOneWasCalled = true
46+
})
47+
48+
var routeTwoWasCalled = false
49+
let routeTwo = MockRoute(path: path, performAction: { _ in
50+
routeTwoWasCalled = true
51+
})
52+
53+
54+
let sut = UniversalLinkRouter(routes: [routeOne, routeTwo])
55+
56+
guard let url = components.url else {
57+
XCTFail()
58+
return
59+
}
60+
61+
// When
62+
sut.handle(url: url)
63+
64+
// Then
65+
XCTAssertTrue(routeOneWasCalled)
66+
XCTAssertFalse(routeTwoWasCalled)
67+
}
68+
69+
func test_handle_when_there_no_routes_matching_then_bounces_url() {
70+
// Given
71+
let url = URL(string: "woocommerce.com/a/nice/path")!
72+
73+
var routeOneWasCalled = false
74+
let routeOne = MockRoute(path: "a/different/path", performAction: { _ in
75+
routeOneWasCalled = true
76+
})
77+
78+
var bouncingURL = url
79+
let urlOpener = MockURLOpener(open: { url in
80+
bouncingURL = url
81+
})
82+
let sut = UniversalLinkRouter(routes: [routeOne], bouncingURLOpener: urlOpener)
83+
84+
// When
85+
sut.handle(url: url)
86+
87+
// Then
88+
XCTAssertEqual(bouncingURL, url)
89+
XCTAssertFalse(routeOneWasCalled)
90+
}
91+
}

0 commit comments

Comments
 (0)