Skip to content

Commit ace2b98

Browse files
committed
Inject searchViewModel from container
1 parent a2d0dc8 commit ace2b98

File tree

6 files changed

+60
-84
lines changed

6 files changed

+60
-84
lines changed

WooCommerce/Classes/Bookings/BookingList/BookingListContainerView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct BookingListContainerView: View {
1919
ForEach(BookingListTab.allCases, id: \.rawValue) { tab in
2020
BookingListView(
2121
viewModel: viewModel.listViewModel(for: tab),
22+
searchViewModel: viewModel.searchViewModel(for: tab),
2223
selectedBooking: $selectedBooking
2324
)
2425
.tag(tab)

WooCommerce/Classes/Bookings/BookingList/BookingListContainerViewModel.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ final class BookingListContainerViewModel: ObservableObject {
77
private let upcomingListViewModel: BookingListViewModel
88
private let allListViewModel: BookingListViewModel
99

10+
private let todaySearchViewModel: BookingSearchViewModel
11+
private let upcomingSearchViewModel: BookingSearchViewModel
12+
private let allSearchViewModel: BookingSearchViewModel
13+
1014
@Published var selectedTab: BookingListTab = .today
1115
@Published var searchQuery: String = ""
1216

@@ -18,16 +22,29 @@ final class BookingListContainerViewModel: ObservableObject {
1822
self.todayListViewModel = BookingListViewModel(
1923
siteID: siteID,
2024
type: .today,
21-
searchQueryPublisher: searchQueryPublisher
2225
)
2326
self.upcomingListViewModel = BookingListViewModel(
2427
siteID: siteID,
2528
type: .upcoming,
26-
searchQueryPublisher: searchQueryPublisher
2729
)
2830
self.allListViewModel = BookingListViewModel(
2931
siteID: siteID,
3032
type: .all,
33+
)
34+
35+
self.todaySearchViewModel = BookingSearchViewModel(
36+
siteID: siteID,
37+
type: .today,
38+
searchQueryPublisher: searchQueryPublisher
39+
)
40+
self.upcomingSearchViewModel = BookingSearchViewModel(
41+
siteID: siteID,
42+
type: .upcoming,
43+
searchQueryPublisher: searchQueryPublisher
44+
)
45+
self.allSearchViewModel = BookingSearchViewModel(
46+
siteID: siteID,
47+
type: .all,
3148
searchQueryPublisher: searchQueryPublisher
3249
)
3350

@@ -47,6 +64,17 @@ final class BookingListContainerViewModel: ObservableObject {
4764
allListViewModel
4865
}
4966
}
67+
68+
func searchViewModel(for tab: BookingListTab) -> BookingSearchViewModel {
69+
switch tab {
70+
case .today:
71+
todaySearchViewModel
72+
case .upcoming:
73+
upcomingSearchViewModel
74+
case .all:
75+
allSearchViewModel
76+
}
77+
}
5078
}
5179

5280
enum BookingListTab: Int, CaseIterable {

WooCommerce/Classes/Bookings/BookingList/BookingListView.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,29 @@ import struct Yosemite.Booking
44
struct BookingListView: View {
55
@ObservedObject private var viewModel: BookingListViewModel
66
@ObservedObject private var searchViewModel: BookingSearchViewModel
7+
78
@StateObject private var connectivityMonitor = ConnectivityMonitor()
89
@ScaledMetric private var scale: CGFloat = 1.0
10+
911
@Binding var selectedBooking: Booking?
1012

11-
init(viewModel: BookingListViewModel, selectedBooking: Binding<Booking?>) {
13+
init(viewModel: BookingListViewModel,
14+
searchViewModel: BookingSearchViewModel,
15+
selectedBooking: Binding<Booking?>) {
1216
self.viewModel = viewModel
13-
self.searchViewModel = viewModel.searchViewModel
17+
self.searchViewModel = searchViewModel
1418
self._selectedBooking = selectedBooking
1519
}
1620

1721
var body: some View {
18-
VStack {
19-
mainContentView
20-
.overlay {
21-
searchContentView
22-
.renderedIf(searchViewModel.currentSearchQuery.isNotEmpty)
23-
}
24-
}
25-
.task {
26-
viewModel.loadBookings()
27-
}
22+
mainContentView
23+
.task {
24+
viewModel.loadBookings()
25+
}
26+
.overlay {
27+
searchContentView
28+
.renderedIf(searchViewModel.currentSearchQuery.isNotEmpty)
29+
}
2830
}
2931
}
3032

@@ -67,7 +69,7 @@ private extension BookingListView {
6769
} else {
6870
bookingList(with: searchViewModel.searchResults,
6971
onNextPage: { searchViewModel.onLoadNextPageAction() },
70-
onRefresh: {await searchViewModel.onRefreshAction()})
72+
onRefresh: { await searchViewModel.onRefreshAction() })
7173
}
7274
}
7375
.overlay(alignment: .bottom) {

WooCommerce/Classes/Bookings/BookingList/BookingListViewModel.swift

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ final class BookingListViewModel: ObservableObject {
1111

1212
@Published var errorFetching = false
1313

14-
/// Search view model for handling search functionality
15-
let searchViewModel: BookingSearchViewModel
16-
1714
var hasFilters: Bool {
1815
// TODO: Update when adding filters
1916
return false
@@ -41,6 +38,9 @@ final class BookingListViewModel: ObservableObject {
4138
/// Tracks if the infinite scroll indicator should be displayed.
4239
@Published private(set) var shouldShowBottomActivityIndicator = false
4340

41+
/// Tracks if initial load has been triggered.
42+
private var hasLoadedInitially = false
43+
4444
/// Supports infinite scroll.
4545
private let paginationTracker: PaginationTracker
4646
private let pageFirstIndex: Int = PaginationTracker.Defaults.pageFirstIndex
@@ -64,7 +64,6 @@ final class BookingListViewModel: ObservableObject {
6464

6565
init(siteID: Int64,
6666
type: BookingListTab,
67-
searchQueryPublisher: AnyPublisher<String, Never>,
6867
stores: StoresManager = ServiceLocator.stores,
6968
storage: StorageManagerType = ServiceLocator.storageManager,
7069
currentDate: Date = Date()) {
@@ -74,20 +73,15 @@ final class BookingListViewModel: ObservableObject {
7473
self.storage = storage
7574
self.currentDate = currentDate
7675
self.paginationTracker = PaginationTracker(pageFirstIndex: pageFirstIndex)
77-
self.searchViewModel = BookingSearchViewModel(
78-
siteID: siteID,
79-
type: type,
80-
searchQueryPublisher: searchQueryPublisher,
81-
stores: stores,
82-
currentDate: currentDate
83-
)
8476

8577
configureResultsController()
8678
configurePaginationTracker()
8779
}
8880

8981
/// Called when loading the first page of bookings.
9082
func loadBookings() {
83+
guard !hasLoadedInitially else { return }
84+
hasLoadedInitially = true
9185
paginationTracker.syncFirstPage()
9286
}
9387

WooCommerce/WooCommerceTests/ViewRelated/Bookings/BookingListViewModelTests.swift

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ struct BookingListViewModelTests {
1919
storageManager.viewStorage
2020
}
2121

22-
/// Search query publisher for tests
23-
private let searchQueryPublisher = PassthroughSubject<String, Never>().eraseToAnyPublisher()
24-
2522
init() {
2623
storageManager = MockStorageManager()
2724
}
@@ -38,7 +35,7 @@ struct BookingListViewModelTests {
3835
}
3936
invocationCountOfLoadBookings += 1
4037
}
41-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores)
38+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
4239

4340
// Then
4441
#expect(viewModel.syncState == .empty)
@@ -55,7 +52,7 @@ struct BookingListViewModelTests {
5552
}
5653
invocationCountOfLoadBookings += 1
5754
}
58-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores)
55+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
5956

6057
// When
6158
viewModel.loadBookings()
@@ -66,7 +63,7 @@ struct BookingListViewModelTests {
6663

6764
@Test func state_is_syncing_first_page_upon_load_bookings_if_no_existing_booking_in_storage() {
6865
// Given
69-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher)
66+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all)
7067

7168
// When
7269
viewModel.loadBookings()
@@ -80,7 +77,6 @@ struct BookingListViewModelTests {
8077
insertBookings([existingBooking])
8178
let viewModel = BookingListViewModel(siteID: sampleSiteID,
8279
type: .all,
83-
searchQueryPublisher: searchQueryPublisher,
8480
stores: MockStoresManager(sessionManager: .testingInstance),
8581
storage: storageManager)
8682

@@ -104,7 +100,6 @@ struct BookingListViewModelTests {
104100
}
105101
let viewModel = BookingListViewModel(siteID: sampleSiteID,
106102
type: .all,
107-
searchQueryPublisher: searchQueryPublisher,
108103
stores: stores,
109104
storage: storageManager)
110105

@@ -142,7 +137,6 @@ struct BookingListViewModelTests {
142137
}
143138
let viewModel = BookingListViewModel(siteID: sampleSiteID,
144139
type: .all,
145-
searchQueryPublisher: searchQueryPublisher,
146140
stores: stores,
147141
storage: storageManager)
148142

@@ -187,7 +181,6 @@ struct BookingListViewModelTests {
187181

188182
let viewModel = BookingListViewModel(siteID: sampleSiteID,
189183
type: .all,
190-
searchQueryPublisher: searchQueryPublisher,
191184
stores: stores,
192185
storage: storageManager)
193186

@@ -233,7 +226,6 @@ struct BookingListViewModelTests {
233226
}
234227
let viewModel = BookingListViewModel(siteID: sampleSiteID,
235228
type: .all,
236-
searchQueryPublisher: searchQueryPublisher,
237229
stores: stores,
238230
storage: storageManager)
239231

@@ -258,7 +250,6 @@ struct BookingListViewModelTests {
258250
}
259251
let viewModel = BookingListViewModel(siteID: sampleSiteID,
260252
type: .all,
261-
searchQueryPublisher: searchQueryPublisher,
262253
stores: stores,
263254
storage: storageManager)
264255

@@ -284,7 +275,6 @@ struct BookingListViewModelTests {
284275
}
285276
let viewModel = BookingListViewModel(siteID: sampleSiteID,
286277
type: .all,
287-
searchQueryPublisher: searchQueryPublisher,
288278
stores: stores,
289279
storage: storageManager)
290280

@@ -313,7 +303,7 @@ struct BookingListViewModelTests {
313303

314304
onCompletion(.success(false))
315305
}
316-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores)
306+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
317307

318308
// When
319309
await viewModel.onRefreshAction()
@@ -341,7 +331,7 @@ struct BookingListViewModelTests {
341331
onCompletion(.success(false))
342332
}
343333

344-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .today, searchQueryPublisher: searchQueryPublisher, stores: stores, currentDate: testDate)
334+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .today, stores: stores, currentDate: testDate)
345335

346336
// When
347337
viewModel.loadBookings()
@@ -369,7 +359,6 @@ struct BookingListViewModelTests {
369359

370360
let viewModel = BookingListViewModel(siteID: sampleSiteID,
371361
type: .upcoming,
372-
searchQueryPublisher: searchQueryPublisher,
373362
stores: stores,
374363
currentDate: testDate)
375364

@@ -397,7 +386,7 @@ struct BookingListViewModelTests {
397386
onCompletion(.success(false))
398387
}
399388

400-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores, currentDate: testDate)
389+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores, currentDate: testDate)
401390

402391
// When
403392
viewModel.loadBookings()
@@ -422,7 +411,7 @@ struct BookingListViewModelTests {
422411
onCompletion(.success(false))
423412
}
424413

425-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores)
414+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
426415

427416
// When
428417
viewModel.loadBookings()
@@ -446,7 +435,7 @@ struct BookingListViewModelTests {
446435
onCompletion(.success(actionCallCount == 1)) // First call has next page, second doesn't
447436
}
448437

449-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores)
438+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
450439

451440
// When
452441
viewModel.loadBookings() // First page
@@ -470,7 +459,7 @@ struct BookingListViewModelTests {
470459
onCompletion(.success(false))
471460
}
472461

473-
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, searchQueryPublisher: searchQueryPublisher, stores: stores)
462+
let viewModel = BookingListViewModel(siteID: sampleSiteID, type: .all, stores: stores)
474463

475464
// When
476465
await viewModel.onRefreshAction()
@@ -498,7 +487,6 @@ struct BookingListViewModelTests {
498487

499488
let viewModel = BookingListViewModel(siteID: sampleSiteID,
500489
type: .today,
501-
searchQueryPublisher: searchQueryPublisher,
502490
stores: MockStoresManager(sessionManager: .testingInstance),
503491
storage: storageManager,
504492
currentDate: testDate)
@@ -524,7 +512,6 @@ struct BookingListViewModelTests {
524512

525513
let viewModel = BookingListViewModel(siteID: sampleSiteID,
526514
type: .upcoming,
527-
searchQueryPublisher: searchQueryPublisher,
528515
stores: MockStoresManager(sessionManager: .testingInstance),
529516
storage: storageManager,
530517
currentDate: testDate)
@@ -554,7 +541,6 @@ struct BookingListViewModelTests {
554541

555542
let viewModel = BookingListViewModel(siteID: sampleSiteID,
556543
type: .all,
557-
searchQueryPublisher: searchQueryPublisher,
558544
stores: MockStoresManager(sessionManager: .testingInstance),
559545
storage: storageManager,
560546
currentDate: testDate)
@@ -582,7 +568,6 @@ struct BookingListViewModelTests {
582568

583569
let viewModel = BookingListViewModel(siteID: sampleSiteID,
584570
type: .all,
585-
searchQueryPublisher: searchQueryPublisher,
586571
stores: MockStoresManager(sessionManager: .testingInstance),
587572
storage: storageManager,
588573
currentDate: testDate)

WooCommerce/WooCommerceTests/ViewRelated/Bookings/BookingSearchViewModelTests.swift

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,6 @@ struct BookingSearchViewModelTests {
3030
#expect(viewModel.currentSearchQuery == "test query")
3131
}
3232

33-
@Test func search_results_are_cleared_when_query_becomes_empty() async throws {
34-
// Given
35-
let searchQuerySubject = PassthroughSubject<String, Never>()
36-
let stores = MockStoresManager(sessionManager: .testingInstance)
37-
let booking = Booking.fake().copy(siteID: sampleSiteID, bookingID: 1, startDate: Date())
38-
stores.whenReceivingAction(ofType: BookingAction.self) { action in
39-
guard case let .searchBookings(_, _, _, _, _, _, onCompletion) = action else {
40-
return
41-
}
42-
onCompletion(.success([booking]))
43-
}
44-
45-
let viewModel = BookingSearchViewModel(
46-
siteID: sampleSiteID,
47-
type: .all,
48-
searchQueryPublisher: searchQuerySubject.eraseToAnyPublisher(),
49-
stores: stores
50-
)
51-
52-
// When - perform search
53-
searchQuerySubject.send("test")
54-
try await Task.sleep(nanoseconds: 400_000_000) // Wait for debounce + search
55-
56-
#expect(viewModel.searchResults.count == 1)
57-
58-
// Clear query
59-
searchQuerySubject.send("")
60-
try await Task.sleep(nanoseconds: 400_000_000)
61-
62-
// Then
63-
#expect(viewModel.searchResults.isEmpty)
64-
#expect(viewModel.isSearching == false)
65-
}
66-
6733
// MARK: - Search action
6834

6935
@Test func search_bookings_is_dispatched_when_query_is_not_empty() async throws {

0 commit comments

Comments
 (0)