AudioEngineTests.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import XCTest
  2. @testable import MixBoard
  3. @MainActor
  4. final class AudioEngineTests: XCTestCase {
  5. // MARK: - Error Descriptions
  6. func testFileNotFoundErrorDescription() {
  7. let error = AudioEngineError.fileNotFound("missing.mp3")
  8. XCTAssertEqual(error.errorDescription, "Audio file not found: missing.mp3")
  9. }
  10. func testFileNotFoundErrorWithEmptyName() {
  11. let error = AudioEngineError.fileNotFound("")
  12. XCTAssertNotNil(error.errorDescription)
  13. }
  14. // MARK: - Initial State
  15. func testInitialIsPlayingFalse() {
  16. let engine = AudioEngine()
  17. XCTAssertFalse(engine.isPlaying)
  18. }
  19. func testInitialCurrentTimeZero() {
  20. let engine = AudioEngine()
  21. XCTAssertEqual(engine.currentTime, 0)
  22. }
  23. func testInitialDurationZero() {
  24. let engine = AudioEngine()
  25. XCTAssertEqual(engine.duration, 0)
  26. }
  27. func testDefaultVolume() {
  28. let engine = AudioEngine()
  29. XCTAssertEqual(engine.volume, 0.8, accuracy: 0.001)
  30. }
  31. func testCurrentTrackNil() {
  32. let engine = AudioEngine()
  33. XCTAssertNil(engine.currentTrack)
  34. }
  35. // MARK: - Volume
  36. func testSetVolume() {
  37. let engine = AudioEngine()
  38. engine.volume = 0.5
  39. XCTAssertEqual(engine.volume, 0.5, accuracy: 0.001)
  40. }
  41. func testSetVolumeZero() {
  42. let engine = AudioEngine()
  43. engine.volume = 0
  44. XCTAssertEqual(engine.volume, 0, accuracy: 0.001)
  45. }
  46. func testSetVolumeMax() {
  47. let engine = AudioEngine()
  48. engine.volume = 1.0
  49. XCTAssertEqual(engine.volume, 1.0, accuracy: 0.001)
  50. }
  51. // MARK: - Load Track Error Paths
  52. func testLoadTrackFileNotFound() {
  53. let engine = AudioEngine()
  54. let track = Track(title: "Missing", filePath: "nonexistent/path/missing.mp3")
  55. XCTAssertThrowsError(try engine.loadTrack(track)) { error in
  56. guard case AudioEngineError.fileNotFound = error else {
  57. XCTFail("Expected fileNotFound error, got \(error)")
  58. return
  59. }
  60. }
  61. }
  62. func testLoadTrackResetsPlayingState() {
  63. let engine = AudioEngine()
  64. // Even a failed load should leave isPlaying = false
  65. let track = Track(title: "Missing", filePath: "does/not/exist.mp3")
  66. try? engine.loadTrack(track)
  67. XCTAssertFalse(engine.isPlaying)
  68. }
  69. // MARK: - Stop
  70. func testStopResetsState() {
  71. let engine = AudioEngine()
  72. engine.stop()
  73. XCTAssertFalse(engine.isPlaying)
  74. XCTAssertEqual(engine.currentTime, 0)
  75. }
  76. // MARK: - Pause Without File
  77. func testPauseWithoutFile() {
  78. let engine = AudioEngine()
  79. engine.pause()
  80. XCTAssertFalse(engine.isPlaying)
  81. }
  82. // MARK: - Toggle Play/Pause Without File
  83. func testTogglePlayPauseWithNoFile() {
  84. let engine = AudioEngine()
  85. // Should not crash
  86. engine.togglePlayPause()
  87. XCTAssertFalse(engine.isPlaying)
  88. }
  89. // MARK: - EQ
  90. func testSetEQValidBand() {
  91. let engine = AudioEngine()
  92. // Should not crash
  93. engine.setEQ(band: 0, gain: 5.0)
  94. engine.setEQ(band: 1, gain: -3.0)
  95. engine.setEQ(band: 2, gain: 0.0)
  96. }
  97. func testSetEQOutOfRangeBand() {
  98. let engine = AudioEngine()
  99. // Should not crash — guard protects against out-of-range indices
  100. engine.setEQ(band: 99, gain: 10.0)
  101. engine.setEQ(band: -1, gain: 10.0)
  102. engine.setEQ(band: 3, gain: 10.0)
  103. engine.setEQ(band: 1000, gain: 10.0)
  104. }
  105. // MARK: - Seek Without File
  106. func testSeekToWithoutFile() {
  107. let engine = AudioEngine()
  108. engine.seek(to: 30.0)
  109. // Should not crash; currentTime should be clamped
  110. XCTAssertEqual(engine.currentTime, 0, accuracy: 0.01)
  111. }
  112. func testSeekByWithoutFile() {
  113. let engine = AudioEngine()
  114. engine.seek(by: 10.0)
  115. XCTAssertEqual(engine.currentTime, 0, accuracy: 0.01)
  116. }
  117. // MARK: - Update Current Time
  118. func testUpdateCurrentTimeNotPlaying() {
  119. let engine = AudioEngine()
  120. engine.updateCurrentTime()
  121. // Should not crash or change time
  122. XCTAssertEqual(engine.currentTime, 0)
  123. }
  124. // MARK: - Playback Callback
  125. func testOnPlaybackFinishedCallback() {
  126. let engine = AudioEngine()
  127. var callbackCalled = false
  128. engine.onPlaybackFinished = { callbackCalled = true }
  129. XCTAssertFalse(callbackCalled)
  130. // The callback should be set but not called until playback actually finishes
  131. XCTAssertNotNil(engine.onPlaybackFinished)
  132. }
  133. }