Skip to content

Commit

Permalink
Add tests for channel list filtering and sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
laevandus committed Dec 2, 2024
1 parent c181a0c commit 22b4bd8
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 6 deletions.
14 changes: 12 additions & 2 deletions Sources/StreamChat/Database/DTOs/ChannelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,11 @@ extension NSManagedObjectContext {
}

// Note: membership payload should be saved before all the members
var membershipData: (dto: MemberDTO, payload: MemberPayload)?
if let membership = payload.membership {
let membership = try saveMember(payload: membership, channelId: payload.channel.cid, query: nil, cache: cache)
dto.membership = membership
let membershipDTO = try saveMember(payload: membership, channelId: payload.channel.cid, query: nil, cache: cache)
dto.membership = membershipDTO
membershipData = (membershipDTO, membership)
} else {
dto.membership = nil
}
Expand All @@ -343,6 +345,14 @@ extension NSManagedObjectContext {
let member = try saveMember(payload: $0, channelId: payload.channel.cid, query: nil, cache: cache)
dto.members.insert(member)
}

// TODO: remove this when backend has deployed the fix
// Prioritise values from membership:
// membership has pinned_at, but members does not for that user
// notification_muted has correct state for members, but not for membership
if let membershipData {
membershipData.dto.pinnedAt = membershipData.payload.pinnedAt?.bridgeDate
}

dto.watcherCount = Int64(clamping: payload.watcherCount ?? 0)

Expand Down
4 changes: 3 additions & 1 deletion Sources/StreamChat/Extensions/URLRequest+cURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ extension URLRequest {
.replacingOccurrences(of: "\"", with: "\\\"")
cURL.append("-d \"\(escapedBody)\"")
}
cURL.append("\"\(url.absoluteString)\"")
let urlString = url.absoluteString
.replacingOccurrences(of: "$", with: "%24") // encoded JSON payload
cURL.append("\"\(urlString)\"")
return cURL.joined(separator: " \\\n\t")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ final class ChannelListFilterScope_Tests: XCTestCase {
XCTAssertEqual(Key<Date>.lastUpdatedAt.keyPathString, "lastMessageAt")
XCTAssertEqual(Key<Bool>.joined.keyPathString, "membership")
XCTAssertEqual(Key<Bool>.muted.keyPathString, "mute")
XCTAssertEqual(Key<Bool>.pinned.keyPathString, "membership.pinnedAt")
XCTAssertNil(Key<InviteFilterValue>.invite.keyPathString)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ final class ChannelListSortingKey_Tests: XCTestCase {
.memberCount,
.cid,
.hasUnread,
.unreadCount
.unreadCount,
.pinnedAt
]

for key in sortingKeys {
Expand Down Expand Up @@ -84,6 +85,14 @@ final class ChannelListSortingKey_Tests: XCTestCase {
key.localKey,
NSExpression(forKeyPath: \ChannelDTO.currentUserUnreadMessagesCount).keyPath
)
case .pinnedAt:
XCTAssertNotNil(key.sortDescriptor(isAscending: true))
XCTAssertEqual(key.remoteKey, "pinned_at")
XCTAssertFalse(key.requiresRuntimeSorting)
XCTAssertEqual(
key.localKey,
NSExpression(forKeyPath: \ChannelDTO.membership?.pinnedAt).keyPath
)
default:
XCTFail()
}
Expand Down
76 changes: 74 additions & 2 deletions Tests/StreamChatTests/StateLayer/ChannelList_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,72 @@ final class ChannelList_Tests: XCTestCase {
cancellable.cancel()
}

func test_observingLocalStore_whenStoreChangesWithPinnedChannels_thenStateChanges() async throws {
let expectation = XCTestExpectation(description: "State changed")
let incomingChannelListPayload = makeMatchingChannelListPayload(
channelCount: 2,
createdAtOffset: 0,
membersCreator: { _, channelOffset in
[
.dummy(
user: .dummy(userId: self.memberId),
pinnedAt: Date(timeIntervalSinceReferenceDate: TimeInterval(channelOffset))
),
.dummy()
]
}
)
let cancellable = await channelList.state.$channels
.dropFirst() // ignore initial
.sink { channels in
XCTAssertEqual(incomingChannelListPayload.channels.map(\.channel.cid.rawValue), channels.map(\.cid.rawValue))
XCTAssertTrue(channels.allSatisfy(\.isPinned), channels.filter { !$0.isPinned }.map(\.cid.rawValue).joined())
expectation.fulfill()
}
try await env.client.mockDatabaseContainer.write { session in
session.saveChannelList(payload: incomingChannelListPayload, query: self.channelList.query)
}
await fulfillmentCompatibility(of: [expectation], timeout: defaultTimeout)
cancellable.cancel()
}

func test_observingLocalStore_whenStoreChangesAndSortingByPinnedAt_thenStateChanges() async throws {
let expectation = XCTestExpectation(description: "State changed")
let incomingChannelListPayload = makeMatchingChannelListPayload(
channelCount: 5,
createdAtOffset: 0,
membersCreator: { _, channelOffset in
[
.dummy(
user: .dummy(userId: self.memberId),
pinnedAt: channelOffset < 2 ? Date(timeIntervalSinceReferenceDate: TimeInterval(channelOffset)) : nil
),
.dummy()
]
}
)
await setUpChannelList(
usesMockedChannelUpdater: false,
sort: [
.init(key: .pinnedAt, isAscending: false),
.init(key: .cid, isAscending: true)
]
)
let cancellable = await channelList.state.$channels
.dropFirst() // ignore initial
.sink { channels in
// cid1 has newer pinned date, therefore it is the first one. cid2, cid3, cid4 are sorted by cid because pinned date is equal (nil)
XCTAssertEqual(["messaging:cid1", "messaging:cid0", "messaging:cid2", "messaging:cid3", "messaging:cid4"], channels.map(\.cid.rawValue))
XCTAssertEqual([true, true, false, false, false], channels.map(\.isPinned))
expectation.fulfill()
}
try await env.client.mockDatabaseContainer.write { session in
session.saveChannelList(payload: incomingChannelListPayload, query: self.channelList.query)
}
await fulfillmentCompatibility(of: [expectation], timeout: defaultTimeout)
cancellable.cancel()
}

// MARK: - Linking and Unlinking Channels

func test_observingEvents_whenAddedToChannelEventReceived_thenChannelIsLinkedAndStateUpdates() async throws {
Expand Down Expand Up @@ -352,11 +418,15 @@ final class ChannelList_Tests: XCTestCase {
@MainActor private func setUpChannelList(
usesMockedChannelUpdater: Bool,
loadState: Bool = true,
filter: Filter<ChannelListFilterScope>? = nil,
sort: [Sorting<ChannelListSortingKey>] = [.init(key: .createdAt, isAscending: true)],
dynamicFilter: ((ChatChannel) -> Bool)? = nil
) {
channelList = ChannelList(
query: ChannelListQuery(filter: .in(.members, values: [memberId]), sort: sort),
query: ChannelListQuery(
filter: filter ?? .in(.members, values: [memberId]),
sort: sort
),
dynamicFilter: dynamicFilter,
client: env.client,
environment: env.channelListEnvironment(usesMockedUpdater: usesMockedChannelUpdater)
Expand All @@ -380,15 +450,17 @@ final class ChannelList_Tests: XCTestCase {
channelCount: Int,
createdAtOffset: Int,
namePrefix: String = "Name",
membersCreator: ((ChannelId, Int) -> [MemberPayload])? = nil,
messagesCreator: ((ChannelId, Int) -> [MessagePayload])? = nil
) -> ChannelListPayload {
let channelPayloads = (0..<channelCount)
.map {
let channelId = ChannelId(type: .messaging, id: "cid\($0 + createdAtOffset)")
let members = membersCreator?(channelId, $0 + createdAtOffset) ?? [.dummy(user: .dummy(userId: memberId))]
return dummyPayload(
with: channelId,
name: "\(namePrefix) \($0 + createdAtOffset)",
members: [.dummy(user: .dummy(userId: memberId))],
members: members,
messages: messagesCreator?(channelId, $0 + createdAtOffset),
createdAt: Date(timeIntervalSinceReferenceDate: TimeInterval($0 + createdAtOffset))
)
Expand Down

0 comments on commit 22b4bd8

Please sign in to comment.