ALFMOB-150: Persist Wishlist & Bag#70
Conversation
ALFMOB-150: Persist Wishlist & Bag
…list-and-bag Revert "ALFMOB-150: Persist Wishlist & Bag"
|
Architecture and test coverage both look solid — the domain-protocol + DTO split is the right call and the tests are thorough. A few things worth resolving before merge, mostly around the bag side of the shared DTO and a couple of silent-failure cases. Main thing: the simplification of dropping full variant fidelity makes sense for the wishlist (row is display-only, PDP re-fetches), but the same DTO is doing duty for the bag, and the bag will feed checkout. The DTO drops Smaller stuff inline. |
There was a problem hiding this comment.
Pull request overview
This PR introduces persistence for Wishlist and Bag items across app sessions by adding a UserDefaults-backed storage layer and DTO mapping so domain models remain free of Codable conformance. It also includes follow-up behavioral/UI adjustments in Wishlist/PLP/PDP to better align interactions with product/variant selection constraints.
Changes:
- Add
WishlistStoreProtocol/BagStoreProtocoland a sharedUserDefaultsStoreimplementation usingPersistedProductDTOsnapshots. - Update
WishlistService/BagServiceto use the new stores and add unit tests for services, the store, and DTO round-trips (plus memory leak tracking helper). - Adjust Wishlist/PLP removal semantics (product-id based) and update PDP add-to-bag gating + size preselection behavior.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Alfie/AlfieKit/Tests/WishlistTests/WishlistTests.swift | Adds WishlistViewModel tests for navigation/delete behaviors. |
| Alfie/AlfieKit/Tests/ProductListingTests/ProductListingViewModelTests.swift | Adds PLP wishlist heart toggle test coverage (product-id matching). |
| Alfie/AlfieKit/Tests/ProductDetailsTests/ProductDetailsViewModelTests.swift | Adds PDP tests for size preselection and add-to-bag enablement rules. |
| Alfie/AlfieKit/Tests/CoreTests/ServiceTests/WishlistServiceTests.swift | Adds unit tests for store-backed WishlistService behavior. |
| Alfie/AlfieKit/Tests/CoreTests/ServiceTests/UserDefaultsStoreTests.swift | Adds tests for UserDefaultsStore save/load and DTO round-trip. |
| Alfie/AlfieKit/Tests/CoreTests/ServiceTests/PersistedProductDTOTests.swift | Adds detailed mapping + Codable round-trip tests for persistence DTOs. |
| Alfie/AlfieKit/Tests/CoreTests/ServiceTests/BagServiceTests.swift | Adds unit tests for store-backed BagService behavior. |
| Alfie/AlfieKit/Sources/Wishlist/UI/WishlistViewModel.swift | Changes delete to remove-by-product-id; changes “add to bag” handling to navigate to PDP. |
| Alfie/AlfieKit/Sources/Wishlist/UI/WishlistView.swift | Updates preview dependencies after Wishlist dependency container changes. |
| Alfie/AlfieKit/Sources/Wishlist/Models/WishlistDependencyContainer.swift | Removes bag dependency from the Wishlist container initializer. |
| Alfie/AlfieKit/Sources/TestUtils/Helpers/XCTestCase+MemoryLeak.swift | Introduces trackForMemoryLeak teardown helper for tests. |
| Alfie/AlfieKit/Sources/SharedUI/Components/ProductCards/VerticalProductCard.swift | Removes size row from vertical product card UI and related accessibility id. |
| Alfie/AlfieKit/Sources/ProductListing/UI/ProductListingViewModel.swift | Updates wishlist matching/removal to use product id (not SKU/variant id). |
| Alfie/AlfieKit/Sources/ProductDetails/UI/ProductDetailsViewModel.swift | Adds add-to-bag gating logic, exposes new derived state, removes size auto-selection. |
| Alfie/AlfieKit/Sources/ProductDetails/UI/ProductDetailsView.swift | Consumes new PDP view model properties for size selector and CTA enablement/text. |
| Alfie/AlfieKit/Sources/Model/UI/ProductCard/VerticalProductCardViewModel.swift | Removes size fields from the vertical product card view model. |
| Alfie/AlfieKit/Sources/Model/Services/Wishlist/WishlistStoreProtocol.swift | Adds wishlist persistence boundary protocol. |
| Alfie/AlfieKit/Sources/Model/Services/Wishlist/WishlistServiceProtocol.swift | Changes wishlist removal API to remove by product id. |
| Alfie/AlfieKit/Sources/Model/Services/Bag/BagStoreProtocol.swift | Adds bag persistence boundary protocol. |
| Alfie/AlfieKit/Sources/Model/Models/ProductDetails/Protocols/ProductDetailsViewModelProtocol.swift | Extends PDP protocol with new derived state requirements. |
| Alfie/AlfieKit/Sources/Mocks/Core/Services/MockWishlistStore.swift | Adds wishlist store mock for service testing. |
| Alfie/AlfieKit/Sources/Mocks/Core/Services/MockWishlistService.swift | Updates mock wishlist API to remove by product id. |
| Alfie/AlfieKit/Sources/Mocks/Core/Services/MockBagStore.swift | Adds bag store mock for service testing. |
| Alfie/AlfieKit/Sources/Mocks/Core/Features/MockProductDetailsViewModel.swift | Updates mock PDP view model to satisfy new protocol requirements. |
| Alfie/AlfieKit/Sources/Core/Services/Wishlist/WishlistService.swift | Refactors wishlist service to store-backed implementation. |
| Alfie/AlfieKit/Sources/Core/Services/Persistence/UserDefaultsStore.swift | Adds shared UserDefaults-backed store implementing bag + wishlist store protocols. |
| Alfie/AlfieKit/Sources/Core/Services/Persistence/PersistedProductDTO.swift | Adds persistence DTOs + mapping to/from SelectedProduct. |
| Alfie/AlfieKit/Sources/Core/Services/Bag/BagService.swift | Refactors bag service to store-backed implementation. |
| Alfie/AlfieKit/Sources/AppFeature/Navigation/AppFeatureViewModel.swift | Updates wishlist dependency injection after container signature change. |
| Alfie/Alfie/Service/ServiceProvider.swift | Wires BagService/WishlistService to UserDefaultsStore using new StorageKey values. |
| Alfie/Alfie/Helpers/StorageKey.swift | Adds storage key enum for bag/wishlist persistence. |
| Alfie/Alfie/Alfie.xctestplan | Adds WishlistTests target to the test plan. |
| @@ -0,0 +1,18 @@ | |||
| import XCTest | |||
|
|
|||
| extension XCTestCase { | |||
|
|
||
| public func addProduct(_ product: SelectedProduct) { | ||
| var products = store.load() | ||
| guard !products.contains(where: { $0.id == product.id }) else { return } |
| func removeProduct(_ product: SelectedProduct) | ||
| func removeProduct(withId productId: String) | ||
| func getWishlistContent() -> [SelectedProduct] | ||
| } |
| public func didTapAddToBag(for selectedProduct: SelectedProduct) { | ||
| dependencies.bagService.addProduct(selectedProduct) | ||
| dependencies.analytics.trackAddToBag(productID: selectedProduct.product.id) | ||
| navigate(.productDetails(.productDetails(.selectedProduct(selectedProduct)))) | ||
| } |
| public var priceType: PriceType | ||
| public var colorTitle: String? | ||
| public var color: String? | ||
| public var sizeTitle: String? | ||
| public var size: String? | ||
| public var addToBagTitle: String? |
| self.wishlistService = wishlistService | ||
| self.bagService = bagService | ||
| self.analytics = analytics | ||
| } |
Ticket
ALFMOB-150
Summary
UserDefaults.WishlistStoreProtocol/BagStoreProtocol(domain layer) decouple services from the storage backend, making future backend sync a drop-in replacement.UserDefaultsStore(infrastructure) implements both protocols and maps domainSelectedProduct↔ internalPersistedProductDTO(Codable). KeepsProduct/PriceTypedomain models free ofCodableconformance.WishlistService/BagServicenow load on init and save on every add/remove; public protocol surfaces unchanged so no consumer (ViewModels, PDP, PLP) had to be touched.StorageKeyenum exposing.wishlistItemsand.bagItems— no user-scoped namespacing, so login/logout and guest usage all share the same persisted data.WishlistService,BagService,UserDefaultsStore, and the DTO round-trip, including memory-leak tracking viatrackForMemoryLeak.Screenshot