react-native-audio-browser 0.2.0-next.10 → 0.2.0-next.12
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 +4 -4
- package/android/build.gradle +6 -5
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
- package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +46 -5
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +86 -27
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +20 -6
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +249 -86
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +69 -30
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +345 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +152 -8
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +218 -10
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +131 -143
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/JsonModels.swift +187 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +34 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +47 -0
- package/ios/Browser/TabBarEntries.swift +2 -2
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +105 -67
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
- package/ios/Extension/Section+Copying.swift +31 -0
- package/ios/Extension/Track+Copying.swift +6 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/HybridAudioBrowser.swift +113 -35
- package/ios/Model/NitroTypeStubs.swift +64 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/NowPlayingUpdater.swift +1 -1
- package/ios/Player/PlaybackCoordinator.swift +2 -2
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/JsonModelsTests.swift +75 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +4 -4
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +61 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +67 -0
- package/ios/Tests/TabBarEntriesTests.swift +18 -18
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser-config.js +11 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +2 -2
- package/lib/commonjs/features/favorites.js +13 -10
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +2 -3
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -1
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +2 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +14 -3
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +11 -4
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +144 -87
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -0
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser-config.js +11 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +2 -2
- package/lib/module/features/favorites.js +13 -10
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/state.js +2 -3
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -1
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +2 -0
- package/lib/module/features/player/validateOptions.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +14 -3
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +11 -4
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/utils/LazyNativeEmitter.js +21 -10
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +32 -24
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +144 -87
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +2 -2
- package/lib/typescript/src/features/favorites.d.ts +13 -10
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +2 -3
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +21 -19
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +5 -2
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +2 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +33 -53
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
- package/nitrogen/generated/android/c++/JSection.hpp +114 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
- package/nitrogen/generated/ios/swift/Section.swift +100 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +20 -73
- package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
- package/nitrogen/generated/shared/c++/Section.hpp +106 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +15 -31
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
- package/package.json +3 -2
- package/src/AudioBrowser.ts +1 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +11 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +2 -2
- package/src/features/favorites.ts +13 -10
- package/src/features/hook-wiring.test.ts +63 -15
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/state.ts +2 -3
- package/src/features/playback/trackPlaybackTime.ts +1 -1
- package/src/features/player/options.ts +27 -19
- package/src/features/player/setup.test.ts +12 -3
- package/src/features/player/setup.ts +3 -1
- package/src/features/player/validateOptions.ts +2 -0
- package/src/features/queue/activeTrack.ts +14 -3
- package/src/features/queue/queue.ts +11 -4
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +40 -0
- package/src/specs/audio-browser.nitro.ts +4 -3
- package/src/style-resolution.test.ts +139 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +268 -136
- package/src/types/browser.ts +34 -55
- package/src/utils/LazyNativeEmitter.ts +20 -10
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.ts +37 -22
- package/src/web/browser/BrowserManager.ts +176 -95
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
package com.audiobrowser.browser
|
|
2
2
|
|
|
3
|
+
import androidx.media3.common.C
|
|
4
|
+
import androidx.media3.common.MediaItem
|
|
5
|
+
import com.audiobrowser.TestFixtures.resolvedTrack
|
|
6
|
+
import com.audiobrowser.TestFixtures.section
|
|
7
|
+
import com.audiobrowser.TestFixtures.staticRoute
|
|
8
|
+
import com.audiobrowser.TestFixtures.track
|
|
9
|
+
import com.audiobrowser.util.BrowserPathHelper
|
|
3
10
|
import kotlinx.coroutines.runBlocking
|
|
4
|
-
import org.junit.Assert
|
|
11
|
+
import org.junit.Assert.assertEquals
|
|
12
|
+
import org.junit.Assert.assertNull
|
|
13
|
+
import org.junit.Assert.fail
|
|
5
14
|
import org.junit.Before
|
|
6
15
|
import org.junit.Test
|
|
7
16
|
import org.junit.runner.RunWith
|
|
8
17
|
import org.robolectric.RobolectricTestRunner
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Navigation and queue-expansion glue.
|
|
21
|
+
*
|
|
22
|
+
* [BrowserManager.expandQueueFromContextualPath] is the seam between the page a listener tapped in
|
|
23
|
+
* and the queue that starts playing. Its two halves are covered on their own — the section logic in
|
|
24
|
+
* [SectionScopeTest], the caller's single-track fallback in the player tests — but the wiring
|
|
25
|
+
* between them is what decides whether tapping row 3 of a page resumes the right station, and only
|
|
26
|
+
* these tests exercise it.
|
|
27
|
+
*
|
|
28
|
+
* Routes serve `browseStatic`, the one browse arm that needs neither the JNI bridge nor HTTP.
|
|
29
|
+
* Robolectric is required for `android.net.Uri` (contextual-path parsing) and
|
|
30
|
+
* `android.util.LruCache` (the content cache).
|
|
31
|
+
*/
|
|
17
32
|
@RunWith(RobolectricTestRunner::class)
|
|
18
33
|
class BrowserManagerTest {
|
|
19
34
|
|
|
@@ -24,6 +39,31 @@ class BrowserManagerTest {
|
|
|
24
39
|
browserManager = BrowserManager()
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
/** Configures a single static route at [path] serving [children] as its page. */
|
|
43
|
+
private fun servePage(
|
|
44
|
+
path: String,
|
|
45
|
+
vararg children: com.margelo.nitro.audiobrowser.Track,
|
|
46
|
+
singleTrack: Boolean = false,
|
|
47
|
+
) {
|
|
48
|
+
browserManager.config =
|
|
49
|
+
BrowserConfig(
|
|
50
|
+
routes =
|
|
51
|
+
arrayOf(staticRoute(path, resolvedTrack(path = path, children = arrayOf(*children)))),
|
|
52
|
+
singleTrack = singleTrack,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Configures a single static route at [path] serving [sections] as its page. */
|
|
57
|
+
private fun serveSections(path: String, vararg sections: com.margelo.nitro.audiobrowser.Section) {
|
|
58
|
+
browserManager.config =
|
|
59
|
+
BrowserConfig(
|
|
60
|
+
routes =
|
|
61
|
+
arrayOf(staticRoute(path, resolvedTrack(path = path, sections = arrayOf(*sections))))
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun contextual(path: String, trackId: String) = BrowserPathHelper.build(path, trackId)
|
|
66
|
+
|
|
27
67
|
@Test
|
|
28
68
|
fun `getPath returns default path initially`() {
|
|
29
69
|
assertEquals("/", browserManager.getPath())
|
|
@@ -64,4 +104,301 @@ class BrowserManagerTest {
|
|
|
64
104
|
} catch (_: ContentNotFoundException) {}
|
|
65
105
|
}
|
|
66
106
|
}
|
|
107
|
+
|
|
108
|
+
@Test
|
|
109
|
+
fun `navigate serves a static route and gives playable children contextual urls`() = runBlocking {
|
|
110
|
+
servePage("/library", track(src = "a", title = "A"), track(src = "b", title = "B"))
|
|
111
|
+
|
|
112
|
+
val resolved = browserManager.navigate("/library")
|
|
113
|
+
|
|
114
|
+
assertEquals(listOf("A", "B"), resolved.flattenedChildren?.map { it.title })
|
|
115
|
+
// The tap target: a child's path is rewritten to point back at this page
|
|
116
|
+
// plus its own id and page position (the duplicate-identity tie-breaker).
|
|
117
|
+
assertEquals(
|
|
118
|
+
listOf(
|
|
119
|
+
BrowserPathHelper.build("/library", "a", 0),
|
|
120
|
+
BrowserPathHelper.build("/library", "b", 1),
|
|
121
|
+
),
|
|
122
|
+
resolved.flattenedChildren?.map { it.path },
|
|
123
|
+
)
|
|
124
|
+
assertEquals("/library", browserManager.getPath())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// MARK: - expandQueueFromContextualPath
|
|
128
|
+
|
|
129
|
+
@Test
|
|
130
|
+
fun `expandQueueFromContextualPath scopes the queue to the tapped section`() = runBlocking {
|
|
131
|
+
serveSections(
|
|
132
|
+
"/home",
|
|
133
|
+
section(title = "Recent", children = arrayOf(track(src = "a"), track(src = "b"))),
|
|
134
|
+
section(title = "Popular", children = arrayOf(track(src = "c"), track(src = "d"))),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
val expanded = browserManager.expandQueueFromContextualPath(contextual("/home", "c"))
|
|
138
|
+
|
|
139
|
+
// "Popular" only — a page aggregating sections must not leak next/previous across them.
|
|
140
|
+
val (queue, index) = requireNotNull(expanded)
|
|
141
|
+
assertEquals(listOf("c", "d"), queue.map { it.src })
|
|
142
|
+
assertEquals(0, index)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@Test
|
|
146
|
+
fun `expandQueueFromContextualPath indexes the tapped track within its section`() = runBlocking {
|
|
147
|
+
serveSections(
|
|
148
|
+
"/home",
|
|
149
|
+
section(title = "Recent", children = arrayOf(track(src = "a"))),
|
|
150
|
+
section(title = "Popular", children = arrayOf(track(src = "b"), track(src = "c"))),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
val (queue, index) =
|
|
154
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "c")))
|
|
155
|
+
|
|
156
|
+
assertEquals(listOf("b", "c"), queue.map { it.src })
|
|
157
|
+
assertEquals(1, index)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@Test
|
|
161
|
+
fun `expandQueueFromContextualPath pins the tapped copy of a duplicate identity`() = runBlocking {
|
|
162
|
+
// The same src twice in one section (normal for music playlists): the
|
|
163
|
+
// stamped page index selects the tapped copy, so "next" continues from
|
|
164
|
+
// there instead of from the first copy.
|
|
165
|
+
servePage("/playlist", track(src = "a"), track(src = "b"), track(src = "a"), track(src = "c"))
|
|
166
|
+
|
|
167
|
+
val (queue, index) =
|
|
168
|
+
requireNotNull(
|
|
169
|
+
browserManager.expandQueueFromContextualPath(BrowserPathHelper.build("/playlist", "a", 2))
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
assertEquals(listOf("a", "b", "a", "c"), queue.map { it.src })
|
|
173
|
+
assertEquals(2, index)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun `expandQueueFromContextualPath scopes a cross-section duplicate to the tapped section`() =
|
|
178
|
+
runBlocking {
|
|
179
|
+
// The same identity in a rail section AND the list section below it:
|
|
180
|
+
// the stamped flat index resolves the tap to the list section.
|
|
181
|
+
serveSections(
|
|
182
|
+
"/home",
|
|
183
|
+
section(
|
|
184
|
+
title = "Row",
|
|
185
|
+
style =
|
|
186
|
+
com.margelo.nitro.audiobrowser.SectionStyle(
|
|
187
|
+
display = com.margelo.nitro.audiobrowser.StyleDisplay.GRID,
|
|
188
|
+
artworkRendering = null,
|
|
189
|
+
gridWrap = false,
|
|
190
|
+
),
|
|
191
|
+
children = arrayOf(track(src = "dup")),
|
|
192
|
+
),
|
|
193
|
+
section(title = "Stations", children = arrayOf(track(src = "dup"), track(src = "b"))),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
val (queue, index) =
|
|
197
|
+
requireNotNull(
|
|
198
|
+
browserManager.expandQueueFromContextualPath(BrowserPathHelper.build("/home", "dup", 1))
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
assertEquals(listOf("dup", "b"), queue.map { it.src })
|
|
202
|
+
assertEquals(0, index)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@Test
|
|
206
|
+
fun `expandQueueFromContextualPath falls back to the first match on a stale index`() =
|
|
207
|
+
runBlocking {
|
|
208
|
+
// The list shifted since the stamp: the child at the index no longer
|
|
209
|
+
// carries the identity, so resolution ignores the index and falls back
|
|
210
|
+
// to the first identity match instead of aborting.
|
|
211
|
+
servePage("/home", track(src = "x"), track(src = "a"), track(src = "b"))
|
|
212
|
+
|
|
213
|
+
val (queue, index) =
|
|
214
|
+
requireNotNull(
|
|
215
|
+
browserManager.expandQueueFromContextualPath(BrowserPathHelper.build("/home", "a", 0))
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
assertEquals(listOf("x", "a", "b"), queue.map { it.src })
|
|
219
|
+
assertEquals(1, index)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Test
|
|
223
|
+
fun `expandQueueFromContextualPath returns null when the id has vanished from the page`() =
|
|
224
|
+
runBlocking {
|
|
225
|
+
// The stored path points at a track the page no longer lists — the page changed under a
|
|
226
|
+
// resumed session. Queueing the current list would resume the wrong station, so expansion
|
|
227
|
+
// aborts and the caller falls back to the stored single track.
|
|
228
|
+
servePage("/home", track(src = "a"), track(src = "b"))
|
|
229
|
+
|
|
230
|
+
assertNull(browserManager.expandQueueFromContextualPath(contextual("/home", "gone")))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@Test
|
|
234
|
+
fun `expandQueueFromContextualPath returns null for a non-contextual path`() = runBlocking {
|
|
235
|
+
servePage("/home", track(src = "a"))
|
|
236
|
+
|
|
237
|
+
assertNull(browserManager.expandQueueFromContextualPath("/home"))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Test
|
|
241
|
+
fun `expandQueueFromContextualPath returns null when the page has no children`() = runBlocking {
|
|
242
|
+
browserManager.config =
|
|
243
|
+
BrowserConfig(routes = arrayOf(staticRoute("/home", resolvedTrack(path = "/home"))))
|
|
244
|
+
|
|
245
|
+
assertNull(browserManager.expandQueueFromContextualPath(contextual("/home", "a")))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@Test
|
|
249
|
+
fun `expandQueueFromContextualPath scopes a rail section to its own tiles`() = runBlocking {
|
|
250
|
+
serveSections(
|
|
251
|
+
"/home",
|
|
252
|
+
section(
|
|
253
|
+
title = "Most Played",
|
|
254
|
+
style =
|
|
255
|
+
com.margelo.nitro.audiobrowser.SectionStyle(
|
|
256
|
+
display = com.margelo.nitro.audiobrowser.StyleDisplay.GRID,
|
|
257
|
+
artworkRendering = null,
|
|
258
|
+
gridWrap = false,
|
|
259
|
+
),
|
|
260
|
+
children = arrayOf(track(src = "r1"), track(src = "r2")),
|
|
261
|
+
),
|
|
262
|
+
section(children = arrayOf(track(src = "a"))),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
val (queue, index) =
|
|
266
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "r2")))
|
|
267
|
+
|
|
268
|
+
// A tile tap queues its section, not the list rows beside it.
|
|
269
|
+
assertEquals(listOf("r1", "r2"), queue.map { it.src })
|
|
270
|
+
assertEquals(1, index)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@Test
|
|
274
|
+
fun `expandQueueFromContextualPath drops unplayable siblings`() = runBlocking {
|
|
275
|
+
serveSections(
|
|
276
|
+
"/home",
|
|
277
|
+
section(
|
|
278
|
+
title = "Mixed",
|
|
279
|
+
children =
|
|
280
|
+
arrayOf(
|
|
281
|
+
track(src = "a"),
|
|
282
|
+
// A browsable row sitting inside the same section — no src, so not queueable.
|
|
283
|
+
track(src = null).copy(path = "/sub"),
|
|
284
|
+
track(src = "b"),
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
val (queue, index) =
|
|
290
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "b")))
|
|
291
|
+
|
|
292
|
+
assertEquals(listOf("a", "b"), queue.map { it.src })
|
|
293
|
+
assertEquals(1, index)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@Test
|
|
297
|
+
fun `navigate stamps the identity, not the src, into id-bearing children's contextual urls`() =
|
|
298
|
+
runBlocking {
|
|
299
|
+
servePage(
|
|
300
|
+
"/home",
|
|
301
|
+
track(id = "stable-a", src = "https://s/a.mp3", title = "A"),
|
|
302
|
+
track(src = "https://s/b.mp3", title = "B"),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
val resolved = browserManager.navigate("/home")
|
|
306
|
+
|
|
307
|
+
// id when non-blank, else src — Track.identity (ADR 0008).
|
|
308
|
+
assertEquals(
|
|
309
|
+
listOf(
|
|
310
|
+
BrowserPathHelper.build("/home", "stable-a", 0),
|
|
311
|
+
BrowserPathHelper.build("/home", "https://s/b.mp3", 1),
|
|
312
|
+
),
|
|
313
|
+
resolved.flattenedChildren?.map { it.path },
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@Test
|
|
318
|
+
fun `expandQueueFromContextualPath finds the selected index by identity`() = runBlocking {
|
|
319
|
+
servePage(
|
|
320
|
+
"/home",
|
|
321
|
+
track(id = "stable-a", src = "https://s/a.mp3"),
|
|
322
|
+
track(id = "stable-b", src = "https://s/b.mp3"),
|
|
323
|
+
track(id = "stable-c", src = "https://s/c.mp3"),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
val (queue, index) =
|
|
327
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "stable-c")))
|
|
328
|
+
|
|
329
|
+
assertEquals(listOf("stable-a", "stable-b", "stable-c"), queue.map { it.id })
|
|
330
|
+
assertEquals(2, index)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@Test
|
|
334
|
+
fun `getCachedTrack resolves an id-valued trackId through the cache`() = runBlocking {
|
|
335
|
+
servePage("/home", track(id = "stable-b", src = "https://s/b.mp3", title = "B"))
|
|
336
|
+
browserManager.navigate("/home")
|
|
337
|
+
|
|
338
|
+
// A contextual mediaId whose __trackId is the identity (the id), with a parent path that was
|
|
339
|
+
// never cached directly — the extract-then-lookup path must find it via the id key.
|
|
340
|
+
val mediaId = contextual("/elsewhere", "stable-b")
|
|
341
|
+
|
|
342
|
+
assertEquals("B", browserManager.getCachedTrack(mediaId)?.title)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@Test
|
|
346
|
+
fun `expandQueueFromContextualPath honours singleTrack`() = runBlocking {
|
|
347
|
+
servePage("/home", track(src = "a"), track(src = "b"), singleTrack = true)
|
|
348
|
+
|
|
349
|
+
val (queue, index) =
|
|
350
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "b")))
|
|
351
|
+
|
|
352
|
+
assertEquals(listOf("b"), queue.map { it.src })
|
|
353
|
+
assertEquals(0, index)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// --- Track.disabled: queue expansion excludes unavailable tracks ---
|
|
357
|
+
|
|
358
|
+
@Test
|
|
359
|
+
fun `expandQueueFromContextualPath excludes disabled tracks`() = runBlocking {
|
|
360
|
+
servePage(
|
|
361
|
+
"/home",
|
|
362
|
+
track(src = "a"),
|
|
363
|
+
track(src = "unavailable", disabled = true),
|
|
364
|
+
track(src = "b"),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
val (queue, index) =
|
|
368
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "b")))
|
|
369
|
+
|
|
370
|
+
assertEquals(listOf("a", "b"), queue.map { it.src })
|
|
371
|
+
assertEquals(1, index)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
@Test
|
|
375
|
+
fun `expandQueueFromContextualPath aborts when the tapped track is disabled`() = runBlocking {
|
|
376
|
+
// A disabled track never plays: with the tapped identity excluded from the
|
|
377
|
+
// playable list, expansion aborts, and every caller's fallback refuses the
|
|
378
|
+
// disabled track itself — navigateTrack's guard on tap,
|
|
379
|
+
// onPlaybackResumption's refusal on resume.
|
|
380
|
+
servePage("/home", track(src = "a"), track(src = "unavailable", disabled = true))
|
|
381
|
+
|
|
382
|
+
assertNull(browserManager.expandQueueFromContextualPath(contextual("/home", "unavailable")))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@Test
|
|
386
|
+
fun `resolveMediaItemsForPlayback refuses a disabled track on the legacy INDEX_UNSET path`() =
|
|
387
|
+
runBlocking {
|
|
388
|
+
// Android Auto and Assistant request playback through Media3's legacy
|
|
389
|
+
// path, which passes C.INDEX_UNSET ("the default item") — the refusal
|
|
390
|
+
// must cover it, or a stale browse row for a now-disabled track plays.
|
|
391
|
+
servePage("/home", track(src = "unavailable", disabled = true))
|
|
392
|
+
browserManager.resolve("/home")
|
|
393
|
+
|
|
394
|
+
val item = MediaItem.Builder().setMediaId("unavailable").build()
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
browserManager.resolveMediaItemsForPlayback(listOf(item), C.INDEX_UNSET, 0)
|
|
398
|
+
fail("Expected refusal of a disabled track")
|
|
399
|
+
} catch (expected: IllegalStateException) {
|
|
400
|
+
// Refused by failing the future: Media3 surfaces an error to the
|
|
401
|
+
// controller and live playback stays untouched.
|
|
402
|
+
}
|
|
403
|
+
}
|
|
67
404
|
}
|
|
@@ -91,6 +91,44 @@ class BrowserUrlResolutionTest {
|
|
|
91
91
|
assertEquals("https://api.example.com/artwork/abc", source?.uri)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
@Test
|
|
95
|
+
fun `resolveArtworkUrl leaves the id token literal for a track without an id`() = runTest {
|
|
96
|
+
bm.config =
|
|
97
|
+
BrowserConfig(
|
|
98
|
+
request = transformableConfig(baseUrl = "https://api.example.com"),
|
|
99
|
+
artwork = artworkConfig(path = "/artwork/{id}"),
|
|
100
|
+
)
|
|
101
|
+
// The config still runs (no blanket skip); the token stays literal so the resulting request
|
|
102
|
+
// 404s with a self-describing URL.
|
|
103
|
+
val source = bm.resolveArtworkUrl(track(artwork = null, id = null))
|
|
104
|
+
assertEquals("https://api.example.com/artwork/{id}", source?.uri)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test
|
|
108
|
+
fun `resolveArtworkUrl treats a blank id like a missing one`() = runTest {
|
|
109
|
+
bm.config =
|
|
110
|
+
BrowserConfig(
|
|
111
|
+
request = transformableConfig(baseUrl = "https://api.example.com"),
|
|
112
|
+
artwork = artworkConfig(path = "/artwork/{id}"),
|
|
113
|
+
)
|
|
114
|
+
val source = bm.resolveArtworkUrl(track(artwork = null, id = ""))
|
|
115
|
+
assertEquals("https://api.example.com/artwork/{id}", source?.uri)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test
|
|
119
|
+
fun `resolveArtworkUrl leaves the id token literal in header values without an id`() = runTest {
|
|
120
|
+
bm.config =
|
|
121
|
+
BrowserConfig(
|
|
122
|
+
request = transformableConfig(baseUrl = "https://api.example.com"),
|
|
123
|
+
artwork = artworkConfig(path = "/artwork", headers = mapOf("x-track" to "{id}")),
|
|
124
|
+
)
|
|
125
|
+
val source = bm.resolveArtworkUrl(track(artwork = null, id = null))
|
|
126
|
+
assertEquals("{id}", source?.headers?.get("x-track"))
|
|
127
|
+
|
|
128
|
+
val substituted = bm.resolveArtworkUrl(track(artwork = null, id = "abc"))
|
|
129
|
+
assertEquals("abc", substituted?.headers?.get("x-track"))
|
|
130
|
+
}
|
|
131
|
+
|
|
94
132
|
@Test
|
|
95
133
|
fun `resolveArtworkUrl applies image query params from the image context`() = runTest {
|
|
96
134
|
bm.config =
|
|
@@ -35,7 +35,7 @@ class JsonArtworkTest {
|
|
|
35
35
|
// so one unparseable row rejected the whole page rather than losing an image.
|
|
36
36
|
val resolved =
|
|
37
37
|
json.decodeFromString<JsonResolvedTrack>(
|
|
38
|
-
"""{"
|
|
38
|
+
"""{"path":"/favorites","title":"Favorites","children":[
|
|
39
39
|
{"title":"Playlist","artwork":{"light":"https://e.example/l.png","dark":"https://e.example/d.png"}}
|
|
40
40
|
]}"""
|
|
41
41
|
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package com.audiobrowser.browser
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.audiobrowser.ArtworkRendering
|
|
4
|
+
import com.margelo.nitro.audiobrowser.StyleDisplay
|
|
5
|
+
import kotlinx.serialization.json.Json
|
|
6
|
+
import org.junit.Assert.assertEquals
|
|
7
|
+
import org.junit.Assert.assertNull
|
|
8
|
+
import org.junit.Test
|
|
9
|
+
|
|
10
|
+
/** Style blocks on the wire, decoded tolerantly (ADR 0011). */
|
|
11
|
+
class JsonStyleTest {
|
|
12
|
+
private val json = Json { ignoreUnknownKeys = true }
|
|
13
|
+
|
|
14
|
+
@Test
|
|
15
|
+
fun `decodes a block onto TrackStyle`() {
|
|
16
|
+
val track =
|
|
17
|
+
json.decodeFromString<JsonTrack>(
|
|
18
|
+
"""{"title":"X","style":{"display":"grid","artworkRendering":"stencil"}}"""
|
|
19
|
+
)
|
|
20
|
+
val style = track.toNitro().style
|
|
21
|
+
assertEquals(StyleDisplay.GRID, style?.display)
|
|
22
|
+
assertEquals(ArtworkRendering.STENCIL, style?.artworkRendering)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun `decodes a block onto SectionStyle`() {
|
|
27
|
+
val resolved =
|
|
28
|
+
json.decodeFromString<JsonResolvedTrack>(
|
|
29
|
+
"""{"path":"/home","title":"Home","sections":[
|
|
30
|
+
{"style":{"display":"grid","gridWrap":false},"children":[{"title":"C","src":"s"}]}
|
|
31
|
+
]}"""
|
|
32
|
+
)
|
|
33
|
+
val style = resolved.toNitro().sections?.first()?.style
|
|
34
|
+
assertEquals(StyleDisplay.GRID, style?.display)
|
|
35
|
+
assertEquals(false, style?.gridWrap)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Test
|
|
39
|
+
fun `a stale string style decodes as no declaration - never a parse failure`() {
|
|
40
|
+
// The retired string vocabulary must never kill the page (tolerant
|
|
41
|
+
// decoding, ADR 0011).
|
|
42
|
+
val resolved =
|
|
43
|
+
json.decodeFromString<JsonResolvedTrack>(
|
|
44
|
+
"""{"path":"/home","title":"Home","style":"rail","children":[
|
|
45
|
+
{"title":"C","src":"s","style":"grid"}
|
|
46
|
+
]}"""
|
|
47
|
+
)
|
|
48
|
+
val nitro = resolved.toNitro()
|
|
49
|
+
assertNull(nitro.style)
|
|
50
|
+
assertNull(nitro.children?.first()?.style)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
fun `unknown enum values decode as no declaration`() {
|
|
55
|
+
// All-unknown declarations collapse to "no block" — one shape for "no
|
|
56
|
+
// declaration" on every platform.
|
|
57
|
+
val track =
|
|
58
|
+
json.decodeFromString<JsonTrack>(
|
|
59
|
+
"""{"title":"X","style":{"display":"carousel","artworkRendering":"embossed","gridWrap":"yes"}}"""
|
|
60
|
+
)
|
|
61
|
+
assertNull(track.toNitro().style)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test
|
|
65
|
+
fun `an empty block decodes as no declaration`() {
|
|
66
|
+
val track = json.decodeFromString<JsonTrack>("""{"title":"X","style":{}}""")
|
|
67
|
+
assertNull(track.toNitro().style)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Test
|
|
71
|
+
fun `a quoted boolean gridWrap is a wrong-typed field - no declaration`() {
|
|
72
|
+
// iOS's strict-typed tolerant decode drops a quoted "false"; the lenient
|
|
73
|
+
// Kotlin mapper must not read it either, or the same payload renders a
|
|
74
|
+
// teaser shelf on one platform and a wrapping grid on the other.
|
|
75
|
+
val resolved =
|
|
76
|
+
json.decodeFromString<JsonResolvedTrack>(
|
|
77
|
+
"""{"path":"/home","title":"Home","sections":[
|
|
78
|
+
{"style":{"display":"grid","gridWrap":"false"},"children":[{"title":"C","src":"s"}]}
|
|
79
|
+
]}"""
|
|
80
|
+
)
|
|
81
|
+
val style = resolved.toNitro().sections?.first()?.style
|
|
82
|
+
assertEquals(StyleDisplay.GRID, style?.display)
|
|
83
|
+
assertNull(style?.gridWrap)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Test
|
|
87
|
+
fun `decodes disabled`() {
|
|
88
|
+
val track = json.decodeFromString<JsonTrack>("""{"title":"X","src":"s","disabled":true}""")
|
|
89
|
+
assertEquals(true, track.toNitro().disabled)
|
|
90
|
+
}
|
|
91
|
+
}
|