Skip to content

Commit 71319c4

Browse files
authored
Fix querying threads by disabled channels crashing (#3813)
* Fix querying threads by disabled channels crashing * Update CHANGELOG Added a fix for querying threads by disabled channels crashing.
1 parent 37eea22 commit 71319c4

File tree

3 files changed

+133
-2
lines changed

3 files changed

+133
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
33

44
# Upcoming
55

6-
### 🔄 Changed
6+
## StreamChat
7+
### 🐞 Fixed
8+
- Fix querying threads by disabled channels crashing [#3813](https://github.com/GetStream/stream-chat-swift/pull/3813)
79

810
# [4.88.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.88.0)
911
_September 09, 2025_

Sources/StreamChat/Query/ThreadListQuery.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public extension FilterKey where Scope == ThreadListFilterScope {
120120
static var channelDisabled: FilterKey<Scope, Bool> {
121121
.init(
122122
rawValue: "channel.disabled",
123-
keyPathString: #keyPath(ChannelDTO.isDisabled)
123+
keyPathString: #keyPath(ThreadDTO.channel.isDisabled)
124124
)
125125
}
126126
}

Tests/StreamChatTests/Controllers/ThreadListController/ChatThreadListController_Tests.swift

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,64 @@ final class ChatThreadListController_Tests: XCTestCase {
208208
XCTAssertEqual(controller.threads.count, 3)
209209
XCTAssertEqual(delegate.threads.map(\.title), ["1", "2", "3"])
210210
}
211+
212+
// MARK: - Filter Predicate Tests
213+
214+
func test_filterPredicate_channelDisabled_returnsExpectedResults() throws {
215+
let threadId1 = MessageId.unique
216+
let threadId2 = MessageId.unique
217+
let threadId3 = MessageId.unique
218+
219+
try assertThreadFilterPredicate(
220+
.equal(.channelDisabled, to: true),
221+
threadsInDB: [
222+
.dummy(
223+
parentMessageId: threadId1,
224+
channel: .dummy(cid: .unique, isDisabled: true),
225+
title: "Disabled Channel Thread 1"
226+
),
227+
.dummy(
228+
parentMessageId: threadId2,
229+
channel: .dummy(cid: .unique, isDisabled: false),
230+
title: "Enabled Channel Thread 1"
231+
),
232+
.dummy(
233+
parentMessageId: threadId3,
234+
channel: .dummy(cid: .unique, isDisabled: true),
235+
title: "Disabled Channel Thread 2"
236+
)
237+
],
238+
expectedResult: [threadId1, threadId3]
239+
)
240+
}
241+
242+
func test_filterPredicate_channelEnabled_returnsExpectedResults() throws {
243+
let threadId1 = MessageId.unique
244+
let threadId2 = MessageId.unique
245+
let threadId3 = MessageId.unique
246+
247+
try assertThreadFilterPredicate(
248+
.equal(.channelDisabled, to: false),
249+
threadsInDB: [
250+
.dummy(
251+
parentMessageId: threadId1,
252+
channel: .dummy(cid: .unique, isDisabled: true),
253+
title: "Disabled Channel Thread 1"
254+
),
255+
.dummy(
256+
parentMessageId: threadId2,
257+
channel: .dummy(cid: .unique, isDisabled: false),
258+
title: "Enabled Channel Thread 1"
259+
),
260+
.dummy(
261+
parentMessageId: threadId3,
262+
channel: .dummy(cid: .unique, isDisabled: false),
263+
title: "Enabled Channel Thread 2"
264+
)
265+
],
266+
expectedResult: [threadId2, threadId3]
267+
)
268+
}
211269
}
212270

213271
// MARK: - Helpers
@@ -236,4 +294,75 @@ extension ChatThreadListController_Tests {
236294
)
237295
)
238296
}
297+
298+
private func assertThreadFilterPredicate(
299+
_ filter: @autoclosure () -> Filter<ThreadListFilterScope>,
300+
sort: [Sorting<ThreadListSortingKey>] = [],
301+
threadsInDB: @escaping @autoclosure () -> [ThreadPayload],
302+
expectedResult: @autoclosure () -> [MessageId],
303+
file: StaticString = #file,
304+
line: UInt = #line
305+
) throws {
306+
let query = ThreadListQuery(
307+
watch: true,
308+
filter: filter(),
309+
sort: sort
310+
)
311+
controller = makeController(query: query)
312+
313+
// Simulate `synchronize` call
314+
controller.synchronize()
315+
316+
// Wait for initial threads update
317+
waitForInitialThreadsUpdate()
318+
319+
XCTAssertEqual(controller.threads.map(\.parentMessageId), [], file: file, line: line)
320+
321+
// Simulate changes in the DB:
322+
_ = try waitFor {
323+
client.databaseContainer.write({ session in
324+
session.saveThreadList(payload: .init(
325+
threads: threadsInDB(),
326+
next: nil
327+
))
328+
}, completion: $0)
329+
}
330+
331+
// Assert the resulting value is updated
332+
XCTAssertEqual(
333+
controller.threads.map(\.parentMessageId).sorted(),
334+
expectedResult().sorted(),
335+
file: file,
336+
line: line
337+
)
338+
}
339+
340+
private func waitForInitialThreadsUpdate(file: StaticString = #file, line: UInt = #line) {
341+
waitForThreadsUpdate(file: file, line: line) {}
342+
}
343+
344+
private func waitForThreadsUpdate(file: StaticString = #file, line: UInt = #line, block: () -> Void) {
345+
let threadsExpectation = expectation(description: "Threads update")
346+
let delegate = ThreadsUpdateWaiter(threadsExpectation: threadsExpectation)
347+
controller.delegate = delegate
348+
block()
349+
wait(for: [threadsExpectation], timeout: defaultTimeout)
350+
}
351+
}
352+
353+
private class ThreadsUpdateWaiter: ChatThreadListControllerDelegate {
354+
weak var threadsExpectation: XCTestExpectation?
355+
356+
var didChangeThreadsCount: Int?
357+
358+
init(threadsExpectation: XCTestExpectation?) {
359+
self.threadsExpectation = threadsExpectation
360+
}
361+
362+
func controller(_ controller: ChatThreadListController, didChangeThreads changes: [ListChange<ChatThread>]) {
363+
DispatchQueue.main.async {
364+
self.didChangeThreadsCount = controller.threads.count
365+
self.threadsExpectation?.fulfill()
366+
}
367+
}
239368
}

0 commit comments

Comments
 (0)