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.
- package/README.md +22 -18
- package/android/build.gradle +13 -10
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +76 -21
- package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +1 -2
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +1 -2
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +6 -7
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +11 -11
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +9 -5
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +0 -1
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +2 -4
- package/android/src/main/java/com/audiobrowser/player/Player.kt +30 -8
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
- package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +2 -1
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +8 -7
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +52 -1
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +167 -8
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +2 -1
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +2 -4
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +100 -56
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
- package/ios/Browser/BrowserManager.swift +2 -27
- package/ios/Browser/SearchDrillIn.swift +1 -1
- package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
- package/ios/HybridAudioBrowser.swift +87 -56
- package/ios/Model/NitroTypeStubs.swift +18 -2
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/Option/SessionCategories.swift +3 -2
- package/ios/Player/MediaIntentCriteria.swift +4 -4
- package/ios/Player/MediaLoader.swift +3 -1
- package/ios/Player/PlaybackCoordinator.swift +4 -1
- package/ios/Player/PlaybackErrorHandler.swift +4 -4
- package/ios/Player/QueueManager.swift +10 -3
- package/ios/Player/RetryManager.swift +79 -18
- package/ios/Player/ShuffleOrder.swift +10 -0
- package/ios/Player/SleepTimerManager.swift +43 -15
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +9 -6
- package/ios/Tests/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/MediaLoaderTests.swift +10 -13
- package/ios/Tests/PlaybackCoordinatorTests.swift +10 -7
- package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/QueueManagerTests.swift +50 -4
- package/ios/Tests/RemoteCommandControllerTests.swift +156 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/RetryManagerTests.swift +89 -0
- package/ios/Tests/SectionScopeTests.swift +2 -2
- package/ios/Tests/SleepTimerManagerTests.swift +22 -19
- package/ios/Tests/Support/ManualSleepTimerScheduler.swift +47 -0
- package/ios/Tests/Support/TestTypes.swift +1 -1
- package/ios/Tests/TabBarEntriesTests.swift +7 -7
- package/ios/TrackPlayer.swift +6 -1
- package/ios/TrackPlayerCallbacks.swift +5 -38
- package/ios/Util/LRUCache.swift +4 -1
- package/lib/commonjs/AudioBrowser.js +0 -11
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser.js +1 -1
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +3 -3
- package/lib/commonjs/features/favorites.js +1 -1
- package/lib/commonjs/features/gate.js +16 -1
- package/lib/commonjs/features/gate.js.map +1 -1
- package/lib/commonjs/features/metadata.js +20 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -1
- package/lib/commonjs/features/output.js +1 -1
- package/lib/commonjs/features/playback/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +1 -1
- package/lib/commonjs/features/playback/state.js +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/commonjs/features/playback/volume.js +1 -1
- package/lib/commonjs/features/player/options.js +48 -34
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +15 -8
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +21 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +1 -1
- package/lib/commonjs/features/queue/queue.js +2 -2
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/remoteControls.js +13 -92
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +13 -1
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
- package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +8 -5
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/web/NativeAudioBrowser.js +1 -1
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +1 -1
- package/lib/module/AudioBrowser.js +1 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser.js +1 -1
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +3 -3
- package/lib/module/features/favorites.js +1 -1
- package/lib/module/features/gate.js +16 -1
- package/lib/module/features/gate.js.map +1 -1
- package/lib/module/features/metadata.js +20 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -1
- package/lib/module/features/output.js +1 -1
- package/lib/module/features/playback/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +1 -1
- package/lib/module/features/playback/state.js +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -5
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/module/features/playback/volume.js +1 -1
- package/lib/module/features/player/options.js +47 -32
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +14 -7
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +17 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +1 -1
- package/lib/module/features/queue/queue.js +2 -2
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/remoteControls.js +13 -92
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +13 -1
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/test-utils/reactNativeStub.js +58 -0
- package/lib/module/test-utils/reactNativeStub.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +8 -5
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/web/NativeAudioBrowser.js +1 -1
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +0 -1
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +3 -3
- package/lib/typescript/src/features/favorites.d.ts +1 -1
- package/lib/typescript/src/features/gate.d.ts +18 -2
- package/lib/typescript/src/features/gate.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +20 -3
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/output.d.ts +1 -1
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts +87 -37
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +14 -4
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +9 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +2 -2
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts +23 -22
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +8 -4
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -1
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +5 -5
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +11 -11
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +5 -5
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +5 -5
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JOptions.hpp +5 -5
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +36 -51
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +6 -6
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +8 -8
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/package.json +12 -4
- package/src/AudioBrowser.ts +1 -1
- package/src/CLAUDE.md +52 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser.ts +1 -1
- package/src/features/carConnection.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +3 -3
- package/src/features/favorites.ts +1 -1
- package/src/features/gate.ts +16 -1
- package/src/features/hook-wiring.test.ts +254 -0
- package/src/features/metadata.ts +20 -3
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.test.ts +169 -0
- package/src/features/playback/progress.ts +1 -1
- package/src/features/playback/state.ts +1 -1
- package/src/features/playback/trackPlaybackTime.test.ts +16 -6
- package/src/features/playback/trackPlaybackTime.ts +0 -5
- package/src/features/playback/volume.ts +1 -1
- package/src/features/player/options.ts +99 -57
- package/src/features/player/setup.test.ts +12 -4
- package/src/features/player/setup.ts +19 -9
- package/src/features/player/validateOptions.ts +19 -0
- package/src/features/queue/activeTrack.ts +1 -1
- package/src/features/queue/queue.ts +2 -2
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -92
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +9 -1
- package/src/nitro-stub-drift.test.ts +237 -0
- package/src/public-surface.test.ts +107 -0
- package/src/specs/audio-browser.nitro.ts +1 -1
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/utils/LazyNativeEmitter.ts +9 -6
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/web/NativeAudioBrowser.ts +1 -1
- package/src/web/browser/BrowserManager.ts +1 -1
- package/ios/Model/MediaURL.swift +0 -67
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
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
|
-
* [
|
|
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:
|
|
49
|
-
): List<Pair<
|
|
50
|
-
|
|
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
|
|
58
|
-
assertEquals("$control", 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(
|
|
66
|
-
assertFalse(caps.isEnabled(
|
|
67
|
-
assertTrue(caps.isEnabled(
|
|
68
|
-
assertTrue(caps.isEnabled(
|
|
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(
|
|
74
|
-
assertTrue(capabilities(pause = false).isEnabled(
|
|
75
|
-
assertFalse(capabilities(play = false, pause = false).isEnabled(
|
|
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(
|
|
81
|
-
assertFalse(capabilities(favorite = false).isEnabled(
|
|
82
|
-
assertTrue(capabilities(favorite = true).isEnabled(
|
|
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:
|
|
90
|
+
// MARK: deriveButtonSlots — capability defaults
|
|
86
91
|
|
|
87
92
|
@Test
|
|
88
|
-
fun `default layout puts skip on the primary
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
108
|
-
|
|
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
|
|
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(
|
|
123
|
+
assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
// MARK:
|
|
126
|
+
// MARK: deriveButtonSlots — explicit layout
|
|
122
127
|
|
|
123
128
|
@Test
|
|
124
|
-
fun `explicit layout maps every
|
|
125
|
-
val
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 `
|
|
147
|
-
val layout =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
206
|
-
*
|
|
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`() {
|