From eb12e0741d37217d4ae66348a6c813e58176228c Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Thu, 29 Aug 2024 15:03:09 +0100 Subject: [PATCH] Implement DefaultChatClient.rooms Part of #19. --- Example/AblyChatExample/ContentView.swift | 2 +- .../AblyChatExample/Mocks/MockRealtime.swift | 9 +++++ Sources/AblyChat/ChatClient.swift | 33 +++++++++---------- Sources/AblyChat/Rooms.swift | 21 ++++++++++++ Tests/AblyChatTests/AblyChatTests.swift | 8 ----- .../DefaultChatClientTests.swift | 27 +++++++++++++++ Tests/AblyChatTests/Mocks/MockRealtime.swift | 9 +++++ 7 files changed, 83 insertions(+), 26 deletions(-) delete mode 100644 Tests/AblyChatTests/AblyChatTests.swift create mode 100644 Tests/AblyChatTests/DefaultChatClientTests.swift diff --git a/Example/AblyChatExample/ContentView.swift b/Example/AblyChatExample/ContentView.swift index 750c754d..a0de7edf 100644 --- a/Example/AblyChatExample/ContentView.swift +++ b/Example/AblyChatExample/ContentView.swift @@ -4,7 +4,7 @@ import SwiftUI struct ContentView: View { /// Just used to check that we can successfully import and use the AblyChat library. TODO remove this once we start building the library @State private var ablyChatClient = DefaultChatClient( - realtime: MockRealtime(key: ""), + realtime: MockRealtime.create(), clientOptions: ClientOptions() ) diff --git a/Example/AblyChatExample/Mocks/MockRealtime.swift b/Example/AblyChatExample/Mocks/MockRealtime.swift index 3eddf059..11951d3d 100644 --- a/Example/AblyChatExample/Mocks/MockRealtime.swift +++ b/Example/AblyChatExample/Mocks/MockRealtime.swift @@ -16,6 +16,15 @@ final class MockRealtime: NSObject, ARTRealtimeProtocol, Sendable { required init(token _: String) {} + /** + Creates an instance of MockRealtime. + + This exists to give a convenient way to create an instance, because `init` is marked as unavailable in `ARTRealtimeProtocol`. + */ + static func create() -> MockRealtime { + MockRealtime(key: "") + } + func time(_: @escaping ARTDateTimeCallback) { fatalError("Not implemented") } diff --git a/Sources/AblyChat/ChatClient.swift b/Sources/AblyChat/ChatClient.swift index 31048120..44ac63cf 100644 --- a/Sources/AblyChat/ChatClient.swift +++ b/Sources/AblyChat/ChatClient.swift @@ -10,28 +10,22 @@ public protocol ChatClient: AnyObject, Sendable { public typealias RealtimeClient = any(ARTRealtimeProtocol & Sendable) -public final class DefaultChatClient: ChatClient { - public init(realtime _: RealtimeClient, clientOptions _: ClientOptions?) { - // This one doesn’t do `fatalError`, so that I can call it in the example app +public actor DefaultChatClient: ChatClient { + public let realtime: RealtimeClient + public nonisolated let clientOptions: ClientOptions + public nonisolated let rooms: Rooms + + public init(realtime: RealtimeClient, clientOptions: ClientOptions?) { + self.realtime = realtime + self.clientOptions = clientOptions ?? .init() + rooms = DefaultRooms(realtime: realtime, clientOptions: self.clientOptions) } - public var rooms: any Rooms { + public nonisolated var connection: any Connection { fatalError("Not yet implemented") } - public var connection: any Connection { - fatalError("Not yet implemented") - } - - public var clientID: String { - fatalError("Not yet implemented") - } - - public var realtime: RealtimeClient { - fatalError("Not yet implemented") - } - - public var clientOptions: ClientOptions { + public nonisolated var clientID: String { fatalError("Not yet implemented") } } @@ -44,4 +38,9 @@ public struct ClientOptions: Sendable { self.logHandler = logHandler self.logLevel = logLevel } + + /// Used for comparing these instances in tests without having to make this Equatable, which I’m not yet sure makes sense (we’ll decide in https://github.com/ably-labs/ably-chat-swift/issues/10) + internal func isEqualForTestPurposes(_ other: ClientOptions) -> Bool { + logHandler === other.logHandler && logLevel == other.logLevel + } } diff --git a/Sources/AblyChat/Rooms.swift b/Sources/AblyChat/Rooms.swift index f09478a4..390c721b 100644 --- a/Sources/AblyChat/Rooms.swift +++ b/Sources/AblyChat/Rooms.swift @@ -1,5 +1,26 @@ +import Ably + public protocol Rooms: AnyObject, Sendable { func get(roomID: String, options: RoomOptions) throws -> any Room func release(roomID: String) async throws var clientOptions: ClientOptions { get } } + +internal actor DefaultRooms: Rooms { + /// Exposed so that we can test it. + internal nonisolated let realtime: RealtimeClient + internal nonisolated let clientOptions: ClientOptions + + internal init(realtime: RealtimeClient, clientOptions: ClientOptions) { + self.realtime = realtime + self.clientOptions = clientOptions + } + + internal nonisolated func get(roomID _: String, options _: RoomOptions) throws -> any Room { + fatalError("Not yet implemented") + } + + internal func release(roomID _: String) async throws { + fatalError("Not yet implemented") + } +} diff --git a/Tests/AblyChatTests/AblyChatTests.swift b/Tests/AblyChatTests/AblyChatTests.swift deleted file mode 100644 index c8c5c0bc..00000000 --- a/Tests/AblyChatTests/AblyChatTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -@testable import AblyChat -import XCTest - -final class AblyChatTests: XCTestCase { - func testExample() throws { - XCTAssertNoThrow(DefaultChatClient(realtime: MockRealtime(key: ""), clientOptions: ClientOptions())) - } -} diff --git a/Tests/AblyChatTests/DefaultChatClientTests.swift b/Tests/AblyChatTests/DefaultChatClientTests.swift new file mode 100644 index 00000000..9798d50d --- /dev/null +++ b/Tests/AblyChatTests/DefaultChatClientTests.swift @@ -0,0 +1,27 @@ +@testable import AblyChat +import XCTest + +class DefaultChatClientTests: XCTestCase { + func test_init_withoutClientOptions() { + // Given: An instance of DefaultChatClient is created with nil clientOptions + let client = DefaultChatClient(realtime: MockRealtime.create(), clientOptions: nil) + + // Then: It uses the default client options + let defaultOptions = ClientOptions() + XCTAssertTrue(client.clientOptions.isEqualForTestPurposes(defaultOptions)) + } + + func test_rooms() throws { + // Given: An instance of DefaultChatClient + let realtime = MockRealtime.create() + let options = ClientOptions() + let client = DefaultChatClient(realtime: realtime, clientOptions: options) + + // Then: Its `rooms` property returns an instance of DefaultRooms with the same realtime client and client options + let rooms = client.rooms + + let defaultRooms = try XCTUnwrap(rooms as? DefaultRooms) + XCTAssertIdentical(defaultRooms.realtime, realtime) + XCTAssertTrue(defaultRooms.clientOptions.isEqualForTestPurposes(options)) + } +} diff --git a/Tests/AblyChatTests/Mocks/MockRealtime.swift b/Tests/AblyChatTests/Mocks/MockRealtime.swift index d7f80771..d0f8c4a9 100644 --- a/Tests/AblyChatTests/Mocks/MockRealtime.swift +++ b/Tests/AblyChatTests/Mocks/MockRealtime.swift @@ -17,6 +17,15 @@ final class MockRealtime: NSObject, ARTRealtimeProtocol, Sendable { required init(token _: String) {} + /** + Creates an instance of MockRealtime. + + This exists to give a convenient way to create an instance, because `init` is marked as unavailable in `ARTRealtimeProtocol`. + */ + static func create() -> MockRealtime { + MockRealtime(key: "") + } + func time(_: @escaping ARTDateTimeCallback) { fatalError("Not implemented") }