Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,37 @@ import protocol Yosemite.PointOfSaleItemServiceProtocol

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

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

@MainActor
func loadNextItems(base: ItemListBaseItem) async {
debugPrint("🍍 CouponsController::loadNextItems called")
itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:]))
// TODO:
// Pagination https://github.com/woocommerce/woocommerce-ios/issues/15343
await loadFirstPage()
}
}

@available(iOS 17.0, *)
private extension PointOfSaleCouponsController {
@MainActor
func loadFirstPage() async {
do {
let coupons = try await itemProvider.providePointOfSaleItems(pageNumber: 1).items
itemsViewState = ItemsViewState(containerState: .content,
itemsStack: .init(root: .loaded(coupons, hasMoreItems: false),
itemStates: [:]))
} catch {
debugPrint(error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ private struct ItemListRow: View {
}, label: {
VariationCardView(variation: variation)
})
case .coupon:
EmptyView()
case let .coupon(coupon):
Button(action: {
posModel.addToCart(item)
}, label: {
CouponRowView(couponItem: .init(id: coupon.id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Great to see both list and cart functionality integrating so easily 👍

code: coupon.code))
})
}
}
}
Expand Down
8 changes: 6 additions & 2 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,7 @@
680BA59A2A4C377900F5559D /* UpgradeViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680BA5992A4C377900F5559D /* UpgradeViewState.swift */; };
680E36B52BD8B9B900E8BCEA /* OrderSubscriptionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 680E36B42BD8B9B900E8BCEA /* OrderSubscriptionTableViewCell.xib */; };
680E36B72BD8C49F00E8BCEA /* OrderSubscriptionTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680E36B62BD8C49F00E8BCEA /* OrderSubscriptionTableViewCellViewModel.swift */; };
6818E7C12D93C76700677C16 /* PointOfSaleCouponsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */; };
681BB5FC2D676047008AF8BB /* POSSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681BB5FB2D676043008AF8BB /* POSSpacing.swift */; };
681BB5FE2D676061008AF8BB /* POSPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681BB5FD2D676060008AF8BB /* POSPadding.swift */; };
682210ED2909666600814E14 /* CustomerSearchUICommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */; };
Expand Down Expand Up @@ -1607,8 +1608,8 @@
68A905012ACCFC13004C71D3 /* CollapsibleProductCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A905002ACCFC13004C71D3 /* CollapsibleProductCard.swift */; };
68AC9D292ACE598B0042F784 /* ProductImageThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */; };
68AF3C3B2D01481C006F1ED2 /* POSReceiptEligibilityBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */; };
68B681162D9257810098D5CD /* PointOfSaleCouponsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */; };
68B3BA262D9147480000B2F2 /* AISettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B3BA252D9147440000B2F2 /* AISettingsView.swift */; };
68B681162D9257810098D5CD /* PointOfSaleCouponsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */; };
68B6F22B2ADE7ED500D171FC /* TooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B6F22A2ADE7ED500D171FC /* TooltipView.swift */; };
68C31B712A8617C500AE5C5A /* NewNoteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C31B702A8617C500AE5C5A /* NewNoteViewModel.swift */; };
68C53CBE2C1FE59B00C6D80B /* ItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */; };
Expand Down Expand Up @@ -4741,6 +4742,7 @@
680BA5992A4C377900F5559D /* UpgradeViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeViewState.swift; sourceTree = "<group>"; };
680E36B42BD8B9B900E8BCEA /* OrderSubscriptionTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OrderSubscriptionTableViewCell.xib; sourceTree = "<group>"; };
680E36B62BD8C49F00E8BCEA /* OrderSubscriptionTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderSubscriptionTableViewCellViewModel.swift; sourceTree = "<group>"; };
6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsControllerTests.swift; sourceTree = "<group>"; };
681BB5FB2D676043008AF8BB /* POSSpacing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSSpacing.swift; sourceTree = "<group>"; };
681BB5FD2D676060008AF8BB /* POSPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSPadding.swift; sourceTree = "<group>"; };
682210EC2909666600814E14 /* CustomerSearchUICommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerSearchUICommandTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4787,8 +4789,8 @@
68A905002ACCFC13004C71D3 /* CollapsibleProductCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleProductCard.swift; sourceTree = "<group>"; };
68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageThumbnail.swift; sourceTree = "<group>"; };
68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSReceiptEligibilityBanner.swift; sourceTree = "<group>"; };
68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsController.swift; sourceTree = "<group>"; };
68B3BA252D9147440000B2F2 /* AISettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AISettingsView.swift; sourceTree = "<group>"; };
68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsController.swift; sourceTree = "<group>"; };
68B6F22A2ADE7ED500D171FC /* TooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipView.swift; sourceTree = "<group>"; };
68C31B702A8617C500AE5C5A /* NewNoteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteViewModel.swift; sourceTree = "<group>"; };
68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8215,6 +8217,7 @@
200BA15C2CF0A9D90006DC5B /* Controllers */ = {
isa = PBXGroup;
children = (
6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */,
20DB185C2CF5E7560018D3E1 /* PointOfSaleOrderControllerTests.swift */,
200BA15D2CF0A9EB0006DC5B /* PointOfSaleItemsControllerTests.swift */,
);
Expand Down Expand Up @@ -17372,6 +17375,7 @@
B95A45EC2A77D7A60073A91F /* CustomerSelectorViewModelTests.swift in Sources */,
3178C1FD26409360000D771A /* BluetoothCardReaderSettingsConnectedViewModelTests.swift in Sources */,
02FE89C7231FAA4100E85EF8 /* MainTabBarControllerTests.swift in Sources */,
6818E7C12D93C76700677C16 /* PointOfSaleCouponsControllerTests.swift in Sources */,
B63AAF4B254AD2C6000B28A2 /* URL+SurveyViewControllerTests.swift in Sources */,
D802548C26552F41001B2CC1 /* CardPresentModalProcessingTests.swift in Sources */,
02952B5127808B08008E9BA3 /* StoreStatsPeriodViewModelTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
@testable import WooCommerce
import Testing
import Foundation

import protocol Yosemite.PointOfSaleItemServiceProtocol
import enum Yosemite.POSItem
import struct Yosemite.POSCoupon
import struct Yosemite.PagedItems
import struct Yosemite.POSVariableParentProduct

final class MockPointOfSaleCouponService: PointOfSaleItemServiceProtocol {
var shouldReturnZeroItems = false

func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems<POSItem> {
if shouldReturnZeroItems {
return .init(items: [], hasMorePages: false)
} else {
return .init(items: Self.makeInitialCoupons(),
hasMorePages: false)
}
}

func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems<POSItem> {
return .init(items: [], hasMorePages: false)
}

static func makeInitialCoupons() -> [POSItem] {
let coupon1 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84A")) ?? UUID(), code: "VALID1"))
let coupon2 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84B")) ?? UUID(), code: "VALID2"))
let coupon3 = POSItem.coupon(POSCoupon(id: UUID(uuidString: ("DC55E3B9-9D83-4C07-82A7-4C300A50E84C")) ?? UUID(), code: "VALID3"))
return [coupon1, coupon2, coupon3]
}
}

struct PointOfSaleCouponsControllerTests {
@available(iOS 17.0, *)
@Test func loadItems_when_empty_coupons_then_results_in_empty_loaded_state() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
couponProvider.shouldReturnZeroItems = true
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

let expectedItemStackState = ItemsStackState(root: .loaded([], hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
await sut.loadItems(base: .root)

// Then
#expect(sut.itemsViewState == expectedViewState)
}

@available(iOS 17.0, *)
@Test func loadItems_when_some_coupons_then_results_in_coupons_loaded_state() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
await sut.loadItems(base: .root)

// Then
#expect(sut.itemsViewState == expectedViewState)
}

@available(iOS 17.0, *)
@Test func refreshItems_when_empty_coupons_then_results_in_empty_loaded_state() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
couponProvider.shouldReturnZeroItems = true
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

let expectedItemStackState = ItemsStackState(root: .loaded([], hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
await sut.refreshItems(base: .root)

// Then
#expect(sut.itemsViewState == expectedViewState)
}

@available(iOS 17.0, *)
@Test func refreshItems_when_some_coupons_then_results_in_coupons_loaded_state() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
await sut.refreshItems(base: .root)

// Then
#expect(sut.itemsViewState == expectedViewState)
}

@available(iOS 17.0, *)
@Test func loadNextItems_when_empty_coupons_then_results_in_empty_loaded_state() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
couponProvider.shouldReturnZeroItems = true
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

let expectedItemStackState = ItemsStackState(root: .loaded([], hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
await sut.loadNextItems(base: .root)

// Then
#expect(sut.itemsViewState == expectedViewState)
}

@available(iOS 17.0, *)
@Test func loadNextItems_when_some_coupons_then_results_in_coupons_loaded_state() async throws {
// Given
let couponProvider = MockPointOfSaleCouponService()
let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons()
let sut = PointOfSaleCouponsController(itemProvider: couponProvider)

let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:])
let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState)

// When
await sut.loadNextItems(base: .root)

// Then
#expect(sut.itemsViewState == expectedViewState)
}
}
Loading