Skip to content

Commit 1241471

Browse files
authored
[Woo POS] Coupons: Provide coupons to ItemList (happy path) (#15417)
2 parents 9d56402 + 38b15ce commit 1241471

File tree

4 files changed

+174
-8
lines changed

4 files changed

+174
-8
lines changed

WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,37 @@ import protocol Yosemite.PointOfSaleItemServiceProtocol
1818

1919
@MainActor
2020
func loadItems(base: ItemListBaseItem) async {
21-
debugPrint("🍍 CouponsController::loadItems called")
22-
itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:]))
21+
// TODO:
22+
// Handle unhappy path
23+
await loadFirstPage()
2324
}
2425

26+
@MainActor
2527
func refreshItems(base: ItemListBaseItem) async {
26-
debugPrint("🍍 CouponsController::refreshItems called")
27-
itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:]))
28+
// TODO:
29+
// Handle unhappy path
30+
await loadFirstPage()
2831
}
2932

33+
@MainActor
3034
func loadNextItems(base: ItemListBaseItem) async {
31-
debugPrint("🍍 CouponsController::loadNextItems called")
32-
itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:]))
35+
// TODO:
36+
// Pagination https://github.com/woocommerce/woocommerce-ios/issues/15343
37+
await loadFirstPage()
38+
}
39+
}
40+
41+
@available(iOS 17.0, *)
42+
private extension PointOfSaleCouponsController {
43+
@MainActor
44+
func loadFirstPage() async {
45+
do {
46+
let coupons = try await itemProvider.providePointOfSaleItems(pageNumber: 1).items
47+
itemsViewState = ItemsViewState(containerState: .content,
48+
itemsStack: .init(root: .loaded(coupons, hasMoreItems: false),
49+
itemStates: [:]))
50+
} catch {
51+
debugPrint(error)
52+
}
3353
}
3454
}

WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,13 @@ private struct ItemListRow: View {
116116
}, label: {
117117
VariationCardView(variation: variation)
118118
})
119-
case .coupon:
120-
EmptyView()
119+
case let .coupon(coupon):
120+
Button(action: {
121+
posModel.addToCart(item)
122+
}, label: {
123+
CouponRowView(couponItem: .init(id: coupon.id,
124+
code: coupon.code))
125+
})
121126
}
122127
}
123128
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,7 @@
15611561
680BA59A2A4C377900F5559D /* UpgradeViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680BA5992A4C377900F5559D /* UpgradeViewState.swift */; };
15621562
680E36B52BD8B9B900E8BCEA /* OrderSubscriptionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 680E36B42BD8B9B900E8BCEA /* OrderSubscriptionTableViewCell.xib */; };
15631563
680E36B72BD8C49F00E8BCEA /* OrderSubscriptionTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680E36B62BD8C49F00E8BCEA /* OrderSubscriptionTableViewCellViewModel.swift */; };
1564+
6818E7C12D93C76700677C16 /* PointOfSaleCouponsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */; };
15641565
681BB5FC2D676047008AF8BB /* POSSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681BB5FB2D676043008AF8BB /* POSSpacing.swift */; };
15651566
681BB5FE2D676061008AF8BB /* POSPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681BB5FD2D676060008AF8BB /* POSPadding.swift */; };
15661567
682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */; };
@@ -4741,6 +4742,7 @@
47414742
680BA5992A4C377900F5559D /* UpgradeViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeViewState.swift; sourceTree = "<group>"; };
47424743
680E36B42BD8B9B900E8BCEA /* OrderSubscriptionTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OrderSubscriptionTableViewCell.xib; sourceTree = "<group>"; };
47434744
680E36B62BD8C49F00E8BCEA /* OrderSubscriptionTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderSubscriptionTableViewCellViewModel.swift; sourceTree = "<group>"; };
4745+
6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsControllerTests.swift; sourceTree = "<group>"; };
47444746
681BB5FB2D676043008AF8BB /* POSSpacing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSSpacing.swift; sourceTree = "<group>"; };
47454747
681BB5FD2D676060008AF8BB /* POSPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSPadding.swift; sourceTree = "<group>"; };
47464748
682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSearchUICommandTests.swift; sourceTree = "<group>"; };
@@ -8215,6 +8217,7 @@
82158217
200BA15C2CF0A9D90006DC5B /* Controllers */ = {
82168218
isa = PBXGroup;
82178219
children = (
8220+
6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */,
82188221
20DB185C2CF5E7560018D3E1 /* PointOfSaleOrderControllerTests.swift */,
82198222
200BA15D2CF0A9EB0006DC5B /* PointOfSaleItemsControllerTests.swift */,
82208223
);
@@ -17372,6 +17375,7 @@
1737217375
B95A45EC2A77D7A60073A91F /* CustomerSelectorViewModelTests.swift in Sources */,
1737317376
3178C1FD26409360000D771A /* BluetoothCardReaderSettingsConnectedViewModelTests.swift in Sources */,
1737417377
02FE89C7231FAA4100E85EF8 /* MainTabBarControllerTests.swift in Sources */,
17378+
6818E7C12D93C76700677C16 /* PointOfSaleCouponsControllerTests.swift in Sources */,
1737517379
B63AAF4B254AD2C6000B28A2 /* URL+SurveyViewControllerTests.swift in Sources */,
1737617380
D802548C26552F41001B2CC1 /* CardPresentModalProcessingTests.swift in Sources */,
1737717381
02952B5127808B08008E9BA3 /* StoreStatsPeriodViewModelTests.swift in Sources */,
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
@testable import WooCommerce
2+
import Testing
3+
import Foundation
4+
5+
import protocol Yosemite.PointOfSaleItemServiceProtocol
6+
import enum Yosemite.POSItem
7+
import struct Yosemite.POSCoupon
8+
import struct Yosemite.PagedItems
9+
import struct Yosemite.POSVariableParentProduct
10+
11+
final class MockPointOfSaleCouponService: PointOfSaleItemServiceProtocol {
12+
var shouldReturnZeroItems = false
13+
14+
func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems<POSItem> {
15+
if shouldReturnZeroItems {
16+
return .init(items: [], hasMorePages: false)
17+
} else {
18+
return .init(items: Self.makeInitialCoupons(),
19+
hasMorePages: false)
20+
}
21+
}
22+
23+
func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems<POSItem> {
24+
return .init(items: [], hasMorePages: false)
25+
}
26+
27+
static func makeInitialCoupons() -> [POSItem] {
28+
let coupon1 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84A")) ?? UUID(), code: "VALID1"))
29+
let coupon2 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84B")) ?? UUID(), code: "VALID2"))
30+
let coupon3 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84C")) ?? UUID(), code: "VALID3"))
31+
return [coupon1, coupon2, coupon3]
32+
}
33+
}
34+
35+
struct PointOfSaleCouponsControllerTests {
36+
@available(iOS 17.0, *)
37+
@Test func loadItems_when_empty_coupons_then_results_in_empty_loaded_state() async throws {
38+
// Given
39+
let couponProvider = MockPointOfSaleCouponService()
40+
couponProvider.shouldReturnZeroItems = true
41+
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
42+
43+
let expectedItemStackState = ItemsStackState(root: .loaded([], hasMoreItems: false), itemStates: [:])
44+
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)
45+
46+
// When
47+
await sut.loadItems(base: .root)
48+
49+
// Then
50+
#expect(sut.itemsViewState == expectedViewState)
51+
}
52+
53+
@available(iOS 17.0, *)
54+
@Test func loadItems_when_some_coupons_then_results_in_coupons_loaded_state() async throws {
55+
// Given
56+
let couponProvider = MockPointOfSaleCouponService()
57+
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
58+
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
59+
60+
let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
61+
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)
62+
63+
// When
64+
await sut.loadItems(base: .root)
65+
66+
// Then
67+
#expect(sut.itemsViewState == expectedViewState)
68+
}
69+
70+
@available(iOS 17.0, *)
71+
@Test func refreshItems_when_empty_coupons_then_results_in_empty_loaded_state() async throws {
72+
// Given
73+
let couponProvider = MockPointOfSaleCouponService()
74+
couponProvider.shouldReturnZeroItems = true
75+
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
76+
77+
let expectedItemStackState = ItemsStackState(root: .loaded([], hasMoreItems: false), itemStates: [:])
78+
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)
79+
80+
// When
81+
await sut.refreshItems(base: .root)
82+
83+
// Then
84+
#expect(sut.itemsViewState == expectedViewState)
85+
}
86+
87+
@available(iOS 17.0, *)
88+
@Test func refreshItems_when_some_coupons_then_results_in_coupons_loaded_state() async throws {
89+
// Given
90+
let couponProvider = MockPointOfSaleCouponService()
91+
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
92+
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
93+
94+
let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
95+
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)
96+
97+
// When
98+
await sut.refreshItems(base: .root)
99+
100+
// Then
101+
#expect(sut.itemsViewState == expectedViewState)
102+
}
103+
104+
@available(iOS 17.0, *)
105+
@Test func loadNextItems_when_empty_coupons_then_results_in_empty_loaded_state() async throws {
106+
// Given
107+
let couponProvider = MockPointOfSaleCouponService()
108+
couponProvider.shouldReturnZeroItems = true
109+
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
110+
111+
let expectedItemStackState = ItemsStackState(root: .loaded([], hasMoreItems: false), itemStates: [:])
112+
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)
113+
114+
// When
115+
await sut.loadNextItems(base: .root)
116+
117+
// Then
118+
#expect(sut.itemsViewState == expectedViewState)
119+
}
120+
121+
@available(iOS 17.0, *)
122+
@Test func loadNextItems_when_some_coupons_then_results_in_coupons_loaded_state() async throws {
123+
// Given
124+
let couponProvider = MockPointOfSaleCouponService()
125+
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
126+
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)
127+
128+
let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
129+
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)
130+
131+
// When
132+
await sut.loadNextItems(base: .root)
133+
134+
// Then
135+
#expect(sut.itemsViewState == expectedViewState)
136+
}
137+
}

0 commit comments

Comments
 (0)