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