diff --git a/Sources/AblyChat/Room.swift b/Sources/AblyChat/Room.swift index 80e168b..20bf57c 100644 --- a/Sources/AblyChat/Room.swift +++ b/Sources/AblyChat/Room.swift @@ -423,3 +423,14 @@ internal actor DefaultRoom } } } + +#if DEBUG +extension DefaultRoom { + var testsOnly_lifecycleManager: DefaultRoomLifecycleManager { + guard let lifecycleManager = lifecycleManager as? DefaultRoomLifecycleManager else { + preconditionFailure("DefaultRoomLifecycleManager is expected here.") + } + return lifecycleManager + } +} +#endif diff --git a/Tests/AblyChatTests/DefaultRoomPresenceTests.swift b/Tests/AblyChatTests/DefaultRoomPresenceTests.swift index 710f21f..7c26353 100644 --- a/Tests/AblyChatTests/DefaultRoomPresenceTests.swift +++ b/Tests/AblyChatTests/DefaultRoomPresenceTests.swift @@ -135,6 +135,32 @@ struct DefaultRoomPresenceTests { } } + // @specPartial CHA-PR3d (WIP: needs failure case) + @Test + func usersMayEnterPresenceWhileAttaching() async throws { + // Given + let realtimePresence = MockRealtimePresence(["client1"].map { .init(clientId: $0) }) + let channelsList = [ + MockRealtimeChannel(name: "basketball::$chat::$chatMessages", attachResult: .delayedSuccess, mockPresence: realtimePresence), + ] + let channels = MockChannels(channels: channelsList) + let realtime = MockRealtime.create(channels: channels) + let room = try await DefaultRoom(realtime: realtime, chatAPI: ChatAPI(realtime: realtime), roomID: "basketball", options: .init(presence: .init()), logger: TestLogger(), lifecycleManagerFactory: DefaultRoomLifecycleManagerFactory()) + + let lifecycleManager = await room.testsOnly_lifecycleManager + let attachingStatusWaitSubscription = await lifecycleManager.testsOnly_subscribeToStatusChangeWaitEvents() + + // When: The room is in the attaching state and presence enter is called + Task { + try await lifecycleManager.performAttachOperation() + } + try await room.presence.enter() + + // Then: The manager waits for its room status to change + _ = try #require(await attachingStatusWaitSubscription.first { _ in true }) + #expect(await room.status == .attached) + } + // @spec CHA-PR7a // @spec CHA-PR7b // @spec CHA-PR7c diff --git a/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift b/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift index 26026cc..c7fe4da 100644 --- a/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift +++ b/Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift @@ -91,11 +91,20 @@ final class MockRealtimeChannel: NSObject, RealtimeChannelProtocol { case success case failure(ARTErrorInfo) + case delayedSuccess + case delayedFailure(ARTErrorInfo) + func performCallback(_ callback: ARTCallback?) { switch self { case .success: callback?(nil) + case .delayedSuccess: + callback?(nil) case let .failure(error): + sleep(10) // arbitrary + callback?(error) + case let .delayedFailure(error): + sleep(10) // arbitrary callback?(error) } } @@ -184,7 +193,7 @@ final class MockRealtimeChannel: NSObject, RealtimeChannelProtocol { } func on(_: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener { - fatalError("Not implemented") + ARTEventListener() } func once(_: ARTChannelEvent, callback _: @escaping (ARTChannelStateChange) -> Void) -> ARTEventListener {