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
|
@@ -10,7 +10,7 @@ struct SFSymbolTests {
|
|
|
10
10
|
let action = await CarPlayArtworkResolver.resolve(
|
|
11
11
|
artwork: "sf:heart.fill",
|
|
12
12
|
artworkSourceUri: nil,
|
|
13
|
-
|
|
13
|
+
stencil: false,
|
|
14
14
|
targetWidth: 40, targetHeight: 40,
|
|
15
15
|
displayScale: 2.0,
|
|
16
16
|
urlResolver: nil,
|
|
@@ -22,7 +22,7 @@ struct SFSymbolTests {
|
|
|
22
22
|
let action = await CarPlayArtworkResolver.resolve(
|
|
23
23
|
artwork: "sf:play.circle?bg=#000&fg=#fff",
|
|
24
24
|
artworkSourceUri: nil,
|
|
25
|
-
|
|
25
|
+
stencil: false,
|
|
26
26
|
targetWidth: 24, targetHeight: 24,
|
|
27
27
|
displayScale: 3.0,
|
|
28
28
|
urlResolver: nil,
|
|
@@ -35,7 +35,7 @@ struct SFSymbolTests {
|
|
|
35
35
|
let action = await CarPlayArtworkResolver.resolve(
|
|
36
36
|
artwork: "sf:music.note",
|
|
37
37
|
artworkSourceUri: nil,
|
|
38
|
-
|
|
38
|
+
stencil: false,
|
|
39
39
|
targetWidth: 40, targetHeight: 40,
|
|
40
40
|
displayScale: 2.0,
|
|
41
41
|
urlResolver: { _, _ in
|
|
@@ -51,7 +51,7 @@ struct SFSymbolTests {
|
|
|
51
51
|
let action = await CarPlayArtworkResolver.resolve(
|
|
52
52
|
artwork: nil,
|
|
53
53
|
artworkSourceUri: "sf:star.fill",
|
|
54
|
-
|
|
54
|
+
stencil: false,
|
|
55
55
|
targetWidth: 20, targetHeight: 20,
|
|
56
56
|
displayScale: 2.0,
|
|
57
57
|
urlResolver: nil,
|
|
@@ -68,7 +68,7 @@ struct NoArtworkTests {
|
|
|
68
68
|
let action = await CarPlayArtworkResolver.resolve(
|
|
69
69
|
artwork: nil,
|
|
70
70
|
artworkSourceUri: nil,
|
|
71
|
-
|
|
71
|
+
stencil: false,
|
|
72
72
|
targetWidth: 40, targetHeight: 40,
|
|
73
73
|
displayScale: 2.0,
|
|
74
74
|
urlResolver: nil,
|
|
@@ -80,7 +80,7 @@ struct NoArtworkTests {
|
|
|
80
80
|
let action = await CarPlayArtworkResolver.resolve(
|
|
81
81
|
artwork: nil,
|
|
82
82
|
artworkSourceUri: nil,
|
|
83
|
-
|
|
83
|
+
stencil: false,
|
|
84
84
|
targetWidth: 40, targetHeight: 40,
|
|
85
85
|
displayScale: 2.0,
|
|
86
86
|
urlResolver: { _, _ in nil },
|
|
@@ -97,7 +97,7 @@ struct URLResolutionTests {
|
|
|
97
97
|
let action = await CarPlayArtworkResolver.resolve(
|
|
98
98
|
artwork: "https://example.com/art.jpg",
|
|
99
99
|
artworkSourceUri: nil,
|
|
100
|
-
|
|
100
|
+
stencil: false,
|
|
101
101
|
targetWidth: 40, targetHeight: 40,
|
|
102
102
|
displayScale: 2.0,
|
|
103
103
|
urlResolver: { pixelWidth, pixelHeight in
|
|
@@ -121,7 +121,7 @@ struct URLResolutionTests {
|
|
|
121
121
|
let action = await CarPlayArtworkResolver.resolve(
|
|
122
122
|
artwork: "https://example.com/art.jpg",
|
|
123
123
|
artworkSourceUri: nil,
|
|
124
|
-
|
|
124
|
+
stencil: false,
|
|
125
125
|
targetWidth: 24, targetHeight: 24,
|
|
126
126
|
displayScale: 3.0,
|
|
127
127
|
urlResolver: { _, _ in
|
|
@@ -140,7 +140,7 @@ struct URLResolutionTests {
|
|
|
140
140
|
let action = await CarPlayArtworkResolver.resolve(
|
|
141
141
|
artwork: "https://example.com/art.jpg",
|
|
142
142
|
artworkSourceUri: nil,
|
|
143
|
-
|
|
143
|
+
stencil: false,
|
|
144
144
|
targetWidth: 40, targetHeight: 40,
|
|
145
145
|
displayScale: 2.0,
|
|
146
146
|
urlResolver: { _, _ in nil },
|
|
@@ -157,7 +157,7 @@ struct URLResolutionTests {
|
|
|
157
157
|
let action = await CarPlayArtworkResolver.resolve(
|
|
158
158
|
artwork: "https://example.com/fallback.jpg",
|
|
159
159
|
artworkSourceUri: nil,
|
|
160
|
-
|
|
160
|
+
stencil: false,
|
|
161
161
|
targetWidth: 40, targetHeight: 40,
|
|
162
162
|
displayScale: 2.0,
|
|
163
163
|
urlResolver: { _, _ in
|
|
@@ -180,7 +180,7 @@ struct URLResolutionTests {
|
|
|
180
180
|
_ = await CarPlayArtworkResolver.resolve(
|
|
181
181
|
artwork: "https://example.com/art.jpg",
|
|
182
182
|
artworkSourceUri: nil,
|
|
183
|
-
|
|
183
|
+
stencil: false,
|
|
184
184
|
targetWidth: 50, targetHeight: 30,
|
|
185
185
|
displayScale: 2.5,
|
|
186
186
|
urlResolver: { w, h in
|
|
@@ -202,7 +202,7 @@ struct DirectFallbackTests {
|
|
|
202
202
|
let action = await CarPlayArtworkResolver.resolve(
|
|
203
203
|
artwork: "https://example.com/artwork.png",
|
|
204
204
|
artworkSourceUri: nil,
|
|
205
|
-
|
|
205
|
+
stencil: false,
|
|
206
206
|
targetWidth: 40, targetHeight: 40,
|
|
207
207
|
displayScale: 2.0,
|
|
208
208
|
urlResolver: nil,
|
|
@@ -219,7 +219,7 @@ struct DirectFallbackTests {
|
|
|
219
219
|
let action = await CarPlayArtworkResolver.resolve(
|
|
220
220
|
artwork: nil,
|
|
221
221
|
artworkSourceUri: "https://example.com/source.jpg",
|
|
222
|
-
|
|
222
|
+
stencil: false,
|
|
223
223
|
targetWidth: 40, targetHeight: 40,
|
|
224
224
|
displayScale: 2.0,
|
|
225
225
|
urlResolver: nil,
|
|
@@ -236,7 +236,7 @@ struct DirectFallbackTests {
|
|
|
236
236
|
let action = await CarPlayArtworkResolver.resolve(
|
|
237
237
|
artwork: "https://example.com/artwork.jpg",
|
|
238
238
|
artworkSourceUri: "https://example.com/source.jpg",
|
|
239
|
-
|
|
239
|
+
stencil: false,
|
|
240
240
|
targetWidth: 40, targetHeight: 40,
|
|
241
241
|
displayScale: 2.0,
|
|
242
242
|
urlResolver: nil,
|
|
@@ -253,7 +253,7 @@ struct DirectFallbackTests {
|
|
|
253
253
|
let action = await CarPlayArtworkResolver.resolve(
|
|
254
254
|
artwork: "",
|
|
255
255
|
artworkSourceUri: nil,
|
|
256
|
-
|
|
256
|
+
stencil: false,
|
|
257
257
|
targetWidth: 40, targetHeight: 40,
|
|
258
258
|
displayScale: 2.0,
|
|
259
259
|
urlResolver: nil,
|
|
@@ -270,7 +270,7 @@ struct SVGDetectionTests {
|
|
|
270
270
|
let action = await CarPlayArtworkResolver.resolve(
|
|
271
271
|
artwork: "https://example.com/icon.svg",
|
|
272
272
|
artworkSourceUri: nil,
|
|
273
|
-
|
|
273
|
+
stencil: false,
|
|
274
274
|
targetWidth: 40, targetHeight: 40,
|
|
275
275
|
displayScale: 2.0,
|
|
276
276
|
urlResolver: nil,
|
|
@@ -287,7 +287,7 @@ struct SVGDetectionTests {
|
|
|
287
287
|
let action = await CarPlayArtworkResolver.resolve(
|
|
288
288
|
artwork: "https://example.com/icon.SVG",
|
|
289
289
|
artworkSourceUri: nil,
|
|
290
|
-
|
|
290
|
+
stencil: false,
|
|
291
291
|
targetWidth: 40, targetHeight: 40,
|
|
292
292
|
displayScale: 2.0,
|
|
293
293
|
urlResolver: nil,
|
|
@@ -304,7 +304,7 @@ struct SVGDetectionTests {
|
|
|
304
304
|
let action = await CarPlayArtworkResolver.resolve(
|
|
305
305
|
artwork: "https://example.com/art.jpg",
|
|
306
306
|
artworkSourceUri: nil,
|
|
307
|
-
|
|
307
|
+
stencil: false,
|
|
308
308
|
targetWidth: 40, targetHeight: 40,
|
|
309
309
|
displayScale: 2.0,
|
|
310
310
|
urlResolver: { _, _ in
|
|
@@ -323,7 +323,7 @@ struct SVGDetectionTests {
|
|
|
323
323
|
let action = await CarPlayArtworkResolver.resolve(
|
|
324
324
|
artwork: "https://example.com/photo.jpg",
|
|
325
325
|
artworkSourceUri: nil,
|
|
326
|
-
|
|
326
|
+
stencil: false,
|
|
327
327
|
targetWidth: 40, targetHeight: 40,
|
|
328
328
|
displayScale: 2.0,
|
|
329
329
|
urlResolver: nil,
|
|
@@ -341,11 +341,11 @@ struct SVGDetectionTests {
|
|
|
341
341
|
|
|
342
342
|
@Suite("tinting")
|
|
343
343
|
struct TintingTests {
|
|
344
|
-
@Test func
|
|
344
|
+
@Test func stencilTrue_setsShouldTint() async {
|
|
345
345
|
let action = await CarPlayArtworkResolver.resolve(
|
|
346
346
|
artwork: "https://example.com/icon.png",
|
|
347
347
|
artworkSourceUri: nil,
|
|
348
|
-
|
|
348
|
+
stencil: true,
|
|
349
349
|
targetWidth: 40, targetHeight: 40,
|
|
350
350
|
displayScale: 2.0,
|
|
351
351
|
urlResolver: nil,
|
|
@@ -358,28 +358,11 @@ struct TintingTests {
|
|
|
358
358
|
))
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
@Test func
|
|
361
|
+
@Test func stencilFalse_doesNotTint() async {
|
|
362
362
|
let action = await CarPlayArtworkResolver.resolve(
|
|
363
363
|
artwork: "https://example.com/photo.jpg",
|
|
364
364
|
artworkSourceUri: nil,
|
|
365
|
-
|
|
366
|
-
targetWidth: 40, targetHeight: 40,
|
|
367
|
-
displayScale: 2.0,
|
|
368
|
-
urlResolver: nil,
|
|
369
|
-
)
|
|
370
|
-
#expect(action == .fetch(
|
|
371
|
-
uri: "https://example.com/photo.jpg",
|
|
372
|
-
headers: nil,
|
|
373
|
-
shouldTint: false,
|
|
374
|
-
isSvg: false,
|
|
375
|
-
))
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
@Test func artworkCarPlayTintedNil_defaultsToFalse() async {
|
|
379
|
-
let action = await CarPlayArtworkResolver.resolve(
|
|
380
|
-
artwork: "https://example.com/photo.jpg",
|
|
381
|
-
artworkSourceUri: nil,
|
|
382
|
-
artworkCarPlayTinted: nil,
|
|
365
|
+
stencil: false,
|
|
383
366
|
targetWidth: 40, targetHeight: 40,
|
|
384
367
|
displayScale: 2.0,
|
|
385
368
|
urlResolver: nil,
|
|
@@ -396,7 +379,7 @@ struct TintingTests {
|
|
|
396
379
|
let action = await CarPlayArtworkResolver.resolve(
|
|
397
380
|
artwork: "https://example.com/icon.png",
|
|
398
381
|
artworkSourceUri: nil,
|
|
399
|
-
|
|
382
|
+
stencil: true,
|
|
400
383
|
targetWidth: 40, targetHeight: 40,
|
|
401
384
|
displayScale: 2.0,
|
|
402
385
|
urlResolver: { _, _ in
|
|
@@ -40,7 +40,7 @@ struct JsonTrackArtworkTests {
|
|
|
40
40
|
// The shape that shipped broken: a container decodes its children eagerly,
|
|
41
41
|
// so one unparseable row rejected the whole page rather than losing an image.
|
|
42
42
|
let json = """
|
|
43
|
-
{ "
|
|
43
|
+
{ "path": "/favorites", "title": "Favorites", "children": [
|
|
44
44
|
{ "title": "Playlist", "artwork": { "light": "https://e.example/l.png", "dark": "https://e.example/d.png" } }
|
|
45
45
|
] }
|
|
46
46
|
""".data(using: .utf8)!
|
|
@@ -56,3 +56,77 @@ struct JsonTrackArtworkTests {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
// MARK: - Style blocks (ADR 0011)
|
|
61
|
+
|
|
62
|
+
@Suite("JsonTrack.style")
|
|
63
|
+
struct JsonStyleTests {
|
|
64
|
+
@Test func decodesABlockOntoTrackStyle() throws {
|
|
65
|
+
let json = Data(
|
|
66
|
+
"""
|
|
67
|
+
{"title":"X","style":{"display":"grid","artworkRendering":"stencil"}}
|
|
68
|
+
""".utf8)
|
|
69
|
+
let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
|
|
70
|
+
#expect(track.style?.display == .grid)
|
|
71
|
+
#expect(track.style?.artworkRendering == .stencil)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Test func decodesABlockOntoSectionStyle() throws {
|
|
75
|
+
let json = Data(
|
|
76
|
+
"""
|
|
77
|
+
{"path":"/home","title":"Home","sections":[
|
|
78
|
+
{"style":{"display":"grid","gridWrap":false},"children":[{"title":"C","src":"s"}]}
|
|
79
|
+
]}
|
|
80
|
+
""".utf8)
|
|
81
|
+
let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
|
|
82
|
+
let style = resolved.sections?.first?.style
|
|
83
|
+
#expect(style?.display == .grid)
|
|
84
|
+
#expect(style?.gridWrap == false)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Test func staleStringStyleDecodesAsNoDeclaration() throws {
|
|
88
|
+
// The retired string vocabulary must never kill the page (tolerant
|
|
89
|
+
// decoding, ADR 0011) — it decodes as an empty block.
|
|
90
|
+
let json = Data(
|
|
91
|
+
"""
|
|
92
|
+
{"path":"/home","title":"Home","style":"rail","children":[
|
|
93
|
+
{"title":"C","src":"s","style":"grid"}
|
|
94
|
+
]}
|
|
95
|
+
""".utf8)
|
|
96
|
+
let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
|
|
97
|
+
#expect(resolved.style == nil)
|
|
98
|
+
#expect(resolved.children?.first?.style == nil)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Test func unknownEnumValuesDecodeAsNoDeclaration() throws {
|
|
102
|
+
let json = Data(
|
|
103
|
+
"""
|
|
104
|
+
{"title":"X","style":{"display":"carousel","artworkRendering":"embossed","gridWrap":"yes"}}
|
|
105
|
+
""".utf8)
|
|
106
|
+
let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
|
|
107
|
+
// All-unknown declarations collapse to "no block" — one shape for "no
|
|
108
|
+
// declaration" on every platform.
|
|
109
|
+
#expect(track.style == nil)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Test func decodesDisabled() throws {
|
|
113
|
+
let json = Data("""
|
|
114
|
+
{"title":"X","src":"s","disabled":true}
|
|
115
|
+
""".utf8)
|
|
116
|
+
let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
|
|
117
|
+
#expect(track.disabled == true)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Test func styleAndDisabledSurviveThePersistenceRoundTrip() throws {
|
|
121
|
+
// Restored queues keep their visible fidelity across process death.
|
|
122
|
+
let original = JsonTrack(
|
|
123
|
+
title: "X", src: "s",
|
|
124
|
+
style: JsonStyle(display: "grid", artworkRendering: "stencil"),
|
|
125
|
+
disabled: true,
|
|
126
|
+
)
|
|
127
|
+
let encoded = try JSONEncoder().encode(original)
|
|
128
|
+
let decoded = try JSONDecoder().decode(JsonTrack.self, from: encoded)
|
|
129
|
+
#expect(decoded.style == original.style)
|
|
130
|
+
#expect(decoded.disabled == true)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -14,10 +14,11 @@ struct PlaybackStateStoreTests {
|
|
|
14
14
|
|
|
15
15
|
private func sampleTrack() -> JsonTrack {
|
|
16
16
|
JsonTrack(
|
|
17
|
-
id: "abc",
|
|
18
|
-
artist: "City, Country",
|
|
17
|
+
id: "abc", path: nil, title: "Test FM", subtitle: nil, artwork: nil,
|
|
18
|
+
artist: "City, Country", albumPath: nil, album: nil, description: nil,
|
|
19
19
|
genre: nil, duration: nil, src: "/listen/abc/channel.mp3", request: nil,
|
|
20
|
-
style:
|
|
20
|
+
style: JsonStyle(display: "grid", artworkRendering: "stencil"),
|
|
21
|
+
disabled: false, live: true,
|
|
21
22
|
)
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -813,13 +813,13 @@ struct ComputedPropertyTests {
|
|
|
813
813
|
let q = QueueManager()
|
|
814
814
|
q.setQueue(tracks("a", "b", "c", "d"))
|
|
815
815
|
q.shuffleEnabled = true
|
|
816
|
-
let nextFromProp = q.nextTracks.map
|
|
816
|
+
let nextFromProp = q.nextTracks.map { $0.id! }
|
|
817
817
|
// Navigate forward only as many times as nextTracks reports
|
|
818
818
|
// (nextTracks does not include wrapped tracks)
|
|
819
819
|
var nextFromNav: [String] = []
|
|
820
820
|
for _ in 0 ..< nextFromProp.count {
|
|
821
821
|
_ = q.next()
|
|
822
|
-
nextFromNav.append(q.currentTrack!.id)
|
|
822
|
+
nextFromNav.append(q.currentTrack!.id!)
|
|
823
823
|
}
|
|
824
824
|
#expect(nextFromProp == nextFromNav)
|
|
825
825
|
}
|
|
@@ -832,12 +832,12 @@ struct ComputedPropertyTests {
|
|
|
832
832
|
for _ in 0 ..< 3 {
|
|
833
833
|
_ = q.next()
|
|
834
834
|
}
|
|
835
|
-
let prevFromProp = q.previousTracks.map
|
|
835
|
+
let prevFromProp = q.previousTracks.map { $0.id! }
|
|
836
836
|
// Navigate backward the same number of times
|
|
837
837
|
var prevFromNav: [String] = []
|
|
838
838
|
for _ in 0 ..< prevFromProp.count {
|
|
839
839
|
_ = q.previous()
|
|
840
|
-
prevFromNav.insert(q.currentTrack!.id
|
|
840
|
+
prevFromNav.insert(q.currentTrack!.id!, at: 0)
|
|
841
841
|
}
|
|
842
842
|
#expect(prevFromProp == prevFromNav)
|
|
843
843
|
}
|
|
@@ -3,17 +3,18 @@ import Testing
|
|
|
3
3
|
@testable import AudioBrowserTestable
|
|
4
4
|
|
|
5
5
|
private func makeResolvedTrack(
|
|
6
|
-
|
|
6
|
+
path: String = "/test",
|
|
7
7
|
title: String = "Test Track",
|
|
8
8
|
) -> ResolvedTrack {
|
|
9
9
|
ResolvedTrack(
|
|
10
|
-
|
|
10
|
+
path: path,
|
|
11
|
+
style: nil,
|
|
12
|
+
sections: nil,
|
|
11
13
|
children: nil,
|
|
12
14
|
carPlaySiriListButton: nil,
|
|
13
15
|
src: nil,
|
|
14
16
|
artwork: nil,
|
|
15
17
|
artworkSource: nil,
|
|
16
|
-
artworkCarPlayTinted: nil,
|
|
17
18
|
title: title,
|
|
18
19
|
subtitle: nil,
|
|
19
20
|
artist: nil,
|
|
@@ -21,25 +22,23 @@ private func makeResolvedTrack(
|
|
|
21
22
|
description: nil,
|
|
22
23
|
genre: nil,
|
|
23
24
|
duration: nil,
|
|
24
|
-
|
|
25
|
-
childrenStyle: nil,
|
|
25
|
+
disabled: nil,
|
|
26
26
|
favorited: nil,
|
|
27
|
-
groupTitle: nil,
|
|
28
27
|
live: nil,
|
|
29
|
-
imageRow: nil,
|
|
30
28
|
)
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
34
32
|
ResolvedTrack(
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
path: "/original",
|
|
34
|
+
style: SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil),
|
|
35
|
+
sections: [Section(title: "Group A", subtitle: nil, style: nil, path: nil, children: [])],
|
|
36
|
+
children: [Track(id: "t1", path: "/t1")],
|
|
37
37
|
carPlaySiriListButton: .top,
|
|
38
38
|
id: "original-id",
|
|
39
39
|
src: "src.mp3",
|
|
40
40
|
artwork: .first("art.jpg"),
|
|
41
41
|
artworkSource: ImageSource(uri: "resolved-art.jpg"),
|
|
42
|
-
artworkCarPlayTinted: true,
|
|
43
42
|
title: "Original Title",
|
|
44
43
|
subtitle: "Original Subtitle",
|
|
45
44
|
artist: "Original Artist",
|
|
@@ -47,12 +46,9 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
47
46
|
description: "Original Description",
|
|
48
47
|
genre: "Rock",
|
|
49
48
|
duration: 180.0,
|
|
50
|
-
|
|
51
|
-
childrenStyle: .grid,
|
|
49
|
+
disabled: false,
|
|
52
50
|
favorited: true,
|
|
53
|
-
groupTitle: "Group A",
|
|
54
51
|
live: false,
|
|
55
|
-
imageRow: [ImageRowItem(title: "Row 1")],
|
|
56
52
|
)
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -64,12 +60,12 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
64
60
|
#expect(copy == original)
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
// MARK: - non-optional fields (
|
|
63
|
+
// MARK: - non-optional fields (path, title)
|
|
68
64
|
|
|
69
|
-
@Test func
|
|
70
|
-
let original = makeResolvedTrack(
|
|
71
|
-
let copy = original.copying(
|
|
72
|
-
#expect(copy.
|
|
65
|
+
@Test func copyingOverridesPath() {
|
|
66
|
+
let original = makeResolvedTrack(path: "/old")
|
|
67
|
+
let copy = original.copying(path: "/new")
|
|
68
|
+
#expect(copy.path == "/new")
|
|
73
69
|
#expect(copy.title == "Test Track")
|
|
74
70
|
}
|
|
75
71
|
|
|
@@ -77,14 +73,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
77
73
|
let original = makeResolvedTrack(title: "Old")
|
|
78
74
|
let copy = original.copying(title: "New")
|
|
79
75
|
#expect(copy.title == "New")
|
|
80
|
-
#expect(copy.
|
|
76
|
+
#expect(copy.path == "/test")
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
// MARK: - optional fields: set value
|
|
84
80
|
|
|
85
81
|
@Test func copyingOverridesChildren() {
|
|
86
82
|
let original = makeResolvedTrack()
|
|
87
|
-
let children = [Track(id: "c1",
|
|
83
|
+
let children = [Track(id: "c1", path: "/c1"), Track(id: "c2", path: "/c2")]
|
|
88
84
|
let copy = original.copying(children: children)
|
|
89
85
|
#expect(copy.children?.count == 2)
|
|
90
86
|
#expect(copy.children?[0].id == "c1")
|
|
@@ -135,8 +131,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
135
131
|
|
|
136
132
|
@Test func copyingOverridesStyle() {
|
|
137
133
|
let original = makeResolvedTrack()
|
|
138
|
-
let copy = original.copying(style: .grid)
|
|
139
|
-
#expect(copy.style == .grid)
|
|
134
|
+
let copy = original.copying(style: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: nil))
|
|
135
|
+
#expect(copy.style?.display == .grid)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Test func copyingOverridesDisabled() {
|
|
139
|
+
let original = makeResolvedTrack()
|
|
140
|
+
let copy = original.copying(disabled: true)
|
|
141
|
+
#expect(copy.disabled == true)
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
@Test func copyingOverridesFavorited() {
|
|
@@ -157,11 +159,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
157
159
|
#expect(copy.carPlaySiriListButton == .bottom)
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
@Test func
|
|
162
|
+
@Test func copyingOverridesSections() {
|
|
161
163
|
let original = makeResolvedTrack()
|
|
162
|
-
let
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
let sections = [
|
|
165
|
+
Section(title: "A", subtitle: nil, style: nil, path: nil, children: [Track(id: "c1", src: "c1")]),
|
|
166
|
+
]
|
|
167
|
+
let copy = original.copying(sections: sections)
|
|
168
|
+
#expect(copy.sections == sections)
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
// MARK: - optional fields: set to nil via .some(nil)
|
|
@@ -206,13 +210,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
206
210
|
@Test func copyingOverridesMultipleFields() {
|
|
207
211
|
let original = makeResolvedTrack()
|
|
208
212
|
let copy = original.copying(
|
|
209
|
-
|
|
213
|
+
path: "/updated",
|
|
210
214
|
title: "Updated Title",
|
|
211
215
|
artist: "New Artist",
|
|
212
216
|
duration: 240.0,
|
|
213
217
|
favorited: true,
|
|
214
218
|
)
|
|
215
|
-
#expect(copy.
|
|
219
|
+
#expect(copy.path == "/updated")
|
|
216
220
|
#expect(copy.title == "Updated Title")
|
|
217
221
|
#expect(copy.artist == "New Artist")
|
|
218
222
|
#expect(copy.duration == 240.0)
|
|
@@ -230,23 +234,21 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
230
234
|
let copy = original.copying(title: "Changed Only Title")
|
|
231
235
|
#expect(copy.title == "Changed Only Title")
|
|
232
236
|
// All other fields unchanged
|
|
233
|
-
#expect(copy.
|
|
237
|
+
#expect(copy.path == "/original")
|
|
234
238
|
#expect(copy.children?.count == 1)
|
|
235
239
|
#expect(copy.carPlaySiriListButton == .top)
|
|
236
240
|
#expect(copy.src == "src.mp3")
|
|
237
241
|
#expect(copy.artwork?.url == "art.jpg")
|
|
238
242
|
#expect(copy.artworkSource == ImageSource(uri: "resolved-art.jpg"))
|
|
239
|
-
#expect(copy.artworkCarPlayTinted == true)
|
|
240
243
|
#expect(copy.subtitle == "Original Subtitle")
|
|
241
244
|
#expect(copy.artist == "Original Artist")
|
|
242
245
|
#expect(copy.album == "Original Album")
|
|
243
246
|
#expect(copy.description == "Original Description")
|
|
244
247
|
#expect(copy.genre == "Rock")
|
|
245
248
|
#expect(copy.duration == 180.0)
|
|
246
|
-
#expect(copy.style == .
|
|
247
|
-
#expect(copy.
|
|
249
|
+
#expect(copy.style == SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil))
|
|
250
|
+
#expect(copy.disabled == false)
|
|
248
251
|
#expect(copy.favorited == true)
|
|
249
|
-
#expect(copy.
|
|
252
|
+
#expect(copy.sections?.first?.title == "Group A")
|
|
250
253
|
#expect(copy.live == false)
|
|
251
|
-
#expect(copy.imageRow == [ImageRowItem(title: "Row 1")])
|
|
252
254
|
}
|
|
@@ -4,14 +4,14 @@ import Testing
|
|
|
4
4
|
|
|
5
5
|
private struct Item: PlayableSearchItem {
|
|
6
6
|
let src: String?
|
|
7
|
-
let
|
|
7
|
+
let path: String?
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
@Suite("SearchDrillIn.playable")
|
|
11
11
|
struct SearchDrillInTests {
|
|
12
12
|
/// A resolver that fails the test if the drill-in path calls it.
|
|
13
|
-
private func unusedResolver(_
|
|
14
|
-
Issue.record("resolveChildren should not have been called (
|
|
13
|
+
private func unusedResolver(_ path: String) async throws -> [Item] {
|
|
14
|
+
Issue.record("resolveChildren should not have been called (path: \(path))")
|
|
15
15
|
return []
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -22,9 +22,9 @@ struct SearchDrillInTests {
|
|
|
22
22
|
|
|
23
23
|
@Test func firstPlayable_returnsFlatPlayable_inOrder_withoutResolving() async throws {
|
|
24
24
|
let items = [
|
|
25
|
-
Item(src: "/a.mp3",
|
|
26
|
-
Item(src: nil,
|
|
27
|
-
Item(src: "/b.mp3",
|
|
25
|
+
Item(src: "/a.mp3", path: nil),
|
|
26
|
+
Item(src: nil, path: "/page/x"), // browsable, filtered out
|
|
27
|
+
Item(src: "/b.mp3", path: nil),
|
|
28
28
|
]
|
|
29
29
|
let result = try await SearchDrillIn.playable(from: items, resolveChildren: unusedResolver)
|
|
30
30
|
#expect(result?.compactMap(\.src) == ["/a.mp3", "/b.mp3"])
|
|
@@ -32,31 +32,31 @@ struct SearchDrillInTests {
|
|
|
32
32
|
|
|
33
33
|
@Test func firstBrowsable_drillsIntoIt_andPlaysItsChildren() async throws {
|
|
34
34
|
let items = [
|
|
35
|
-
Item(src: nil,
|
|
36
|
-
Item(src: "/later.mp3",
|
|
35
|
+
Item(src: nil, path: "/page/place"), // browsable first result
|
|
36
|
+
Item(src: "/later.mp3", path: nil), // would win without drill-in
|
|
37
37
|
]
|
|
38
|
-
let result = try await SearchDrillIn.playable(from: items) {
|
|
39
|
-
#expect(
|
|
40
|
-
return [Item(src: "/station1.mp3",
|
|
38
|
+
let result = try await SearchDrillIn.playable(from: items) { path in
|
|
39
|
+
#expect(path == "/page/place")
|
|
40
|
+
return [Item(src: "/station1.mp3", path: nil), Item(src: "/station2.mp3", path: nil)]
|
|
41
41
|
}
|
|
42
42
|
#expect(result?.compactMap(\.src) == ["/station1.mp3", "/station2.mp3"])
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
@Test func browsableDrillYieldsNothing_fallsBackToFlat() async throws {
|
|
46
46
|
let items = [
|
|
47
|
-
Item(src: nil,
|
|
48
|
-
Item(src: "/fallback.mp3",
|
|
47
|
+
Item(src: nil, path: "/page/empty"),
|
|
48
|
+
Item(src: "/fallback.mp3", path: nil),
|
|
49
49
|
]
|
|
50
50
|
let result = try await SearchDrillIn.playable(from: items) { _ in
|
|
51
|
-
[Item(src: nil,
|
|
51
|
+
[Item(src: nil, path: "/page/nested")] // nothing playable inside
|
|
52
52
|
}
|
|
53
53
|
#expect(result?.compactMap(\.src) == ["/fallback.mp3"])
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
@Test func nothingPlayableAnywhere_returnsNil() async throws {
|
|
57
|
-
let items = [Item(src: nil,
|
|
57
|
+
let items = [Item(src: nil, path: "/page/a"), Item(src: nil, path: "/page/b")]
|
|
58
58
|
let result = try await SearchDrillIn.playable(from: items) { _ in
|
|
59
|
-
[Item(src: nil,
|
|
59
|
+
[Item(src: nil, path: "/page/nested")]
|
|
60
60
|
}
|
|
61
61
|
#expect(result == nil)
|
|
62
62
|
}
|
|
@@ -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
|
+
}
|