react-native-audio-browser 0.2.0-next.1 → 0.2.0-next.11

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 (290) hide show
  1. package/README.md +22 -18
  2. package/android/build.gradle +13 -10
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +76 -21
  6. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  8. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +1 -2
  10. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +1 -2
  11. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +6 -7
  12. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  13. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +11 -11
  14. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  15. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +9 -5
  16. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  17. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +0 -1
  18. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +2 -4
  19. package/android/src/main/java/com/audiobrowser/player/Player.kt +30 -8
  20. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
  21. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +2 -1
  22. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
  23. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
  24. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  25. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
  26. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
  27. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
  28. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +8 -7
  29. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  30. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +52 -1
  31. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
  32. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +167 -8
  33. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +2 -1
  34. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +2 -4
  35. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +100 -56
  36. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  37. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
  38. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
  39. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
  40. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
  41. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  42. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
  43. package/ios/Browser/BrowserManager.swift +2 -27
  44. package/ios/Browser/SearchDrillIn.swift +1 -1
  45. package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
  46. package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
  47. package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
  48. package/ios/HybridAudioBrowser.swift +87 -56
  49. package/ios/Model/NitroTypeStubs.swift +18 -2
  50. package/ios/Model/RemoteCommand.swift +18 -0
  51. package/ios/Model/TrackPlayerError.swift +2 -2
  52. package/ios/Option/SessionCategories.swift +3 -2
  53. package/ios/Player/MediaIntentCriteria.swift +4 -4
  54. package/ios/Player/MediaLoader.swift +3 -1
  55. package/ios/Player/PlaybackCoordinator.swift +4 -1
  56. package/ios/Player/PlaybackErrorHandler.swift +4 -4
  57. package/ios/Player/QueueManager.swift +10 -3
  58. package/ios/Player/RetryManager.swift +79 -18
  59. package/ios/Player/ShuffleOrder.swift +10 -0
  60. package/ios/Player/SleepTimerManager.swift +43 -15
  61. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  62. package/ios/RemoteCommand/RemoteCommandController.swift +9 -6
  63. package/ios/Tests/LRUCacheTests.swift +111 -0
  64. package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
  65. package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
  66. package/ios/Tests/MediaLoaderTests.swift +10 -13
  67. package/ios/Tests/PlaybackCoordinatorTests.swift +10 -7
  68. package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
  69. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  70. package/ios/Tests/QueueManagerTests.swift +50 -4
  71. package/ios/Tests/RemoteCommandControllerTests.swift +156 -0
  72. package/ios/Tests/RemoteCommandTests.swift +67 -0
  73. package/ios/Tests/RetryManagerTests.swift +89 -0
  74. package/ios/Tests/SectionScopeTests.swift +2 -2
  75. package/ios/Tests/SleepTimerManagerTests.swift +22 -19
  76. package/ios/Tests/Support/ManualSleepTimerScheduler.swift +47 -0
  77. package/ios/Tests/Support/TestTypes.swift +1 -1
  78. package/ios/Tests/TabBarEntriesTests.swift +7 -7
  79. package/ios/TrackPlayer.swift +6 -1
  80. package/ios/TrackPlayerCallbacks.swift +5 -38
  81. package/ios/Util/LRUCache.swift +4 -1
  82. package/lib/commonjs/AudioBrowser.js +0 -11
  83. package/lib/commonjs/AudioBrowser.js.map +1 -1
  84. package/lib/commonjs/CLAUDE.md +52 -0
  85. package/lib/commonjs/features/browser.js +1 -1
  86. package/lib/commonjs/features/browser.js.map +1 -1
  87. package/lib/commonjs/features/carConnection.js +1 -1
  88. package/lib/commonjs/features/equalizer.js +1 -1
  89. package/lib/commonjs/features/errors.js +3 -3
  90. package/lib/commonjs/features/favorites.js +1 -1
  91. package/lib/commonjs/features/gate.js +16 -1
  92. package/lib/commonjs/features/gate.js.map +1 -1
  93. package/lib/commonjs/features/metadata.js +20 -3
  94. package/lib/commonjs/features/metadata.js.map +1 -1
  95. package/lib/commonjs/features/network.js +1 -1
  96. package/lib/commonjs/features/nowPlaying.js +1 -1
  97. package/lib/commonjs/features/output.js +1 -1
  98. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  99. package/lib/commonjs/features/playback/playing.js +1 -1
  100. package/lib/commonjs/features/playback/progress.js +1 -1
  101. package/lib/commonjs/features/playback/state.js +1 -1
  102. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
  103. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
  104. package/lib/commonjs/features/playback/volume.js +1 -1
  105. package/lib/commonjs/features/player/options.js +48 -34
  106. package/lib/commonjs/features/player/options.js.map +1 -1
  107. package/lib/commonjs/features/player/setup.js +15 -8
  108. package/lib/commonjs/features/player/setup.js.map +1 -1
  109. package/lib/commonjs/features/player/validateOptions.js +21 -0
  110. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  111. package/lib/commonjs/features/queue/activeTrack.js +1 -1
  112. package/lib/commonjs/features/queue/queue.js +2 -2
  113. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  114. package/lib/commonjs/features/queue/shuffle.js +1 -1
  115. package/lib/commonjs/features/remoteControls.js +13 -92
  116. package/lib/commonjs/features/remoteControls.js.map +1 -1
  117. package/lib/commonjs/features/sleepTimer.js +13 -1
  118. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  119. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  120. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  121. package/lib/commonjs/utils/LazyNativeEmitter.js +8 -5
  122. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  123. package/lib/commonjs/web/NativeAudioBrowser.js +1 -1
  124. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  125. package/lib/commonjs/web/browser/BrowserManager.js +1 -1
  126. package/lib/module/AudioBrowser.js +1 -1
  127. package/lib/module/AudioBrowser.js.map +1 -1
  128. package/lib/module/CLAUDE.md +52 -0
  129. package/lib/module/features/browser.js +1 -1
  130. package/lib/module/features/browser.js.map +1 -1
  131. package/lib/module/features/carConnection.js +1 -1
  132. package/lib/module/features/equalizer.js +1 -1
  133. package/lib/module/features/errors.js +3 -3
  134. package/lib/module/features/favorites.js +1 -1
  135. package/lib/module/features/gate.js +16 -1
  136. package/lib/module/features/gate.js.map +1 -1
  137. package/lib/module/features/metadata.js +20 -3
  138. package/lib/module/features/metadata.js.map +1 -1
  139. package/lib/module/features/network.js +1 -1
  140. package/lib/module/features/nowPlaying.js +1 -1
  141. package/lib/module/features/output.js +1 -1
  142. package/lib/module/features/playback/playWhenReady.js +1 -1
  143. package/lib/module/features/playback/playing.js +1 -1
  144. package/lib/module/features/playback/progress.js +1 -1
  145. package/lib/module/features/playback/state.js +1 -1
  146. package/lib/module/features/playback/trackPlaybackTime.js +0 -5
  147. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
  148. package/lib/module/features/playback/volume.js +1 -1
  149. package/lib/module/features/player/options.js +47 -32
  150. package/lib/module/features/player/options.js.map +1 -1
  151. package/lib/module/features/player/setup.js +14 -7
  152. package/lib/module/features/player/setup.js.map +1 -1
  153. package/lib/module/features/player/validateOptions.js +17 -0
  154. package/lib/module/features/player/validateOptions.js.map +1 -0
  155. package/lib/module/features/queue/activeTrack.js +1 -1
  156. package/lib/module/features/queue/queue.js +2 -2
  157. package/lib/module/features/queue/repeatMode.js +1 -1
  158. package/lib/module/features/queue/shuffle.js +1 -1
  159. package/lib/module/features/remoteControls.js +13 -92
  160. package/lib/module/features/remoteControls.js.map +1 -1
  161. package/lib/module/features/sleepTimer.js +13 -1
  162. package/lib/module/features/sleepTimer.js.map +1 -1
  163. package/lib/module/test-utils/reactNativeStub.js +58 -0
  164. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  165. package/lib/module/utils/LazyNativeEmitter.js +8 -5
  166. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  167. package/lib/module/web/NativeAudioBrowser.js +1 -1
  168. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  169. package/lib/module/web/browser/BrowserManager.js +1 -1
  170. package/lib/typescript/src/AudioBrowser.d.ts +0 -1
  171. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  172. package/lib/typescript/src/features/carConnection.d.ts +1 -1
  173. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  174. package/lib/typescript/src/features/errors.d.ts +3 -3
  175. package/lib/typescript/src/features/favorites.d.ts +1 -1
  176. package/lib/typescript/src/features/gate.d.ts +18 -2
  177. package/lib/typescript/src/features/gate.d.ts.map +1 -1
  178. package/lib/typescript/src/features/metadata.d.ts +20 -3
  179. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  180. package/lib/typescript/src/features/network.d.ts +1 -1
  181. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  182. package/lib/typescript/src/features/output.d.ts +1 -1
  183. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  184. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  185. package/lib/typescript/src/features/playback/progress.d.ts +1 -1
  186. package/lib/typescript/src/features/playback/state.d.ts +1 -1
  187. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  188. package/lib/typescript/src/features/player/options.d.ts +87 -37
  189. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  190. package/lib/typescript/src/features/player/setup.d.ts +14 -4
  191. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  192. package/lib/typescript/src/features/player/validateOptions.d.ts +9 -0
  193. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  194. package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
  195. package/lib/typescript/src/features/queue/queue.d.ts +2 -2
  196. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  197. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  198. package/lib/typescript/src/features/remoteControls.d.ts +23 -22
  199. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  200. package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
  201. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  202. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  203. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  204. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +8 -4
  205. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  206. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -1
  207. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  208. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +5 -5
  209. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +11 -11
  210. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
  211. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +5 -5
  212. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +5 -5
  213. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  214. package/nitrogen/generated/android/c++/JOptions.hpp +5 -5
  215. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  216. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  217. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  218. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  219. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  220. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
  221. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  222. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  223. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  224. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  225. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +36 -51
  226. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +6 -6
  227. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +8 -8
  228. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  229. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
  230. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
  231. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  232. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  233. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  234. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  235. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  236. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
  237. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
  238. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  239. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  240. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  241. package/package.json +12 -4
  242. package/src/AudioBrowser.ts +1 -1
  243. package/src/CLAUDE.md +52 -0
  244. package/src/features/battery.test.ts +79 -0
  245. package/src/features/browser.ts +1 -1
  246. package/src/features/carConnection.ts +1 -1
  247. package/src/features/equalizer.ts +1 -1
  248. package/src/features/errors.ts +3 -3
  249. package/src/features/favorites.ts +1 -1
  250. package/src/features/gate.ts +16 -1
  251. package/src/features/hook-wiring.test.ts +254 -0
  252. package/src/features/metadata.ts +20 -3
  253. package/src/features/network.ts +1 -1
  254. package/src/features/nowPlaying.ts +1 -1
  255. package/src/features/output.ts +1 -1
  256. package/src/features/playback/playWhenReady.ts +1 -1
  257. package/src/features/playback/playing.ts +1 -1
  258. package/src/features/playback/progress.test.ts +169 -0
  259. package/src/features/playback/progress.ts +1 -1
  260. package/src/features/playback/state.ts +1 -1
  261. package/src/features/playback/trackPlaybackTime.test.ts +16 -6
  262. package/src/features/playback/trackPlaybackTime.ts +0 -5
  263. package/src/features/playback/volume.ts +1 -1
  264. package/src/features/player/options.ts +99 -57
  265. package/src/features/player/setup.test.ts +12 -4
  266. package/src/features/player/setup.ts +19 -9
  267. package/src/features/player/validateOptions.ts +19 -0
  268. package/src/features/queue/activeTrack.ts +1 -1
  269. package/src/features/queue/queue.ts +2 -2
  270. package/src/features/queue/repeatMode.ts +1 -1
  271. package/src/features/queue/shuffle.ts +1 -1
  272. package/src/features/remoteControls.ts +13 -92
  273. package/src/features/sleepTimer.test.ts +172 -0
  274. package/src/features/sleepTimer.ts +9 -1
  275. package/src/nitro-stub-drift.test.ts +237 -0
  276. package/src/public-surface.test.ts +107 -0
  277. package/src/specs/audio-browser.nitro.ts +1 -1
  278. package/src/test-utils/reactNativeStub.ts +60 -0
  279. package/src/utils/LazyNativeEmitter.ts +9 -6
  280. package/src/utils/useDebug.test.ts +182 -0
  281. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  282. package/src/web/NativeAudioBrowser.ts +1 -1
  283. package/src/web/browser/BrowserManager.ts +1 -1
  284. package/ios/Model/MediaURL.swift +0 -67
  285. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  286. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  287. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  289. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  290. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -1,19 +1,32 @@
1
1
  package com.audiobrowser.browser
2
2
 
3
+ import com.audiobrowser.TestFixtures.imageRowItem
4
+ import com.audiobrowser.TestFixtures.resolvedTrack
5
+ import com.audiobrowser.TestFixtures.staticRoute
6
+ import com.audiobrowser.TestFixtures.track
7
+ import com.audiobrowser.util.BrowserPathHelper
3
8
  import kotlinx.coroutines.runBlocking
4
- import org.junit.Assert.*
9
+ import org.junit.Assert.assertEquals
10
+ import org.junit.Assert.assertNull
11
+ import org.junit.Assert.fail
5
12
  import org.junit.Before
6
13
  import org.junit.Test
7
14
  import org.junit.runner.RunWith
8
15
  import org.robolectric.RobolectricTestRunner
9
16
 
10
- // TODO: Most tests disabled — BrowserConfig API changed (routes is now Array<NativeRouteEntry>).
11
- // When revived, add expandQueueFromContextualUrl glue tests: found id → its
12
- // SectionScope section as the queue; vanished id → null (single-track
13
- // fallback). The section logic and the caller fallback are covered
14
- // (SectionScopeTest, TrackSelectorTests); the wiring between them is not.
15
- // Tests that used BrowserSource/BrowserList need to be rewritten.
16
-
17
+ /**
18
+ * Navigation and queue-expansion glue.
19
+ *
20
+ * [BrowserManager.expandQueueFromContextualUrl] is the seam between the page a listener tapped in
21
+ * and the queue that starts playing. Its two halves are covered on their own — the section logic in
22
+ * [SectionScopeTest], the caller's single-track fallback in the player tests — but the wiring
23
+ * between them is what decides whether tapping row 3 of a page resumes the right station, and only
24
+ * these tests exercise it.
25
+ *
26
+ * Routes serve `browseStatic`, the one browse arm that needs neither the JNI bridge nor HTTP.
27
+ * Robolectric is required for `android.net.Uri` (contextual-URL parsing) and
28
+ * `android.util.LruCache` (the content cache).
29
+ */
17
30
  @RunWith(RobolectricTestRunner::class)
18
31
  class BrowserManagerTest {
19
32
 
@@ -24,6 +37,22 @@ class BrowserManagerTest {
24
37
  browserManager = BrowserManager()
25
38
  }
26
39
 
40
+ /** Configures a single static route at [path] serving [children] as its page. */
41
+ private fun servePage(
42
+ path: String,
43
+ vararg children: com.margelo.nitro.audiobrowser.Track,
44
+ singleTrack: Boolean = false,
45
+ ) {
46
+ browserManager.config =
47
+ BrowserConfig(
48
+ routes =
49
+ arrayOf(staticRoute(path, resolvedTrack(url = path, children = arrayOf(*children)))),
50
+ singleTrack = singleTrack,
51
+ )
52
+ }
53
+
54
+ private fun contextual(path: String, trackId: String) = BrowserPathHelper.build(path, trackId)
55
+
27
56
  @Test
28
57
  fun `getPath returns default path initially`() {
29
58
  assertEquals("/", browserManager.getPath())
@@ -64,4 +93,134 @@ class BrowserManagerTest {
64
93
  } catch (_: ContentNotFoundException) {}
65
94
  }
66
95
  }
96
+
97
+ @Test
98
+ fun `navigate serves a static route and gives playable children contextual urls`() = runBlocking {
99
+ servePage("/library", track(src = "a", title = "A"), track(src = "b", title = "B"))
100
+
101
+ val resolved = browserManager.navigate("/library")
102
+
103
+ assertEquals(listOf("A", "B"), resolved.children?.map { it.title })
104
+ // The tap target: a child's url is rewritten to point back at this page plus its own id.
105
+ assertEquals(
106
+ listOf(contextual("/library", "a"), contextual("/library", "b")),
107
+ resolved.children?.map { it.url },
108
+ )
109
+ assertEquals("/library", browserManager.getPath())
110
+ }
111
+
112
+ // MARK: - expandQueueFromContextualUrl
113
+
114
+ @Test
115
+ fun `expandQueueFromContextualUrl scopes the queue to the tapped section`() = runBlocking {
116
+ servePage(
117
+ "/home",
118
+ track(src = "a", groupTitle = "Recent"),
119
+ track(src = "b", groupTitle = "Recent"),
120
+ track(src = "c", groupTitle = "Popular"),
121
+ track(src = "d", groupTitle = "Popular"),
122
+ )
123
+
124
+ val expanded = browserManager.expandQueueFromContextualUrl(contextual("/home", "c"))
125
+
126
+ // "Popular" only — a page aggregating sections must not leak next/previous across them.
127
+ val (queue, index) = requireNotNull(expanded)
128
+ assertEquals(listOf("c", "d"), queue.map { it.src })
129
+ assertEquals(0, index)
130
+ }
131
+
132
+ @Test
133
+ fun `expandQueueFromContextualUrl indexes the tapped track within its section`() = runBlocking {
134
+ servePage(
135
+ "/home",
136
+ track(src = "a", groupTitle = "Recent"),
137
+ track(src = "b", groupTitle = "Popular"),
138
+ track(src = "c", groupTitle = "Popular"),
139
+ )
140
+
141
+ val (queue, index) =
142
+ requireNotNull(browserManager.expandQueueFromContextualUrl(contextual("/home", "c")))
143
+
144
+ assertEquals(listOf("b", "c"), queue.map { it.src })
145
+ assertEquals(1, index)
146
+ }
147
+
148
+ @Test
149
+ fun `expandQueueFromContextualUrl returns null when the id has vanished from the page`() =
150
+ runBlocking {
151
+ // The stored url points at a track the page no longer lists — the page changed under a
152
+ // resumed session. Queueing the current list would resume the wrong station, so expansion
153
+ // aborts and the caller falls back to the stored single track.
154
+ servePage("/home", track(src = "a"), track(src = "b"))
155
+
156
+ assertNull(browserManager.expandQueueFromContextualUrl(contextual("/home", "gone")))
157
+ }
158
+
159
+ @Test
160
+ fun `expandQueueFromContextualUrl returns null for a non-contextual url`() = runBlocking {
161
+ servePage("/home", track(src = "a"))
162
+
163
+ assertNull(browserManager.expandQueueFromContextualUrl("/home"))
164
+ }
165
+
166
+ @Test
167
+ fun `expandQueueFromContextualUrl returns null when the page has no children`() = runBlocking {
168
+ browserManager.config =
169
+ BrowserConfig(routes = arrayOf(staticRoute("/home", resolvedTrack(url = "/home"))))
170
+
171
+ assertNull(browserManager.expandQueueFromContextualUrl(contextual("/home", "a")))
172
+ }
173
+
174
+ @Test
175
+ fun `expandQueueFromContextualUrl expands an image row into its items`() = runBlocking {
176
+ servePage(
177
+ "/home",
178
+ track(
179
+ title = "Most Played",
180
+ src = null,
181
+ imageRow = arrayOf(imageRowItem("r1"), imageRowItem("r2")),
182
+ ),
183
+ track(src = "a"),
184
+ )
185
+
186
+ val (queue, index) =
187
+ requireNotNull(browserManager.expandQueueFromContextualUrl(contextual("/home", "r2")))
188
+
189
+ // A tile tap queues its row, not the list rows beside it.
190
+ assertEquals(listOf("r1", "r2"), queue.map { it.src })
191
+ assertEquals(1, index)
192
+ }
193
+
194
+ @Test
195
+ fun `expandQueueFromContextualUrl drops unplayable siblings`() = runBlocking {
196
+ servePage(
197
+ "/home",
198
+ track(src = "a", groupTitle = "Mixed"),
199
+ // A browsable row sitting inside the same section — no src, so not queueable.
200
+ track(src = null, groupTitle = "Mixed").copy(url = "/sub"),
201
+ track(src = "b", groupTitle = "Mixed"),
202
+ )
203
+
204
+ val (queue, index) =
205
+ requireNotNull(browserManager.expandQueueFromContextualUrl(contextual("/home", "b")))
206
+
207
+ assertEquals(listOf("a", "b"), queue.map { it.src })
208
+ assertEquals(1, index)
209
+ }
210
+
211
+ @Test
212
+ fun `expandQueueFromContextualUrl honours singleTrack`() = runBlocking {
213
+ servePage(
214
+ "/home",
215
+ track(src = "a", groupTitle = "Recent"),
216
+ track(src = "b", groupTitle = "Recent"),
217
+ singleTrack = true,
218
+ )
219
+
220
+ val (queue, index) =
221
+ requireNotNull(browserManager.expandQueueFromContextualUrl(contextual("/home", "b")))
222
+
223
+ assertEquals(listOf("b"), queue.map { it.src })
224
+ assertEquals(0, index)
225
+ }
67
226
  }
@@ -10,7 +10,8 @@ class JsonArtworkTest {
10
10
 
11
11
  @Test
12
12
  fun `decodes a single url`() {
13
- val track = json.decodeFromString<JsonTrack>("""{"title":"X","artwork":"https://e.example/a.png"}""")
13
+ val track =
14
+ json.decodeFromString<JsonTrack>("""{"title":"X","artwork":"https://e.example/a.png"}""")
14
15
  assertEquals(
15
16
  Variant_String_ArtworkVariants.First("https://e.example/a.png"),
16
17
  track.toNitro().artwork,
@@ -59,8 +59,7 @@ class SectionScopeTest {
59
59
  val row = track(title = "Most Played", src = null, imageRow = items)
60
60
  val children = listOf(row, track(src = "a"))
61
61
 
62
- val section =
63
- SectionScope.section(children, "s2") as SectionScope.Section.ImageRow
62
+ val section = SectionScope.section(children, "s2") as SectionScope.Section.ImageRow
64
63
  assertEquals(listOf("s1", "s2"), section.items.map { it.src })
65
64
  }
66
65
 
@@ -76,8 +75,7 @@ class SectionScopeTest {
76
75
  val row = track(title = "Row", src = null, imageRow = arrayOf(imageRowItem("dup")))
77
76
  val children = listOf(row, track(src = "dup"), track(src = "b"))
78
77
 
79
- val section =
80
- SectionScope.section(children, "dup") as SectionScope.Section.ImageRow
78
+ val section = SectionScope.section(children, "dup") as SectionScope.Section.ImageRow
81
79
  assertEquals(listOf("dup"), section.items.map { it.src })
82
80
  }
83
81
 
@@ -1,8 +1,8 @@
1
1
  package com.audiobrowser.player
2
2
 
3
- import com.margelo.nitro.audiobrowser.NotificationButton
4
- import com.margelo.nitro.audiobrowser.NotificationButtonLayout
5
3
  import com.margelo.nitro.audiobrowser.PlayerCapabilities
4
+ import com.margelo.nitro.audiobrowser.RemoteButton
5
+ import com.margelo.nitro.audiobrowser.RemoteButtonLayout
6
6
  import com.margelo.nitro.audiobrowser.Variant_Boolean_FavoriteConfig
7
7
  import org.junit.Assert.assertEquals
8
8
  import org.junit.Assert.assertFalse
@@ -12,8 +12,7 @@ import org.junit.Test
12
12
  /**
13
13
  * Truth tables for the pure Capability decision core: [PlayerCapabilities.isEnabled] (one rule for
14
14
  * "available by default, only false disables; favorite is opt-in; play/pause is one control") and
15
- * [deriveNotificationSlots] (the single notification slot derivation that both the button layout
16
- * and the notification player commands consume).
15
+ * [deriveButtonSlots] (the single slot derivation every Media3 assembly consumes).
17
16
  */
18
17
  class CapabilityControlsTest {
19
18
 
@@ -43,117 +42,162 @@ class CapabilityControlsTest {
43
42
  playbackRate = null,
44
43
  )
45
44
 
45
+ private fun layout(
46
+ back: RemoteButton? = null,
47
+ forward: RemoteButton? = null,
48
+ overflow: Array<RemoteButton> = emptyArray(),
49
+ ) = RemoteButtonLayout(back = back, forward = forward, overflow = overflow)
50
+
46
51
  private fun slots(
47
52
  capabilities: PlayerCapabilities,
48
- layout: NotificationButtonLayout? = null,
49
- ): List<Pair<NotificationButton, NotificationSlot>> =
50
- deriveNotificationSlots(capabilities, layout).map { it.button to it.slot }
53
+ layout: RemoteButtonLayout? = null,
54
+ ): List<Pair<RemoteButton, ButtonSlot>> =
55
+ deriveButtonSlots(capabilities, layout).map { it.button to it.slot }
51
56
 
52
57
  // MARK: isEnabled
53
58
 
54
59
  @Test
55
60
  fun `everything but favorite is enabled by default`() {
56
61
  val defaults = capabilities()
57
- for (control in Control.entries) {
58
- assertEquals("$control", control != Control.FAVORITE, defaults.isEnabled(control))
62
+ for (control in Capability.entries) {
63
+ assertEquals("$control", control != Capability.FAVORITE, defaults.isEnabled(control))
59
64
  }
60
65
  }
61
66
 
62
67
  @Test
63
68
  fun `only an explicit false disables a control`() {
64
69
  val caps = capabilities(skipToNext = false, jumpBackward = false)
65
- assertFalse(caps.isEnabled(Control.SKIP_TO_NEXT))
66
- assertFalse(caps.isEnabled(Control.JUMP_BACKWARD))
67
- assertTrue(caps.isEnabled(Control.SKIP_TO_PREVIOUS))
68
- assertTrue(caps.isEnabled(Control.JUMP_FORWARD))
70
+ assertFalse(caps.isEnabled(Capability.SKIP_TO_NEXT))
71
+ assertFalse(caps.isEnabled(Capability.JUMP_BACKWARD))
72
+ assertTrue(caps.isEnabled(Capability.SKIP_TO_PREVIOUS))
73
+ assertTrue(caps.isEnabled(Capability.JUMP_FORWARD))
69
74
  }
70
75
 
71
76
  @Test
72
77
  fun `play pause is one control - it survives a single half being disabled`() {
73
- assertTrue(capabilities(play = false).isEnabled(Control.PLAY_PAUSE))
74
- assertTrue(capabilities(pause = false).isEnabled(Control.PLAY_PAUSE))
75
- assertFalse(capabilities(play = false, pause = false).isEnabled(Control.PLAY_PAUSE))
78
+ assertTrue(capabilities(play = false).isEnabled(Capability.PLAY_PAUSE))
79
+ assertTrue(capabilities(pause = false).isEnabled(Capability.PLAY_PAUSE))
80
+ assertFalse(capabilities(play = false, pause = false).isEnabled(Capability.PLAY_PAUSE))
76
81
  }
77
82
 
78
83
  @Test
79
84
  fun `favorite is opt-in`() {
80
- assertFalse(capabilities().isEnabled(Control.FAVORITE))
81
- assertFalse(capabilities(favorite = false).isEnabled(Control.FAVORITE))
82
- assertTrue(capabilities(favorite = true).isEnabled(Control.FAVORITE))
85
+ assertFalse(capabilities().isEnabled(Capability.FAVORITE))
86
+ assertFalse(capabilities(favorite = false).isEnabled(Capability.FAVORITE))
87
+ assertTrue(capabilities(favorite = true).isEnabled(Capability.FAVORITE))
83
88
  }
84
89
 
85
- // MARK: deriveNotificationSlots — capability defaults
90
+ // MARK: deriveButtonSlots — capability defaults
86
91
 
87
92
  @Test
88
- fun `default layout puts skip on the primary slots, jump on the secondary, favorite in overflow`() {
93
+ fun `default layout puts skip on the primary positions, jump and favorite in overflow`() {
89
94
  val derived = slots(capabilities(favorite = true))
90
95
  assertEquals(
91
96
  listOf(
92
- NotificationButton.SKIP_TO_PREVIOUS to NotificationSlot.BACK,
93
- NotificationButton.SKIP_TO_NEXT to NotificationSlot.FORWARD,
94
- NotificationButton.JUMP_BACKWARD to NotificationSlot.BACK_SECONDARY,
95
- NotificationButton.JUMP_FORWARD to NotificationSlot.FORWARD_SECONDARY,
96
- NotificationButton.FAVORITE to NotificationSlot.OVERFLOW,
97
+ RemoteButton.SKIP_TO_PREVIOUS to ButtonSlot.BACK,
98
+ RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD,
99
+ RemoteButton.JUMP_BACKWARD to ButtonSlot.OVERFLOW,
100
+ RemoteButton.JUMP_FORWARD to ButtonSlot.OVERFLOW,
101
+ RemoteButton.FAVORITE to ButtonSlot.OVERFLOW,
97
102
  ),
98
103
  derived,
99
104
  )
100
105
  }
101
106
 
102
107
  @Test
103
- fun `jump falls back to the primary slot when skip is disabled`() {
108
+ fun `jump falls back to a primary position when skip is disabled`() {
104
109
  val derived = slots(capabilities(skipToPrevious = false, skipToNext = false))
105
110
  assertEquals(
106
111
  listOf(
107
- NotificationButton.JUMP_BACKWARD to NotificationSlot.BACK,
108
- NotificationButton.JUMP_FORWARD to NotificationSlot.FORWARD,
112
+ RemoteButton.JUMP_BACKWARD to ButtonSlot.BACK,
113
+ RemoteButton.JUMP_FORWARD to ButtonSlot.FORWARD,
109
114
  ),
110
115
  derived,
111
116
  )
112
117
  }
113
118
 
114
119
  @Test
115
- fun `a fully disabled side leaves its slot empty`() {
120
+ fun `a fully disabled side leaves its position empty`() {
116
121
  val derived =
117
122
  slots(capabilities(skipToPrevious = false, jumpBackward = false, jumpForward = false))
118
- assertEquals(listOf(NotificationButton.SKIP_TO_NEXT to NotificationSlot.FORWARD), derived)
123
+ assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
119
124
  }
120
125
 
121
- // MARK: deriveNotificationSlots — explicit layout
126
+ // MARK: deriveButtonSlots — explicit layout
122
127
 
123
128
  @Test
124
- fun `explicit layout maps every slot and keeps overflow order`() {
125
- val layout =
126
- NotificationButtonLayout(
127
- back = NotificationButton.JUMP_BACKWARD,
128
- forward = NotificationButton.JUMP_FORWARD,
129
- backSecondary = null,
130
- forwardSecondary = null,
131
- overflow = arrayOf(NotificationButton.SKIP_TO_PREVIOUS, NotificationButton.SKIP_TO_NEXT),
129
+ fun `an explicit layout maps every field and keeps overflow order`() {
130
+ val derived =
131
+ slots(
132
+ capabilities(),
133
+ layout(
134
+ back = RemoteButton.JUMP_BACKWARD,
135
+ forward = RemoteButton.JUMP_FORWARD,
136
+ overflow = arrayOf(RemoteButton.SKIP_TO_PREVIOUS, RemoteButton.SKIP_TO_NEXT),
137
+ ),
132
138
  )
133
- val derived = slots(capabilities(), layout)
134
139
  assertEquals(
135
140
  listOf(
136
- NotificationButton.JUMP_BACKWARD to NotificationSlot.BACK,
137
- NotificationButton.JUMP_FORWARD to NotificationSlot.FORWARD,
138
- NotificationButton.SKIP_TO_PREVIOUS to NotificationSlot.OVERFLOW,
139
- NotificationButton.SKIP_TO_NEXT to NotificationSlot.OVERFLOW,
141
+ RemoteButton.JUMP_BACKWARD to ButtonSlot.BACK,
142
+ RemoteButton.JUMP_FORWARD to ButtonSlot.FORWARD,
143
+ RemoteButton.SKIP_TO_PREVIOUS to ButtonSlot.OVERFLOW,
144
+ RemoteButton.SKIP_TO_NEXT to ButtonSlot.OVERFLOW,
140
145
  ),
141
146
  derived,
142
147
  )
143
148
  }
144
149
 
145
150
  @Test
146
- fun `explicit layout entries are dropped when their capability is disabled`() {
147
- val layout =
148
- NotificationButtonLayout(
149
- back = NotificationButton.SKIP_TO_PREVIOUS,
150
- forward = NotificationButton.SKIP_TO_NEXT,
151
- backSecondary = null,
152
- forwardSecondary = null,
153
- overflow = arrayOf(NotificationButton.FAVORITE),
151
+ fun `a null field leaves that position empty`() {
152
+ val derived = slots(capabilities(), layout(back = null, forward = RemoteButton.SKIP_TO_NEXT))
153
+ assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
154
+ }
155
+
156
+ @Test
157
+ fun `a layout is never merged with the capability defaults`() {
158
+ // Nitro collapses an omitted and a null enum field to the same Kotlin null, so a per-field
159
+ // merge is not expressible: an empty position must stay empty rather than falling back to the
160
+ // capability default. Every capability here is enabled, so a merge would add skip and jump.
161
+ val derived =
162
+ slots(capabilities(favorite = true), layout(overflow = arrayOf(RemoteButton.FAVORITE)))
163
+ assertEquals(listOf(RemoteButton.FAVORITE to ButtonSlot.OVERFLOW), derived)
164
+ }
165
+
166
+ @Test
167
+ fun `a disabled entry drops out without promoting anything into its position`() {
168
+ // favorite unset (= disabled), skipToPrevious explicitly off. skip-to-next must stay on
169
+ // FORWARD rather than sliding into the vacated BACK position.
170
+ val derived =
171
+ slots(
172
+ capabilities(skipToPrevious = false),
173
+ layout(
174
+ back = RemoteButton.SKIP_TO_PREVIOUS,
175
+ forward = RemoteButton.SKIP_TO_NEXT,
176
+ overflow = arrayOf(RemoteButton.FAVORITE),
177
+ ),
154
178
  )
155
- // favorite unset (= disabled), skipToPrevious explicitly off
156
- val derived = slots(capabilities(skipToPrevious = false), layout)
157
- assertEquals(listOf(NotificationButton.SKIP_TO_NEXT to NotificationSlot.FORWARD), derived)
179
+ assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
180
+ }
181
+
182
+ // MARK: sameAs
183
+
184
+ @Test
185
+ fun `sameAs compares overflow by content, not by array identity`() {
186
+ val a = layout(back = RemoteButton.JUMP_BACKWARD, overflow = arrayOf(RemoteButton.FAVORITE))
187
+ val b = layout(back = RemoteButton.JUMP_BACKWARD, overflow = arrayOf(RemoteButton.FAVORITE))
188
+ // Distinct Array instances — data-class `==` would call these different and republish the
189
+ // layout to Android Auto on every unrelated options update.
190
+ assertTrue(a.sameAs(b))
191
+ assertFalse(a.sameAs(layout(back = RemoteButton.JUMP_BACKWARD)))
192
+ assertFalse(
193
+ a.sameAs(layout(back = RemoteButton.JUMP_FORWARD, overflow = arrayOf(RemoteButton.FAVORITE)))
194
+ )
195
+ }
196
+
197
+ @Test
198
+ fun `sameAs treats null as its own layout`() {
199
+ assertTrue(null.sameAs(null))
200
+ assertFalse(layout(back = RemoteButton.SKIP_TO_NEXT).sameAs(null))
201
+ assertFalse(null.sameAs(layout(back = RemoteButton.SKIP_TO_NEXT)))
158
202
  }
159
203
  }
@@ -0,0 +1,45 @@
1
+ package com.audiobrowser.player
2
+
3
+ import androidx.media3.session.CommandButton
4
+ import org.junit.Assert.assertArrayEquals
5
+ import org.junit.Assert.assertTrue
6
+ import org.junit.Test
7
+
8
+ /**
9
+ * Guards the slot preference lists handed to Media3.
10
+ *
11
+ * The invariant that matters is the OVERFLOW fallback: every flattening keeps a button only if it
12
+ * won SLOT_BACK, won SLOT_FORWARD, or names SLOT_OVERFLOW. A slot that stops naming OVERFLOW
13
+ * silently deletes its button from the notification and the car the moment its primary position is
14
+ * unavailable, so the loop below asserts the property for every slot rather than only today's
15
+ * three.
16
+ */
17
+ class MediaSessionSlotMappingTest {
18
+
19
+ @Test
20
+ fun `primary slots are preferred with an overflow fallback`() {
21
+ assertArrayEquals(
22
+ intArrayOf(CommandButton.SLOT_BACK, CommandButton.SLOT_OVERFLOW),
23
+ media3SlotsFor(ButtonSlot.BACK),
24
+ )
25
+ assertArrayEquals(
26
+ intArrayOf(CommandButton.SLOT_FORWARD, CommandButton.SLOT_OVERFLOW),
27
+ media3SlotsFor(ButtonSlot.FORWARD),
28
+ )
29
+ }
30
+
31
+ @Test
32
+ fun `overflow does not repeat itself`() {
33
+ assertArrayEquals(intArrayOf(CommandButton.SLOT_OVERFLOW), media3SlotsFor(ButtonSlot.OVERFLOW))
34
+ }
35
+
36
+ @Test
37
+ fun `every slot names OVERFLOW so nothing can be dropped`() {
38
+ for (slot in ButtonSlot.entries) {
39
+ assertTrue(
40
+ "$slot must name SLOT_OVERFLOW or its button is dropped from the notification and the car",
41
+ media3SlotsFor(slot).contains(CommandButton.SLOT_OVERFLOW),
42
+ )
43
+ }
44
+ }
45
+ }
@@ -132,8 +132,8 @@ class PlaybackErrorClassifierTest {
132
132
 
133
133
  /**
134
134
  * Load errors are raw IOExceptions from the transport layer; the policy only retries network
135
- * ones, so the advisory classification is UNREACHABLE unless the device is offline or the
136
- * server actually answered.
135
+ * ones, so the advisory classification is UNREACHABLE unless the device is offline or the server
136
+ * actually answered.
137
137
  */
138
138
  @Test
139
139
  fun `retrying transport failure is unreachable`() {
@@ -158,8 +158,7 @@ class PlaybackErrorClassifierTest {
158
158
  /** The HTTP status is dug out of the cause chain and mapped through the shared status table. */
159
159
  @Test
160
160
  fun `retrying HTTP failure carries the status`() {
161
- val error =
162
- PlaybackErrorClassifier.retryingLoadError(httpException(503), online = true)
161
+ val error = PlaybackErrorClassifier.retryingLoadError(httpException(503), online = true)
163
162
  assertEquals(PlaybackErrorKind.SERVER_ERROR, error.kind)
164
163
  assertEquals(503.0, error.statusCode!!, 0.0)
165
164
  assertEquals(true, error.retrying)
@@ -201,9 +201,9 @@ class RetryLoadErrorHandlingPolicyBudgetTest {
201
201
  }
202
202
 
203
203
  /**
204
- * Offline failures must not burn the first-connect budget (a station tapped in a tunnel parks
205
- * for connectivity); the budget's clock starts at the first error observed online, so a dead
206
- * station still gets a fast verdict after restoration.
204
+ * Offline failures must not burn the first-connect budget (a station tapped in a tunnel parks for
205
+ * connectivity); the budget's clock starts at the first error observed online, so a dead station
206
+ * still gets a fast verdict after restoration.
207
207
  */
208
208
  @Test
209
209
  fun `offline time does not burn the first-connect budget`() {