/* Copyright 2021 The Matrix.org Foundation C.I.C Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import Foundation import XCTest @testable import Riot class MXKRoomDataSourceTests: XCTestCase { // MARK: - Destruction tests func testDestroyRemovesAllBubbles() { let dataSource = StubMXKRoomDataSource() dataSource.destroy() XCTAssert(dataSource.getBubbles()?.isEmpty != false) } func testDestroyDeallocatesAllBubbles() throws { let dataSource = StubMXKRoomDataSource() weak var first = try XCTUnwrap(dataSource.getBubbles()?.first) weak var last = try XCTUnwrap(dataSource.getBubbles()?.last) dataSource.destroy() XCTAssertNil(first) XCTAssertNil(last) } // MARK: - Collapsing tests func testCollapseBubblesWhenProcessingTogether() throws { let dataSource = try FakeMXKRoomDataSource.make() try dataSource.queueEvent1() try dataSource.queueEvent2() awaitEventProcessing(for: dataSource) dataSource.verifyCollapsedEvents(2) } func testCollapseBubblesWhenProcessingAlone() throws { let dataSource = try FakeMXKRoomDataSource.make() try dataSource.queueEvent1() awaitEventProcessing(for: dataSource) try dataSource.queueEvent2() awaitEventProcessing(for: dataSource) dataSource.verifyCollapsedEvents(2) } private func awaitEventProcessing(for dataSource: MXKRoomDataSource) { let e = expectation(description: "The wai-ai-ting is the hardest part") dataSource.processQueuedEvents { _, _ in e.fulfill() } waitForExpectations(timeout: 2) { error in XCTAssertNil(error) } } } // MARK: - Test doubles private final class StubMXKRoomDataSource: MXKRoomDataSource { override init() { super.init() let data1 = MXKRoomBubbleCellData() let data2 = MXKRoomBubbleCellData() let data3 = MXKRoomBubbleCellData() data1.nextCollapsableCellData = data2 data2.prevCollapsableCellData = data1 data2.nextCollapsableCellData = data3 data3.prevCollapsableCellData = data2 replaceBubbles([data1, data2, data3]) } } private final class FakeMXKRoomDataSource: MXKRoomDataSource { class func make() throws -> FakeMXKRoomDataSource { let dataSource = try XCTUnwrap(FakeMXKRoomDataSource(roomId: "!foofoofoofoofoofoo:matrix.org", andMatrixSession: nil)) dataSource.registerCellDataClass(CollapsibleBubbleCellData.self, forCellIdentifier: kMXKRoomBubbleCellDataIdentifier) dataSource.eventFormatter = CountingEventFormatter(matrixSession: nil) return dataSource } override var state: MXKDataSourceState { MXKDataSourceStateReady } override var roomState: MXRoomState! { nil } func queueEvent1() throws { try queueEvent(json: #"{"sender":"@alice:matrix.org","content":{"displayname":"bob","membership":"invite"},"origin_server_ts":1616488993287,"state_key":"@bob:matrix.org","room_id":"!foofoofoofoofoofoo:matrix.org","event_id":"$lGK3budX5w009ErtQwE9ZFhwyUUAV9DqEN5yb2fI4Do","type":"m.room.member","unsigned":{"age":1204610,"prev_sender":"@alice:matrix.org","prev_content":{"membership":"leave"},"replaces_state":"$9mQ6RtscXqHCxWqOElI-eP_kwpkuPd2Czm3UHviGoyE"}}"#) } func queueEvent2() throws { try queueEvent(json: #"{"sender":"@alice:matrix.org","content":{"displayname":"john","membership":"invite"},"origin_server_ts":1616488967295,"state_key":"@john:matrix.org","room_id":"!foofoofoofoofoofoo:matrix.org","event_id":"$-00slfAluxVTP2VWytgDThTmh3nLd0WJD6gzBo2scJM","type":"m.room.member","unsigned":{"age":1712006,"prev_sender":"@alice:matrix.org","prev_content":{"membership":"leave"},"replaces_state":"$NRNkCMKeKK5NtTfWkMfTlMr5Ygw60Q2CQYnJNkbzyrs"}}"#) } private func queueEvent(json: String) throws { let data = try XCTUnwrap(json.data(using: .utf8)) let dict = try XCTUnwrap((try JSONSerialization.jsonObject(with: data, options: [])) as? [AnyHashable: Any]) let event = MXEvent(fromJSON: dict) queueEvent(forProcessing: event, with: nil, direction: __MXTimelineDirectionForwards) } func verifyCollapsedEvents(_ number: Int) { let message = getBubbles()?.first?.collapsedAttributedTextMessage.string XCTAssertEqual(message, "\(number)") } } private final class CollapsibleBubbleCellData: MXKRoomBubbleCellData { override init() { super.init() } required init!(event: MXEvent!, andRoomState roomState: MXRoomState!, andRoomDataSource roomDataSource: MXKRoomDataSource!) { super.init(event: event, andRoomState: roomState, andRoomDataSource: roomDataSource) collapsable = true } override func collapse(with cellData: MXKRoomBubbleCellDataStoring!) -> Bool { true } } private final class CountingEventFormatter: MXKEventFormatter { override func attributedString(from events: [MXEvent]!, with roomState: MXRoomState!, error: UnsafeMutablePointer!) -> NSAttributedString! { NSAttributedString(string: "\(events.count)") } }