2022-07-06 14:49:05 +01:00
//
2024-09-06 16:34:30 +03:00
// C o p y r i g h t 2 0 2 2 - 2 0 2 4 N e w V e c t o r L t d .
2022-02-14 18:05:21 +02:00
//
2025-01-06 11:27:37 +01:00
// S P D X - L i c e n s e - I d e n t i f i e r : A G P L - 3 . 0 - o n l y O R L i c e n s e R e f - E l e m e n t - C o m m e r c i a l
// P l e a s e s e e L I C E N S E f i l e s i n t h e r e p o s i t o r y r o o t f o r f u l l d e t a i l s .
2022-02-14 18:05:21 +02:00
//
2023-03-28 14:06:53 +01:00
@ testable import ElementX
2023-08-08 14:25:23 +02:00
import Combine
2024-10-16 19:08:34 +03:00
import MatrixRustSDK
2022-02-14 18:05:21 +02:00
import XCTest
2022-03-08 14:24:33 +02:00
2023-03-28 14:06:53 +01:00
@ MainActor
2024-08-13 13:36:40 +02:00
class TimelineViewModelTests : XCTestCase {
2023-06-09 18:27:08 +02:00
var userIndicatorControllerMock : UserIndicatorControllerMock !
2023-08-08 14:25:23 +02:00
var cancellables = Set < AnyCancellable > ( )
2023-06-09 18:27:08 +02:00
override func setUp ( ) async throws {
2024-03-21 14:01:23 +02:00
AppSettings . resetAllSettings ( )
2023-09-14 12:53:33 +03:00
cancellables . removeAll ( )
2023-06-09 18:27:08 +02:00
userIndicatorControllerMock = UserIndicatorControllerMock . default
}
override func tearDown ( ) async throws {
userIndicatorControllerMock = nil
}
2023-08-01 11:53:02 +01:00
// MARK: - M e s s a g e G r o u p i n g
2023-06-09 18:27:08 +02:00
2023-03-28 14:06:53 +01:00
func testMessageGrouping ( ) {
// G i v e n 3 m e s s a g e s f r o m B o b .
let items = [
TextRoomTimelineItem ( text : " Message 1 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 2 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 3 " ,
sender : " bob " )
]
// W h e n s h o w i n g t h e m i n a t i m e l i n e .
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-03-28 14:06:53 +01:00
timelineController . timelineItems = items
2024-04-25 18:32:33 +01:00
let viewModel = makeViewModel ( timelineController : timelineController )
2023-03-28 14:06:53 +01:00
// T h e n t h e m e s s a g e s s h o u l d b e g r o u p e d t o g e t h e r .
2024-12-06 16:58:14 +02:00
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 0 ] . groupStyle , . first , " Nothing should prevent the first message from being grouped. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 1 ] . groupStyle , . middle , " Nothing should prevent the middle message from being grouped. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 2 ] . groupStyle , . last , " Nothing should prevent the last message from being grouped. " )
2023-03-28 14:06:53 +01:00
}
func testMessageGroupingMultipleSenders ( ) {
// G i v e n s o m e i n t e r l e a v e d m e s s a g e s f r o m B o b a n d A l i c e .
let items = [
TextRoomTimelineItem ( text : " Message 1 " ,
sender : " alice " ) ,
TextRoomTimelineItem ( text : " Message 2 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 3 " ,
sender : " alice " ) ,
TextRoomTimelineItem ( text : " Message 4 " ,
sender : " alice " ) ,
TextRoomTimelineItem ( text : " Message 5 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 6 " ,
sender : " bob " )
]
// W h e n s h o w i n g t h e m i n a t i m e l i n e .
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-03-28 14:06:53 +01:00
timelineController . timelineItems = items
2024-04-25 18:32:33 +01:00
let viewModel = makeViewModel ( timelineController : timelineController )
2023-03-28 14:06:53 +01:00
// T h e n t h e m e s s a g e s s h o u l d b e g r o u p e d b y s e n d e r .
2024-12-06 16:58:14 +02:00
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 0 ] . groupStyle , . single , " A message should not be grouped when the sender changes. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 1 ] . groupStyle , . single , " A message should not be grouped when the sender changes. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 2 ] . groupStyle , . first , " A group should start with a new sender if there are more messages from that sender. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 3 ] . groupStyle , . last , " A group should be ended when the sender changes in the next message. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 4 ] . groupStyle , . first , " A group should start with a new sender if there are more messages from that sender. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 5 ] . groupStyle , . last , " A group should be ended when the sender changes in the next message. " )
2023-03-28 14:06:53 +01:00
}
func testMessageGroupingWithLeadingReactions ( ) {
// G i v e n 3 m e s s a g e s f r o m B o b w h e r e t h e f i r s t m e s s a g e h a s a r e a c t i o n .
let items = [
TextRoomTimelineItem ( text : " Message 1 " ,
sender : " bob " ,
addReactions : true ) ,
TextRoomTimelineItem ( text : " Message 2 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 3 " ,
sender : " bob " )
]
// W h e n s h o w i n g t h e m i n a t i m e l i n e .
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-03-28 14:06:53 +01:00
timelineController . timelineItems = items
2024-04-25 18:32:33 +01:00
let viewModel = makeViewModel ( timelineController : timelineController )
2023-03-28 14:06:53 +01:00
// T h e n t h e f i r s t m e s s a g e s h o u l d n o t b e g r o u p e d b u t t h e o t h e r t w o s h o u l d .
2024-12-06 16:58:14 +02:00
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 0 ] . groupStyle , . single , " When the first message has reactions it should not be grouped. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 1 ] . groupStyle , . first , " A new group should be made when the preceding message has reactions. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 2 ] . groupStyle , . last , " Nothing should prevent the last message from being grouped. " )
2023-03-28 14:06:53 +01:00
}
func testMessageGroupingWithInnerReactions ( ) {
// G i v e n 3 m e s s a g e s f r o m B o b w h e r e t h e m i d d l e m e s s a g e h a s a r e a c t i o n .
let items = [
TextRoomTimelineItem ( text : " Message 1 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 2 " ,
sender : " bob " ,
addReactions : true ) ,
TextRoomTimelineItem ( text : " Message 3 " ,
sender : " bob " )
]
// W h e n s h o w i n g t h e m i n a t i m e l i n e .
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-03-28 14:06:53 +01:00
timelineController . timelineItems = items
2024-04-25 18:32:33 +01:00
let viewModel = makeViewModel ( timelineController : timelineController )
2023-03-28 14:06:53 +01:00
// T h e n t h e f i r s t a n d s e c o n d m e s s a g e s s h o u l d b e g r o u p e d a n d t h e l a s t o n e s h o u l d n o t .
2024-12-06 16:58:14 +02:00
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 0 ] . groupStyle , . first , " Nothing should prevent the first message from being grouped. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 1 ] . groupStyle , . last , " When the message has reactions, the group should end here. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 2 ] . groupStyle , . single , " The last message should not be grouped when the preceding message has reactions. " )
2023-03-28 14:06:53 +01:00
}
func testMessageGroupingWithTrailingReactions ( ) {
// G i v e n 3 m e s s a g e s f r o m B o b w h e r e t h e l a s t m e s s a g e h a s a r e a c t i o n .
let items = [
TextRoomTimelineItem ( text : " Message 1 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 2 " ,
sender : " bob " ) ,
TextRoomTimelineItem ( text : " Message 3 " ,
sender : " bob " ,
addReactions : true )
]
// W h e n s h o w i n g t h e m i n a t i m e l i n e .
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-03-28 14:06:53 +01:00
timelineController . timelineItems = items
2024-04-25 18:32:33 +01:00
let viewModel = makeViewModel ( timelineController : timelineController )
2023-03-28 14:06:53 +01:00
// T h e n t h e m e s s a g e s s h o u l d b e g r o u p e d t o g e t h e r .
2024-12-06 16:58:14 +02:00
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 0 ] . groupStyle , . first , " Nothing should prevent the first message from being grouped. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 1 ] . groupStyle , . middle , " Nothing should prevent the second message from being grouped. " )
XCTAssertEqual ( viewModel . state . timelineState . itemViewStates [ 2 ] . groupStyle , . last , " Reactions on the last message should not prevent it from being grouped. " )
2023-03-28 14:06:53 +01:00
}
2023-08-01 11:53:02 +01:00
2024-04-25 18:32:33 +01:00
// MARK: - F o c u s s i n g
func testFocusItem ( ) async throws {
// G i v e n a r o o m w i t h 3 i t e m s l o a d e d i n a l i v e t i m e l i n e .
let items = [ TextRoomTimelineItem ( eventID : " t1 " ) ,
TextRoomTimelineItem ( eventID : " t2 " ) ,
TextRoomTimelineItem ( eventID : " t3 " ) ]
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2024-04-25 18:32:33 +01:00
timelineController . timelineItems = items
let viewModel = makeViewModel ( timelineController : timelineController )
XCTAssertEqual ( timelineController . focusOnEventCallCount , 0 )
2024-12-06 16:58:14 +02:00
XCTAssertTrue ( viewModel . context . viewState . timelineState . isLive )
XCTAssertNil ( viewModel . context . viewState . timelineState . focussedEvent )
2024-04-25 18:32:33 +01:00
// W h e n f o c u s s i n g o n a n i t e m t h a t i s n ' t l o a d e d .
2024-12-06 16:58:14 +02:00
let deferred = deferFulfillment ( viewModel . context . $ viewState ) { ! $0 . timelineState . isLive }
2024-04-25 18:32:33 +01:00
await viewModel . focusOnEvent ( eventID : " t4 " )
try await deferred . fulfill ( )
// T h e n a n e w t i m e l i n e s h o u l d b e l o a d e d a n d t h e r o o m f o c u s s e d o n t h a t e v e n t .
XCTAssertEqual ( timelineController . focusOnEventCallCount , 1 )
2024-12-06 16:58:14 +02:00
XCTAssertFalse ( viewModel . context . viewState . timelineState . isLive )
XCTAssertEqual ( viewModel . context . viewState . timelineState . focussedEvent , . init ( eventID : " t4 " , appearance : . immediate ) )
2024-04-25 18:32:33 +01:00
}
func testFocusLoadedItem ( ) async throws {
// G i v e n a r o o m w i t h 3 i t e m s l o a d e d i n a l i v e t i m e l i n e .
let items = [ TextRoomTimelineItem ( eventID : " t1 " ) ,
TextRoomTimelineItem ( eventID : " t2 " ) ,
TextRoomTimelineItem ( eventID : " t3 " ) ]
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2024-04-25 18:32:33 +01:00
timelineController . timelineItems = items
let viewModel = makeViewModel ( timelineController : timelineController )
XCTAssertEqual ( timelineController . focusOnEventCallCount , 0 )
2024-12-06 16:58:14 +02:00
XCTAssertTrue ( viewModel . context . viewState . timelineState . isLive )
XCTAssertNil ( viewModel . context . viewState . timelineState . focussedEvent )
2024-04-25 18:32:33 +01:00
// W h e n f o c u s s i n g o n a l o a d e d i t e m .
2024-12-06 16:58:14 +02:00
let deferred = deferFailure ( viewModel . context . $ viewState , timeout : 1 ) { ! $0 . timelineState . isLive }
2024-04-25 18:32:33 +01:00
await viewModel . focusOnEvent ( eventID : " t1 " )
try await deferred . fulfill ( )
// T h e n t h e t i m e l i n e s h o u l d r e m a i n l i v e a n d t h e i t e m s h o u l d b e f o c u s s e d .
XCTAssertEqual ( timelineController . focusOnEventCallCount , 0 )
2024-12-06 16:58:14 +02:00
XCTAssertTrue ( viewModel . context . viewState . timelineState . isLive )
XCTAssertEqual ( viewModel . context . viewState . timelineState . focussedEvent , . init ( eventID : " t1 " , appearance : . animated ) )
2024-04-25 18:32:33 +01:00
}
func testFocusLive ( ) async throws {
// G i v e n a r o o m w i t h a n o n - l i v e t i m e l i n e f o c u s s e d o n a p a r t i c u l a r e v e n t .
let items = [ TextRoomTimelineItem ( eventID : " t1 " ) ,
TextRoomTimelineItem ( eventID : " t2 " ) ,
TextRoomTimelineItem ( eventID : " t3 " ) ]
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2024-04-25 18:32:33 +01:00
timelineController . timelineItems = items
let viewModel = makeViewModel ( timelineController : timelineController )
2024-12-06 16:58:14 +02:00
var deferred = deferFulfillment ( viewModel . context . $ viewState ) { ! $0 . timelineState . isLive }
2024-04-25 18:32:33 +01:00
await viewModel . focusOnEvent ( eventID : " t4 " )
try await deferred . fulfill ( )
XCTAssertEqual ( timelineController . focusLiveCallCount , 0 )
2024-12-06 16:58:14 +02:00
XCTAssertFalse ( viewModel . context . viewState . timelineState . isLive )
XCTAssertEqual ( viewModel . context . viewState . timelineState . focussedEvent , . init ( eventID : " t4 " , appearance : . immediate ) )
2024-04-25 18:32:33 +01:00
// W h e n s w i t c h i n g b a c k t o a l i v e t i m e l i n e .
2024-12-06 16:58:14 +02:00
deferred = deferFulfillment ( viewModel . context . $ viewState ) { $0 . timelineState . isLive }
2024-04-25 18:32:33 +01:00
viewModel . context . send ( viewAction : . focusLive )
try await deferred . fulfill ( )
// T h e n t h e t i m e l i n e s h o u l d s w i t c h b a c k t o b e i n g l i v e a n d t h e e v e n t f o c u s s h o u l d b e r e m o v e d .
XCTAssertEqual ( timelineController . focusLiveCallCount , 1 )
2024-12-06 16:58:14 +02:00
XCTAssertTrue ( viewModel . context . viewState . timelineState . isLive )
XCTAssertNil ( viewModel . context . viewState . timelineState . focussedEvent )
2024-04-30 17:25:32 +01:00
}
func testInitialFocusViewState ( ) async throws {
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2024-04-30 17:25:32 +01:00
let viewModel = makeViewModel ( focussedEventID : " t10 " , timelineController : timelineController )
2024-12-06 16:58:14 +02:00
XCTAssertEqual ( viewModel . context . viewState . timelineState . focussedEvent , . init ( eventID : " t10 " , appearance : . immediate ) )
2024-04-25 18:32:33 +01:00
}
2023-08-01 11:53:02 +01:00
// MARK: - R e a d R e c e i p t s
// s w i f t l i n t : d i s a b l e f o r c e _ u n w r a p p i n g
func testSendReadReceipt ( ) async throws {
// G i v e n a r o o m w i t h o n l y t e x t i t e m s i n t h e t i m e l i n e
let items = [ TextRoomTimelineItem ( eventID : " t1 " ) ,
TextRoomTimelineItem ( eventID : " t2 " ) ,
TextRoomTimelineItem ( eventID : " t3 " ) ]
2024-08-12 10:57:51 +01:00
let ( viewModel , _ , timelineProxy , _ ) = readReceiptsConfiguration ( with : items )
2023-08-01 11:53:02 +01:00
// W h e n s e n d i n g a r e a d r e c e i p t f o r t h e l a s t i t e m .
viewModel . context . send ( viewAction : . sendReadReceiptIfNeeded ( items . last ! . id ) )
2023-09-26 13:28:29 +03:00
try await Task . sleep ( for : . milliseconds ( 100 ) )
2023-08-01 11:53:02 +01:00
// T h e n t h e r e c e i p t s h o u l d b e s e n t .
2024-01-25 15:47:33 +01:00
XCTAssertEqual ( timelineProxy . sendReadReceiptForTypeCalled , true )
let arguments = timelineProxy . sendReadReceiptForTypeReceivedArguments
XCTAssertEqual ( arguments ? . eventID , " t3 " )
XCTAssertEqual ( arguments ? . type , . read )
2023-08-01 11:53:02 +01:00
}
func testSendReadReceiptWithoutEvents ( ) async throws {
// G i v e n a r o o m w i t h o n l y v i r t u a l i t e m s .
2025-02-04 09:50:46 +00:00
let items = [ SeparatorRoomTimelineItem ( uniqueID : . init ( " v1 " ) ) ,
SeparatorRoomTimelineItem ( uniqueID : . init ( " v2 " ) ) ,
SeparatorRoomTimelineItem ( uniqueID : . init ( " v3 " ) ) ]
2024-08-12 10:57:51 +01:00
let ( viewModel , _ , timelineProxy , _ ) = readReceiptsConfiguration ( with : items )
2023-08-01 11:53:02 +01:00
// W h e n s e n d i n g a r e a d r e c e i p t f o r t h e l a s t i t e m .
viewModel . context . send ( viewAction : . sendReadReceiptIfNeeded ( items . last ! . id ) )
2023-09-26 13:28:29 +03:00
try await Task . sleep ( for : . milliseconds ( 100 ) )
2023-08-01 11:53:02 +01:00
// T h e n n o t h i n g s h o u l d b e s e n t .
2024-01-25 15:47:33 +01:00
XCTAssertEqual ( timelineProxy . sendReadReceiptForTypeCalled , false )
2023-08-01 11:53:02 +01:00
}
func testSendReadReceiptVirtualLast ( ) async throws {
// G i v e n a r o o m w h e r e t h e l a s t e v e n t i s a v i r t u a l i t e m .
let items : [ RoomTimelineItemProtocol ] = [ TextRoomTimelineItem ( eventID : " t1 " ) ,
TextRoomTimelineItem ( eventID : " t2 " ) ,
2025-02-04 09:50:46 +00:00
SeparatorRoomTimelineItem ( uniqueID : . init ( " v3 " ) ) ]
2024-08-12 10:57:51 +01:00
let ( viewModel , _ , _ , _ ) = readReceiptsConfiguration ( with : items )
2023-08-01 11:53:02 +01:00
// W h e n s e n d i n g a r e a d r e c e i p t f o r t h e l a s t i t e m .
viewModel . context . send ( viewAction : . sendReadReceiptIfNeeded ( items . last ! . id ) )
2023-09-26 13:28:29 +03:00
try await Task . sleep ( for : . milliseconds ( 100 ) )
2023-08-01 11:53:02 +01:00
}
// s w i f t l i n t : e n a b l e f o r c e _ u n w r a p p i n g
// s w i f t l i n t : d i s a b l e : n e x t l a r g e _ t u p l e
2024-08-13 13:36:40 +02:00
private func readReceiptsConfiguration ( with items : [ RoomTimelineItemProtocol ] ) -> ( TimelineViewModel ,
2024-08-20 16:13:27 +03:00
JoinedRoomProxyMock ,
2023-11-28 20:01:35 +01:00
TimelineProxyMock ,
2025-02-03 14:14:01 +00:00
MockTimelineController ) {
2024-08-20 16:13:27 +03:00
let roomProxy = JoinedRoomProxyMock ( . init ( name : " " ) )
2023-12-04 19:58:01 +02:00
2023-11-28 20:01:35 +01:00
let timelineProxy = TimelineProxyMock ( )
2023-12-04 19:58:01 +02:00
2023-11-28 20:01:35 +01:00
roomProxy . timeline = timelineProxy
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-08-01 11:53:02 +01:00
2024-01-25 15:47:33 +01:00
timelineProxy . sendReadReceiptForTypeReturnValue = . success ( ( ) )
2023-12-04 19:58:01 +02:00
2023-08-01 11:53:02 +01:00
timelineController . timelineItems = items
timelineController . roomProxy = roomProxy
2023-07-11 10:44:06 +02:00
2024-08-13 13:36:40 +02:00
let viewModel = TimelineViewModel ( roomProxy : roomProxy ,
timelineController : timelineController ,
2024-10-02 17:41:08 +01:00
mediaProvider : MediaProviderMock ( configuration : . init ( ) ) ,
2024-08-13 13:36:40 +02:00
mediaPlayerProvider : MediaPlayerProviderMock ( ) ,
voiceMessageMediaManager : VoiceMessageMediaManagerMock ( ) ,
userIndicatorController : userIndicatorControllerMock ,
appMediator : AppMediatorMock . default ,
appSettings : ServiceLocator . shared . settings ,
2024-10-25 19:58:56 +03:00
analyticsService : ServiceLocator . shared . analytics ,
2025-02-03 14:14:01 +00:00
emojiProvider : EmojiProvider ( appSettings : ServiceLocator . shared . settings ) ,
2025-02-25 14:46:01 +01:00
timelineControllerFactory : TimelineControllerFactoryMock ( . init ( ) ) ,
clientProxy : ClientProxyMock ( . init ( ) ) )
2024-08-12 10:57:51 +01:00
return ( viewModel , roomProxy , timelineProxy , timelineController )
2023-07-11 10:44:06 +02:00
}
2023-11-21 13:38:39 +01:00
func testShowReadReceipts ( ) async throws {
let receipts : [ ReadReceipt ] = [ . init ( userID : " @alice:matrix.org " , formattedTimestamp : " 12:00 " ) ,
. init ( userID : " @charlie:matrix.org " , formattedTimestamp : " 11:00 " ) ]
// G i v e n 3 m e s s a g e s f r o m B o b w h e r e t h e m i d d l e m e s s a g e h a s a r e a c t i o n .
let message = TextRoomTimelineItem ( text : " Test " ,
sender : " bob " ,
addReadReceipts : receipts )
let id = message . id
// W h e n s h o w i n g t h e m i n a t i m e l i n e .
2025-02-03 14:14:01 +00:00
let timelineController = MockTimelineController ( )
2023-11-21 13:38:39 +01:00
timelineController . timelineItems = [ message ]
2024-08-20 16:13:27 +03:00
let viewModel = TimelineViewModel ( roomProxy : JoinedRoomProxyMock ( . init ( name : " " , members : [ RoomMemberProxyMock . mockAlice , RoomMemberProxyMock . mockCharlie ] ) ) ,
2024-08-13 13:36:40 +02:00
timelineController : timelineController ,
2024-10-02 17:41:08 +01:00
mediaProvider : MediaProviderMock ( configuration : . init ( ) ) ,
2024-08-13 13:36:40 +02:00
mediaPlayerProvider : MediaPlayerProviderMock ( ) ,
voiceMessageMediaManager : VoiceMessageMediaManagerMock ( ) ,
userIndicatorController : userIndicatorControllerMock ,
appMediator : AppMediatorMock . default ,
appSettings : ServiceLocator . shared . settings ,
2024-10-25 19:58:56 +03:00
analyticsService : ServiceLocator . shared . analytics ,
2025-02-03 14:14:01 +00:00
emojiProvider : EmojiProvider ( appSettings : ServiceLocator . shared . settings ) ,
2025-02-25 14:46:01 +01:00
timelineControllerFactory : TimelineControllerFactoryMock ( . init ( ) ) ,
clientProxy : ClientProxyMock ( . init ( ) ) )
2023-11-21 13:38:39 +01:00
let deferred = deferFulfillment ( viewModel . context . $ viewState ) { value in
value . bindings . readReceiptsSummaryInfo ? . orderedReceipts = = receipts
}
2024-05-09 14:35:47 +03:00
viewModel . context . send ( viewAction : . displayReadReceipts ( itemID : id ) )
2023-11-21 13:38:39 +01:00
try await deferred . fulfill ( )
}
2024-04-25 18:32:33 +01:00
2024-08-14 12:38:10 +02:00
// MARK: - P i n s
func testPinnedEvents ( ) async throws {
2024-10-28 12:29:31 +00:00
var configuration = JoinedRoomProxyMockConfiguration ( name : " " ,
pinnedEventIDs : . init ( [ " test1 " ] ) )
let roomProxyMock = JoinedRoomProxyMock ( configuration )
let infoSubject = CurrentValueSubject < RoomInfoProxy , Never > ( . init ( roomInfo : RoomInfo ( configuration ) ) )
roomProxyMock . underlyingInfoPublisher = infoSubject . asCurrentValuePublisher ( )
2024-09-19 11:03:26 +01:00
2024-08-14 12:38:10 +02:00
let viewModel = TimelineViewModel ( roomProxy : roomProxyMock ,
2025-02-03 14:14:01 +00:00
timelineController : MockTimelineController ( ) ,
2024-10-02 17:41:08 +01:00
mediaProvider : MediaProviderMock ( configuration : . init ( ) ) ,
2024-08-14 12:38:10 +02:00
mediaPlayerProvider : MediaPlayerProviderMock ( ) ,
voiceMessageMediaManager : VoiceMessageMediaManagerMock ( ) ,
userIndicatorController : userIndicatorControllerMock ,
appMediator : AppMediatorMock . default ,
appSettings : ServiceLocator . shared . settings ,
2024-10-25 19:58:56 +03:00
analyticsService : ServiceLocator . shared . analytics ,
2025-02-03 14:14:01 +00:00
emojiProvider : EmojiProvider ( appSettings : ServiceLocator . shared . settings ) ,
2025-02-25 14:46:01 +01:00
timelineControllerFactory : TimelineControllerFactoryMock ( . init ( ) ) ,
clientProxy : ClientProxyMock ( . init ( ) ) )
2024-10-28 12:29:31 +00:00
XCTAssertEqual ( configuration . pinnedEventIDs , viewModel . context . viewState . pinnedEventIDs )
2024-08-14 12:38:10 +02:00
2024-10-28 12:29:31 +00:00
configuration . pinnedEventIDs = [ " test1 " , " test2 " ]
let deferred = deferFulfillment ( viewModel . context . $ viewState ) { value in
2024-08-14 12:38:10 +02:00
value . pinnedEventIDs = = [ " test1 " , " test2 " ]
}
2024-10-28 12:29:31 +00:00
infoSubject . send ( . init ( roomInfo : RoomInfo ( configuration ) ) )
2024-08-14 12:38:10 +02:00
try await deferred . fulfill ( )
}
func testCanUserPinEvents ( ) async throws {
2024-10-28 12:29:31 +00:00
let configuration = JoinedRoomProxyMockConfiguration ( name : " " , canUserPin : true )
let roomProxyMock = JoinedRoomProxyMock ( configuration )
let infoSubject = CurrentValueSubject < RoomInfoProxy , Never > ( . init ( roomInfo : RoomInfo ( configuration ) ) )
roomProxyMock . underlyingInfoPublisher = infoSubject . asCurrentValuePublisher ( )
2024-09-19 11:03:26 +01:00
2024-08-14 12:38:10 +02:00
let viewModel = TimelineViewModel ( roomProxy : roomProxyMock ,
2025-02-03 14:14:01 +00:00
timelineController : MockTimelineController ( ) ,
2024-10-02 17:41:08 +01:00
mediaProvider : MediaProviderMock ( configuration : . init ( ) ) ,
2024-08-14 12:38:10 +02:00
mediaPlayerProvider : MediaPlayerProviderMock ( ) ,
voiceMessageMediaManager : VoiceMessageMediaManagerMock ( ) ,
userIndicatorController : userIndicatorControllerMock ,
appMediator : AppMediatorMock . default ,
appSettings : ServiceLocator . shared . settings ,
2024-10-25 19:58:56 +03:00
analyticsService : ServiceLocator . shared . analytics ,
2025-02-03 14:14:01 +00:00
emojiProvider : EmojiProvider ( appSettings : ServiceLocator . shared . settings ) ,
2025-02-25 14:46:01 +01:00
timelineControllerFactory : TimelineControllerFactoryMock ( . init ( ) ) ,
clientProxy : ClientProxyMock ( . init ( ) ) )
2024-08-14 12:38:10 +02:00
var deferred = deferFulfillment ( viewModel . context . $ viewState ) { value in
2024-09-19 11:03:26 +01:00
value . canCurrentUserPin
2024-08-14 12:38:10 +02:00
}
try await deferred . fulfill ( )
2024-09-19 11:03:26 +01:00
roomProxyMock . canUserPinOrUnpinUserIDReturnValue = . success ( false )
2024-08-14 12:38:10 +02:00
deferred = deferFulfillment ( viewModel . context . $ viewState ) { value in
2024-09-19 11:03:26 +01:00
! value . canCurrentUserPin
2024-08-14 12:38:10 +02:00
}
2024-10-28 12:29:31 +00:00
infoSubject . send ( . init ( roomInfo : RoomInfo ( configuration ) ) )
2024-08-14 12:38:10 +02:00
try await deferred . fulfill ( )
}
2024-04-25 18:32:33 +01:00
// MARK: - H e l p e r s
2024-08-20 16:13:27 +03:00
private func makeViewModel ( roomProxy : JoinedRoomProxyProtocol ? = nil ,
2024-04-30 17:25:32 +01:00
focussedEventID : String ? = nil ,
2025-02-03 14:14:01 +00:00
timelineController : TimelineControllerProtocol ) -> TimelineViewModel {
2024-08-20 16:13:27 +03:00
TimelineViewModel ( roomProxy : roomProxy ? ? JoinedRoomProxyMock ( . init ( name : " " ) ) ,
2024-08-13 13:36:40 +02:00
focussedEventID : focussedEventID ,
timelineController : timelineController ,
2024-10-02 17:41:08 +01:00
mediaProvider : MediaProviderMock ( configuration : . init ( ) ) ,
2024-08-13 13:36:40 +02:00
mediaPlayerProvider : MediaPlayerProviderMock ( ) ,
voiceMessageMediaManager : VoiceMessageMediaManagerMock ( ) ,
userIndicatorController : userIndicatorControllerMock ,
appMediator : AppMediatorMock . default ,
appSettings : ServiceLocator . shared . settings ,
2024-10-25 19:58:56 +03:00
analyticsService : ServiceLocator . shared . analytics ,
2025-02-03 14:14:01 +00:00
emojiProvider : EmojiProvider ( appSettings : ServiceLocator . shared . settings ) ,
2025-02-25 14:46:01 +01:00
timelineControllerFactory : TimelineControllerFactoryMock ( . init ( ) ) ,
clientProxy : ClientProxyMock ( . init ( ) ) )
2024-04-25 18:32:33 +01:00
}
2023-03-28 14:06:53 +01:00
}
private extension TextRoomTimelineItem {
2023-11-21 13:38:39 +01:00
init ( text : String , sender : String , addReactions : Bool = false , addReadReceipts : [ ReadReceipt ] = [ ] ) {
2024-01-03 17:41:25 +02:00
let reactions = addReactions ? [ AggregatedReaction ( accountOwnerID : " bob " , key : " 🦄 " , senders : [ ReactionSender ( id : sender , timestamp : Date ( ) ) ] ) ] : [ ]
2024-10-16 14:07:54 +03:00
self . init ( id : . randomEvent ,
2024-12-06 13:55:29 +00:00
timestamp : . mock ,
2023-03-28 14:06:53 +01:00
isOutgoing : sender = = " bob " ,
isEditable : sender = = " bob " ,
2023-09-27 12:22:23 +03:00
canBeRepliedTo : true ,
2023-09-15 12:58:30 +02:00
isThreaded : false ,
2023-03-28 14:06:53 +01:00
sender : . init ( id : " @ \( sender ) :server.com " , displayName : sender ) ,
2023-05-09 16:09:38 +03:00
content : . init ( body : text ) ,
2023-11-21 13:38:39 +01:00
properties : RoomTimelineItemProperties ( reactions : reactions , orderedReadReceipts : addReadReceipts ) )
2023-03-28 14:06:53 +01:00
}
}
2023-08-01 11:53:02 +01:00
private extension SeparatorRoomTimelineItem {
2025-02-04 09:50:46 +00:00
init ( uniqueID : TimelineItemIdentifier . UniqueID ) {
2024-12-11 13:32:29 +02:00
self . init ( id : . virtual ( uniqueID : uniqueID ) , timestamp : . mock )
2023-08-01 11:53:02 +01:00
}
}
private extension TextRoomTimelineItem {
init ( eventID : String ) {
2025-02-04 09:50:46 +00:00
self . init ( id : . event ( uniqueID : . init ( UUID ( ) . uuidString ) , eventOrTransactionID : . eventID ( eventID ) ) ,
2024-12-06 13:55:29 +00:00
timestamp : . mock ,
2023-08-01 11:53:02 +01:00
isOutgoing : false ,
isEditable : false ,
2023-09-27 12:22:23 +03:00
canBeRepliedTo : true ,
2023-09-15 12:58:30 +02:00
isThreaded : false ,
2023-08-01 11:53:02 +01:00
sender : . init ( id : " " ) ,
content : . init ( body : " Hello, World! " ) )
}
}