Skip to content

ALFMOB-150: Persist Wishlist & Bag#70

Open
khoinguyen-mindera wants to merge 28 commits into
mainfrom
ALFMOB-150-persist-wishlist-and-bag
Open

ALFMOB-150: Persist Wishlist & Bag#70
khoinguyen-mindera wants to merge 28 commits into
mainfrom
ALFMOB-150-persist-wishlist-and-bag

Conversation

@khoinguyen-mindera
Copy link
Copy Markdown
Contributor

@khoinguyen-mindera khoinguyen-mindera commented May 15, 2026

Ticket

ALFMOB-150

Summary

  • Persist Wishlist and Bag across app sessions using UserDefaults.
  • New WishlistStoreProtocol / BagStoreProtocol (domain layer) decouple services from the storage backend, making future backend sync a drop-in replacement.
  • Shared UserDefaultsStore (infrastructure) implements both protocols and maps domain SelectedProduct ↔ internal PersistedProductDTO (Codable). Keeps Product/PriceType domain models free of Codable conformance.
  • WishlistService / BagService now load on init and save on every add/remove; public protocol surfaces unchanged so no consumer (ViewModels, PDP, PLP) had to be touched.
  • Single shared StorageKey enum exposing .wishlistItems and .bagItems — no user-scoped namespacing, so login/logout and guest usage all share the same persisted data.
  • Data is plain JSON, unencrypted, no item limit, removed on app uninstall.
  • Added unit tests for WishlistService, BagService, UserDefaultsStore, and the DTO round-trip, including memory-leak tracking via trackForMemoryLeak.

Screenshot

…list-and-bag

Revert "ALFMOB-150: Persist Wishlist & Bag"
Base automatically changed from ALFMOB-162-improve-wishlist-functionality to main May 19, 2026 11:25
@amccall-mindera
Copy link
Copy Markdown
Contributor

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 currencyCode and the numeric amount — keeping only amountFormatted. After relaunch, you can render the bag row but can't compute a subtotal, apply a discount, or convert currency without a refetch. Is the plan that the bag also re-hydrates on open (same as PDP), or is there a follow-up to extend the DTO for bag-side fidelity?

Smaller stuff inline.

Copilot AI review requested due to automatic review settings May 23, 2026 03:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 / BagStoreProtocol and a shared UserDefaultsStore implementation using PersistedProductDTO snapshots.
  • Update WishlistService / BagService to 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]
}
Comment on lines 41 to 43
public func didTapAddToBag(for selectedProduct: SelectedProduct) {
dependencies.bagService.addProduct(selectedProduct)
dependencies.analytics.trackAddToBag(productID: selectedProduct.product.id)
navigate(.productDetails(.productDetails(.selectedProduct(selectedProduct))))
}
Comment on lines 9 to 12
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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants