Skip to content

Commit dc3ddc9

Browse files
authored
Merge pull request #8660 from woocommerce/feat/8558-domain-selector-yosemite
Domain selector for paid domains: Yosemite layer changes
2 parents 024fbf8 + 9daeca0 commit dc3ddc9

File tree

4 files changed

+132
-5
lines changed

4 files changed

+132
-5
lines changed

Yosemite/Yosemite/Actions/DomainAction.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,20 @@ import Foundation
44
//
55
public enum DomainAction: Action {
66
case loadFreeDomainSuggestions(query: String, completion: (Result<[FreeDomainSuggestion], Error>) -> Void)
7+
case loadPaidDomainSuggestions(query: String, completion: (Result<[PaidDomainSuggestion], Error>) -> Void)
78
case loadDomains(siteID: Int64, completion: (Result<[SiteDomain], Error>) -> Void)
89
}
10+
11+
/// Necessary data for the domain selector flow with paid domains.
12+
public struct PaidDomainSuggestion: Equatable {
13+
/// ID of the WPCOM product.
14+
public let productID: Int64
15+
/// Domain name.
16+
public let name: String
17+
/// Duration of the product subscription (e.g. "year"), localized on the backend.
18+
public let term: String
19+
/// Cost string including the currency.
20+
public let cost: String
21+
/// Optional sale cost string including the currency.
22+
public let saleCost: String?
23+
}

Yosemite/Yosemite/Stores/DomainStore.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public final class DomainStore: Store {
3333
switch action {
3434
case .loadFreeDomainSuggestions(let query, let completion):
3535
loadFreeDomainSuggestions(query: query, completion: completion)
36+
case .loadPaidDomainSuggestions(let query, let completion):
37+
loadPaidDomainSuggestions(query: query, completion: completion)
3638
case .loadDomains(let siteID, let completion):
3739
loadDomains(siteID: siteID, completion: completion)
3840
}
@@ -47,6 +49,35 @@ private extension DomainStore {
4749
}
4850
}
4951

52+
func loadPaidDomainSuggestions(query: String, completion: @escaping (Result<[PaidDomainSuggestion], Error>) -> Void) {
53+
Task { @MainActor in
54+
do {
55+
// Fetches domain products and domain suggestions at the same time.
56+
async let domainProducts = remote.loadDomainProducts()
57+
async let domainSuggestions = remote.loadPaidDomainSuggestions(query: query)
58+
let domainProductsByID = try await domainProducts.reduce([Int64: DomainProduct](), { partialResult, product in
59+
var productsByID = partialResult
60+
productsByID[product.productID] = product
61+
return productsByID
62+
})
63+
let paidDomainSuggestions: [PaidDomainSuggestion] = try await domainSuggestions.compactMap { domainSuggestion in
64+
let productID = domainSuggestion.productID
65+
guard let domainProduct = domainProductsByID[productID] else {
66+
return nil
67+
}
68+
return PaidDomainSuggestion(productID: domainSuggestion.productID,
69+
name: domainSuggestion.name,
70+
term: domainProduct.term,
71+
cost: domainProduct.cost,
72+
saleCost: domainProduct.saleCost)
73+
}
74+
completion(.success(paidDomainSuggestions))
75+
} catch {
76+
completion(.failure(error))
77+
}
78+
}
79+
}
80+
5081
func loadDomains(siteID: Int64, completion: @escaping (Result<[SiteDomain], Error>) -> Void) {
5182
Task { @MainActor in
5283
let result = await Result { try await remote.loadDomains(siteID: siteID) }

Yosemite/YosemiteTests/Mocks/Networking/Remote/MockDomainRemote.swift

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import XCTest
44
/// Mock for `DomainRemote`.
55
///
66
final class MockDomainRemote {
7-
/// The results to return in `loadDomainSuggestions`.
7+
/// The results to return in `loadFreeDomainSuggestions`.
88
private var loadDomainSuggestionsResult: Result<[FreeDomainSuggestion], Error>?
99

10+
/// The results to return in `loadPaidDomainSuggestions`.
11+
private var loadPaidDomainSuggestionsResult: Result<[PaidDomainSuggestion], Error>?
12+
13+
/// The results to return in `loadDomainProducts`.
14+
private var loadDomainProductsResult: Result<[DomainProduct], Error>?
15+
1016
/// The results to return in `loadDomains`.
1117
private var loadDomainsResult: Result<[SiteDomain], Error>?
1218

@@ -15,6 +21,16 @@ final class MockDomainRemote {
1521
loadDomainSuggestionsResult = result
1622
}
1723

24+
/// Returns the value when `loadPaidDomainSuggestions` is called.
25+
func whenLoadingPaidDomainSuggestions(thenReturn result: Result<[PaidDomainSuggestion], Error>) {
26+
loadPaidDomainSuggestionsResult = result
27+
}
28+
29+
/// Returns the value when `loadDomainProducts` is called.
30+
func whenLoadingDomainProducts(thenReturn result: Result<[DomainProduct], Error>) {
31+
loadDomainProductsResult = result
32+
}
33+
1834
/// Returns the value when `loadDomains` is called.
1935
func whenLoadingDomains(thenReturn result: Result<[SiteDomain], Error>) {
2036
loadDomainsResult = result
@@ -31,13 +47,19 @@ extension MockDomainRemote: DomainRemoteProtocol {
3147
}
3248

3349
func loadPaidDomainSuggestions(query: String) async throws -> [PaidDomainSuggestion] {
34-
// TODO: 8558 - Yosemite layer for paid domains
35-
throw NetworkError.notFound
50+
guard let result = loadPaidDomainSuggestionsResult else {
51+
XCTFail("Could not find result for loading domain suggestions.")
52+
throw NetworkError.notFound
53+
}
54+
return try result.get()
3655
}
3756

3857
func loadDomainProducts() async throws -> [DomainProduct] {
39-
// TODO: 8558 - Yosemite layer for paid domains
40-
throw NetworkError.notFound
58+
guard let result = loadDomainProductsResult else {
59+
XCTFail("Could not find result for loading domain products.")
60+
throw NetworkError.notFound
61+
}
62+
return try result.get()
4163
}
4264

4365
func loadDomains(siteID: Int64) async throws -> [SiteDomain] {

Yosemite/YosemiteTests/Stores/DomainStoreTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,65 @@ final class DomainStoreTests: XCTestCase {
7171
XCTAssertEqual(error as? NetworkError, .timeout)
7272
}
7373

74+
// MARK: - `loadPaidDomainSuggestions`
75+
76+
func test_loadPaidDomainSuggestions_returns_suggestions_on_success() throws {
77+
// Given
78+
remote.whenLoadingPaidDomainSuggestions(thenReturn: .success([.init(name: "paid.domain", productID: 203, supportsPrivacy: true)]))
79+
remote.whenLoadingDomainProducts(thenReturn: .success([.init(productID: 203, term: "year", cost: "NT$610.00", saleCost: "NT$154.00")]))
80+
81+
// When
82+
let result = waitFor { promise in
83+
self.store.onAction(DomainAction.loadPaidDomainSuggestions(query: "domain") { result in
84+
promise(result)
85+
})
86+
}
87+
88+
// Then
89+
XCTAssertTrue(result.isSuccess)
90+
let suggestions = try XCTUnwrap(result.get())
91+
XCTAssertEqual(suggestions, [.init(productID: 203, name: "paid.domain", term: "year", cost: "NT$610.00", saleCost: "NT$154.00")])
92+
}
93+
94+
func test_loadPaidDomainSuggestions_returns_empty_suggestions_from_failed_productID_mapping() throws {
95+
// Given
96+
remote.whenLoadingPaidDomainSuggestions(thenReturn: .success([.init(name: "paid.domain", productID: 203, supportsPrivacy: true)]))
97+
// The product ID does not match the domain suggestion.
98+
remote.whenLoadingDomainProducts(thenReturn: .success([.init(productID: 156, term: "year", cost: "NT$610.00", saleCost: "NT$154.00")]))
99+
100+
// When
101+
let result = waitFor { promise in
102+
self.store.onAction(DomainAction.loadPaidDomainSuggestions(query: "domain") { result in
103+
promise(result)
104+
})
105+
}
106+
107+
// Then
108+
XCTAssertTrue(result.isSuccess)
109+
let suggestions = try XCTUnwrap(result.get())
110+
XCTAssertEqual(suggestions, [])
111+
}
112+
113+
func test_loadPaidDomainSuggestions_returns_error_on_failure() throws {
114+
// Given
115+
remote.whenLoadingPaidDomainSuggestions(thenReturn: .failure(NetworkError.invalidURL))
116+
remote.whenLoadingDomainProducts(thenReturn: .failure(NetworkError.timeout))
117+
118+
// When
119+
let result = waitFor { promise in
120+
self.store.onAction(DomainAction.loadPaidDomainSuggestions(query: "domain") { result in
121+
promise(result)
122+
})
123+
}
124+
125+
// Then
126+
XCTAssertTrue(result.isFailure)
127+
let error = try XCTUnwrap(result.failure)
128+
// The error of `loadDomainProducts` is returned since it is the first async call.
129+
XCTAssertEqual(error as? NetworkError, .timeout)
130+
}
131+
132+
74133
// MARK: - `loadDomains`
75134

76135
func test_loadDomains_returns_domains_on_success() throws {

0 commit comments

Comments
 (0)