diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift index 410773793..f1c1e4801 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift @@ -32,6 +32,12 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { } } + func fromPlain(_ string: String?) async -> AttributedString? { + await Task.detached { + fromPlain(string) + }.value + } + func fromPlain(_ string: String?) -> AttributedString? { guard let string = string else { return nil @@ -44,6 +50,12 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol { return try? AttributedString(mutableAttributedString, including: \.elementX) } + func fromHTML(_ htmlString: String?) async -> AttributedString? { + await Task.detached { + fromHTML(htmlString) + }.value + } + // Do not use the default HTML renderer of NSAttributedString because this method // runs on the UI thread which we want to avoid because renderHTMLString is called // most of the time from a background thread. diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderProtocol.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderProtocol.swift index 56e12710a..d1738d94c 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderProtocol.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilderProtocol.swift @@ -15,7 +15,10 @@ struct AttributedStringBuilderComponent: Hashable { protocol AttributedStringBuilderProtocol { func fromPlain(_ string: String?) -> AttributedString? + func fromPlain(_ string: String?) async -> AttributedString? + func fromHTML(_ htmlString: String?) -> AttributedString? + func fromHTML(_ htmlString: String?) async -> AttributedString? func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]? } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index c9691c095..a6da705da 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -70,7 +70,7 @@ struct HomeScreenRoom: Identifiable, Equatable { var displayName: String? - let topic: String? + var topic: String? var lastMessage: String? var avatar: UIImage? diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 84bf41e9a..26a8e7010 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -23,6 +23,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol private var roomUpdateListeners = Set() + private var roomsUpdateTask: Task? { + willSet { + roomsUpdateTask?.cancel() + } + } private var roomSummaries: [RoomSummaryProtocol]? { didSet { @@ -56,14 +61,29 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) { - self.roomSummaries = roomSummaries - - state.rooms = roomSummaries.map { roomSummary in - buildOrUpdateRoomFromSummary(roomSummary) + roomsUpdateTask = Task { + await updateWithRoomSummaries(roomSummaries) + } + } + + private func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) async { + var rooms = [HomeScreenRoom]() + for summary in roomSummaries { + if Task.isCancelled { + return + } + + rooms.append(await buildOrUpdateRoomForSummary(summary)) } - roomUpdateListeners.removeAll() + if Task.isCancelled { + return + } + state.rooms = rooms + self.roomSummaries = roomSummaries + + roomUpdateListeners.removeAll() roomSummaries.forEach { roomSummary in roomSummary.callbacks .receive(on: DispatchQueue.main) @@ -72,10 +92,16 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return } - switch callback { - case .updatedData: + Task { if let index = self.state.rooms.firstIndex(where: { $0.id == roomSummary.id }) { - self.state.rooms[index] = self.buildOrUpdateRoomFromSummary(roomSummary) + switch callback { + case .updatedLastMessage: + var room = self.state.rooms[index] + room.lastMessage = await self.lastMessageFromEventBrief(roomSummary.lastMessage) + self.state.rooms[index] = room + default: + self.state.rooms[index] = await self.buildOrUpdateRoomForSummary(roomSummary) + } } } } @@ -110,14 +136,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol Task { await roomSummary.loadDetails() } } - private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom { - let lastMessage = lastMessageFromEventBrief(roomSummary.lastMessage) - + private func buildOrUpdateRoomForSummary(_ roomSummary: RoomSummaryProtocol) async -> HomeScreenRoom { guard var room = state.rooms.first(where: { $0.id == roomSummary.id }) else { return HomeScreenRoom(id: roomSummary.id, displayName: roomSummary.displayName, topic: roomSummary.topic, - lastMessage: lastMessage, + lastMessage: await lastMessageFromEventBrief(roomSummary.lastMessage), avatar: roomSummary.avatar, isDirect: roomSummary.isDirect, isEncrypted: roomSummary.isEncrypted, @@ -127,12 +151,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol room.avatar = roomSummary.avatar room.displayName = roomSummary.displayName - room.lastMessage = lastMessage - + room.topic = roomSummary.topic + return room } - private func lastMessageFromEventBrief(_ eventBrief: EventBrief?) -> String? { + private func lastMessageFromEventBrief(_ eventBrief: EventBrief?) async -> String? { guard let eventBrief = eventBrief else { return nil } @@ -140,7 +164,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol let senderDisplayName = senderDisplayNameForBrief(eventBrief) if let htmlBody = eventBrief.htmlBody, - let lastMessageAttributedString = attributedStringBuilder.fromHTML(htmlBody) { + let lastMessageAttributedString = await attributedStringBuilder.fromHTML(htmlBody) { return "\(senderDisplayName): \(String(lastMessageAttributedString.characters))" } else { return "\(senderDisplayName): \(eventBrief.body)" diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index f70d7f94b..a52823077 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -57,9 +57,7 @@ class RoomProxy: RoomProxyProtocol { room.setDelegate(delegate: nil) } - var id: String { - room.id() - } + lazy var id: String = room.id() var name: String? { room.name() diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift index 50e91b78e..4ba34abe2 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -46,21 +46,21 @@ class RoomSummary: RoomSummaryProtocol { roomProxy.isTombstoned } + private(set) var avatar: UIImage? { + didSet { + callbacks.send(.updatedAvatar) + } + } + private(set) var displayName: String? { didSet { - callbacks.send(.updatedData) + callbacks.send(.updatedDisplayName) } } private(set) var lastMessage: EventBrief? { didSet { - callbacks.send(.updatedData) - } - } - - private(set) var avatar: UIImage? { - didSet { - callbacks.send(.updatedData) + callbacks.send(.updatedLastMessage) } } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift index 8561effcc..01d31097c 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProtocol.swift @@ -10,7 +10,9 @@ import Combine import UIKit enum RoomSummaryCallback { - case updatedData + case updatedAvatar + case updatedDisplayName + case updatedLastMessage } @MainActor diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index a8dd9b4cc..1984d4c2f 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -18,6 +18,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol { private let memberDetailProvider: MemberDetailProviderProtocol private var cancellables = Set() + private var timelineItemsUpdateTask: Task? { + willSet { + timelineItemsUpdateTask?.cancel() + } + } let callbacks = PassthroughSubject() @@ -96,10 +101,20 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } private func updateTimelineItems() { + timelineItemsUpdateTask = Task { + await asyncUpdateTimelineItems() + } + } + + private func asyncUpdateTimelineItems() async { var newTimelineItems = [RoomTimelineItemProtocol]() var previousMessage: RoomMessageProtocol? for message in timelineProvider.messages { + if Task.isCancelled { + return + } + let areMessagesFromTheSameDay = haveSameDay(lhs: previousMessage, rhs: message) let shouldAddSectionHeader = !areMessagesFromTheSameDay @@ -111,13 +126,17 @@ class RoomTimelineController: RoomTimelineControllerProtocol { let areMessagesFromTheSameSender = (previousMessage?.sender == message.sender) let shouldShowSenderDetails = !areMessagesFromTheSameSender || !areMessagesFromTheSameDay - newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message: message, - isOutgoing: message.sender == userId, - showSenderDetails: shouldShowSenderDetails)) + newTimelineItems.append(await timelineItemFactory.buildTimelineItemFor(message: message, + isOutgoing: message.sender == userId, + showSenderDetails: shouldShowSenderDetails)) previousMessage = message } + if Task.isCancelled { + return + } + timelineItems = newTimelineItems callbacks.send(.updatedTimelineItems) diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index cde4ade40..db50359e0 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -22,20 +22,20 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { self.attributedStringBuilder = attributedStringBuilder } - func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) -> RoomTimelineItemProtocol { + func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) async -> RoomTimelineItemProtocol { let displayName = memberDetailProvider.displayNameForUserId(message.sender) let avatarURL = memberDetailProvider.avatarURLStringForUserId(message.sender) let avatarImage = mediaProvider.imageFromURLString(avatarURL) switch message { case let message as TextRoomMessage: - return buildTextTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) + return await buildTextTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) case let message as ImageRoomMessage: - return buildImageTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) + return await buildImageTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) case let message as NoticeRoomMessage: - return buildNoticeTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) + return await buildNoticeTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) case let message as EmoteRoomMessage: - return buildEmoteTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) + return await buildEmoteTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage) default: fatalError("Unknown room message.") } @@ -47,8 +47,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { _ isOutgoing: Bool, _ showSenderDetails: Bool, _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { - let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) + _ avatarImage: UIImage?) async -> RoomTimelineItemProtocol { + let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) return TextRoomTimelineItem(id: message.id, @@ -66,7 +66,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { _ isOutgoing: Bool, _ showSenderDetails: Bool, _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ avatarImage: UIImage?) async -> RoomTimelineItemProtocol { var aspectRatio: CGFloat? if let width = message.width, let height = message.height { @@ -93,8 +93,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { _ isOutgoing: Bool, _ showSenderDetails: Bool, _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { - let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) + _ avatarImage: UIImage?) async -> RoomTimelineItemProtocol { + let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) return NoticeRoomTimelineItem(id: message.id, @@ -112,8 +112,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { _ isOutgoing: Bool, _ showSenderDetails: Bool, _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { - let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) + _ avatarImage: UIImage?) async -> RoomTimelineItemProtocol { + let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) return EmoteRoomTimelineItem(id: message.id, diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift index a0eaa40a5..55e340499 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift @@ -10,5 +10,5 @@ import Foundation @MainActor protocol RoomTimelineItemFactoryProtocol { - func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) -> RoomTimelineItemProtocol + func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) async -> RoomTimelineItemProtocol } diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift index 59ca3659c..24ebe08bd 100644 --- a/UnitTests/Sources/AttributedStringBuilderTests.swift +++ b/UnitTests/Sources/AttributedStringBuilderTests.swift @@ -13,14 +13,14 @@ class AttributedStringBuilderTests: XCTestCase { let attributedStringBuilder = AttributedStringBuilder() let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2) - func testRenderHTMLStringWithHeaders() { + func testRenderHTMLStringWithHeaders() async { let h1HTMLString = "

Large Heading

" let h2HTMLString = "

Smaller Heading

" let h3HTMLString = "

Acceptable Heading

" - guard let h1AttributedString = attributedStringBuilder.fromHTML(h1HTMLString), - let h2AttributedString = attributedStringBuilder.fromHTML(h2HTMLString), - let h3AttributedString = attributedStringBuilder.fromHTML(h3HTMLString) else { + guard let h1AttributedString = await attributedStringBuilder.fromHTML(h1HTMLString), + let h2AttributedString = await attributedStringBuilder.fromHTML(h2HTMLString), + let h3AttributedString = await attributedStringBuilder.fromHTML(h3HTMLString) else { XCTFail("Could not build the attributed string") return } @@ -48,10 +48,10 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssert(h1Font.pointSize <= maxHeaderPointSize) } - func testRenderHTMLStringWithPreCode() { + func testRenderHTMLStringWithPreCode() async { let htmlString = "
1\n2\n3\n4\n
" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -68,10 +68,10 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(regex.numberOfMatches(in: string, options: [], range: .init(location: 0, length: string.count)), 3) } - func testRenderHTMLStringWithLink() { + func testRenderHTMLStringWithLink() async { let htmlString = "This text contains a link." - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -85,10 +85,10 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(link?.host, "www.matrix.org") } - func testRenderPlainStringWithLink() { + func testRenderPlainStringWithLink() async { let plainString = "This text contains a https://www.matrix.org link." - guard let attributedString = attributedStringBuilder.fromPlain(plainString) else { + guard let attributedString = await attributedStringBuilder.fromPlain(plainString) else { XCTFail("Could not build the attributed string") return } @@ -102,14 +102,14 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(link?.host, "www.matrix.org") } - func testRenderHTMLStringWithLinkInHeader() { + func testRenderHTMLStringWithLinkInHeader() async { let h1HTMLString = "

Matrix.org

" let h2HTMLString = "

Matrix.org

" let h3HTMLString = "

Matrix.org

" - guard let h1AttributedString = attributedStringBuilder.fromHTML(h1HTMLString), - let h2AttributedString = attributedStringBuilder.fromHTML(h2HTMLString), - let h3AttributedString = attributedStringBuilder.fromHTML(h3HTMLString) else { + guard let h1AttributedString = await attributedStringBuilder.fromHTML(h1HTMLString), + let h2AttributedString = await attributedStringBuilder.fromHTML(h2HTMLString), + let h3AttributedString = await attributedStringBuilder.fromHTML(h3HTMLString) else { XCTFail("Could not build the attributed string") return } @@ -140,10 +140,10 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(h3AttributedString.runs.first?.link?.host, "www.matrix.org") } - func testRenderHTMLStringWithIFrame() { + func testRenderHTMLStringWithIFrame() async { let htmlString = "" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -151,38 +151,38 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertNil(attributedString.uiKit.attachment, "iFrame attachments should be removed as they're not included in the allowedHTMLTags array.") } - func testUserIdLink() { + func testUserIdLink() async { let userId = "@user:matrix.org" let string = "The user is \(userId)." - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: userId) - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: userId) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: userId) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: userId) } - func testRoomAliasLink() { + func testRoomAliasLink() async { let roomAlias = "#matrix:matrix.org" let string = "The room alias is \(roomAlias)." - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: roomAlias) - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: roomAlias) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: roomAlias) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: roomAlias) } - func testRoomIdLink() { + func testRoomIdLink() async { let roomId = "!roomidentifier:matrix.org" let string = "The room is \(roomId)." - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: roomId) - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: roomId) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: roomId) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: roomId) } - func testEventIdLink() { + func testEventIdLink() async { let eventId = "$eventidentifier:matrix.org" let string = "The event is \(eventId)." - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expected: eventId) - checkMatrixEntityLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expected: eventId) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromHTML(string), expected: eventId) + checkMatrixEntityLinkIn(attributedString: await attributedStringBuilder.fromPlain(string), expected: eventId) } - func testDefaultFont() { + func testDefaultFont() async { let htmlString = "Test string." - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -194,10 +194,10 @@ class AttributedStringBuilderTests: XCTestCase { } } - func testDefaultForegroundColor() { + func testDefaultForegroundColor() async { let htmlString = "Test string." - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -209,11 +209,11 @@ class AttributedStringBuilderTests: XCTestCase { } } - func testCustomForegroundColor() { + func testCustomForegroundColor() async { // swiftlint:disable:next line_length let htmlString = "Rain www.matrix.org bow" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -234,10 +234,10 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertTrue(foundLink) } - func testSingleBlockquote() { + func testSingleBlockquote() async { let htmlString = "
Blockquote
" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -256,14 +256,14 @@ class AttributedStringBuilderTests: XCTestCase { } // swiftlint:disable line_length - func testBlockquoteWithinText() { + func testBlockquoteWithinText() async { let htmlString = """ The text before the blockquote
For 50 years, WWF has been protecting the future of nature. The world's leading conservation organization, WWF works in 100 countries and is supported by 1.2 million members in the United States and close to 5 million globally.
The text after the blockquote """ - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -283,10 +283,10 @@ class AttributedStringBuilderTests: XCTestCase { // swiftlint:enable line_length - func testBlockquoteWithLink() { + func testBlockquoteWithLink() async { let htmlString = "
Blockquote with a link in it
" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -311,14 +311,14 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertNotNil(foundBlockquoteAndLink, "Couldn't find blockquote or link") } - func testMultipleGroupedBlockquotes() { + func testMultipleGroupedBlockquotes() async { let htmlString = """
First blockquote with a link in it
Second blockquote with a link in it
Third blockquote with a link in it
""" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return } @@ -337,7 +337,7 @@ class AttributedStringBuilderTests: XCTestCase { XCTAssertEqual(numberOfBlockquotes, 3, "Couldn't find all the blockquotes") } - func testMultipleSeparatedBlockquotes() { + func testMultipleSeparatedBlockquotes() async { let htmlString = """ First
blockquote with a link in it
@@ -347,7 +347,7 @@ class AttributedStringBuilderTests: XCTestCase {
blockquote with a link in it
""" - guard let attributedString = attributedStringBuilder.fromHTML(htmlString) else { + guard let attributedString = await attributedStringBuilder.fromHTML(htmlString) else { XCTFail("Could not build the attributed string") return }