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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
|
|
3
|
+
@testable import AudioBrowserTestable
|
|
4
|
+
|
|
5
|
+
// The (resolved style, platform capability) → rendered-form mapping (ADR
|
|
6
|
+
// 0011): degradation drops decorations before layout, and the one layout drop
|
|
7
|
+
// is a wrapping grid on a platform without a wrapping tile container.
|
|
8
|
+
|
|
9
|
+
@Suite("SectionPresentation")
|
|
10
|
+
struct SectionPresentationTests {
|
|
11
|
+
private func style(display: StyleDisplay?, gridWrap: Bool? = nil) -> SectionStyle {
|
|
12
|
+
SectionStyle(gridWrap: gridWrap, display: display, artworkRendering: nil)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Test func noStyleRendersAList() {
|
|
16
|
+
#expect(SectionPresentation(for: nil, supportsWrappingGrid: true) == .list)
|
|
17
|
+
#expect(SectionPresentation(for: nil, supportsWrappingGrid: false) == .list)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test func listDisplayRendersAList() {
|
|
21
|
+
#expect(SectionPresentation(for: style(display: .list), supportsWrappingGrid: true) == .list)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Test func wrappingGridNeedsTheWrappingContainer() {
|
|
25
|
+
#expect(
|
|
26
|
+
SectionPresentation(for: style(display: .grid), supportsWrappingGrid: true) == .wrappingGrid)
|
|
27
|
+
// The one layout drop: a wrapping grid has nowhere to go but a list —
|
|
28
|
+
// every item stays reachable instead of truncating at an unknowable width.
|
|
29
|
+
#expect(SectionPresentation(for: style(display: .grid), supportsWrappingGrid: false) == .list)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test func singleLineGridRendersOnEveryOS() {
|
|
33
|
+
// gridWrap: false is the legacy image row's own shape, so it never
|
|
34
|
+
// degrades — declaring it INCREASES pre-26 fidelity.
|
|
35
|
+
#expect(
|
|
36
|
+
SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: true)
|
|
37
|
+
== .singleLineRow)
|
|
38
|
+
#expect(
|
|
39
|
+
SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: false)
|
|
40
|
+
== .singleLineRow)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test func explicitWrapTrueBehavesLikeTheDefault() {
|
|
44
|
+
#expect(
|
|
45
|
+
SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: true)
|
|
46
|
+
== .wrappingGrid)
|
|
47
|
+
#expect(
|
|
48
|
+
SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: false)
|
|
49
|
+
== .list)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test func gridWrapIsInertOnLists() {
|
|
53
|
+
// Declaring a decoration never changes the layout you'd get without it.
|
|
54
|
+
#expect(
|
|
55
|
+
SectionPresentation(for: style(display: .list, gridWrap: false), supportsWrappingGrid: true)
|
|
56
|
+
== .list)
|
|
57
|
+
#expect(
|
|
58
|
+
SectionPresentation(for: style(display: nil, gridWrap: false), supportsWrappingGrid: true)
|
|
59
|
+
== .list)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -2,81 +2,113 @@
|
|
|
2
2
|
import Testing
|
|
3
3
|
|
|
4
4
|
struct SectionScopeTests {
|
|
5
|
-
private func track(_ src: String,
|
|
6
|
-
Track(id: src, src: src
|
|
5
|
+
private func track(_ src: String, id: String? = nil) -> Track {
|
|
6
|
+
Track(id: id ?? src, src: src)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
private func
|
|
10
|
-
|
|
11
|
-
return tracks.map(\.src)
|
|
9
|
+
private func section(_ title: String? = nil, _ tracks: [Track]) -> Section {
|
|
10
|
+
Section(title: title, children: tracks)
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
private func
|
|
15
|
-
|
|
16
|
-
return items.map(\.src)
|
|
13
|
+
private func srcs(_ scoped: SectionScope.Scoped?) -> [String?]? {
|
|
14
|
+
scoped.map { $0.section.children.map(\.src) }
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
track("
|
|
25
|
-
track("
|
|
17
|
+
// Two sections sharing an identity — the duplicate-heavy shape the flat
|
|
18
|
+
// model had to disambiguate by precedence; sections + the stamped flat
|
|
19
|
+
// index make it exact.
|
|
20
|
+
private var sections: [Section] {
|
|
21
|
+
[
|
|
22
|
+
section("First", [track("dup"), track("x")]),
|
|
23
|
+
section("Second", [track("y"), track("dup"), track("z")]),
|
|
26
24
|
]
|
|
27
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "c")) == ["c", "d"])
|
|
28
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "a")) == ["a", "b"])
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
@Test func
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
track("c"),
|
|
36
|
-
track("d", group: "Second"),
|
|
37
|
-
]
|
|
38
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "b")) == ["b", "c"])
|
|
27
|
+
@Test func fallsBackToTheFirstSectionContainingTheIdentity() {
|
|
28
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup")
|
|
29
|
+
#expect(srcs(scoped) == ["dup", "x"])
|
|
30
|
+
#expect(scoped?.tappedOffset == nil)
|
|
39
31
|
}
|
|
40
32
|
|
|
41
|
-
@Test func
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
]
|
|
47
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "a")) == ["a"])
|
|
33
|
+
@Test func flatIndexPinsTheTappedSection() {
|
|
34
|
+
// Flat index 3 = second section, offset 1.
|
|
35
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 3)
|
|
36
|
+
#expect(srcs(scoped) == ["y", "dup", "z"])
|
|
37
|
+
#expect(scoped?.tappedOffset == 1)
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
@Test func
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
@Test func flatIndexPinsTheExactCopyWithinASection() {
|
|
41
|
+
let playlist = [section(nil, [track("a"), track("b"), track("a"), track("c")])]
|
|
42
|
+
let scoped = SectionScope.scoped(in: playlist, containing: "a", tappedIndex: 2)
|
|
43
|
+
#expect(scoped?.tappedOffset == 2)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test func staleIndexFallsBackToTheFirstIdentityMatch() {
|
|
47
|
+
// The child at the stamped index no longer carries the tapped identity
|
|
48
|
+
// (the list shifted) — resolution ignores the index and pins nothing.
|
|
49
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 1)
|
|
50
|
+
#expect(srcs(scoped) == ["dup", "x"])
|
|
51
|
+
#expect(scoped?.tappedOffset == nil)
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
@Test func
|
|
62
|
-
|
|
54
|
+
@Test func outOfRangeIndexIsIgnored() {
|
|
55
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 99)
|
|
56
|
+
#expect(srcs(scoped) == ["dup", "x"])
|
|
57
|
+
#expect(scoped?.tappedOffset == nil)
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@Test func imageRowWinsOverAFlatListDuplicate() {
|
|
68
|
-
var row = Track(id: "row", title: "Row")
|
|
69
|
-
row.imageRow = [ImageRowItem(src: "dup", title: "Dup")]
|
|
70
|
-
let children = [row, track("dup"), track("b")]
|
|
71
|
-
#expect(imageRowSrcs(SectionScope.section(of: children, containing: "dup")) == ["dup"])
|
|
60
|
+
@Test func vanishedIdentityReturnsNil() {
|
|
61
|
+
#expect(SectionScope.scoped(in: sections, containing: "gone") == nil)
|
|
72
62
|
}
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
// MARK: - Identity (id when non-blank, else src)
|
|
65
|
+
|
|
66
|
+
@Test func matchesChildByIdWhenSrcDiffers() {
|
|
75
67
|
let children = [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
track("dup", group: "Second"),
|
|
68
|
+
Track(id: "stable-a", src: "https://cdn.example/a?token=1"),
|
|
69
|
+
Track(id: "stable-b", src: "https://cdn.example/b?token=2"),
|
|
79
70
|
]
|
|
80
|
-
|
|
71
|
+
let scoped = SectionScope.scoped(in: [section(nil, children)], containing: "stable-b")
|
|
72
|
+
#expect(srcs(scoped) == ["https://cdn.example/a?token=1", "https://cdn.example/b?token=2"])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Test func blankIdFallsBackToSrc() {
|
|
76
|
+
let children = [Track(id: "", src: "s1"), Track(id: "", src: "s2")]
|
|
77
|
+
let scoped = SectionScope.scoped(in: [section(nil, children)], containing: "s2")
|
|
78
|
+
#expect(srcs(scoped) == ["s1", "s2"])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MARK: - Page shape helpers
|
|
82
|
+
|
|
83
|
+
@Test func childrenSugarNormalizesToOneUntitledSection() {
|
|
84
|
+
let page = ResolvedTrack(path: "/p", children: [track("a"), track("b")], title: "P")
|
|
85
|
+
let sections = page.normalizedSections
|
|
86
|
+
#expect(sections?.count == 1)
|
|
87
|
+
#expect(sections?.first?.title == nil)
|
|
88
|
+
#expect(sections?.first?.children.map(\.src) == ["a", "b"])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test func sectionsWinOverChildren() {
|
|
92
|
+
let page = ResolvedTrack(
|
|
93
|
+
path: "/p",
|
|
94
|
+
sections: [section("S", [track("a")])],
|
|
95
|
+
children: [track("zzz")],
|
|
96
|
+
title: "P",
|
|
97
|
+
)
|
|
98
|
+
#expect(page.normalizedSections?.count == 1)
|
|
99
|
+
#expect(page.normalizedSections?.first?.title == "S")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Test func flattenedChildrenConcatenateInSectionOrder() {
|
|
103
|
+
let page = ResolvedTrack(
|
|
104
|
+
path: "/p",
|
|
105
|
+
sections: [section("A", [track("a1"), track("a2")]), section(nil, [track("b1")])],
|
|
106
|
+
title: "P",
|
|
107
|
+
)
|
|
108
|
+
#expect(page.flattenedChildren?.map(\.src) == ["a1", "a2", "b1"])
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Test func childlessPageHasNoSections() {
|
|
112
|
+
#expect(ResolvedTrack(path: "/p", title: "P").normalizedSections == nil)
|
|
81
113
|
}
|
|
82
114
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
|
|
3
|
+
@testable import AudioBrowserTestable
|
|
4
|
+
|
|
5
|
+
// Resolution semantics (ADR 0011): container properties and `display` resolve
|
|
6
|
+
// by scope override (`section ?? page`), inherited item properties resolve
|
|
7
|
+
// `track ?? section ?? page` — and `display` never inherits onto an item.
|
|
8
|
+
|
|
9
|
+
@Suite("StyleResolver.sectionStyle")
|
|
10
|
+
struct SectionStyleResolutionTests {
|
|
11
|
+
@Test func sectionOverridesThePagePerProperty() {
|
|
12
|
+
let resolved = StyleResolver.sectionStyle(
|
|
13
|
+
section: SectionStyle(gridWrap: nil, display: .list, artworkRendering: nil),
|
|
14
|
+
page: SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil),
|
|
15
|
+
)
|
|
16
|
+
// Declared on the section: wins. Undeclared: the page's scope-wide value.
|
|
17
|
+
#expect(resolved.display == .list)
|
|
18
|
+
#expect(resolved.artworkRendering == .stencil)
|
|
19
|
+
#expect(resolved.gridWrap == false)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test func aStylelessSectionTakesThePageBlock() {
|
|
23
|
+
let resolved = StyleResolver.sectionStyle(
|
|
24
|
+
section: nil,
|
|
25
|
+
page: SectionStyle(gridWrap: true, display: .grid, artworkRendering: nil),
|
|
26
|
+
)
|
|
27
|
+
#expect(resolved.display == .grid)
|
|
28
|
+
#expect(resolved.gridWrap == true)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test func noDeclarationsResolveToAnEmptyBlock() {
|
|
32
|
+
let resolved = StyleResolver.sectionStyle(section: nil, page: nil)
|
|
33
|
+
#expect(resolved.display == nil)
|
|
34
|
+
#expect(resolved.artworkRendering == nil)
|
|
35
|
+
#expect(resolved.gridWrap == nil)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Suite("StyleResolver.trackStyle")
|
|
40
|
+
struct TrackStyleResolutionTests {
|
|
41
|
+
@Test func trackOverridesTheSectionPerProperty() {
|
|
42
|
+
let resolved = StyleResolver.trackStyle(
|
|
43
|
+
track: TrackStyle(display: nil, artworkRendering: .original),
|
|
44
|
+
section: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: .stencil),
|
|
45
|
+
)
|
|
46
|
+
#expect(resolved.artworkRendering == .original)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Test func aStylelessTrackInheritsTheSectionValue() {
|
|
50
|
+
let resolved = StyleResolver.trackStyle(
|
|
51
|
+
track: nil,
|
|
52
|
+
section: SectionStyle(gridWrap: nil, display: nil, artworkRendering: .stencil),
|
|
53
|
+
)
|
|
54
|
+
#expect(resolved.artworkRendering == .stencil)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test func displayNeverInheritsOntoAnItem() {
|
|
58
|
+
// The track's own `display` is the page promise for the page IT opens;
|
|
59
|
+
// the section's describes its children's layout. Neither may surface as
|
|
60
|
+
// the item's resolved display.
|
|
61
|
+
let resolved = StyleResolver.trackStyle(
|
|
62
|
+
track: TrackStyle(display: .grid, artworkRendering: nil),
|
|
63
|
+
section: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: nil),
|
|
64
|
+
)
|
|
65
|
+
#expect(resolved.display == nil)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -4,59 +4,59 @@ import Testing
|
|
|
4
4
|
|
|
5
5
|
struct TabBarEntriesTests {
|
|
6
6
|
private func tab(
|
|
7
|
-
_ title: String,
|
|
7
|
+
_ title: String, path: String, artwork: String? = nil,
|
|
8
8
|
artworkSource: ImageSource? = nil, artist: String? = nil,
|
|
9
9
|
) -> Track {
|
|
10
10
|
Track(
|
|
11
|
-
id:
|
|
12
|
-
artworkSource: artworkSource,
|
|
11
|
+
id: path, path: path, artwork: artwork.map { .first($0) },
|
|
12
|
+
artworkSource: artworkSource, title: title, artist: artist,
|
|
13
13
|
)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
@Test func identicalListsAreSame() {
|
|
17
|
-
let tabs = [tab("Explore",
|
|
17
|
+
let tabs = [tab("Explore", path: "/explore"), tab("Browse", path: "/browse")]
|
|
18
18
|
#expect(TabBarEntries.same(tabs, tabs))
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
@Test func titleChangeDiffers() {
|
|
22
22
|
#expect(
|
|
23
23
|
!TabBarEntries.same(
|
|
24
|
-
[tab("Explore",
|
|
25
|
-
[tab("Verkennen",
|
|
24
|
+
[tab("Explore", path: "/explore")],
|
|
25
|
+
[tab("Verkennen", path: "/explore")],
|
|
26
26
|
))
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
@Test func
|
|
29
|
+
@Test func pathChangeDiffers() {
|
|
30
30
|
#expect(
|
|
31
31
|
!TabBarEntries.same(
|
|
32
|
-
[tab("Explore",
|
|
33
|
-
[tab("Explore",
|
|
32
|
+
[tab("Explore", path: "/explore")],
|
|
33
|
+
[tab("Explore", path: "/discover")],
|
|
34
34
|
))
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
@Test func artworkChangeDiffers() {
|
|
38
38
|
#expect(
|
|
39
39
|
!TabBarEntries.same(
|
|
40
|
-
[tab("Explore",
|
|
41
|
-
[tab("Explore",
|
|
40
|
+
[tab("Explore", path: "/explore", artwork: "sf:globe")],
|
|
41
|
+
[tab("Explore", path: "/explore", artwork: "sf:map")],
|
|
42
42
|
))
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
@Test func artworkSourceUriChangeDiffers() {
|
|
46
46
|
#expect(
|
|
47
47
|
!TabBarEntries.same(
|
|
48
|
-
[tab("Explore",
|
|
49
|
-
[tab("Explore",
|
|
48
|
+
[tab("Explore", path: "/explore", artworkSource: ImageSource(uri: "https://a/1.png"))],
|
|
49
|
+
[tab("Explore", path: "/explore", artworkSource: ImageSource(uri: "https://a/2.png"))],
|
|
50
50
|
))
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
@Test func countChangeDiffers() {
|
|
54
|
-
let tabs = [tab("Explore",
|
|
55
|
-
#expect(!TabBarEntries.same(tabs, tabs + [tab("Browse",
|
|
54
|
+
let tabs = [tab("Explore", path: "/explore")]
|
|
55
|
+
#expect(!TabBarEntries.same(tabs, tabs + [tab("Browse", path: "/browse")]))
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
@Test func nilOldDiffers() {
|
|
59
|
-
#expect(!TabBarEntries.same(nil, [tab("Explore",
|
|
59
|
+
#expect(!TabBarEntries.same(nil, [tab("Explore", path: "/explore")]))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
@Test func nonRenderedFieldChangeIsSame() {
|
|
@@ -64,8 +64,8 @@ struct TabBarEntriesTests {
|
|
|
64
64
|
// drift without churning the tab bar.
|
|
65
65
|
#expect(
|
|
66
66
|
TabBarEntries.same(
|
|
67
|
-
[tab("Explore",
|
|
68
|
-
[tab("Explore",
|
|
67
|
+
[tab("Explore", path: "/explore", artist: "a")],
|
|
68
|
+
[tab("Explore", path: "/explore", artist: "b")],
|
|
69
69
|
))
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
@testable import AudioBrowserTestable
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
/// `{id}` template substitution used by artwork URL resolution
|
|
5
|
+
/// (BrowserManager+URLResolution.substituteTrackId). A track without a non-blank id must leave
|
|
6
|
+
/// the token LITERALLY in place — never substitute an empty string, never skip — so the
|
|
7
|
+
/// resulting request 404s with a self-describing URL (the resolver logs a warning keyed off
|
|
8
|
+
/// `containsToken`). Mirrors Android's `substituteTrackId` / `configContainsIdToken`.
|
|
9
|
+
struct TrackIdTemplateTests {
|
|
10
|
+
// MARK: substitution with an id
|
|
11
|
+
|
|
12
|
+
@Test func substitutesTokenInString() {
|
|
13
|
+
#expect(TrackIdTemplate.substitute("/artwork/{id}", id: "abc") == "/artwork/abc")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test func substitutesEveryOccurrence() {
|
|
17
|
+
#expect(TrackIdTemplate.substitute("/{id}/art/{id}.png", id: "x") == "/x/art/x.png")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test func substitutesTokenInDictionaryValues() {
|
|
21
|
+
let subbed = TrackIdTemplate.substitute(["x-track": "{id}", "plain": "v"], id: "abc")
|
|
22
|
+
#expect(subbed == ["x-track": "abc", "plain": "v"])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test func valueWithoutTokenIsUnchanged() {
|
|
26
|
+
#expect(TrackIdTemplate.substitute("/artwork/static.png", id: "abc") == "/artwork/static.png")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: token left literal without an id
|
|
30
|
+
|
|
31
|
+
@Test func nilIdLeavesTokenLiteral() {
|
|
32
|
+
#expect(TrackIdTemplate.substitute("/artwork/{id}", id: nil) == "/artwork/{id}")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test func blankIdLeavesTokenLiteral() {
|
|
36
|
+
#expect(TrackIdTemplate.substitute("/artwork/{id}", id: "") == "/artwork/{id}")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Test func nilIdLeavesDictionaryValuesLiteral() {
|
|
40
|
+
#expect(TrackIdTemplate.substitute(["x-track": "{id}"], id: nil) == ["x-track": "{id}"])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test func nilValuePassesThrough() {
|
|
44
|
+
#expect(TrackIdTemplate.substitute(String?.none, id: "abc") == nil)
|
|
45
|
+
#expect(TrackIdTemplate.substitute([String: String]?.none, id: nil) == nil)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: unfilled-token detection (drives the single warning)
|
|
49
|
+
|
|
50
|
+
@Test func detectsUnfilledTokenInString() {
|
|
51
|
+
#expect(TrackIdTemplate.containsToken("/artwork/{id}"))
|
|
52
|
+
#expect(!TrackIdTemplate.containsToken("/artwork/abc"))
|
|
53
|
+
#expect(!TrackIdTemplate.containsToken(String?.none))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test func detectsUnfilledTokenInDictionaryValues() {
|
|
57
|
+
#expect(TrackIdTemplate.containsToken(["x-track": "{id}"]))
|
|
58
|
+
#expect(!TrackIdTemplate.containsToken(["x-track": "abc"]))
|
|
59
|
+
#expect(!TrackIdTemplate.containsToken([String: String]?.none))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test func substitutionThenDetectionNeverBothMatch() {
|
|
63
|
+
// With an id, substitution clears the token, so no warning fires.
|
|
64
|
+
let subbed = TrackIdTemplate.substitute("/artwork/{id}", id: "abc")
|
|
65
|
+
#expect(!TrackIdTemplate.containsToken(subbed))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@testable import AudioBrowserTestable
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
struct TrackIdentityTests {
|
|
5
|
+
@Test func idWinsOverSrc() {
|
|
6
|
+
#expect(Track(id: "stable", src: "https://cdn.example/stream").identity == "stable")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@Test func blankIdFallsBackToSrc() {
|
|
10
|
+
#expect(Track(id: "", src: "https://cdn.example/stream").identity == "https://cdn.example/stream")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Test func nilIdFallsBackToSrc() {
|
|
14
|
+
#expect(Track(src: "https://cdn.example/stream").identity == "https://cdn.example/stream")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Test func neitherIdNorSrcHasNoIdentity() {
|
|
18
|
+
#expect(Track(path: "/browse/only").identity == nil)
|
|
19
|
+
}
|
|
20
|
+
}
|