react-native-queue-player 1.0.3 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/android/src/main/java/com/margelo/nitro/queueplayer/BufferStateTranslator.kt +1 -1
  2. package/android/src/main/java/com/margelo/nitro/queueplayer/CarConnectionState.kt +46 -0
  3. package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +1 -3
  4. package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +2 -4
  5. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +7 -9
  6. package/android/src/main/java/com/margelo/nitro/queueplayer/MilestoneTracker.kt +1 -2
  7. package/android/src/main/java/com/margelo/nitro/queueplayer/NotificationProvider.kt +6 -0
  8. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +33 -25
  9. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +16 -1
  10. package/android/src/main/java/com/margelo/nitro/queueplayer/ProgressEmissionGate.kt +1 -1
  11. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +1 -1
  12. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +1 -1
  13. package/android/src/main/java/com/margelo/nitro/queueplayer/RemoteCommandDeduper.kt +1 -1
  14. package/android/src/main/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayer.kt +9 -0
  15. package/android/src/main/java/com/margelo/nitro/queueplayer/SleepTimerCore.kt +1 -1
  16. package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +40 -27
  17. package/android/src/main/java/com/margelo/nitro/queueplayer/TransportStateTranslator.kt +2 -2
  18. package/android/src/main/res/values/strings.xml +0 -3
  19. package/android/src/test/java/com/margelo/nitro/queueplayer/AudioPipelineRenderersFactoryTest.kt +2 -5
  20. package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +1 -0
  21. package/android/src/test/java/com/margelo/nitro/queueplayer/BrowseCallbackRegistryTest.kt +1 -4
  22. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +1 -2
  23. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineReplayGainTest.kt +3 -7
  24. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +1 -2
  25. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +1 -3
  26. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +2 -3
  27. package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +1 -2
  28. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +0 -2
  29. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +3 -8
  30. package/android/src/test/java/com/margelo/nitro/queueplayer/HeadlessJsMediaServiceTest.kt +2 -4
  31. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaButtonDispatchTest.kt +1 -0
  32. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaButtonPreferencesTest.kt +99 -0
  33. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +2 -3
  34. package/android/src/test/java/com/margelo/nitro/queueplayer/MilestoneTrackerTest.kt +1 -3
  35. package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +1 -2
  36. package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
  37. package/android/src/test/java/com/margelo/nitro/queueplayer/PitchCorrectionTest.kt +2 -2
  38. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +148 -7
  39. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +2 -0
  40. package/android/src/test/java/com/margelo/nitro/queueplayer/ProgressEmissionGateTest.kt +1 -3
  41. package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +58 -2
  42. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +1 -2
  43. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +2 -4
  44. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPitchCorrectionTest.kt +1 -2
  45. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPlaybackModeTest.kt +2 -3
  46. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReplayGainTest.kt +1 -3
  47. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +2 -5
  48. package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +1 -2
  49. package/ios/AVPlayerItemQueueItemId.swift +2 -2
  50. package/ios/AVQueueBuilder.swift +12 -14
  51. package/ios/CarPlayCoordinator.swift +38 -0
  52. package/ios/CarPlayQueueBuilder.swift +82 -0
  53. package/ios/CarPlayQueueProvider.swift +73 -0
  54. package/ios/CarPlaySceneDelegate.swift +81 -7
  55. package/ios/CrossfadeEngine.swift +4 -5
  56. package/ios/InputGuards.swift +3 -3
  57. package/ios/LookaheadCache.swift +4 -6
  58. package/ios/LookaheadCachePrefetcher.swift +1 -1
  59. package/ios/MilestoneTracker.swift +1 -2
  60. package/ios/PlaybackErrorMapping.swift +6 -7
  61. package/ios/PlayerStateDerivation.swift +1 -2
  62. package/ios/ProgressEmissionGate.swift +1 -1
  63. package/ios/QueueMutationArithmetic.swift +2 -2
  64. package/ios/QueueSkipArithmetic.swift +8 -12
  65. package/ios/Siri/PlayMediaIntentHandler.swift +1 -5
  66. package/ios/SleepTimerCore.swift +1 -1
  67. package/ios/Tests/AudioTapProviderReplayGainTests.swift +5 -9
  68. package/ios/Tests/CarPlayBridgeTests.swift +2 -6
  69. package/ios/Tests/CarPlayBrowseBuilderTests.swift +1 -2
  70. package/ios/Tests/CarPlayCoordinatorTests.swift +35 -0
  71. package/ios/Tests/CarPlayQueueBuilderTests.swift +145 -0
  72. package/ios/Tests/EQProcessorTests.swift +1 -4
  73. package/ios/Tests/EQTapTests.swift +1 -2
  74. package/ios/Tests/EqualizerAudioMixProviderTests.swift +3 -4
  75. package/ios/Tests/EqualizerHybridTests.swift +1 -3
  76. package/ios/Tests/FFTProcessorTests.swift +10 -10
  77. package/ios/Tests/GaplessEngineLifecycleTests.swift +0 -2
  78. package/ios/Tests/LookaheadCacheEvictionTests.swift +1 -4
  79. package/ios/Tests/MetadataReaderTests.swift +0 -2
  80. package/ios/Tests/MilestoneTrackerTests.swift +1 -3
  81. package/ios/Tests/NowPlayingFormatExtractorTests.swift +3 -4
  82. package/ios/Tests/PlaybackErrorMappingTests.swift +0 -3
  83. package/ios/Tests/PlaybackModeStateMachineTests.swift +5 -9
  84. package/ios/Tests/PlayerStateDerivationTests.swift +1 -2
  85. package/ios/Tests/ProgressEmissionGateTests.swift +1 -3
  86. package/ios/Tests/QueueMutationArithmeticTests.swift +0 -3
  87. package/ios/Tests/RemoteCommandsTests.swift +2 -5
  88. package/ios/Tests/ReplayGainExtractorTests.swift +0 -5
  89. package/ios/Tests/SkipCapabilityTests.swift +4 -7
  90. package/ios/Tests/VoiceDonationTests.swift +4 -4
  91. package/ios/TrackPlayer.swift +52 -2
  92. package/lib/typescript/TrackPlayer.nitro.d.ts +17 -0
  93. package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
  94. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +5 -0
  95. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +4 -0
  97. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +8 -0
  98. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
  99. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +12 -0
  100. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
  101. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -0
  102. package/package.json +1 -1
  103. package/src/TrackPlayer.nitro.ts +18 -0
@@ -3,19 +3,15 @@ import XCTest
3
3
  @testable import QueuePlayer
4
4
 
5
5
  /// Unit tests for the ReplayGain mode + gain-math paths in
6
- /// `AudioTapProvider`. The metadata-extraction async path + per-item
7
- /// recompute walk are covered at the integration layer (auto-suite
8
- /// `replaygain.ts` step). These tests drive the pure
6
+ /// `AudioTapProvider`. These tests drive the pure
9
7
  /// `computeLinearGain(data:mode:)` static helper plus the mode
10
8
  /// getter/setter round-trip.
11
9
  final class AudioTapProviderReplayGainTests: XCTestCase {
12
10
 
13
- // Mode getter / setter round-trip is exercised at the integration
14
- // layer (`replaygain.ts` auto-suite) `getReplayGainMode`'s
15
- // `ReplayGainMode` return type touches the Swift↔C++ interop
16
- // graph, so direct equality from `@testable` XCTest can't observe
17
- // it (NOTES.md §6). The pure gain-math matrix below is the unit
18
- // surface; mode propagation lands in the integration tests.
11
+ // `getReplayGainMode`'s `ReplayGainMode` return type touches the
12
+ // Swift↔C++ interop graph, so direct equality from `@testable`
13
+ // XCTest can't observe it (NOTES.md §6). The pure gain-math matrix
14
+ // below is the unit surface.
19
15
 
20
16
  // MARK: - computeLinearGain — strict mode + selection
21
17
 
@@ -2,9 +2,7 @@ import XCTest
2
2
  @testable import QueuePlayer
3
3
 
4
4
  /// `CarPlayBridge` tests cover only the registration/dispose/clear
5
- /// state machine. Promise-await dispatch is exercised by the
6
- /// production scene delegate + browse builder via integration paths;
7
- /// the unit suite stays focused on the slot semantics. The
5
+ /// state machine the slot semantics. The
8
6
  /// Android-side `BrowseCallbackRegistry` exposes suspending
9
7
  /// `await*Callback()` helpers because its dispatch sites coroutine-
10
8
  /// poll for cold-start race recovery; iOS uses NotificationCenter
@@ -18,9 +16,7 @@ final class CarPlayBridgeTests: XCTestCase {
18
16
  // The browse / playFromId slot reads return a Promise of a
19
17
  // Nitrogen-bridged type, which Swift cxx-interop excludes from
20
18
  // `@testable import`. Their slots use the identical `Slot<>`
21
- // machinery asserted here for connect/disconnect, and their
22
- // dispatch is covered at the integration layer (scene delegate +
23
- // browse builder).
19
+ // machinery asserted here for connect/disconnect.
24
20
  XCTAssertNil(bridge.carConnectCallback())
25
21
  XCTAssertNil(bridge.carDisconnectCallback())
26
22
  }
@@ -5,8 +5,7 @@ import XCTest
5
5
  /// Coverage for [CarPlayBrowseBuilder] — snapshot → template
6
6
  /// conversion, item count + accessory mapping, pagination against
7
7
  /// `CPListTemplate.maximumItemCount`. Direct method calls on the
8
- /// builder; the scene delegate's full lifecycle is covered by the
9
- /// CarPlay Sim manual playbook.
8
+ /// builder; the scene delegate's full lifecycle is out of scope here.
10
9
  final class CarPlayBrowseBuilderTests: XCTestCase {
11
10
 
12
11
  private func item(
@@ -0,0 +1,35 @@
1
+ import XCTest
2
+ @testable import QueuePlayer
3
+
4
+ /// `CarPlayCoordinator` connection-flag tests. The flag is written by the
5
+ /// CarPlay scene delegate at scene attach / detach; this suite locks the
6
+ /// get/set contract the `isCarConnected()` getter reads back.
7
+ final class CarPlayCoordinatorTests: XCTestCase {
8
+
9
+ override func setUp() {
10
+ super.setUp()
11
+ // Shared process-wide singleton — start from a known-disconnected state
12
+ // regardless of test order.
13
+ CarPlayCoordinator.shared.setCarConnected(false)
14
+ }
15
+
16
+ override func tearDown() {
17
+ // Shared process-wide singleton — leave it disconnected so it can't
18
+ // leak a connected state into another suite.
19
+ CarPlayCoordinator.shared.setCarConnected(false)
20
+ super.tearDown()
21
+ }
22
+
23
+ func testCarConnectedFlagReflectsSetter() {
24
+ let coordinator = CarPlayCoordinator.shared
25
+
26
+ coordinator.setCarConnected(false)
27
+ XCTAssertFalse(coordinator.isCarConnected, "disconnected after setCarConnected(false)")
28
+
29
+ coordinator.setCarConnected(true)
30
+ XCTAssertTrue(coordinator.isCarConnected, "connected after setCarConnected(true)")
31
+
32
+ coordinator.setCarConnected(false)
33
+ XCTAssertFalse(coordinator.isCarConnected, "disconnected again after setCarConnected(false)")
34
+ }
35
+ }
@@ -0,0 +1,145 @@
1
+ import CarPlay
2
+ import XCTest
3
+ @testable import QueuePlayer
4
+
5
+ /// Coverage for [CarPlayQueueBuilder] — snapshot → Up Next template
6
+ /// conversion, row count + title/detail mapping, truncation at
7
+ /// `CPListTemplate.maximumItemCount`, current-index marking, and the
8
+ /// row-tap skip closure — plus a [CarPlayQueueProvider] round-trip.
9
+ final class CarPlayQueueBuilderTests: XCTestCase {
10
+
11
+ private func entry(
12
+ _ title: String,
13
+ subtitle: String? = nil,
14
+ artworkUrl: String? = nil
15
+ ) -> CarPlayQueueEntry {
16
+ CarPlayQueueEntry(title: title, subtitle: subtitle, artworkUrl: artworkUrl)
17
+ }
18
+
19
+ private func snapshot(
20
+ _ entries: [CarPlayQueueEntry],
21
+ currentIndex: Int
22
+ ) -> CarPlayQueueSnapshot {
23
+ CarPlayQueueSnapshot(entries: entries, currentIndex: currentIndex)
24
+ }
25
+
26
+ // MARK: - Template construction
27
+
28
+ func testBuildQueueTemplateMapsEntriesToRows() {
29
+ let builder = CarPlayQueueBuilder()
30
+ let loader = ArtworkLoader()
31
+ let template = builder.buildQueueTemplate(
32
+ snapshot: snapshot(
33
+ [
34
+ entry("Everlong", subtitle: "Foo Fighters"),
35
+ entry("Monkey Wrench", subtitle: "Foo Fighters"),
36
+ ],
37
+ currentIndex: 0
38
+ ),
39
+ loader: loader,
40
+ skip: { _ in }
41
+ )
42
+ let items = (template.sections.first?.items ?? []).compactMap { $0 as? CPListItem }
43
+ XCTAssertEqual(items.count, 2)
44
+ XCTAssertEqual(items[0].text, "Everlong")
45
+ XCTAssertEqual(items[0].detailText, "Foo Fighters")
46
+ XCTAssertEqual(items[1].text, "Monkey Wrench")
47
+ }
48
+
49
+ func testBuildQueueTemplateCapsRowsAtMaximumItemCount() {
50
+ let builder = CarPlayQueueBuilder()
51
+ let loader = ArtworkLoader()
52
+ let cap = 5
53
+ let entries = (0..<cap + 4).map { entry("T-\($0)") }
54
+ let template = builder.buildQueueTemplate(
55
+ snapshot: snapshot(entries, currentIndex: 0),
56
+ loader: loader,
57
+ maximumItemCount: cap,
58
+ skip: { _ in }
59
+ )
60
+ let items = template.sections.first?.items ?? []
61
+ XCTAssertEqual(items.count, cap)
62
+ }
63
+
64
+ func testBuildQueueTemplateMarksCurrentIndex() {
65
+ let builder = CarPlayQueueBuilder()
66
+ let loader = ArtworkLoader()
67
+ let template = builder.buildQueueTemplate(
68
+ snapshot: snapshot(
69
+ [entry("A"), entry("B"), entry("C")],
70
+ currentIndex: 1
71
+ ),
72
+ loader: loader,
73
+ skip: { _ in }
74
+ )
75
+ let items = (template.sections.first?.items ?? []).compactMap { $0 as? CPListItem }
76
+ XCTAssertFalse(items[0].isPlaying)
77
+ XCTAssertTrue(items[1].isPlaying)
78
+ XCTAssertFalse(items[2].isPlaying)
79
+ }
80
+
81
+ func testBuildQueueTemplateMarksNothingWhenIndexOutOfRange() {
82
+ let builder = CarPlayQueueBuilder()
83
+ let loader = ArtworkLoader()
84
+ let template = builder.buildQueueTemplate(
85
+ snapshot: snapshot([entry("A"), entry("B")], currentIndex: -1),
86
+ loader: loader,
87
+ skip: { _ in }
88
+ )
89
+ let items = (template.sections.first?.items ?? []).compactMap { $0 as? CPListItem }
90
+ XCTAssertTrue(items.allSatisfy { !$0.isPlaying })
91
+ }
92
+
93
+ // MARK: - Row-tap skip
94
+
95
+ func testRowTapInvokesSkipWithRowIndex() {
96
+ let builder = CarPlayQueueBuilder()
97
+ let loader = ArtworkLoader()
98
+ var skippedTo: Int?
99
+ let template = builder.buildQueueTemplate(
100
+ snapshot: snapshot([entry("A"), entry("B"), entry("C")], currentIndex: 0),
101
+ loader: loader,
102
+ skip: { skippedTo = $0 }
103
+ )
104
+ let items = (template.sections.first?.items ?? []).compactMap { $0 as? CPListItem }
105
+ let exp = expectation(description: "row handler completes")
106
+ items[2].handler?(items[2]) { exp.fulfill() }
107
+ wait(for: [exp], timeout: 2)
108
+ XCTAssertEqual(skippedTo, 2)
109
+ }
110
+
111
+ // MARK: - Provider round-trip
112
+
113
+ func testProviderSetSnapshotMakesSnapshotReadable() {
114
+ let provider = CarPlayQueueProvider()
115
+ XCTAssertNil(provider.getSnapshot())
116
+ provider.setSnapshot(
117
+ snapshot([entry("A", subtitle: "sub")], currentIndex: 0)
118
+ )
119
+ let read = provider.getSnapshot()
120
+ XCTAssertEqual(read?.entries.count, 1)
121
+ XCTAssertEqual(read?.entries.first?.title, "A")
122
+ XCTAssertEqual(read?.entries.first?.subtitle, "sub")
123
+ XCTAssertEqual(read?.currentIndex, 0)
124
+ }
125
+
126
+ func testProviderSetSnapshotNilClearsCache() {
127
+ let provider = CarPlayQueueProvider()
128
+ provider.setSnapshot(snapshot([entry("A")], currentIndex: 0))
129
+ provider.setSnapshot(nil)
130
+ XCTAssertNil(provider.getSnapshot())
131
+ }
132
+
133
+ func testProviderSetSnapshotPostsDidUpdateNotification() {
134
+ let provider = CarPlayQueueProvider()
135
+ let exp = expectation(
136
+ forNotification: CarPlayQueueProvider.didUpdateNotification,
137
+ object: provider,
138
+ handler: nil
139
+ )
140
+ DispatchQueue.global().async {
141
+ provider.setSnapshot(self.snapshot([self.entry("A")], currentIndex: 0))
142
+ }
143
+ wait(for: [exp], timeout: 2)
144
+ }
145
+ }
@@ -1,10 +1,7 @@
1
1
  import XCTest
2
2
  @testable import QueuePlayer
3
3
 
4
- /// Unit coverage for `EQProcessor` cascade. Audible response
5
- /// (sine-sweep magnitude per band) is verified at the integration
6
- /// layer via the demo Maestro EQ suite once the AVPlayerItem mix
7
- /// attach path is wired.
4
+ /// Unit coverage for `EQProcessor` cascade.
8
5
  final class EQProcessorTests: XCTestCase {
9
6
 
10
7
  func testIsNotReadyBeforeChannelCountSet() {
@@ -4,8 +4,7 @@ import XCTest
4
4
 
5
5
  /// Skeleton coverage for `EQTap` — tap creation, HLS detection,
6
6
  /// pass-through process body. The actual tap firing is on the
7
- /// audio render thread and verified at the integration layer via
8
- /// the demo Maestro EQ suite once the DSP body lands.
7
+ /// audio render thread.
9
8
  final class EQTapTests: XCTestCase {
10
9
 
11
10
  func testPassThroughCopiesInputToOutput() {
@@ -2,10 +2,9 @@ import AVFoundation
2
2
  import XCTest
3
3
  @testable import QueuePlayer
4
4
 
5
- /// Provider-layer coverage for the shared `AudioTapProvider`. Real
6
- /// audio routing is verified at the integration layer via the demo
7
- /// Maestro EQ + visualizer suites; these tests cover the
8
- /// configuration + bypass + consumer-registration surface.
5
+ /// Provider-layer coverage for the shared `AudioTapProvider`. These
6
+ /// tests cover the configuration + bypass + consumer-registration
7
+ /// surface.
9
8
  ///
10
9
  /// Filename note: kept at `EqualizerAudioMixProviderTests.swift`
11
10
  /// even though the class is `AudioTapProviderTests`. Renaming the
@@ -3,9 +3,7 @@ import XCTest
3
3
  @testable import QueuePlayer
4
4
 
5
5
  /// Wiring coverage for the iOS `Equalizer` HybridObject.
6
- /// Targets the synchronous internal core; the async Promise-
7
- /// returning surface is exercised at integration via the demo
8
- /// Maestro EQ suite.
6
+ /// Targets the synchronous internal core.
9
7
  final class EqualizerHybridTests: XCTestCase {
10
8
 
11
9
  private var defaults: UserDefaults!
@@ -33,7 +33,7 @@ final class FFTProcessorTests: XCTestCase {
33
33
  samples.withUnsafeBufferPointer { p in
34
34
  proc.ingest(samples: p.baseAddress!, frameCount: n, sampleRate: 48000)
35
35
  }
36
- wait(for: [exp], timeout: 1.0)
36
+ wait(for: [exp], timeout: 5.0)
37
37
  XCTAssertEqual(capturedSampleRate, 48000, accuracy: 0.001)
38
38
  }
39
39
 
@@ -57,7 +57,7 @@ final class FFTProcessorTests: XCTestCase {
57
57
  rest.withUnsafeBufferPointer { p in
58
58
  proc.ingest(samples: p.baseAddress!, frameCount: 256, sampleRate: 44100)
59
59
  }
60
- wait(for: [emitted], timeout: 1.0)
60
+ wait(for: [emitted], timeout: 5.0)
61
61
  XCTAssertEqual(calls, 1)
62
62
  }
63
63
 
@@ -74,7 +74,7 @@ final class FFTProcessorTests: XCTestCase {
74
74
  buffer.withUnsafeBufferPointer { p in
75
75
  proc.ingest(samples: p.baseAddress!, frameCount: 128, sampleRate: 44100)
76
76
  }
77
- wait(for: [exp], timeout: 1.0)
77
+ wait(for: [exp], timeout: 5.0)
78
78
  let samples = try XCTUnwrap(capturedSamples)
79
79
  XCTAssertEqual(samples.size, 128 * MemoryLayout<Float>.size)
80
80
  }
@@ -94,7 +94,7 @@ final class FFTProcessorTests: XCTestCase {
94
94
  buffer.withUnsafeBufferPointer { p in
95
95
  proc.ingest(samples: p.baseAddress!, frameCount: 128, sampleRate: 44100)
96
96
  }
97
- wait(for: [exp], timeout: 1.0)
97
+ wait(for: [exp], timeout: 5.0)
98
98
  XCTAssertTrue(captured)
99
99
  XCTAssertNil(capturedSamples)
100
100
  }
@@ -117,7 +117,7 @@ final class FFTProcessorTests: XCTestCase {
117
117
  samples.withUnsafeBufferPointer { p in
118
118
  proc.ingest(samples: p.baseAddress!, frameCount: n, sampleRate: 48000)
119
119
  }
120
- wait(for: [exp], timeout: 1.0)
120
+ wait(for: [exp], timeout: 5.0)
121
121
  let fft = try XCTUnwrap(capturedFft)
122
122
  let mags = arrayBufferToFloats(fft)
123
123
  let peakBin = mags.indices.max(by: { mags[$0] < mags[$1] }) ?? -1
@@ -205,7 +205,7 @@ final class FFTProcessorTests: XCTestCase {
205
205
  closure(leftIn, leftOut, n, 1, 0, 2)
206
206
  closure(rightIn, rightOut, n, 1, 1, 2)
207
207
 
208
- wait(for: [exp], timeout: 1.0)
208
+ wait(for: [exp], timeout: 5.0)
209
209
  let samples = try XCTUnwrap(capturedSamples)
210
210
  XCTAssertEqual(samples.size, n * MemoryLayout<Float>.size)
211
211
 
@@ -233,7 +233,9 @@ final class FFTProcessorTests: XCTestCase {
233
233
  }
234
234
  let exp = expectation(description: "drained")
235
235
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { exp.fulfill() }
236
- wait(for: [exp], timeout: 1.0)
236
+ // Generous timeout: the 0.1s drain only needs to outlast the 1000ms
237
+ // throttle window; the ceiling is headroom for a loaded CI main queue.
238
+ wait(for: [exp], timeout: 10.0)
237
239
  XCTAssertEqual(calls, 1, "second back-to-back call must be throttled")
238
240
  }
239
241
 
@@ -243,9 +245,7 @@ final class FFTProcessorTests: XCTestCase {
243
245
  // `ingest` resumes, `onFrame` fires again at the documented
244
246
  // intervalMs cadence. Live audio chains express "pause" by
245
247
  // ceasing tap callbacks; the unit-test analogue is a sleep
246
- // window. End-to-end pause/play coverage lives in the
247
- // `visualizer:*` integration suite's `pause-stops-resume-restarts`
248
- // step.
248
+ // window.
249
249
  let n = 256
250
250
  let intervalMs = 1
251
251
  let lock = NSLock()
@@ -4,8 +4,6 @@ import XCTest
4
4
 
5
5
  /// Lifecycle smoke coverage for `GaplessEngine` — empty-queue
6
6
  /// sentinels + idempotent / delegate-clearing release contract.
7
- /// Behavioural coverage of transport + seek runs through the
8
- /// existing TrackPlayer integration suites + Maestro flows.
9
7
  final class GaplessEngineLifecycleTests: XCTestCase {
10
8
 
11
9
  func testEmptyQueueReturnsDocumentedSentinelState() {
@@ -4,10 +4,7 @@ import XCTest
4
4
 
5
5
  /// `LookaheadCacheEviction.computeEvictions` unit tests.
6
6
  ///
7
- /// Pure-logic LRU policy. Plain JUnit-equivalent — no FS access, no
8
- /// URLSession. File + index mutation is tested through the
9
- /// LookaheadCacheTests integration path that drives `download` past
10
- /// the budget.
7
+ /// Pure-logic LRU policy. No FS access, no URLSession.
11
8
  final class LookaheadCacheEvictionTests: XCTestCase {
12
9
 
13
10
  func testNoEvictionWhenUnderBudget() {
@@ -4,8 +4,6 @@ import XCTest
4
4
  /// Byte-level unit tests for `MetadataReader`'s container parsers.
5
5
  /// Builds synthetic ID3v2 / FLAC / MP4 byte sequences in-memory and
6
6
  /// verifies tag extraction surfaces the expected `[key: value]` map.
7
- /// End-to-end "extract from a real media file" coverage lives in
8
- /// the auto-suite `replaygain.ts`.
9
7
  final class MetadataReaderTests: XCTestCase {
10
8
 
11
9
  // MARK: - ID3v2 TXXX
@@ -2,9 +2,7 @@ import XCTest
2
2
  @testable import QueuePlayer
3
3
 
4
4
  /// Unit tests for `MilestoneTracker` — the pure 25/50/75/90% crossing
5
- /// logic behind `onPlaybackMilestone`. Native wiring (progress tick,
6
- /// seek, track-change reset) is covered by the `playback-milestones`
7
- /// demo suite.
5
+ /// logic behind `onPlaybackMilestone`.
8
6
  final class MilestoneTrackerTests: XCTestCase {
9
7
 
10
8
  /// 200s track, ticks every ~10s of media time.
@@ -3,10 +3,9 @@ import CoreMedia
3
3
  import XCTest
4
4
  @testable import QueuePlayer
5
5
 
6
- /// Pure-mapping coverage for `NowPlayingFormatExtractor`. The async
7
- /// `extract(item:)` path is exercised end-to-end through the demo
8
- /// suites; these tests pin the codec / container / MIME tables so a
9
- /// future contributor can't drift the JS-visible enum without a test
6
+ /// Pure-mapping coverage for `NowPlayingFormatExtractor`. These
7
+ /// tests pin the codec / container / MIME tables so a future
8
+ /// contributor can't drift the JS-visible enum without a test
10
9
  /// failure.
11
10
  final class NowPlayingFormatExtractorTests: XCTestCase {
12
11
 
@@ -5,9 +5,6 @@ import AVFoundation
5
5
 
6
6
  /// Unit tests for the iOS half of the cross-platform error-code
7
7
  /// mapping in `PlaybackErrorMapping.swift`.
8
- /// Mirrors the Android-side coverage in
9
- /// `TrackPlayerEventsTest.kt#error mapping classifies Media3 codes`
10
- /// + `error mapping strips query strings`.
11
8
  ///
12
9
  /// **Why string-keyed assertions?** NOTES.md §6 — Swift's
13
10
  /// `-cxx-interoperability-mode=default` hides members of functions
@@ -3,15 +3,11 @@ import XCTest
3
3
 
4
4
  /// Coverage for the pure-Swift `CrossfadeDurationBounds` helper that
5
5
  /// powers `PlaybackModeStateMachine.requireValid`. Full state-machine
6
- /// validation coverage (constructing `PlaybackMode` instances + driving
7
- /// `setRequested`) lives in the Android Robolectric suite — `PlaybackMode`
8
- /// is bridged via Nitro `@_spi` and can't be constructed from a Swift
9
- /// XCTest target, so the throws path is exercised end-to-end via the
10
- /// in-app TS test runner's `playback-mode` suite (the
11
- /// `crossfade-duration-out-of-range-rejected` and
12
- /// `crossfade-duration-non-quantised-rejected` steps in
13
- /// `RNQP-Demo/src/tests/suites/playback-mode.ts`). Mirrors the
14
- /// cxx-interop carve-out already documented in AGENTS.md §2.
6
+ /// validation (constructing `PlaybackMode` instances + driving
7
+ /// `setRequested`) is out of scope here — `PlaybackMode` is bridged
8
+ /// via Nitro `@_spi` and can't be constructed from a Swift XCTest
9
+ /// target. Mirrors the cxx-interop carve-out already documented in
10
+ /// AGENTS.md §2.
15
11
  final class PlaybackModeStateMachineTests: XCTestCase {
16
12
 
17
13
  func testCrossfadeBoundsHelperAcceptsEveryValueOnTheStep() {
@@ -3,8 +3,7 @@ import XCTest
3
3
  @testable import QueuePlayer
4
4
 
5
5
  /// Unit tests for `PlayerStateDerivation` — the pure `PlayerState` mapping
6
- /// shared by both engines. Native wiring (getState + the two emit paths + the
7
- /// end-of-queue latch) is covered by the queue-end / crossfade demo suites.
6
+ /// shared by both engines.
8
7
  final class PlayerStateDerivationTests: XCTestCase {
9
8
 
10
9
  private func derive(
@@ -2,9 +2,7 @@ import XCTest
2
2
  @testable import QueuePlayer
3
3
 
4
4
  /// Unit tests for `ProgressEmissionGate` — the pure throttle behind the
5
- /// configurable / background-reduced `onProgress` cadence. Native wiring
6
- /// (the periodic tick, the cast push, and the UIApplication background /
7
- /// foreground observers) is covered by the progress demo suites.
5
+ /// configurable / background-reduced `onProgress` cadence.
8
6
  ///
9
7
  /// Most tests pass `samplerPeriodMs: 0` to exercise exact interval semantics;
10
8
  /// the jitter + quantization tests pass the production `500` to verify the
@@ -3,9 +3,6 @@ import XCTest
3
3
  @_spi(QueuePlayerTests) import QueuePlayer
4
4
 
5
5
  /// Pure-Swift tests for the queue-mutation index-shift arithmetic.
6
- /// 1:1 mirror of `android/.../QueueMutationArithmeticTest.kt` —
7
- /// every Kotlin test name has a Swift twin so the cross-platform
8
- /// matrix stays in lock-step.
9
6
  final class QueueMutationArithmeticTests: XCTestCase {
10
7
 
11
8
  // MARK: - clampInsertBefore
@@ -6,11 +6,8 @@ import XCTest
6
6
  /// registrations + the per-command `isEnabled` state. Tests pin the
7
7
  /// install / uninstall lifecycle and the `setSkipCapability` ->
8
8
  /// `nextTrackCommand.isEnabled` / `previousTrackCommand.isEnabled`
9
- /// mapping. Dispatch into the owner's `onCommand` closure is verified
10
- /// at the integration layer (Maestro suite + JS-visible reason
11
- /// classification); attempting to fire `MPRemoteCommandCenter`
12
- /// handlers from XCTest would require lib-side SPI we deliberately
13
- /// do not ship.
9
+ /// mapping. Attempting to fire `MPRemoteCommandCenter` handlers from
10
+ /// XCTest would require lib-side SPI we deliberately do not ship.
14
11
  ///
15
12
  /// The shared command center is process-global. `setUp` resets all
16
13
  /// seven commands the lib touches, `tearDown` calls `uninstall()` to
@@ -4,11 +4,6 @@ import XCTest
4
4
  /// Unit tests for `ReplayGainExtractor`'s pure string parsers
5
5
  /// (`parseGainDb`, `parsePeak`) and the `selectGain(mode:)` matrix
6
6
  /// on `ReplayGainData`.
7
- ///
8
- /// Container-level extraction (ID3v2 TXXX, FLAC Vorbis, MP4
9
- /// freeform) is covered by `MetadataReaderTests`. End-to-end
10
- /// extraction-on-load is covered by the auto-suite `replaygain.ts`
11
- /// step.
12
7
  final class ReplayGainExtractorTests: XCTestCase {
13
8
 
14
9
  // Helper: `XCTAssertEqual` with the `accuracy:` overload requires
@@ -2,15 +2,12 @@ import XCTest
2
2
  @testable import QueuePlayer
3
3
  @_spi(QueuePlayerTests) import QueuePlayer
4
4
 
5
- /// Integration tests for the canSkip surface + `onSkipCapabilityChange`
6
- /// listener emission. Pairs with the pure-helper coverage in
7
- /// `SkipIndexTests` (`canSkipNext` / `canSkipPrevious`).
5
+ /// Tests for the canSkip surface + `onSkipCapabilityChange`
6
+ /// listener emission.
8
7
  ///
9
- /// XCTest can cover only the cases where `configure(...)` has not yet
8
+ /// Covers the cases where `configure(...)` has not yet
10
9
  /// stood up an AVQueuePlayer — initial cached-getter state, listener
11
- /// registration + auto-fire, dispose closure correctness. Behavioural
12
- /// coverage of every `recomputeCapabilities()` hook point lives in
13
- /// the Robolectric sibling and the end-to-end demo suite.
10
+ /// registration + auto-fire, dispose closure correctness.
14
11
  final class SkipCapabilityTests: XCTestCase {
15
12
 
16
13
  func testGetCanSkipNextReturnsFalseInitially() throws {
@@ -4,10 +4,10 @@ import XCTest
4
4
  @_spi(QueuePlayerTests) import QueuePlayer
5
5
 
6
6
  /// Smoke coverage for `VoiceDonation.donateVoiceVocabulary`. Live
7
- /// `INVocabulary` delivery is verified via the manual siri-cold-start
8
- /// playbook; this test pins the in-process contract that the method
9
- /// early-returns when `NSSiriUsageDescription` is absent from the
10
- /// host bundle (the XCTest bundle's plist).
7
+ /// `INVocabulary` delivery isn't exercised here; this test pins the
8
+ /// in-process contract that the method early-returns when
9
+ /// `NSSiriUsageDescription` is absent from the host bundle (the XCTest
10
+ /// bundle's plist).
11
11
  @available(iOS 13.0, *)
12
12
  final class VoiceDonationTests: XCTestCase {
13
13
  func testDonateVoiceVocabularyEarlyReturnsWithoutUsageDescription() {