mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Handle media source validation more gracefully. (#3571)
* Handle media source validation more gracefully. * Fix unit tests.
This commit is contained in:
parent
c8627cfd64
commit
49a94e0bdb
@ -21,7 +21,7 @@ enum LoadableImageMediaType: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct LoadableImage<TransformerView: View, PlaceholderView: View>: View {
|
struct LoadableImage<TransformerView: View, PlaceholderView: View>: View {
|
||||||
private let mediaSource: MediaSourceProxy
|
private let mediaSource: MediaSourceProxy?
|
||||||
private let mediaType: LoadableImageMediaType
|
private let mediaType: LoadableImageMediaType
|
||||||
private let blurhash: String?
|
private let blurhash: String?
|
||||||
private let size: CGSize?
|
private let size: CGSize?
|
||||||
@ -60,27 +60,31 @@ struct LoadableImage<TransformerView: View, PlaceholderView: View>: View {
|
|||||||
mediaProvider: MediaProviderProtocol?,
|
mediaProvider: MediaProviderProtocol?,
|
||||||
transformer: @escaping (AnyView) -> TransformerView = { $0 },
|
transformer: @escaping (AnyView) -> TransformerView = { $0 },
|
||||||
placeholder: @escaping () -> PlaceholderView) {
|
placeholder: @escaping () -> PlaceholderView) {
|
||||||
self.init(mediaSource: MediaSourceProxy(url: url, mimeType: nil),
|
mediaSource = try? MediaSourceProxy(url: url, mimeType: nil)
|
||||||
mediaType: mediaType,
|
self.mediaType = mediaType
|
||||||
blurhash: blurhash,
|
self.blurhash = blurhash
|
||||||
size: size,
|
self.size = size
|
||||||
mediaProvider: mediaProvider,
|
self.mediaProvider = mediaProvider
|
||||||
transformer: transformer,
|
self.transformer = transformer
|
||||||
placeholder: placeholder)
|
self.placeholder = placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
LoadableImageContent(mediaSource: mediaSource,
|
if let mediaSource {
|
||||||
mediaType: mediaType,
|
LoadableImageContent(mediaSource: mediaSource,
|
||||||
blurhash: blurhash,
|
mediaType: mediaType,
|
||||||
size: size,
|
blurhash: blurhash,
|
||||||
mediaProvider: mediaProvider,
|
size: size,
|
||||||
transformer: transformer,
|
mediaProvider: mediaProvider,
|
||||||
placeholder: placeholder)
|
transformer: transformer,
|
||||||
.id(stableMediaIdentifier)
|
placeholder: placeholder)
|
||||||
|
.id(stableMediaIdentifier)
|
||||||
|
} else {
|
||||||
|
placeholder()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var stableMediaIdentifier: String {
|
private var stableMediaIdentifier: String? {
|
||||||
switch mediaType {
|
switch mediaType {
|
||||||
case .timelineItem(let uniqueID):
|
case .timelineItem(let uniqueID):
|
||||||
// Consider media for the same item to be the same view
|
// Consider media for the same item to be the same view
|
||||||
@ -88,7 +92,7 @@ struct LoadableImage<TransformerView: View, PlaceholderView: View>: View {
|
|||||||
default:
|
default:
|
||||||
// Binds the lifecycle of the LoadableImage to the associated URL.
|
// Binds the lifecycle of the LoadableImage to the associated URL.
|
||||||
// This fixes the problem of the cache returning old values after a change in the URL.
|
// This fixes the problem of the cache returning old values after a change in the URL.
|
||||||
mediaSource.url.absoluteString
|
mediaSource?.url.absoluteString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,7 +363,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We don't actually know the mime type here, assume it's an image.
|
// We don't actually know the mime type here, assume it's an image.
|
||||||
if case let .success(file) = await mediaProvider.loadFileFromSource(.init(url: url, mimeType: "image/jpeg")) {
|
if let mediaSource = try? MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
|
case let .success(file) = await mediaProvider.loadFileFromSource(mediaSource) {
|
||||||
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: roomProxy.infoPublisher.value.displayName)
|
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: roomProxy.infoPublisher.value.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,8 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
|||||||
defer { userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) }
|
defer { userIndicatorController.retractIndicatorWithId(loadingIndicatorIdentifier) }
|
||||||
|
|
||||||
// We don't actually know the mime type here, assume it's an image.
|
// We don't actually know the mime type here, assume it's an image.
|
||||||
if case let .success(file) = await mediaProvider.loadFileFromSource(.init(url: url, mimeType: "image/jpeg")) {
|
if let mediaSource = try? MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
|
case let .success(file) = await mediaProvider.loadFileFromSource(mediaSource) {
|
||||||
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: roomMemberProxy.displayName)
|
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: roomMemberProxy.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,8 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
|||||||
defer { hideLoadingIndicator() }
|
defer { hideLoadingIndicator() }
|
||||||
|
|
||||||
// We don't actually know the mime type here, assume it's an image.
|
// We don't actually know the mime type here, assume it's an image.
|
||||||
if case let .success(file) = await mediaProvider.loadFileFromSource(.init(url: url, mimeType: "image/jpeg")) {
|
if let mediaSource = try? MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
|
case let .success(file) = await mediaProvider.loadFileFromSource(mediaSource) {
|
||||||
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: userProfile.displayName)
|
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: userProfile.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,8 @@ struct MediaSourceProxy: Hashable {
|
|||||||
self.mimeType = mimeType
|
self.mimeType = mimeType
|
||||||
}
|
}
|
||||||
|
|
||||||
init(url: URL, mimeType: String?) {
|
init(url: URL, mimeType: String?) throws {
|
||||||
guard let mediaSource = try? MediaSource.fromUrl(url: url.absoluteString) else {
|
underlyingSource = try MediaSource.fromUrl(url: url.absoluteString)
|
||||||
fatalError("Unable to create MediaSource from URL: \(url.absoluteString)")
|
|
||||||
}
|
|
||||||
underlyingSource = mediaSource
|
|
||||||
self.url = URL(string: underlyingSource.url())
|
self.url = URL(string: underlyingSource.url())
|
||||||
self.mimeType = mimeType
|
self.mimeType = mimeType
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ struct NotificationItemProxy: NotificationItemProxyProtocol {
|
|||||||
var senderAvatarMediaSource: MediaSourceProxy? {
|
var senderAvatarMediaSource: MediaSourceProxy? {
|
||||||
if let senderAvatarURLString = notificationItem.senderInfo.avatarUrl,
|
if let senderAvatarURLString = notificationItem.senderInfo.avatarUrl,
|
||||||
let senderAvatarURL = URL(string: senderAvatarURLString) {
|
let senderAvatarURL = URL(string: senderAvatarURLString) {
|
||||||
return MediaSourceProxy(url: senderAvatarURL, mimeType: nil)
|
return try? MediaSourceProxy(url: senderAvatarURL, mimeType: nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ struct NotificationItemProxy: NotificationItemProxyProtocol {
|
|||||||
var roomAvatarMediaSource: MediaSourceProxy? {
|
var roomAvatarMediaSource: MediaSourceProxy? {
|
||||||
if let roomAvatarURLString = notificationItem.roomInfo.avatarUrl,
|
if let roomAvatarURLString = notificationItem.roomInfo.avatarUrl,
|
||||||
let roomAvatarURL = URL(string: roomAvatarURLString) {
|
let roomAvatarURL = URL(string: roomAvatarURLString) {
|
||||||
return MediaSourceProxy(url: roomAvatarURL, mimeType: nil)
|
return try? MediaSourceProxy(url: roomAvatarURL, mimeType: nil)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -195,9 +195,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let avatarURL {
|
if let avatarURL, let mediaSource = try? MediaSourceProxy(url: avatarURL, mimeType: nil) {
|
||||||
let mediaSource = MediaSourceProxy(url: avatarURL, mimeType: nil)
|
|
||||||
|
|
||||||
if case let .success(avatarData) = await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) {
|
if case let .success(avatarData) = await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) {
|
||||||
sendMessageIntent.setImage(INImage(imageData: avatarData), forParameterNamed: \.speakableGroupName)
|
sendMessageIntent.setImage(INImage(imageData: avatarData), forParameterNamed: \.speakableGroupName)
|
||||||
} else {
|
} else {
|
||||||
|
@ -243,11 +243,15 @@ struct VideoInfoProxy: Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var mockVideo: VideoInfoProxy {
|
static var mockVideo: VideoInfoProxy {
|
||||||
.init(source: .init(url: .mockMXCVideo, mimeType: nil),
|
guard let mediaSource = try? MediaSourceProxy(url: .mockMXCVideo, mimeType: nil) else {
|
||||||
duration: 100,
|
fatalError("Invalid mock media source URL")
|
||||||
size: .init(width: 1920, height: 1080),
|
}
|
||||||
aspectRatio: 1.78,
|
|
||||||
mimeType: nil)
|
return .init(source: mediaSource,
|
||||||
|
duration: 100,
|
||||||
|
size: .init(width: 1920, height: 1080),
|
||||||
|
aspectRatio: 1.78,
|
||||||
|
mimeType: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,10 +273,6 @@ struct ImageInfoProxy: Hashable {
|
|||||||
self.init(source: .init(source: source, mimeType: mimeType), width: width, height: height, mimeType: mimeType)
|
self.init(source: .init(source: source, mimeType: mimeType), width: width, height: height, mimeType: mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(url: URL, width: UInt64?, height: UInt64?, mimeType: String?) {
|
|
||||||
self.init(source: .init(url: url, mimeType: mimeType), width: width, height: height, mimeType: mimeType)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(source: MediaSourceProxy, width: UInt64?, height: UInt64?, mimeType: String?) {
|
init(source: MediaSourceProxy, width: UInt64?, height: UInt64?, mimeType: String?) {
|
||||||
self.source = source
|
self.source = source
|
||||||
|
|
||||||
@ -292,17 +292,25 @@ struct ImageInfoProxy: Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var mockImage: ImageInfoProxy {
|
static var mockImage: ImageInfoProxy {
|
||||||
.init(source: .init(url: .mockMXCImage, mimeType: "image/png"),
|
guard let mediaSource = try? MediaSourceProxy(url: .mockMXCImage, mimeType: "image/png") else {
|
||||||
size: .init(width: 100, height: 100),
|
fatalError("Invalid mock media source URL")
|
||||||
aspectRatio: 1,
|
}
|
||||||
mimeType: "image/png")
|
|
||||||
|
return .init(source: mediaSource,
|
||||||
|
size: .init(width: 100, height: 100),
|
||||||
|
aspectRatio: 1,
|
||||||
|
mimeType: "image/png")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mockThumbnail: ImageInfoProxy {
|
static var mockThumbnail: ImageInfoProxy {
|
||||||
.init(source: .init(url: .mockMXCImage, mimeType: nil),
|
guard let mediaSource = try? MediaSourceProxy(url: .mockMXCImage, mimeType: "image/png") else {
|
||||||
size: nil,
|
fatalError("Invalid mock media source URL")
|
||||||
aspectRatio: nil,
|
}
|
||||||
mimeType: nil)
|
|
||||||
|
return .init(source: mediaSource,
|
||||||
|
size: nil,
|
||||||
|
aspectRatio: nil,
|
||||||
|
mimeType: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +33,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
case .redactedMessage:
|
case .redactedMessage:
|
||||||
return buildRedactedTimelineItem(eventItemProxy, isOutgoing)
|
return buildRedactedTimelineItem(eventItemProxy, isOutgoing)
|
||||||
case .sticker(let body, let imageInfo, let mediaSource):
|
case .sticker(let body, let imageInfo, let mediaSource):
|
||||||
guard let url = URL(string: mediaSource.url()) else {
|
return buildStickerTimelineItem(eventItemProxy, body, imageInfo, mediaSource, isOutgoing)
|
||||||
MXLog.error("Invalid sticker url string: \(mediaSource.url())")
|
|
||||||
return buildUnsupportedTimelineItem(eventItemProxy, "m.sticker", "Invalid Sticker URL", isOutgoing)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildStickerTimelineItem(eventItemProxy, body, imageInfo, url, isOutgoing)
|
|
||||||
case .failedToParseMessageLike(let eventType, let error):
|
case .failedToParseMessageLike(let eventType, let error):
|
||||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
|
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
|
||||||
case .failedToParseState(let eventType, _, let error):
|
case .failedToParseState(let eventType, _, let error):
|
||||||
@ -119,9 +114,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
|||||||
private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||||
_ body: String,
|
_ body: String,
|
||||||
_ info: MatrixRustSDK.ImageInfo,
|
_ info: MatrixRustSDK.ImageInfo,
|
||||||
_ imageURL: URL,
|
_ mediaSource: MediaSource,
|
||||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||||
let imageInfo = ImageInfoProxy(url: imageURL, width: info.width, height: info.height, mimeType: info.mimetype)
|
let imageInfo = ImageInfoProxy(source: mediaSource, width: info.width, height: info.height, mimeType: info.mimetype)
|
||||||
|
|
||||||
return StickerRoomTimelineItem(id: eventItemProxy.id,
|
return StickerRoomTimelineItem(id: eventItemProxy.id,
|
||||||
body: body,
|
body: body,
|
||||||
|
@ -10,12 +10,12 @@ import MatrixRustSDK
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class MediaLoaderTests: XCTestCase {
|
final class MediaLoaderTests: XCTestCase {
|
||||||
func testMediaRequestCoalescing() async {
|
func testMediaRequestCoalescing() async throws {
|
||||||
let mediaLoadingClient = ClientSDKMock()
|
let mediaLoadingClient = ClientSDKMock()
|
||||||
mediaLoadingClient.getMediaContentMediaSourceReturnValue = Data()
|
mediaLoadingClient.getMediaContentMediaSourceReturnValue = Data()
|
||||||
let mediaLoader = MediaLoader(client: mediaLoadingClient)
|
let mediaLoader = MediaLoader(client: mediaLoadingClient)
|
||||||
|
|
||||||
let mediaSource = MediaSourceProxy(url: .mockMXCFile, mimeType: nil)
|
let mediaSource = try MediaSourceProxy(url: .mockMXCFile, mimeType: nil)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
for _ in 1...10 {
|
for _ in 1...10 {
|
||||||
@ -28,12 +28,12 @@ final class MediaLoaderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMediaThumbnailRequestCoalescing() async {
|
func testMediaThumbnailRequestCoalescing() async throws {
|
||||||
let mediaLoadingClient = ClientSDKMock()
|
let mediaLoadingClient = ClientSDKMock()
|
||||||
mediaLoadingClient.getMediaThumbnailMediaSourceWidthHeightReturnValue = Data()
|
mediaLoadingClient.getMediaThumbnailMediaSourceWidthHeightReturnValue = Data()
|
||||||
let mediaLoader = MediaLoader(client: mediaLoadingClient)
|
let mediaLoader = MediaLoader(client: mediaLoadingClient)
|
||||||
|
|
||||||
let mediaSource = MediaSourceProxy(url: .mockMXCImage, mimeType: nil)
|
let mediaSource = try MediaSourceProxy(url: .mockMXCImage, mimeType: nil)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
for _ in 1...10 {
|
for _ in 1...10 {
|
||||||
|
@ -36,7 +36,7 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let loadTask = mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"))
|
let loadTask = try mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"))
|
||||||
|
|
||||||
let connectivitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.unreachable)
|
let connectivitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.unreachable)
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testLoadingRetriedOnReconnectionCancelsAfterSecondFailure() async throws {
|
func testLoadingRetriedOnReconnectionCancelsAfterSecondFailure() async throws {
|
||||||
let loadTask = mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"))
|
let loadTask = try mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"))
|
||||||
|
|
||||||
let connectivitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
|
let connectivitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test_whenImageFromSourceWithSourceNil_nilReturned() throws {
|
func test_whenImageFromSourceWithSourceNil_nilReturned() throws {
|
||||||
let image = mediaProvider.imageFromSource(nil, size: Avatars.Size.room(on: .timeline).scaledSize)
|
let image = try mediaProvider.imageFromSource(nil, size: Avatars.Size.room(on: .timeline).scaledSize)
|
||||||
XCTAssertNil(image)
|
XCTAssertNil(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,14 +83,14 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
|
let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
|
||||||
let imageForKey = UIImage()
|
let imageForKey = UIImage()
|
||||||
imageCache.retrievedImagesInMemory[key] = imageForKey
|
imageCache.retrievedImagesInMemory[key] = imageForKey
|
||||||
let image = mediaProvider.imageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
let image = try mediaProvider.imageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
size: avatarSize.scaledSize)
|
size: avatarSize.scaledSize)
|
||||||
XCTAssertEqual(image, imageForKey)
|
XCTAssertEqual(image, imageForKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_whenImageFromSourceWithSourceNotNilAndImageNotCached_nilReturned() throws {
|
func test_whenImageFromSourceWithSourceNotNilAndImageNotCached_nilReturned() throws {
|
||||||
let image = mediaProvider.imageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
let image = try mediaProvider.imageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
||||||
size: Avatars.Size.room(on: .timeline).scaledSize)
|
size: Avatars.Size.room(on: .timeline).scaledSize)
|
||||||
XCTAssertNil(image)
|
XCTAssertNil(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +100,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
|
let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
|
||||||
let imageForKey = UIImage()
|
let imageForKey = UIImage()
|
||||||
imageCache.retrievedImagesInMemory[key] = imageForKey
|
imageCache.retrievedImagesInMemory[key] = imageForKey
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
size: avatarSize.scaledSize)
|
size: avatarSize.scaledSize)
|
||||||
XCTAssertEqual(Result.success(imageForKey), result)
|
XCTAssertEqual(Result.success(imageForKey), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,8 +111,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
|
let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
|
||||||
let imageForKey = UIImage()
|
let imageForKey = UIImage()
|
||||||
imageCache.retrievedImages[key] = imageForKey
|
imageCache.retrievedImages[key] = imageForKey
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
size: avatarSize.scaledSize)
|
size: avatarSize.scaledSize)
|
||||||
XCTAssertEqual(Result.success(imageForKey), result)
|
XCTAssertEqual(Result.success(imageForKey), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +122,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
|
|
||||||
mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = expectedImage.pngData()
|
mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = expectedImage.pngData()
|
||||||
|
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
||||||
size: avatarSize.scaledSize)
|
size: avatarSize.scaledSize)
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let image):
|
case .success(let image):
|
||||||
XCTAssertEqual(image.pngData(), expectedImage.pngData())
|
XCTAssertEqual(image.pngData(), expectedImage.pngData())
|
||||||
@ -140,8 +140,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
|
|
||||||
mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = expectedImage.pngData()
|
mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = expectedImage.pngData()
|
||||||
|
|
||||||
_ = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
_ = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"),
|
||||||
size: avatarSize.scaledSize)
|
size: avatarSize.scaledSize)
|
||||||
let storedImage = try XCTUnwrap(imageCache.storedImages[key])
|
let storedImage = try XCTUnwrap(imageCache.storedImages[key])
|
||||||
XCTAssertEqual(expectedImage.pngData(), storedImage.pngData())
|
XCTAssertEqual(expectedImage.pngData(), storedImage.pngData())
|
||||||
}
|
}
|
||||||
@ -151,8 +151,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
|
|
||||||
mediaLoader.loadMediaContentForSourceReturnValue = expectedImage.pngData()
|
mediaLoader.loadMediaContentForSourceReturnValue = expectedImage.pngData()
|
||||||
|
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
||||||
size: nil)
|
size: nil)
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let image):
|
case .success(let image):
|
||||||
XCTAssertEqual(image.pngData(), expectedImage.pngData())
|
XCTAssertEqual(image.pngData(), expectedImage.pngData())
|
||||||
@ -164,8 +164,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndLoadImageThumbnailFails_errorIsThrown() async throws {
|
func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndLoadImageThumbnailFails_errorIsThrown() async throws {
|
||||||
mediaLoader.loadMediaThumbnailForSourceWidthHeightThrowableError = MediaProviderTestsError.error
|
mediaLoader.loadMediaThumbnailForSourceWidthHeightThrowableError = MediaProviderTestsError.error
|
||||||
|
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
||||||
size: Avatars.Size.room(on: .timeline).scaledSize)
|
size: Avatars.Size.room(on: .timeline).scaledSize)
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
XCTFail("Should fail")
|
XCTFail("Should fail")
|
||||||
@ -177,8 +177,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndNoAvatarSizeAndLoadImageContentFails_errorIsThrown() async throws {
|
func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndNoAvatarSizeAndLoadImageContentFails_errorIsThrown() async throws {
|
||||||
mediaLoader.loadMediaContentForSourceThrowableError = MediaProviderTestsError.error
|
mediaLoader.loadMediaContentForSourceThrowableError = MediaProviderTestsError.error
|
||||||
|
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
||||||
size: nil)
|
size: nil)
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
XCTFail("Should fail")
|
XCTFail("Should fail")
|
||||||
@ -190,8 +190,8 @@ final class MediaProviderTests: XCTestCase {
|
|||||||
func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndImageThumbnailIsLoadedWithCorruptedData_errorIsThrown() async throws {
|
func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndImageThumbnailIsLoadedWithCorruptedData_errorIsThrown() async throws {
|
||||||
mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = Data()
|
mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = Data()
|
||||||
|
|
||||||
let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
let result = try await mediaProvider.loadImageFromSource(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"),
|
||||||
size: Avatars.Size.room(on: .timeline).scaledSize)
|
size: Avatars.Size.room(on: .timeline).scaledSize)
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
XCTFail("Should fail")
|
XCTFail("Should fail")
|
||||||
|
@ -26,7 +26,7 @@ class VoiceMessageCacheTests: XCTestCase {
|
|||||||
voiceMessageCache.clearCache()
|
voiceMessageCache.clearCache()
|
||||||
|
|
||||||
fileManager = FileManager.default
|
fileManager = FileManager.default
|
||||||
mediaSource = MediaSourceProxy(url: someURL, mimeType: "audio/ogg")
|
mediaSource = try MediaSourceProxy(url: someURL, mimeType: "audio/ogg")
|
||||||
|
|
||||||
// Create the temporary directory we will use
|
// Create the temporary directory we will use
|
||||||
try fileManager.createDirectory(at: testTemporaryDirectory, withIntermediateDirectories: true)
|
try fileManager.createDirectory(at: testTemporaryDirectory, withIntermediateDirectories: true)
|
||||||
|
@ -28,7 +28,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
|
|||||||
|
|
||||||
func testLoadVoiceMessageFromSourceUnsupportedMedia() async throws {
|
func testLoadVoiceMessageFromSourceUnsupportedMedia() async throws {
|
||||||
// Only "audio/ogg" file are supported
|
// Only "audio/ogg" file are supported
|
||||||
let unsupportedMediaSource = MediaSourceProxy(url: someURL, mimeType: "audio/wav")
|
let unsupportedMediaSource = try MediaSourceProxy(url: someURL, mimeType: "audio/wav")
|
||||||
do {
|
do {
|
||||||
_ = try await voiceMessageMediaManager.loadVoiceMessageFromSource(unsupportedMediaSource, body: nil)
|
_ = try await voiceMessageMediaManager.loadVoiceMessageFromSource(unsupportedMediaSource, body: nil)
|
||||||
XCTFail("A `VoiceMessageMediaManagerError.unsupportedMimeTye` error is expected")
|
XCTFail("A `VoiceMessageMediaManagerError.unsupportedMimeTye` error is expected")
|
||||||
@ -49,7 +49,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
|
|||||||
let cachedConvertedFileURL = URL("/some/url/cached_converted_file.m4a")
|
let cachedConvertedFileURL = URL("/some/url/cached_converted_file.m4a")
|
||||||
|
|
||||||
voiceMessageCache.fileURLForReturnValue = nil
|
voiceMessageCache.fileURLForReturnValue = nil
|
||||||
let mediaSource = MediaSourceProxy(url: someURL, mimeType: "audio/ogg; codecs=opus")
|
let mediaSource = try MediaSourceProxy(url: someURL, mimeType: "audio/ogg; codecs=opus")
|
||||||
mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile))
|
mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile))
|
||||||
voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL)
|
voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL)
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
|
|||||||
func testLoadVoiceMessageFromSourceAlreadyCached() async throws {
|
func testLoadVoiceMessageFromSourceAlreadyCached() async throws {
|
||||||
// Check if the file is already present in cache
|
// Check if the file is already present in cache
|
||||||
voiceMessageCache.fileURLForReturnValue = URL("/converted_file/url")
|
voiceMessageCache.fileURLForReturnValue = URL("/converted_file/url")
|
||||||
let mediaSource = MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
let mediaSource = try MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
||||||
let url = try await voiceMessageMediaManager.loadVoiceMessageFromSource(mediaSource, body: nil)
|
let url = try await voiceMessageMediaManager.loadVoiceMessageFromSource(mediaSource, body: nil)
|
||||||
XCTAssertEqual(url, URL("/converted_file/url"))
|
XCTAssertEqual(url, URL("/converted_file/url"))
|
||||||
// The file must have be search in the cache
|
// The file must have be search in the cache
|
||||||
@ -81,7 +81,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
|
|||||||
// An error must be reported if the file cannot be retrieved
|
// An error must be reported if the file cannot be retrieved
|
||||||
do {
|
do {
|
||||||
voiceMessageCache.fileURLForReturnValue = nil
|
voiceMessageCache.fileURLForReturnValue = nil
|
||||||
let mediaSource = MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
let mediaSource = try MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
||||||
_ = try await voiceMessageMediaManager.loadVoiceMessageFromSource(mediaSource, body: nil)
|
_ = try await voiceMessageMediaManager.loadVoiceMessageFromSource(mediaSource, body: nil)
|
||||||
XCTFail("A `MediaProviderError.failedRetrievingFile` error is expected")
|
XCTFail("A `MediaProviderError.failedRetrievingFile` error is expected")
|
||||||
} catch {
|
} catch {
|
||||||
@ -102,7 +102,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
|
|||||||
|
|
||||||
// Check if the file is not already present in cache
|
// Check if the file is not already present in cache
|
||||||
voiceMessageCache.fileURLForReturnValue = nil
|
voiceMessageCache.fileURLForReturnValue = nil
|
||||||
let mediaSource = MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
let mediaSource = try MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
||||||
mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile))
|
mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile))
|
||||||
let audioConverter = AudioConverterMock()
|
let audioConverter = AudioConverterMock()
|
||||||
voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL)
|
voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL)
|
||||||
@ -145,7 +145,7 @@ class VoiceMessageMediaManagerTests: XCTestCase {
|
|||||||
voiceMessageCache: voiceMessageCache,
|
voiceMessageCache: voiceMessageCache,
|
||||||
audioConverter: audioConverter)
|
audioConverter: audioConverter)
|
||||||
|
|
||||||
let mediaSource = MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
let mediaSource = try MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType)
|
||||||
for _ in 0..<10 {
|
for _ in 0..<10 {
|
||||||
let url = try await voiceMessageMediaManager.loadVoiceMessageFromSource(mediaSource, body: nil)
|
let url = try await voiceMessageMediaManager.loadVoiceMessageFromSource(mediaSource, body: nil)
|
||||||
XCTAssertEqual(url, cachedConvertedFileURL)
|
XCTAssertEqual(url, cachedConvertedFileURL)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user