Skip to content

Commit 163349b

Browse files
committed
Add Thread List View Model test coverage
1 parent b7dea0a commit 163349b

File tree

3 files changed

+194
-4
lines changed

3 files changed

+194
-4
lines changed

Sources/StreamChatSwiftUI/ChatThreadList/ChatThreadListViewModel.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ open class ChatThreadListViewModel: ObservableObject, ChatThreadListControllerDe
8989
/// Re-fetches the threads. If the initial query failed, it will load the initial page.
9090
/// If on the other hand it was a new page that failed, it will re-fetch that page.
9191
public func retryLoadThreads() {
92-
if failedToLoadThreads {
93-
loadThreads()
92+
if failedToLoadMoreThreads {
93+
loadMoreThreads()
9494
return
9595
}
9696

97-
loadMoreThreads()
97+
loadThreads()
9898
}
9999

100100
/// Called when the view appears on screen.
@@ -115,7 +115,8 @@ open class ChatThreadListViewModel: ObservableObject, ChatThreadListControllerDe
115115

116116
/// Loads the initial page of threads.
117117
public func loadThreads() {
118-
isLoading = threadListController.threads.isEmpty == true
118+
let isEmpty = threadListController.threads.isEmpty
119+
isLoading = isEmpty
119120
failedToLoadThreads = false
120121
isReloading = !isEmpty
121122
preselectThreadIfNeeded()

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@
520520
ADE0F5622CB8556F0053B8B9 /* ChatThreadListFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F5612CB8556F0053B8B9 /* ChatThreadListFooterView.swift */; };
521521
ADE0F5642CB9609E0053B8B9 /* ChatThreadListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F5632CB9609E0053B8B9 /* ChatThreadListHeaderView.swift */; };
522522
ADE0F5662CB962470053B8B9 /* ActionBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F5652CB962470053B8B9 /* ActionBannerView.swift */; };
523+
ADE442EE2CBDAAAA0066CDF7 /* ChatThreadListViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE442ED2CBDAAAA0066CDF7 /* ChatThreadListViewModel_Tests.swift */; };
523524
ADE442F02CBDAAB70066CDF7 /* ChatThreadListView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE442EF2CBDAAB70066CDF7 /* ChatThreadListView_Tests.swift */; };
524525
ADE442F22CBDAAC40066CDF7 /* ChatThreadListItemView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE442F12CBDAAC40066CDF7 /* ChatThreadListItemView_Tests.swift */; };
525526
C14A465B284665B100EF498E /* SDKIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A465A284665B100EF498E /* SDKIdentifier.swift */; };
@@ -1111,6 +1112,7 @@
11111112
ADE0F5612CB8556F0053B8B9 /* ChatThreadListFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListFooterView.swift; sourceTree = "<group>"; };
11121113
ADE0F5632CB9609E0053B8B9 /* ChatThreadListHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListHeaderView.swift; sourceTree = "<group>"; };
11131114
ADE0F5652CB962470053B8B9 /* ActionBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBannerView.swift; sourceTree = "<group>"; };
1115+
ADE442ED2CBDAAAA0066CDF7 /* ChatThreadListViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListViewModel_Tests.swift; sourceTree = "<group>"; };
11141116
ADE442EF2CBDAAB70066CDF7 /* ChatThreadListView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListView_Tests.swift; sourceTree = "<group>"; };
11151117
ADE442F12CBDAAC40066CDF7 /* ChatThreadListItemView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListItemView_Tests.swift; sourceTree = "<group>"; };
11161118
C14A465A284665B100EF498E /* SDKIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKIdentifier.swift; sourceTree = "<group>"; };
@@ -2268,6 +2270,7 @@
22682270
ADE442EC2CBDAA320066CDF7 /* ChatThreadList */ = {
22692271
isa = PBXGroup;
22702272
children = (
2273+
ADE442ED2CBDAAAA0066CDF7 /* ChatThreadListViewModel_Tests.swift */,
22712274
ADE442EF2CBDAAB70066CDF7 /* ChatThreadListView_Tests.swift */,
22722275
ADE442F12CBDAAC40066CDF7 /* ChatThreadListItemView_Tests.swift */,
22732276
);
@@ -2966,6 +2969,7 @@
29662969
84C94D0727578BF2007FE2B9 /* RandomDispatchQueue.swift in Sources */,
29672970
847110B628611033004A46D6 /* MessageActions_Tests.swift in Sources */,
29682971
84C94D1227578BF2007FE2B9 /* JSONEncoder+Extensions.swift in Sources */,
2972+
ADE442EE2CBDAAAA0066CDF7 /* ChatThreadListViewModel_Tests.swift in Sources */,
29692973
84E04797284A444E00BAFA17 /* WebSocketPingControllerMock.swift in Sources */,
29702974
8423C34C277DDD250092DCF1 /* MuteCommandHandler_Tests.swift in Sources */,
29712975
84C94D1127578BF2007FE2B9 /* ChannelId.swift in Sources */,
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//
2+
// Copyright © 2024 Stream.io Inc. All rights reserved.
3+
//
4+
5+
@testable import StreamChat
6+
@testable import StreamChatSwiftUI
7+
@testable import StreamChatTestTools
8+
import XCTest
9+
10+
class ChatThreadListViewModel_Tests: StreamChatTestCase {
11+
12+
func test_viewDidAppear_thenLoadsThreads() {
13+
let mockThreadListController = ChatThreadListController_Mock.mock(
14+
query: .init(watch: true)
15+
)
16+
let viewModel = ChatThreadListViewModel(
17+
threadListController: mockThreadListController
18+
)
19+
20+
viewModel.viewDidAppear()
21+
XCTAssertEqual(mockThreadListController.synchronize_callCount, 1)
22+
}
23+
24+
func test_viewDidAppear_whenAlreadyLoadedThreads_thenDoesNotLoadsThreads() {
25+
let mockThreadListController = ChatThreadListController_Mock.mock(
26+
query: .init(watch: true)
27+
)
28+
let viewModel = ChatThreadListViewModel(
29+
threadListController: mockThreadListController
30+
)
31+
32+
viewModel.viewDidAppear()
33+
mockThreadListController.synchronize_completion?(nil)
34+
viewModel.viewDidAppear()
35+
36+
XCTAssertEqual(mockThreadListController.synchronize_callCount, 1)
37+
}
38+
39+
func test_loadThreads_whenInitialEmptyData_whenSuccess() {
40+
let mockThreadListController = ChatThreadListController_Mock.mock(
41+
query: .init(watch: true)
42+
)
43+
mockThreadListController.threads_mock = []
44+
let viewModel = ChatThreadListViewModel(
45+
threadListController: mockThreadListController
46+
)
47+
48+
viewModel.loadThreads()
49+
50+
XCTAssertEqual(viewModel.isLoading, true)
51+
XCTAssertEqual(viewModel.isReloading, false)
52+
XCTAssertEqual(viewModel.failedToLoadThreads, false)
53+
XCTAssertEqual(viewModel.hasLoadedThreads, false)
54+
55+
mockThreadListController.threads_mock = [.mock()]
56+
mockThreadListController.synchronize_completion?(nil)
57+
58+
XCTAssertEqual(viewModel.isLoading, false)
59+
XCTAssertEqual(viewModel.isReloading, false)
60+
XCTAssertEqual(viewModel.failedToLoadThreads, false)
61+
XCTAssertEqual(viewModel.hasLoadedThreads, true)
62+
XCTAssertEqual(viewModel.isEmpty, false)
63+
}
64+
65+
func test_loadThreads_whenCacheAvailable_whenSuccess() {
66+
let mockThreadListController = ChatThreadListController_Mock.mock(
67+
query: .init(watch: true)
68+
)
69+
mockThreadListController.threads_mock = [.mock()]
70+
let viewModel = ChatThreadListViewModel(
71+
threadListController: mockThreadListController
72+
)
73+
74+
viewModel.loadThreads()
75+
76+
XCTAssertEqual(viewModel.isLoading, false)
77+
XCTAssertEqual(viewModel.isReloading, true)
78+
XCTAssertEqual(viewModel.failedToLoadThreads, false)
79+
XCTAssertEqual(viewModel.hasLoadedThreads, false)
80+
81+
mockThreadListController.threads_mock = [.mock(), .mock()]
82+
mockThreadListController.synchronize_completion?(nil)
83+
84+
XCTAssertEqual(viewModel.isLoading, false)
85+
XCTAssertEqual(viewModel.isReloading, false)
86+
XCTAssertEqual(viewModel.failedToLoadThreads, false)
87+
XCTAssertEqual(viewModel.hasLoadedThreads, true)
88+
XCTAssertEqual(viewModel.isEmpty, false)
89+
}
90+
91+
func test_loadThreads_whenError() {
92+
let mockThreadListController = ChatThreadListController_Mock.mock(
93+
query: .init(watch: true)
94+
)
95+
mockThreadListController.threads_mock = []
96+
let viewModel = ChatThreadListViewModel(
97+
threadListController: mockThreadListController
98+
)
99+
100+
viewModel.loadThreads()
101+
mockThreadListController.threads_mock = [.mock()]
102+
mockThreadListController.synchronize_completion?(ClientError("ERROR"))
103+
104+
XCTAssertEqual(viewModel.isLoading, false)
105+
XCTAssertEqual(viewModel.isReloading, false)
106+
XCTAssertEqual(viewModel.failedToLoadThreads, true)
107+
XCTAssertEqual(viewModel.failedToLoadMoreThreads, false)
108+
XCTAssertEqual(viewModel.hasLoadedThreads, false)
109+
}
110+
111+
func test_didAppearThread_whenInsideThreshold_thenLoadMoreThreads() {
112+
let mockThreadListController = ChatThreadListController_Mock.mock(
113+
query: .init(watch: true)
114+
)
115+
let viewModel = ChatThreadListViewModel(
116+
threadListController: mockThreadListController
117+
)
118+
let mockedThreads: [ChatThread] = [
119+
.mock(), .mock(), .mock(), .mock(), .mock(), .mock(), .mock()
120+
]
121+
mockedThreads.forEach { thread in
122+
viewModel.threads.append(thread)
123+
}
124+
125+
XCTAssertEqual(viewModel.isLoadingMoreThreads, false)
126+
127+
viewModel.didAppearThread(at: 5)
128+
129+
XCTAssertEqual(viewModel.isLoadingMoreThreads, true)
130+
}
131+
132+
func test_didAppearThread_whenNotInThreshold_thenDoNotLoadMoreThreads() {
133+
let mockThreadListController = ChatThreadListController_Mock.mock(
134+
query: .init(watch: true)
135+
)
136+
let viewModel = ChatThreadListViewModel(
137+
threadListController: mockThreadListController
138+
)
139+
let mockedThreads: [ChatThread] = [
140+
.mock(), .mock(), .mock(), .mock(), .mock(), .mock(), .mock()
141+
]
142+
mockedThreads.forEach { thread in
143+
viewModel.threads.append(thread)
144+
}
145+
146+
XCTAssertEqual(viewModel.isLoadingMoreThreads, false)
147+
148+
viewModel.didAppearThread(at: 0)
149+
150+
XCTAssertEqual(viewModel.isLoadingMoreThreads, false)
151+
}
152+
153+
func test_didReceiveThreadMessageNewEvent() {
154+
let mockThreadListController = ChatThreadListController_Mock.mock(
155+
query: .init(watch: true)
156+
)
157+
let viewModel = ChatThreadListViewModel(
158+
threadListController: mockThreadListController
159+
)
160+
let eventController = mockThreadListController.client.eventsController()
161+
162+
// 2 Events
163+
viewModel.eventsController(
164+
eventController,
165+
didReceiveEvent: ThreadMessageNewEvent(
166+
message: .mock(parentMessageId: .unique),
167+
channel: .mock(cid: .unique),
168+
unreadCount: .noUnread,
169+
createdAt: .unique
170+
)
171+
)
172+
viewModel.eventsController(
173+
eventController,
174+
didReceiveEvent: ThreadMessageNewEvent(
175+
message: .mock(parentMessageId: .unique),
176+
channel: .mock(cid: .unique),
177+
unreadCount: .noUnread,
178+
createdAt: .unique
179+
)
180+
)
181+
182+
XCTAssertEqual(viewModel.newThreadsCount, 2)
183+
XCTAssertTrue(viewModel.hasNewThreads)
184+
}
185+
}

0 commit comments

Comments
 (0)