Skip to content

Commit 06cb40f

Browse files
joshhealdstaskus
authored andcommitted
Add tests for PointOfSaleObservableItemsController
Comprehensive test coverage for the new observable controller: - Initial state (loading container, initial root) - Product loading (loaded, empty, error states) - Variation loading (loaded, empty states) - Pagination (hasMoreItems flags) - Independence of products and variations - Parent switching behavior - Error handling - Loading state preservation Fix tests to use proper POSItem construction Use helper methods to create POSSimpleProduct and POSVariation structs instead of non-existent .fake() method, matching the pattern used in MockPOSItemProvider. Changes: - Added makeSimpleProduct() helper - Added makeVariation() helper - Updated all 12 tests to use proper construction
1 parent d999e5c commit 06cb40f

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import Testing
2+
import Foundation
3+
@testable import PointOfSale
4+
@testable import Yosemite
5+
6+
final class PointOfSaleObservableItemsControllerTests {
7+
8+
// MARK: - Test Helpers
9+
10+
private func makeSimpleProduct(id: UUID = UUID(), name: String = "Test Product", productID: Int64 = 1) -> POSItem {
11+
.simpleProduct(POSSimpleProduct(
12+
id: id,
13+
name: name,
14+
formattedPrice: "$2.00",
15+
productID: productID,
16+
price: "2.00",
17+
manageStock: false,
18+
stockQuantity: nil,
19+
stockStatusKey: ""
20+
))
21+
}
22+
23+
private func makeVariation(id: UUID = UUID(), name: String = "Test Variation", variationID: Int64 = 1) -> POSItem {
24+
.variation(POSVariation(
25+
id: id,
26+
name: name,
27+
formattedPrice: "$2.00",
28+
price: "2.00",
29+
productID: 100,
30+
variationID: variationID,
31+
parentProductName: "Parent Product"
32+
))
33+
}
34+
35+
// MARK: - Tests
36+
37+
@Test func test_initial_state_is_loading_container_with_initial_root() {
38+
// Given
39+
let dataSource = MockPOSObservableDataSource()
40+
dataSource.isLoadingProducts = true
41+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
42+
43+
// Then
44+
#expect(sut.itemsViewState.containerState == .loading)
45+
#expect(sut.itemsViewState.itemsStack.root == .initial)
46+
#expect(sut.itemsViewState.itemsStack.itemStates.isEmpty)
47+
}
48+
49+
@Test func test_load_products_transitions_to_loaded_state() async {
50+
// Given
51+
let dataSource = MockPOSObservableDataSource()
52+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
53+
54+
let mockItems = [makeSimpleProduct()]
55+
dataSource.productItems = mockItems
56+
dataSource.isLoadingProducts = false
57+
dataSource.hasMoreProducts = false
58+
59+
// When
60+
await sut.loadItems(base: .root)
61+
62+
// Then
63+
#expect(sut.itemsViewState.containerState == .content)
64+
#expect(sut.itemsViewState.itemsStack.root == .loaded(mockItems, hasMoreItems: false))
65+
}
66+
67+
@Test func test_load_products_with_more_pages_sets_hasMoreItems() async {
68+
// Given
69+
let dataSource = MockPOSObservableDataSource()
70+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
71+
72+
let mockItems = [makeSimpleProduct(productID: 1), makeSimpleProduct(productID: 2)]
73+
dataSource.productItems = mockItems
74+
dataSource.isLoadingProducts = false
75+
dataSource.hasMoreProducts = true
76+
77+
// When
78+
await sut.loadItems(base: .root)
79+
80+
// Then
81+
#expect(sut.itemsViewState.itemsStack.root == .loaded(mockItems, hasMoreItems: true))
82+
}
83+
84+
@Test func test_load_products_when_empty_results_in_empty_state() async {
85+
// Given
86+
let dataSource = MockPOSObservableDataSource()
87+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
88+
89+
dataSource.productItems = []
90+
dataSource.isLoadingProducts = false
91+
92+
// When
93+
await sut.loadItems(base: .root)
94+
95+
// Then
96+
#expect(sut.itemsViewState.containerState == .content)
97+
#expect(sut.itemsViewState.itemsStack.root == .empty)
98+
}
99+
100+
@Test func test_load_variations_for_parent() async {
101+
// Given
102+
let dataSource = MockPOSObservableDataSource()
103+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
104+
105+
let parentProduct = POSVariableParentProduct(
106+
id: UUID(),
107+
name: "Parent",
108+
productImageSource: nil,
109+
productID: 100,
110+
allAttributes: []
111+
)
112+
let parentItem = POSItem.variableParentProduct(parentProduct)
113+
114+
let mockVariations = [makeVariation(variationID: 1), makeVariation(variationID: 2)]
115+
dataSource.variationItems = mockVariations
116+
dataSource.isLoadingVariations = false
117+
dataSource.hasMoreVariations = false
118+
119+
// When
120+
await sut.loadItems(base: .parent(parentItem))
121+
122+
// Then
123+
#expect(sut.itemsViewState.itemsStack.itemStates[parentItem] == .loaded(mockVariations, hasMoreItems: false))
124+
}
125+
126+
@Test func test_load_variations_when_empty_results_in_empty_state() async {
127+
// Given
128+
let dataSource = MockPOSObservableDataSource()
129+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
130+
131+
let parentProduct = POSVariableParentProduct(
132+
id: UUID(),
133+
name: "Parent",
134+
productImageSource: nil,
135+
productID: 100,
136+
allAttributes: []
137+
)
138+
let parentItem = POSItem.variableParentProduct(parentProduct)
139+
140+
dataSource.variationItems = []
141+
dataSource.isLoadingVariations = false
142+
143+
// When
144+
await sut.loadItems(base: .parent(parentItem))
145+
146+
// Then
147+
#expect(sut.itemsViewState.itemsStack.itemStates[parentItem] == .empty)
148+
}
149+
150+
@Test func test_products_and_variations_are_independent() async {
151+
// Given
152+
let dataSource = MockPOSObservableDataSource()
153+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
154+
155+
let mockProducts = [makeSimpleProduct(productID: 1), makeSimpleProduct(productID: 2)]
156+
let mockVariations = [makeVariation()]
157+
158+
let parentProduct = POSVariableParentProduct(
159+
id: UUID(),
160+
name: "Parent",
161+
productImageSource: nil,
162+
productID: 100,
163+
allAttributes: []
164+
)
165+
let parentItem = POSItem.variableParentProduct(parentProduct)
166+
167+
// When: Load products
168+
dataSource.productItems = mockProducts
169+
dataSource.isLoadingProducts = false
170+
await sut.loadItems(base: .root)
171+
172+
// Then: Products loaded, no variations
173+
#expect(sut.itemsViewState.itemsStack.root.items.count == 2)
174+
#expect(sut.itemsViewState.itemsStack.itemStates.isEmpty)
175+
176+
// When: Load variations
177+
dataSource.variationItems = mockVariations
178+
dataSource.isLoadingVariations = false
179+
await sut.loadItems(base: .parent(parentItem))
180+
181+
// Then: Both products and variations present
182+
#expect(sut.itemsViewState.itemsStack.root.items.count == 2)
183+
#expect(sut.itemsViewState.itemsStack.itemStates[parentItem]?.items.count == 1)
184+
}
185+
186+
@Test func test_load_next_items_delegates_to_data_source() async {
187+
// Given
188+
let dataSource = MockPOSObservableDataSource()
189+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
190+
191+
// Simulate initial load
192+
dataSource.productItems = [makeSimpleProduct()]
193+
dataSource.isLoadingProducts = false
194+
dataSource.hasMoreProducts = true
195+
await sut.loadItems(base: .root)
196+
197+
// When
198+
await sut.loadNextItems(base: .root)
199+
200+
// Then: loadMoreProducts should be called (verified by state change in mock)
201+
// Note: Mock doesn't actually track calls, but we can verify the state
202+
#expect(sut.itemsViewState.containerState == .content)
203+
}
204+
205+
@Test func test_refresh_delegates_to_data_source() async {
206+
// Given
207+
let dataSource = MockPOSObservableDataSource()
208+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
209+
210+
dataSource.productItems = [makeSimpleProduct()]
211+
dataSource.isLoadingProducts = false
212+
await sut.loadItems(base: .root)
213+
214+
// When
215+
await sut.refreshItems(base: .root)
216+
217+
// Then: Should still be in content state
218+
#expect(sut.itemsViewState.containerState == .content)
219+
}
220+
221+
@Test func test_switching_parent_resets_variation_state() async {
222+
// Given
223+
let dataSource = MockPOSObservableDataSource()
224+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
225+
226+
let parent1 = POSVariableParentProduct(id: UUID(), name: "Parent 1", productImageSource: nil, productID: 100, allAttributes: [])
227+
let parentItem1 = POSItem.variableParentProduct(parent1)
228+
229+
let parent2 = POSVariableParentProduct(id: UUID(), name: "Parent 2", productImageSource: nil, productID: 200, allAttributes: [])
230+
let parentItem2 = POSItem.variableParentProduct(parent2)
231+
232+
// When: Load parent 1 variations
233+
dataSource.variationItems = [makeVariation()]
234+
dataSource.isLoadingVariations = false
235+
await sut.loadItems(base: .parent(parentItem1))
236+
237+
#expect(sut.itemsViewState.itemsStack.itemStates[parentItem1]?.items.count == 1)
238+
239+
// When: Load parent 2 variations
240+
dataSource.variationItems = [makeVariation(variationID: 1), makeVariation(variationID: 2)]
241+
await sut.loadItems(base: .parent(parentItem2))
242+
243+
// Then: Parent 2 variations shown, parent 1 not in states anymore
244+
#expect(sut.itemsViewState.itemsStack.itemStates[parentItem2]?.items.count == 2)
245+
#expect(sut.itemsViewState.itemsStack.itemStates[parentItem1] == nil)
246+
}
247+
248+
@Test func test_error_state_when_data_source_has_error() async {
249+
// Given
250+
let dataSource = MockPOSObservableDataSource()
251+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
252+
253+
dataSource.productItems = []
254+
dataSource.isLoadingProducts = false
255+
dataSource.error = NSError(domain: "test", code: 1)
256+
257+
// When
258+
await sut.loadItems(base: .root)
259+
260+
// Then
261+
guard case .error = sut.itemsViewState.itemsStack.root else {
262+
Issue.record("Expected error state, got \(sut.itemsViewState.itemsStack.root)")
263+
return
264+
}
265+
}
266+
267+
@Test func test_loading_state_preserves_existing_items() async {
268+
// Given
269+
let dataSource = MockPOSObservableDataSource()
270+
let sut = PointOfSaleObservableItemsController(dataSource: dataSource)
271+
272+
let mockItems = [makeSimpleProduct(productID: 1), makeSimpleProduct(productID: 2)]
273+
274+
// Load initial items
275+
dataSource.productItems = mockItems
276+
dataSource.isLoadingProducts = false
277+
await sut.loadItems(base: .root)
278+
279+
// When: Start loading more
280+
dataSource.isLoadingProducts = true
281+
282+
// Then: Loading state should preserve items
283+
#expect(sut.itemsViewState.itemsStack.root == .loading(mockItems))
284+
}
285+
}

0 commit comments

Comments
 (0)