Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,6 @@ If you haven't worked with typed throws before, be aware of a few sharp edges:
- `Dictionary.mapValues` does not support typed throws. We have our own extension `ablyChat_mapValuesWithTypedThrow` which does; use this.
- There are times when the compiler struggles to infer the type of the error thrown within a `do` block. In these cases, you can disable type inference for a `do` block and explicitly specify the type of the thrown error, like: `do throws(InternalError) { … }`.
- The compiler will never infer the type of the error thrown by a closure; you will need to specify this yourself; e.g. `let items = try jsonValues.map { jsonValue throws(InternalError) in … }`.
- It is possible to crash the compiler when using Swift Testing's `#expect(throws: …)` in combination with an `expression` that throws a typed error. See https://github.com/ably/ably-chat-swift/issues/233. A workaround that seems to work, which we're using at the moment (will be able to remove once Xcode 16.3 is released) is to move the code with a typed throw into a separate, non-typed-throw function; for example:
```swift
let doIt = {
try await rooms.get(name: name, options: differentOptions)
}
await #expect {
try await doIt()
} throws: { error in
isChatError(error, withCodeAndStatusCode: .fixedStatusCode(.badRequest))
}
```

### Swift concurrency rough edges

Expand Down
30 changes: 10 additions & 20 deletions Tests/AblyChatTests/DefaultMessageReactionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,13 @@ struct DefaultMessageReactionsTests {
let channel = MockRealtimeChannel(initialState: .attached)
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
// When
try await defaultMessages.reactions.send(forMessageWithSerial: "", params: .init(name: "😐", type: .distinct))
}
await #expect {
try await doIt()
} throws: { error in
// Then
error as? ARTErrorInfo == ARTErrorInfo(chatError: .nonErrorInfoInternalError(.chatAPIChatError(.messageReactionInvalidMessageSerial)))
}

// Then
#expect(thrownError == ARTErrorInfo(chatError: .nonErrorInfoInternalError(.chatAPIChatError(.messageReactionInvalidMessageSerial))))
}

// @spec CHA-MR11a1
Expand All @@ -92,16 +89,13 @@ struct DefaultMessageReactionsTests {
let channel = MockRealtimeChannel(initialState: .attached)
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
// When
try await defaultMessages.reactions.delete(forMessageWithSerial: "", params: .init(name: "😐", type: .distinct))
}
await #expect {
try await doIt()
} throws: { error in
// Then
error as? ARTErrorInfo == ARTErrorInfo(chatError: .nonErrorInfoInternalError(.chatAPIChatError(.messageReactionInvalidMessageSerial)))
}

// Then
#expect(thrownError == ARTErrorInfo(chatError: .nonErrorInfoInternalError(.chatAPIChatError(.messageReactionInvalidMessageSerial))))
}

// @spec CHA-MR3
Expand Down Expand Up @@ -512,14 +506,10 @@ struct DefaultMessageReactionsTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

// When/Then
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await defaultMessages.reactions.clientReactions(forMessageWithSerial: "123456789-000@123456789:000", clientID: nil)
}
await #expect {
try await doIt()
} throws: { error in
(error as? ARTErrorInfo)?.domain == "SomeDomain" && (error as? ARTErrorInfo)?.code == 404
}
#expect(thrownError?.domain == "SomeDomain" && thrownError?.code == 404)
}
}
}
55 changes: 12 additions & 43 deletions Tests/AblyChatTests/DefaultMessagesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,10 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await defaultMessages.send(withParams: .init(text: "hey"))
}
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
}
#expect(thrownError == ARTErrorInfo(domain: "SomeDomain", code: 123))
}

// @spec CHA-M8d
Expand All @@ -207,19 +202,14 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await defaultMessages.update(
forSerial: "0",
params: .init(text: "hey", metadata: [:], headers: [:]),
details: .init(description: "", metadata: [:]),
)
}
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
}
#expect(thrownError == ARTErrorInfo(domain: "SomeDomain", code: 123))
}

// @spec CHA-M9c
Expand All @@ -234,15 +224,10 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await defaultMessages.delete(forSerial: "0", details: nil)
}
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
}
#expect(thrownError == ARTErrorInfo(domain: "SomeDomain", code: 123))
}

// @spec CHA-M13a
Expand Down Expand Up @@ -305,15 +290,10 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await defaultMessages.get(withSerial: "123456789-000@123456789:000")
}
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == ARTErrorInfo(domain: "SomeDomain", code: 123)
}
#expect(thrownError == ARTErrorInfo(domain: "SomeDomain", code: 123))
}

// @spec CHA-M5a
Expand Down Expand Up @@ -508,16 +488,10 @@ struct DefaultMessagesTests {
let subscription = defaultMessages.subscribe()

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await subscription.historyBeforeSubscribe(withParams: .init())
}
// Then
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == artError
}
#expect(thrownError == artError)
}

// @spec CHA-M6a
Expand Down Expand Up @@ -565,16 +539,11 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", logger: TestLogger())

// When
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: ARTErrorInfo.self) {
_ = try await defaultMessages.history(withParams: .init())
}
// Then
await #expect {
try await doIt()
} throws: { error in
error as? ARTErrorInfo == artError
}
#expect(thrownError == artError)
}

// CHA-M4d is currently untestable due to not subscribing to those events on lower level
Expand Down
57 changes: 15 additions & 42 deletions Tests/AblyChatTests/DefaultPresenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,12 @@ struct DefaultPresenceTests {
options: .init(),
)

// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
// When
try await defaultPresence.enter()
}
await #expect {
try await doIt()
} /* Then */ throws: { error in
isChatError(error, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: attachError)
}
// Then
#expect(isChatError(thrownError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: attachError))

// Then
#expect(roomLifecycleManager.callRecorder.hasRecord(
Expand All @@ -132,15 +128,10 @@ struct DefaultPresenceTests {
)

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
_ = try await defaultPresence.enter()
}
await #expect {
try await doIt()
} throws: { error in
isChatError(error, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400))
}
#expect(isChatError(thrownError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400)))
}

// MARK: CHA-PR10
Expand Down Expand Up @@ -213,16 +204,12 @@ struct DefaultPresenceTests {
options: .init(),
)

// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
// When
try await defaultPresence.update()
}
await #expect {
try await doIt()
} /* Then */ throws: { error in
isChatError(error, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: attachError)
}
// Then
#expect(isChatError(thrownError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: attachError))

// Then
#expect(roomLifecycleManager.callRecorder.hasRecord(
Expand All @@ -248,15 +235,10 @@ struct DefaultPresenceTests {
)

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
_ = try await defaultPresence.update()
}
await #expect {
try await doIt()
} throws: { error in
isChatError(error, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400))
}
#expect(isChatError(thrownError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400)))
}

// MARK: CHA-PR4
Expand Down Expand Up @@ -358,15 +340,10 @@ struct DefaultPresenceTests {
)

// Then
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
_ = try await defaultPresence.get()
}
await #expect {
try await doIt()
} throws: { error in
isChatError(error, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400))
}
#expect(isChatError(thrownError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 400)))
}

// @specOneOf(3/4) CHA-PR6c
Expand Down Expand Up @@ -412,16 +389,12 @@ struct DefaultPresenceTests {
options: .init(),
)

// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
// When
try await defaultPresence.get()
}
await #expect {
try await doIt()
} /* Then */ throws: { error in
isChatError(error, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: attachError)
}
// Then
#expect(isChatError(thrownError, withCodeAndStatusCode: .variableStatusCode(.roomInInvalidState, statusCode: 500), cause: attachError))

// Then
#expect(roomLifecycleManager.callRecorder.hasRecord(
Expand Down
19 changes: 5 additions & 14 deletions Tests/AblyChatTests/DefaultRoomsTests.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Ably
@testable import AblyChat
import Testing

Expand Down Expand Up @@ -173,15 +174,10 @@ struct DefaultRoomsTests {
// Then: It throws a `badRequest` error
let differentOptions = RoomOptions(presence: .init(enableEvents: false))

// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
try await rooms.get(named: name, options: differentOptions)
}
await #expect {
try await doIt()
} throws: { error in
isChatError(error, withCodeAndStatusCode: .fixedStatusCode(.badRequest))
}
#expect(isChatError(thrownError, withCodeAndStatusCode: .fixedStatusCode(.badRequest)))
}

// @specOneOf(2/2) CHA-RC1f1 - Tests the case where, per CHA-RC1f4, there is, in the spec’s language, a _future_ in the room map
Expand Down Expand Up @@ -219,15 +215,10 @@ struct DefaultRoomsTests {
// Then: The second call to get(name:options:) throws a `badRequest` error
let differentOptions = RoomOptions(presence: .init(enableEvents: false))

// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
let thrownError = await #expect(throws: (any Error).self) {
try await rooms.get(named: name, options: differentOptions)
}
await #expect {
try await doIt()
} throws: { error in
isChatError(error, withCodeAndStatusCode: .fixedStatusCode(.badRequest))
}
#expect(isChatError(thrownError, withCodeAndStatusCode: .fixedStatusCode(.badRequest)))

// Post-test: Allow the CHA-RC1g release operation to complete
roomReleaseOperation.complete()
Expand Down