Files
letro-ios/UnitTests/Sources/AudioPlayerStateTests.swift
Mauro 173b39a07f Swift Testing for Unit Tests PART 1 (#5119)
* migrated a lot of unit tests to Swift Testing and added a new implementation for deferred fulfillment

more tests migration

Cleaned the code manually to establish some good patterns

more code improvements

some more code improvements

removed empty tests

update project

* more pr suggestions and cleanups

* removed the TestSetup pattern

* fixing claude not reusing tests

* pr suggestion + added indent rule to swiftformat so that we can prevent AIs to change that
2026-02-19 16:20:47 +01:00

309 lines
11 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Combine
@testable import ElementX
import Foundation
import Testing
@MainActor
@Suite
struct AudioPlayerStateTests {
static let audioDuration = 10.0
private var audioPlayerState: AudioPlayerState!
private var audioPlayerMock: AudioPlayerMock!
private var audioPlayerActionsSubject: PassthroughSubject<AudioPlayerAction, Never>!
private var audioPlayerActions: AnyPublisher<AudioPlayerAction, Never> {
audioPlayerActionsSubject.eraseToAnyPublisher()
}
private var audioPlayerSeekCallsSubject: PassthroughSubject<Double, Never>!
private func buildAudioPlayerMock() -> AudioPlayerMock {
let audioPlayerMock = AudioPlayerMock()
audioPlayerMock.underlyingActions = audioPlayerActions
audioPlayerMock.state = .stopped
audioPlayerMock.currentTime = 0.0
audioPlayerMock.duration = 0.0
audioPlayerMock.seekToClosure = { [audioPlayerSeekCallsSubject] progress in
audioPlayerSeekCallsSubject?.send(progress)
}
return audioPlayerMock
}
init() async {
audioPlayerActionsSubject = .init()
audioPlayerSeekCallsSubject = .init()
audioPlayerState = AudioPlayerState(id: .timelineItemIdentifier(.randomEvent), title: "", duration: Self.audioDuration)
audioPlayerMock = buildAudioPlayerMock()
audioPlayerMock.seekToClosure = { [audioPlayerMock] progress in
audioPlayerMock?.currentTime = Self.audioDuration * progress
}
}
@Test
func attach() {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
#expect(audioPlayerState.isAttached)
#expect(audioPlayerState.playbackState == .loading)
}
@Test
mutating func detach() {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
audioPlayerState.detachAudioPlayer()
#expect(audioPlayerMock.stopCalled)
#expect(!audioPlayerState.isAttached)
#expect(audioPlayerState.playbackState == .stopped)
#expect(!audioPlayerState.showProgressIndicator)
}
@Test
func delayedState() async throws {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
#expect(audioPlayerState.isAttached)
#expect(audioPlayerState.playbackState == .loading)
#expect(audioPlayerState.playerButtonPlaybackState == .stopped)
let deferred = deferFulfillment(audioPlayerState.$playerButtonPlaybackState) { output in
switch output {
case .loading:
return true
default:
return false
}
}
try await deferred.fulfill()
#expect(audioPlayerState.playerButtonPlaybackState == .loading)
}
@Test
func otherActionsAreNotDelayed() async throws {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
#expect(audioPlayerState.playbackState == .loading)
#expect(audioPlayerState.playerButtonPlaybackState == .stopped)
let deferred = deferFulfillment(audioPlayerState.$playerButtonPlaybackState) { output in
switch output {
case .playing:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didStartPlaying)
try await deferred.fulfill()
#expect(audioPlayerState.playbackState == .playing)
#expect(audioPlayerState.playerButtonPlaybackState == .playing)
}
@Test
mutating func reportError() {
#expect(audioPlayerState.playbackState == .stopped)
audioPlayerState.reportError()
#expect(audioPlayerState.playbackState == .error)
}
@Test
mutating func updateProgress() async {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
// If we try to set a negative progress, the new progress must be 0.0
await audioPlayerState.updateState(progress: -5.0)
#expect(audioPlayerState.progress == 0.0)
#expect(audioPlayerMock.seekToReceivedProgress == 0.0)
// If we try to set a progress > 1.0, the new progress must be 1.0
await audioPlayerState.updateState(progress: 1.5)
#expect(audioPlayerState.progress == 1.0)
#expect(audioPlayerMock.seekToReceivedProgress == 1.0)
audioPlayerMock.state = .stopped
await audioPlayerState.updateState(progress: 0.4)
#expect(audioPlayerState.progress == 0.4)
#expect(audioPlayerMock.seekToReceivedProgress == 0.4)
#expect(!audioPlayerState.isPublishingProgress)
audioPlayerMock.state = .playing
await audioPlayerState.updateState(progress: 0.4)
#expect(audioPlayerState.progress == 0.4)
#expect(audioPlayerMock.seekToReceivedProgress == 0.4)
#expect(audioPlayerState.isPublishingProgress)
}
@Test
func handlingAudioPlayerActionDidStartLoading() async throws {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .loading:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didStartLoading)
try await deferred.fulfill()
#expect(audioPlayerState.playbackState == .loading)
}
@Test
mutating func handlingAudioPlayerActionDidFinishLoading() async throws {
audioPlayerMock.duration = 10.0
audioPlayerState = AudioPlayerState(id: .timelineItemIdentifier(.randomEvent), title: "", duration: 0)
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .readyToPlay:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didFinishLoading)
try await deferred.fulfill()
// The state is expected to be .readyToPlay
#expect(audioPlayerState.playbackState == .readyToPlay)
// The duration should have been updated with the player's duration
#expect(audioPlayerState.duration == audioPlayerMock.duration)
}
@Test
mutating func handlingAudioPlayerActionDidStartPlaying() async throws {
await audioPlayerState.updateState(progress: 0.4)
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .playing:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didStartPlaying)
try await deferred.fulfill()
#expect(audioPlayerMock.seekToReceivedProgress == 0.4)
#expect(audioPlayerState.playbackState == .playing)
#expect(audioPlayerState.isPublishingProgress)
#expect(audioPlayerState.showProgressIndicator)
}
@Test
mutating func handlingAudioPlayerActionDidPausePlaying() async throws {
await audioPlayerState.updateState(progress: 0.4)
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .stopped:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didPausePlaying)
try await deferred.fulfill()
#expect(audioPlayerState.playbackState == .stopped)
#expect(audioPlayerState.progress == 0.4)
#expect(!audioPlayerState.isPublishingProgress)
#expect(audioPlayerState.showProgressIndicator)
}
@Test
mutating func handlingAudioPlayerActionsidStopPlaying() async throws {
await audioPlayerState.updateState(progress: 0.4)
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .stopped:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didStopPlaying)
try await deferred.fulfill()
#expect(audioPlayerState.playbackState == .stopped)
#expect(audioPlayerState.progress == 0.4)
#expect(!audioPlayerState.isPublishingProgress)
#expect(audioPlayerState.showProgressIndicator)
}
@Test
mutating func audioPlayerActionsDidFinishPlaying() async throws {
await audioPlayerState.updateState(progress: 0.4)
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .stopped:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didFinishPlaying)
try await deferred.fulfill()
#expect(audioPlayerState.playbackState == .stopped)
// Progress should be reset to 0
#expect(audioPlayerState.progress == 0.0)
#expect(!audioPlayerState.isPublishingProgress)
#expect(!audioPlayerState.showProgressIndicator)
}
@Test
func audioPlayerActionsDidFailed() async throws {
audioPlayerState.attachAudioPlayer(audioPlayerMock)
let deferredPlayingState = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .playing:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didStartPlaying)
try await deferredPlayingState.fulfill()
#expect(!audioPlayerState.showProgressIndicator)
let deferred = deferFulfillment(audioPlayerState.$playbackState) { action in
switch action {
case .error:
return true
default:
return false
}
}
audioPlayerActionsSubject.send(.didFailWithError(error: AudioPlayerError.genericError))
try await deferred.fulfill()
#expect(audioPlayerState.playbackState == .error)
#expect(!audioPlayerState.isPublishingProgress)
#expect(!audioPlayerState.showProgressIndicator)
}
}