react-native-audio-browser 0.2.0-next.11 → 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/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/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 +26 -39
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +228 -50
- 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/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 +46 -21
- 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/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 +1 -1
- 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 +40 -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,7 +1,9 @@
|
|
|
1
1
|
package com.audiobrowser.browser
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import androidx.media3.common.C
|
|
4
|
+
import androidx.media3.common.MediaItem
|
|
4
5
|
import com.audiobrowser.TestFixtures.resolvedTrack
|
|
6
|
+
import com.audiobrowser.TestFixtures.section
|
|
5
7
|
import com.audiobrowser.TestFixtures.staticRoute
|
|
6
8
|
import com.audiobrowser.TestFixtures.track
|
|
7
9
|
import com.audiobrowser.util.BrowserPathHelper
|
|
@@ -17,14 +19,14 @@ import org.robolectric.RobolectricTestRunner
|
|
|
17
19
|
/**
|
|
18
20
|
* Navigation and queue-expansion glue.
|
|
19
21
|
*
|
|
20
|
-
* [BrowserManager.
|
|
22
|
+
* [BrowserManager.expandQueueFromContextualPath] is the seam between the page a listener tapped in
|
|
21
23
|
* and the queue that starts playing. Its two halves are covered on their own — the section logic in
|
|
22
24
|
* [SectionScopeTest], the caller's single-track fallback in the player tests — but the wiring
|
|
23
25
|
* between them is what decides whether tapping row 3 of a page resumes the right station, and only
|
|
24
26
|
* these tests exercise it.
|
|
25
27
|
*
|
|
26
28
|
* Routes serve `browseStatic`, the one browse arm that needs neither the JNI bridge nor HTTP.
|
|
27
|
-
* Robolectric is required for `android.net.Uri` (contextual-
|
|
29
|
+
* Robolectric is required for `android.net.Uri` (contextual-path parsing) and
|
|
28
30
|
* `android.util.LruCache` (the content cache).
|
|
29
31
|
*/
|
|
30
32
|
@RunWith(RobolectricTestRunner::class)
|
|
@@ -46,11 +48,20 @@ class BrowserManagerTest {
|
|
|
46
48
|
browserManager.config =
|
|
47
49
|
BrowserConfig(
|
|
48
50
|
routes =
|
|
49
|
-
arrayOf(staticRoute(path, resolvedTrack(
|
|
51
|
+
arrayOf(staticRoute(path, resolvedTrack(path = path, children = arrayOf(*children)))),
|
|
50
52
|
singleTrack = singleTrack,
|
|
51
53
|
)
|
|
52
54
|
}
|
|
53
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
|
+
|
|
54
65
|
private fun contextual(path: String, trackId: String) = BrowserPathHelper.build(path, trackId)
|
|
55
66
|
|
|
56
67
|
@Test
|
|
@@ -100,28 +111,30 @@ class BrowserManagerTest {
|
|
|
100
111
|
|
|
101
112
|
val resolved = browserManager.navigate("/library")
|
|
102
113
|
|
|
103
|
-
assertEquals(listOf("A", "B"), resolved.
|
|
104
|
-
// The tap target: a child's
|
|
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).
|
|
105
117
|
assertEquals(
|
|
106
|
-
listOf(
|
|
107
|
-
|
|
118
|
+
listOf(
|
|
119
|
+
BrowserPathHelper.build("/library", "a", 0),
|
|
120
|
+
BrowserPathHelper.build("/library", "b", 1),
|
|
121
|
+
),
|
|
122
|
+
resolved.flattenedChildren?.map { it.path },
|
|
108
123
|
)
|
|
109
124
|
assertEquals("/library", browserManager.getPath())
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
// MARK: -
|
|
127
|
+
// MARK: - expandQueueFromContextualPath
|
|
113
128
|
|
|
114
129
|
@Test
|
|
115
|
-
fun `
|
|
116
|
-
|
|
130
|
+
fun `expandQueueFromContextualPath scopes the queue to the tapped section`() = runBlocking {
|
|
131
|
+
serveSections(
|
|
117
132
|
"/home",
|
|
118
|
-
track(src = "a",
|
|
119
|
-
track(src = "
|
|
120
|
-
track(src = "c", groupTitle = "Popular"),
|
|
121
|
-
track(src = "d", groupTitle = "Popular"),
|
|
133
|
+
section(title = "Recent", children = arrayOf(track(src = "a"), track(src = "b"))),
|
|
134
|
+
section(title = "Popular", children = arrayOf(track(src = "c"), track(src = "d"))),
|
|
122
135
|
)
|
|
123
136
|
|
|
124
|
-
val expanded = browserManager.
|
|
137
|
+
val expanded = browserManager.expandQueueFromContextualPath(contextual("/home", "c"))
|
|
125
138
|
|
|
126
139
|
// "Popular" only — a page aggregating sections must not leak next/previous across them.
|
|
127
140
|
val (queue, index) = requireNotNull(expanded)
|
|
@@ -130,97 +143,262 @@ class BrowserManagerTest {
|
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
@Test
|
|
133
|
-
fun `
|
|
134
|
-
|
|
146
|
+
fun `expandQueueFromContextualPath indexes the tapped track within its section`() = runBlocking {
|
|
147
|
+
serveSections(
|
|
135
148
|
"/home",
|
|
136
|
-
|
|
137
|
-
track(src = "b",
|
|
138
|
-
track(src = "c", groupTitle = "Popular"),
|
|
149
|
+
section(title = "Recent", children = arrayOf(track(src = "a"))),
|
|
150
|
+
section(title = "Popular", children = arrayOf(track(src = "b"), track(src = "c"))),
|
|
139
151
|
)
|
|
140
152
|
|
|
141
153
|
val (queue, index) =
|
|
142
|
-
requireNotNull(browserManager.
|
|
154
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "c")))
|
|
143
155
|
|
|
144
156
|
assertEquals(listOf("b", "c"), queue.map { it.src })
|
|
145
157
|
assertEquals(1, index)
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
@Test
|
|
149
|
-
fun `
|
|
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`() =
|
|
150
178
|
runBlocking {
|
|
151
|
-
// The
|
|
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
|
|
152
226
|
// resumed session. Queueing the current list would resume the wrong station, so expansion
|
|
153
227
|
// aborts and the caller falls back to the stored single track.
|
|
154
228
|
servePage("/home", track(src = "a"), track(src = "b"))
|
|
155
229
|
|
|
156
|
-
assertNull(browserManager.
|
|
230
|
+
assertNull(browserManager.expandQueueFromContextualPath(contextual("/home", "gone")))
|
|
157
231
|
}
|
|
158
232
|
|
|
159
233
|
@Test
|
|
160
|
-
fun `
|
|
234
|
+
fun `expandQueueFromContextualPath returns null for a non-contextual path`() = runBlocking {
|
|
161
235
|
servePage("/home", track(src = "a"))
|
|
162
236
|
|
|
163
|
-
assertNull(browserManager.
|
|
237
|
+
assertNull(browserManager.expandQueueFromContextualPath("/home"))
|
|
164
238
|
}
|
|
165
239
|
|
|
166
240
|
@Test
|
|
167
|
-
fun `
|
|
241
|
+
fun `expandQueueFromContextualPath returns null when the page has no children`() = runBlocking {
|
|
168
242
|
browserManager.config =
|
|
169
|
-
BrowserConfig(routes = arrayOf(staticRoute("/home", resolvedTrack(
|
|
243
|
+
BrowserConfig(routes = arrayOf(staticRoute("/home", resolvedTrack(path = "/home"))))
|
|
170
244
|
|
|
171
|
-
assertNull(browserManager.
|
|
245
|
+
assertNull(browserManager.expandQueueFromContextualPath(contextual("/home", "a")))
|
|
172
246
|
}
|
|
173
247
|
|
|
174
248
|
@Test
|
|
175
|
-
fun `
|
|
176
|
-
|
|
249
|
+
fun `expandQueueFromContextualPath scopes a rail section to its own tiles`() = runBlocking {
|
|
250
|
+
serveSections(
|
|
177
251
|
"/home",
|
|
178
|
-
|
|
252
|
+
section(
|
|
179
253
|
title = "Most Played",
|
|
180
|
-
|
|
181
|
-
|
|
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")),
|
|
182
261
|
),
|
|
183
|
-
track(src = "a"),
|
|
262
|
+
section(children = arrayOf(track(src = "a"))),
|
|
184
263
|
)
|
|
185
264
|
|
|
186
265
|
val (queue, index) =
|
|
187
|
-
requireNotNull(browserManager.
|
|
266
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "r2")))
|
|
188
267
|
|
|
189
|
-
// A tile tap queues its
|
|
268
|
+
// A tile tap queues its section, not the list rows beside it.
|
|
190
269
|
assertEquals(listOf("r1", "r2"), queue.map { it.src })
|
|
191
270
|
assertEquals(1, index)
|
|
192
271
|
}
|
|
193
272
|
|
|
194
273
|
@Test
|
|
195
|
-
fun `
|
|
196
|
-
|
|
274
|
+
fun `expandQueueFromContextualPath drops unplayable siblings`() = runBlocking {
|
|
275
|
+
serveSections(
|
|
197
276
|
"/home",
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
),
|
|
202
287
|
)
|
|
203
288
|
|
|
204
289
|
val (queue, index) =
|
|
205
|
-
requireNotNull(browserManager.
|
|
290
|
+
requireNotNull(browserManager.expandQueueFromContextualPath(contextual("/home", "b")))
|
|
206
291
|
|
|
207
292
|
assertEquals(listOf("a", "b"), queue.map { it.src })
|
|
208
293
|
assertEquals(1, index)
|
|
209
294
|
}
|
|
210
295
|
|
|
211
296
|
@Test
|
|
212
|
-
fun `
|
|
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 {
|
|
213
319
|
servePage(
|
|
214
320
|
"/home",
|
|
215
|
-
track(
|
|
216
|
-
track(
|
|
217
|
-
|
|
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"),
|
|
218
324
|
)
|
|
219
325
|
|
|
220
326
|
val (queue, index) =
|
|
221
|
-
requireNotNull(browserManager.
|
|
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")))
|
|
222
351
|
|
|
223
352
|
assertEquals(listOf("b"), queue.map { it.src })
|
|
224
353
|
assertEquals(0, index)
|
|
225
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
|
+
}
|
|
226
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
|
+
}
|