react-native-audio-browser 0.2.0-next.22 → 0.2.0-next.24

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 (42) hide show
  1. package/android/gradle.properties +1 -1
  2. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +9 -1
  3. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +6 -0
  4. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +14 -0
  5. package/ios/Browser/BrowserPathHelper.swift +6 -2
  6. package/ios/CarPlay/CPInterfaceController+Safe.swift +93 -23
  7. package/ios/CarPlay/CarPlayController.swift +5 -1
  8. package/ios/CarPlay/TopTemplateReplacer.swift +96 -0
  9. package/ios/Extension/NavigationError+Conversion.swift +7 -0
  10. package/ios/HybridAudioBrowser.swift +4 -1
  11. package/ios/Tests/BrowserPathHelperTests.swift +11 -0
  12. package/ios/Tests/TopTemplateReplacerTests.swift +172 -0
  13. package/lib/commonjs/web/browser/NavigationErrorManager.js +3 -1
  14. package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
  15. package/lib/commonjs/web/util/BrowserPathHelper.js +6 -0
  16. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  17. package/lib/module/web/browser/NavigationErrorManager.js +3 -1
  18. package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
  19. package/lib/module/web/util/BrowserPathHelper.js +6 -0
  20. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  21. package/lib/typescript/src/types/browser.d.ts +8 -1
  22. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  23. package/nitrogen/generated/android/c++/JFormatNavigationErrorParams.hpp +9 -3
  24. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_FormattedNavigationError____FormatNavigationErrorParams.hpp +2 -0
  25. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +4 -0
  26. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +2 -0
  27. package/nitrogen/generated/android/c++/JNavigationErrorKind.hpp +58 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNavigationErrorParams.kt +6 -3
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorKind.kt +23 -0
  30. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +3 -0
  31. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +3 -0
  32. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +3 -0
  33. package/nitrogen/generated/ios/swift/FormatNavigationErrorParams.swift +7 -2
  34. package/nitrogen/generated/ios/swift/NavigationErrorKind.swift +40 -0
  35. package/nitrogen/generated/shared/c++/FormatNavigationErrorParams.hpp +9 -2
  36. package/nitrogen/generated/shared/c++/NavigationErrorKind.hpp +76 -0
  37. package/package.json +1 -1
  38. package/src/types/browser.ts +9 -1
  39. package/src/web/browser/NavigationErrorManager.ts +3 -1
  40. package/src/web/util/BrowserPathHelper.test.ts +16 -0
  41. package/src/web/util/BrowserPathHelper.ts +9 -0
  42. package/website/guide/carplay.md +30 -0
@@ -3,4 +3,4 @@ AudioBrowser_minSdkVersion=23
3
3
  AudioBrowser_targetSdkVersion=35
4
4
  AudioBrowser_compileSdkVersion=35
5
5
  AudioBrowser_ndkVersion=27.1.12297006
6
- AudioBrowser_media3Version=1.11.0
6
+ AudioBrowser_media3Version=1.11.1
@@ -519,7 +519,15 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
519
519
  val default = defaultFormattedError(error)
520
520
  val formatter = _configuration.formatNavigationError ?: return default
521
521
  return formattedOrDefault(mainScope, default) {
522
- formatter(FormatNavigationErrorParams(error, default, path)).await()
522
+ formatter(
523
+ FormatNavigationErrorParams(
524
+ error,
525
+ default,
526
+ path,
527
+ BrowserPathHelper.navigationErrorKind(path),
528
+ )
529
+ )
530
+ .await()
523
531
  }
524
532
  }
525
533
 
@@ -2,6 +2,7 @@ package com.audiobrowser.util
2
2
 
3
3
  import android.net.Uri
4
4
  import androidx.core.net.toUri
5
+ import com.margelo.nitro.audiobrowser.NavigationErrorKind
5
6
  import java.net.URLEncoder
6
7
 
7
8
  /**
@@ -49,6 +50,11 @@ object BrowserPathHelper {
49
50
  // Query parameter name for the tapped child's page position (tie-breaker)
50
51
  private const val CONTEXTUAL_INDEX_PARAM = "__index"
51
52
 
53
+ /** Whether a navigation error on this path came from a browse or a search */
54
+ fun navigationErrorKind(path: String): NavigationErrorKind =
55
+ if (path.startsWith("$SEARCH_PATH_PREFIX?")) NavigationErrorKind.SEARCH
56
+ else NavigationErrorKind.BROWSE
57
+
52
58
  /** Check if a path is a special system path (not a regular navigation path) */
53
59
  fun isSpecialPath(path: String): Boolean {
54
60
  return path == ROOT_PATH || path.startsWith("$SEARCH_PATH_PREFIX?")
@@ -1,5 +1,6 @@
1
1
  package com.audiobrowser.util
2
2
 
3
+ import com.margelo.nitro.audiobrowser.NavigationErrorKind
3
4
  import org.junit.Assert.assertEquals
4
5
  import org.junit.Assert.assertNull
5
6
  import org.junit.Test
@@ -50,4 +51,17 @@ class BrowserPathHelperTest {
50
51
  assertNull(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=x"))
51
52
  assertNull(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=-1"))
52
53
  }
54
+
55
+ @Test
56
+ fun `a search path is a search, anything else a browse`() {
57
+ assertEquals(
58
+ NavigationErrorKind.SEARCH,
59
+ BrowserPathHelper.navigationErrorKind(BrowserPathHelper.createSearchPath("jazz")),
60
+ )
61
+ assertEquals(
62
+ NavigationErrorKind.BROWSE,
63
+ BrowserPathHelper.navigationErrorKind("/search?q=jazz"),
64
+ )
65
+ assertEquals(NavigationErrorKind.BROWSE, BrowserPathHelper.navigationErrorKind("/__searchable"))
66
+ }
53
67
  }
@@ -44,8 +44,12 @@ enum BrowserPathHelper {
44
44
 
45
45
  /// Check if a path is a special system path (not a regular navigation path)
46
46
  static func isSpecialPath(_ path: String) -> Bool {
47
- path == rootPath ||
48
- path.hasPrefix("\(searchPathPrefix)?")
47
+ path == rootPath || isSearchPath(path)
48
+ }
49
+
50
+ /// Check if a path is a search path (`/__search?q=…`)
51
+ static func isSearchPath(_ path: String) -> Bool {
52
+ path.hasPrefix("\(searchPathPrefix)?")
49
53
  }
50
54
 
51
55
  /// Create a search path for a given query
@@ -12,6 +12,10 @@ import os.log
12
12
  /// preconditions are checked up front, the underlying call always gets a
13
13
  /// completion, and every failure is logged, with an optional caller completion
14
14
  /// for call sites that chain on the result.
15
+ ///
16
+ /// A push onto a full stack replaces the top template (`TopTemplateReplacer`).
17
+ /// The stack mutations wait for that replacement to settle before they run,
18
+ /// so none of them lands between its pop and its push.
15
19
  @MainActor
16
20
  extension CPInterfaceController {
17
21
  private static let stackLogger = Logger(subsystem: "com.audiobrowser", category: "TemplateStack")
@@ -26,9 +30,43 @@ extension CPInterfaceController {
26
30
  /// push — taking down the whole app, not just the CarPlay scene.
27
31
  private static let maximumStackDepth = 5
28
32
 
33
+ private static var topReplacerKey: UInt8 = 0
34
+
35
+ private var topReplacer: TopTemplateReplacer {
36
+ if let replacer = objc_getAssociatedObject(self, &Self.topReplacerKey) as? TopTemplateReplacer {
37
+ return replacer
38
+ }
39
+ let replacer = TopTemplateReplacer()
40
+ objc_setAssociatedObject(self, &Self.topReplacerKey, replacer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
41
+ return replacer
42
+ }
43
+
44
+ private func whenStackSettled(_ mutation: @escaping (CPInterfaceController) -> Void) {
45
+ topReplacer.whenSettled { [weak self] in
46
+ guard let self else { return }
47
+ mutation(self)
48
+ }
49
+ }
50
+
51
+ /// Pushes `template`, skipping the raise case (already in the hierarchy). At
52
+ /// the depth limit it replaces the top template.
53
+ func safePush(_ template: CPTemplate, animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
54
+ whenStackSettled { controller in
55
+ let templates = controller.templates
56
+ guard templates.count >= Self.maximumStackDepth, !templates.contains(where: { $0 === template }) else {
57
+ controller.guardedPush(template, animated: animated, completion: completion)
58
+ return
59
+ }
60
+ controller.topReplacer.push(
61
+ template, replacingTopOf: GuardedTemplateStack(controller: controller), animated: animated,
62
+ completion: completion,
63
+ )
64
+ }
65
+ }
66
+
29
67
  /// Pushes `template`, skipping the raise cases (already in the hierarchy,
30
68
  /// stack at the depth limit).
31
- func safePush(_ template: CPTemplate, animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
69
+ fileprivate func guardedPush(_ template: CPTemplate, animated: Bool, completion: ((Bool, Error?) -> Void)?) {
32
70
  guard !templates.contains(where: { $0 === template }) else {
33
71
  Self.stackLogger.warning("push skipped: \(Self.name(of: template)) is already in the stack")
34
72
  completion?(false, nil)
@@ -48,7 +86,7 @@ extension CPInterfaceController {
48
86
  }
49
87
 
50
88
  /// Pops the top template, skipping the raise case (nothing above the root).
51
- func safePop(animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
89
+ fileprivate func guardedPop(animated: Bool, completion: ((Bool, Error?) -> Void)?) {
52
90
  guard templates.count > 1 else {
53
91
  Self.stackLogger.warning("pop skipped: already at the root template")
54
92
  completion?(false, nil)
@@ -62,41 +100,54 @@ extension CPInterfaceController {
62
100
  }
63
101
  }
64
102
 
103
+ /// Pops the top template, skipping the raise case (nothing above the root).
104
+ func safePop(animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
105
+ whenStackSettled { controller in
106
+ controller.guardedPop(animated: animated, completion: completion)
107
+ }
108
+ }
109
+
65
110
  /// Pops back to `template`, skipping the raise case (not in the hierarchy).
66
111
  /// A no-op success when it is already the top template.
67
112
  func safePop(to template: CPTemplate, animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
68
- guard templates.contains(where: { $0 === template }) else {
69
- Self.stackLogger.warning("pop(to:) skipped: \(Self.name(of: template)) is not in the stack")
70
- completion?(false, nil)
71
- return
72
- }
73
- guard topTemplate !== template else {
74
- completion?(true, nil)
75
- return
76
- }
77
- pop(to: template, animated: animated) { popped, error in
78
- if let error {
79
- Self.stackLogger.error("pop(to:) \(Self.name(of: template)) failed: \(error.localizedDescription)")
113
+ whenStackSettled { controller in
114
+ guard controller.templates.contains(where: { $0 === template }) else {
115
+ Self.stackLogger.warning("pop(to:) skipped: \(Self.name(of: template)) is not in the stack")
116
+ completion?(false, nil)
117
+ return
118
+ }
119
+ guard controller.topTemplate !== template else {
120
+ completion?(true, nil)
121
+ return
122
+ }
123
+ controller.pop(to: template, animated: animated) { popped, error in
124
+ if let error {
125
+ Self.stackLogger.error("pop(to:) \(Self.name(of: template)) failed: \(error.localizedDescription)")
126
+ }
127
+ completion?(popped, error)
80
128
  }
81
- completion?(popped, error)
82
129
  }
83
130
  }
84
131
 
85
132
  func safePopToRoot(animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
86
- popToRootTemplate(animated: animated) { popped, error in
87
- if let error {
88
- Self.stackLogger.error("popToRootTemplate failed: \(error.localizedDescription)")
133
+ whenStackSettled { controller in
134
+ controller.popToRootTemplate(animated: animated) { popped, error in
135
+ if let error {
136
+ Self.stackLogger.error("popToRootTemplate failed: \(error.localizedDescription)")
137
+ }
138
+ completion?(popped, error)
89
139
  }
90
- completion?(popped, error)
91
140
  }
92
141
  }
93
142
 
94
143
  func safeSetRoot(_ template: CPTemplate, animated: Bool, completion: ((Bool, Error?) -> Void)? = nil) {
95
- setRootTemplate(template, animated: animated) { set, error in
96
- if let error {
97
- Self.stackLogger.error("setRootTemplate \(Self.name(of: template)) failed: \(error.localizedDescription)")
144
+ whenStackSettled { controller in
145
+ controller.setRootTemplate(template, animated: animated) { set, error in
146
+ if let error {
147
+ Self.stackLogger.error("setRootTemplate \(Self.name(of: template)) failed: \(error.localizedDescription)")
148
+ }
149
+ completion?(set, error)
98
150
  }
99
- completion?(set, error)
100
151
  }
101
152
  }
102
153
 
@@ -124,3 +175,22 @@ extension CPInterfaceController {
124
175
  }
125
176
  }
126
177
  }
178
+
179
+ /// The guarded pop and push a replacement is made of, so a push that still
180
+ /// cannot fit skips and logs rather than raising.
181
+ @MainActor
182
+ private struct GuardedTemplateStack: TemplateStack {
183
+ let controller: CPInterfaceController
184
+
185
+ var templates: [CPTemplate] {
186
+ controller.templates
187
+ }
188
+
189
+ func popTop(completion: @escaping (Bool, Error?) -> Void) {
190
+ controller.guardedPop(animated: false, completion: completion)
191
+ }
192
+
193
+ func push(_ template: CPTemplate, animated: Bool, completion: @escaping (Bool, Error?) -> Void) {
194
+ controller.guardedPush(template, animated: animated, completion: completion)
195
+ }
196
+ }
@@ -784,6 +784,7 @@ public final class RNABCarPlayController: NSObject {
784
784
  guard let formatter = config.formatNavigationError else { return fallback }
785
785
  let params = FormatNavigationErrorParams(
786
786
  error: navError, defaultFormatted: fallback, path: path,
787
+ kind: NavigationErrorKind(path: path),
787
788
  )
788
789
  do {
789
790
  let custom = try await formatter(params).await()
@@ -1317,7 +1318,10 @@ public final class RNABCarPlayController: NSObject {
1317
1318
  if let formatter = config.formatNavigationError {
1318
1319
  logger.debug("Calling formatNavigationError callback...")
1319
1320
  // Call the JS callback and handle result
1320
- let params = FormatNavigationErrorParams(error: error, defaultFormatted: defaultFormatted, path: path)
1321
+ let params = FormatNavigationErrorParams(
1322
+ error: error, defaultFormatted: defaultFormatted, path: path,
1323
+ kind: NavigationErrorKind(path: path),
1324
+ )
1321
1325
  // The result callbacks are @Sendable and hop back explicitly: Nitro resolves
1322
1326
  // its promises synchronously on the JS thread, so a plain closure would
1323
1327
  // inherit this type's MainActor isolation and trap on entry under Swift 6's
@@ -0,0 +1,96 @@
1
+ import Foundation
2
+ import os.log
3
+
4
+ /// The stack mutations a top-template replacement is made of:
5
+ /// `CPInterfaceController` in the app, a fake in tests.
6
+ @MainActor
7
+ protocol TemplateStack {
8
+ associatedtype Template: AnyObject
9
+
10
+ /// Root first.
11
+ var templates: [Template] { get }
12
+
13
+ /// Unanimated.
14
+ func popTop(completion: @escaping (Bool, Error?) -> Void)
15
+ func push(_ template: Template, animated: Bool, completion: @escaping (Bool, Error?) -> Void)
16
+ }
17
+
18
+ /// Pushes onto a full template stack by replacing its top template — an
19
+ /// unanimated pop, then the push — and holds back every other stack mutation
20
+ /// until both have completed. See "Template Depth" in
21
+ /// `website/guide/carplay.md`.
22
+ @MainActor
23
+ final class TopTemplateReplacer {
24
+ private let logger = Logger(subsystem: "com.audiobrowser", category: "TemplateStack")
25
+ private var deferred: [() -> Void] = []
26
+
27
+ /// True from the pop until the push completes.
28
+ private(set) var isReplacing = false
29
+
30
+ /// Runs `mutation` now, or — while a replacement is in flight — once it has
31
+ /// settled, after the mutations that arrived before it.
32
+ func whenSettled(_ mutation: @escaping () -> Void) {
33
+ guard isReplacing else {
34
+ mutation()
35
+ return
36
+ }
37
+ deferred.append(mutation)
38
+ }
39
+
40
+ /// Pushes `template` onto `stack` in place of its top template. When the
41
+ /// push fails, the popped template is pushed back, so the user stays where
42
+ /// they were. `completion` reports the push of `template` itself.
43
+ ///
44
+ /// Refuses a stack with nothing above the root to replace, and a call made
45
+ /// while a replacement is in flight: route it through `whenSettled`.
46
+ func push<Stack: TemplateStack>(
47
+ _ template: Stack.Template,
48
+ replacingTopOf stack: Stack,
49
+ animated: Bool,
50
+ completion: ((Bool, Error?) -> Void)? = nil,
51
+ ) {
52
+ let templates = stack.templates
53
+ guard !isReplacing, templates.count > 1, let top = templates.last else {
54
+ logger.error("push skipped: \(Self.name(of: template)) has no template to replace (\(templates.count) templates)")
55
+ completion?(false, nil)
56
+ return
57
+ }
58
+ isReplacing = true
59
+ logger.info("push at the depth limit: replacing the top, \(Self.name(of: top)), with \(Self.name(of: template))")
60
+
61
+ stack.popTop { [self] popped, error in
62
+ guard popped else {
63
+ logger.error("replace abandoned: pop failed, \(Self.name(of: template)) not pushed (\(stack.templates.count) templates)")
64
+ settle(completion, pushed: false, error: error)
65
+ return
66
+ }
67
+ stack.push(template, animated: animated) { [self] pushed, error in
68
+ guard !pushed else {
69
+ settle(completion, pushed: true, error: error)
70
+ return
71
+ }
72
+ logger.error("replace failed: \(Self.name(of: template)) not pushed, restoring \(Self.name(of: top))")
73
+ stack.push(top, animated: false) { [self] restored, _ in
74
+ if !restored {
75
+ logger.error("replace failed: \(Self.name(of: top)) not restored (\(stack.templates.count) templates)")
76
+ }
77
+ settle(completion, pushed: false, error: error)
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ private func settle(_ completion: ((Bool, Error?) -> Void)?, pushed: Bool, error: Error?) {
84
+ // Still replacing here, so a mutation the completion issues queues behind
85
+ // the ones that arrived during the replacement.
86
+ completion?(pushed, error)
87
+ isReplacing = false
88
+ while !isReplacing, !deferred.isEmpty {
89
+ deferred.removeFirst()()
90
+ }
91
+ }
92
+
93
+ private static func name(of template: AnyObject) -> String {
94
+ String(describing: type(of: template))
95
+ }
96
+ }
@@ -54,3 +54,10 @@ extension NavigationError {
54
54
  return FormattedNavigationError(title: title, message: message.isEmpty ? nil : message)
55
55
  }
56
56
  }
57
+
58
+ extension NavigationErrorKind {
59
+ /// The kind of request a navigation error on `path` came from.
60
+ init(path: String) {
61
+ self = BrowserPathHelper.isSearchPath(path) ? .search : .browse
62
+ }
63
+ }
@@ -465,7 +465,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
465
465
  // Format the error (async if using JS callback, sync for defaults)
466
466
  let defaultFormatted = navError.defaultFormatted()
467
467
  if let formatter = onMainActor({ browserManager.config.formatNavigationError }) {
468
- let params = FormatNavigationErrorParams(error: navError, defaultFormatted: defaultFormatted, path: path)
468
+ let params = FormatNavigationErrorParams(
469
+ error: navError, defaultFormatted: defaultFormatted, path: path,
470
+ kind: NavigationErrorKind(path: path),
471
+ )
469
472
  // Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes.
470
473
  // The result callbacks are @Sendable: a plain closure would inherit MainActor
471
474
  // isolation from this block, and Nitro resolves its promises synchronously on the
@@ -166,3 +166,14 @@ import Testing
166
166
  @Test func isContextualReturnsTrueWithAmpersandSeparator() {
167
167
  #expect(BrowserPathHelper.isContextual("/lib?q=1&__trackId=x") == true)
168
168
  }
169
+
170
+ // MARK: - isSearchPath
171
+
172
+ @Test func isSearchPathMatchesCreatedSearchPaths() {
173
+ #expect(BrowserPathHelper.isSearchPath(BrowserPathHelper.createSearchPath("jazz")) == true)
174
+ }
175
+
176
+ @Test func isSearchPathRejectsBrowsePaths() {
177
+ #expect(BrowserPathHelper.isSearchPath("/search?q=jazz") == false)
178
+ #expect(BrowserPathHelper.isSearchPath("/__searchable") == false)
179
+ }
@@ -0,0 +1,172 @@
1
+ import Foundation
2
+ import Testing
3
+
4
+ @testable import AudioBrowserTestable
5
+
6
+ private final class Page {
7
+ let name: String
8
+ init(_ name: String) { self.name = name }
9
+ }
10
+
11
+ /// A template stack whose mutations complete only when the test steps them,
12
+ /// the way CarPlay's complete on a later run-loop turn.
13
+ @MainActor
14
+ private final class FakeStack: TemplateStack {
15
+ private(set) var templates: [Page]
16
+ private(set) var calls: [String] = []
17
+ var failingPushes: Set<String> = []
18
+ var failsPop = false
19
+ private var pending: [() -> Void] = []
20
+
21
+ init(_ names: String...) {
22
+ templates = names.map(Page.init)
23
+ }
24
+
25
+ var names: [String] {
26
+ templates.map(\.name)
27
+ }
28
+
29
+ func popTop(completion: @escaping (Bool, Error?) -> Void) {
30
+ calls.append("pop")
31
+ pending.append { [self] in
32
+ guard !failsPop else {
33
+ completion(false, nil)
34
+ return
35
+ }
36
+ templates.removeLast()
37
+ completion(true, nil)
38
+ }
39
+ }
40
+
41
+ func push(_ template: Page, animated: Bool, completion: @escaping (Bool, Error?) -> Void) {
42
+ calls.append("push \(template.name)\(animated ? " animated" : "")")
43
+ pending.append { [self] in
44
+ guard !failingPushes.contains(template.name) else {
45
+ completion(false, nil)
46
+ return
47
+ }
48
+ templates.append(template)
49
+ completion(true, nil)
50
+ }
51
+ }
52
+
53
+ func step() {
54
+ pending.removeFirst()()
55
+ }
56
+
57
+ func run() {
58
+ while !pending.isEmpty {
59
+ step()
60
+ }
61
+ }
62
+ }
63
+
64
+ @Suite("TopTemplateReplacer")
65
+ @MainActor
66
+ struct TopTemplateReplacerTests {
67
+ private let replacer = TopTemplateReplacer()
68
+ private let stack = FakeStack("tabs", "A", "B", "C", "D")
69
+
70
+ @Test func replacesTheTopPageAndKeepsTheRest() {
71
+ var result: Bool?
72
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true) { pushed, _ in result = pushed }
73
+ stack.run()
74
+
75
+ #expect(stack.names == ["tabs", "A", "B", "C", "E"])
76
+ #expect(stack.calls == ["pop", "push E animated"])
77
+ #expect(result == true)
78
+ #expect(!replacer.isReplacing)
79
+ }
80
+
81
+ @Test func thePushWaitsForThePopToComplete() {
82
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true)
83
+ #expect(stack.calls == ["pop"])
84
+ stack.step()
85
+ #expect(stack.calls == ["pop", "push E animated"])
86
+ }
87
+
88
+ @Test func aMutationArrivingMidReplacementRunsOnceItHasSettled() {
89
+ var seen: [[String]] = []
90
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true)
91
+ stack.step()
92
+ replacer.whenSettled { seen.append(stack.names) }
93
+ #expect(seen.isEmpty)
94
+ stack.run()
95
+
96
+ #expect(seen == [["tabs", "A", "B", "C", "E"]])
97
+ }
98
+
99
+ @Test func deferredMutationsRunInArrivalOrderBehindASecondReplacement() {
100
+ var order: [String] = []
101
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true)
102
+ replacer.whenSettled { [replacer, stack] in
103
+ order.append("F")
104
+ replacer.push(Page("F"), replacingTopOf: stack, animated: true)
105
+ }
106
+ replacer.whenSettled { order.append("after F: \(stack.names.last ?? "")") }
107
+ stack.run()
108
+
109
+ #expect(stack.names == ["tabs", "A", "B", "C", "F"])
110
+ #expect(order == ["F", "after F: F"])
111
+ }
112
+
113
+ @Test func aMutationIssuedByTheCompletionQueuesBehindEarlierArrivals() {
114
+ var order: [String] = []
115
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true) { [replacer] _, _ in
116
+ replacer.whenSettled { order.append("from completion") }
117
+ }
118
+ replacer.whenSettled { order.append("arrived mid-replacement") }
119
+ stack.run()
120
+
121
+ #expect(order == ["arrived mid-replacement", "from completion"])
122
+ }
123
+
124
+ @Test func aFailedPopLeavesTheStackAsItWas() {
125
+ stack.failsPop = true
126
+ var result: Bool?
127
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true) { pushed, _ in result = pushed }
128
+ stack.run()
129
+
130
+ #expect(stack.names == ["tabs", "A", "B", "C", "D"])
131
+ #expect(stack.calls == ["pop"])
132
+ #expect(result == false)
133
+ #expect(!replacer.isReplacing)
134
+ }
135
+
136
+ @Test func aFailedPushRestoresThePoppedPage() {
137
+ stack.failingPushes = ["E"]
138
+ let top = stack.templates[4]
139
+ var result: Bool?
140
+ var ranAfter = false
141
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true) { pushed, _ in result = pushed }
142
+ replacer.whenSettled { ranAfter = true }
143
+ stack.run()
144
+
145
+ #expect(stack.names == ["tabs", "A", "B", "C", "D"])
146
+ #expect(stack.templates[4] === top)
147
+ #expect(stack.calls == ["pop", "push E animated", "push D"])
148
+ #expect(result == false)
149
+ #expect(ranAfter)
150
+ }
151
+
152
+ @Test func aFailedRestoreStillSettles() {
153
+ stack.failingPushes = ["E", "D"]
154
+ var result: Bool?
155
+ replacer.push(Page("E"), replacingTopOf: stack, animated: true) { pushed, _ in result = pushed }
156
+ stack.run()
157
+
158
+ #expect(stack.names == ["tabs", "A", "B", "C"])
159
+ #expect(result == false)
160
+ #expect(!replacer.isReplacing)
161
+ }
162
+
163
+ @Test func refusesAStackWithNothingAboveTheRoot() {
164
+ let rootOnly = FakeStack("tabs")
165
+ var result: Bool?
166
+ replacer.push(Page("E"), replacingTopOf: rootOnly, animated: true) { pushed, _ in result = pushed }
167
+
168
+ #expect(rootOnly.calls.isEmpty)
169
+ #expect(result == false)
170
+ #expect(!replacer.isReplacing)
171
+ }
172
+ }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.NavigationErrorManager = void 0;
7
+ var _BrowserPathHelper = require("../util/BrowserPathHelper");
7
8
  const ERROR_TITLES = {
8
9
  'network-error': 'Network Error',
9
10
  'http-error': 'Server Error',
@@ -77,7 +78,8 @@ class NavigationErrorManager {
77
78
  const customFormatted = this.formatCallback({
78
79
  error: navError,
79
80
  defaultFormatted,
80
- path
81
+ path,
82
+ kind: _BrowserPathHelper.BrowserPathHelper.navigationErrorKind(path)
81
83
  });
82
84
  this._formattedNavigationError = customFormatted || defaultFormatted;
83
85
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"names":["ERROR_TITLES","NavigationErrorManager","onNavigationError","onFormattedNavigationError","setFormatCallback","callback","formatCallback","clearNavigationError","_navigationError","undefined","_formattedNavigationError","error","setNavigationError","code","message","path","statusCode","navError","statusCodeSuccess","defaultFormatted","title","customFormatted","console","getNavigationError","getFormattedNavigationError","exports"],"sourceRoot":"../../../../src","sources":["web/browser/NavigationErrorManager.ts"],"mappings":";;;;;;AAQA,MAAMA,YAAiD,GAAG;EACxD,eAAe,EAAE,eAAe;EAChC,YAAY,EAAE,cAAc;EAC5B,mBAAmB,EAAE,mBAAmB;EACxC,gBAAgB,EAAE,OAAO;EACzB,eAAe,EAAE,OAAO;EACxB,eAAe,EAAE,cAAc;EAC/B,SAAS,EAAE;AACb,CAAC;;AAED;AACA;AACA;AACA;AACO,MAAMC,sBAAsB,CAAC;EAKlC;EACAC,iBAAiB,GAAyCA,CAAA,KAAM,CAAC,CAAC;EAClEC,0BAA0B,GAEdA,CAAA,KAAM,CAAC,CAAC;;EAEpB;AACF;AACA;EACEC,iBAAiBA,CACfC,QAA6D,EACvD;IACN,IAAI,CAACC,cAAc,GAAGD,QAAQ;EAChC;;EAEA;AACF;AACA;EACEE,oBAAoBA,CAAA,EAAS;IAC3B,IACE,IAAI,CAACC,gBAAgB,KAAKC,SAAS,IACnC,IAAI,CAACC,yBAAyB,KAAKD,SAAS,EAC5C;MACA,IAAI,CAACD,gBAAgB,GAAGC,SAAS;MACjC,IAAI,CAACC,yBAAyB,GAAGD,SAAS;MAC1C,IAAI,CAACP,iBAAiB,CAAC;QAAES,KAAK,EAAEF;MAAU,CAAC,CAAC;MAC5C,IAAI,CAACN,0BAA0B,CAACM,SAAS,CAAC;IAC5C;EACF;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEG,kBAAkBA,CAChBC,IAAyB,EACzBC,OAAe,EACfC,IAAa,EACbC,UAAmB,EACb;IACN,MAAMC,QAAyB,GAAG;MAChCJ,IAAI;MACJC,OAAO;MACPE,UAAU;MACVE,iBAAiB,EAAET;IACrB,CAAC;IACD,IAAI,CAACD,gBAAgB,GAAGS,QAAQ;IAChC,IAAI,CAACf,iBAAiB,CAAC;MAAES,KAAK,EAAEM;IAAS,CAAC,CAAC;;IAE3C;IACA,MAAME,gBAA0C,GAAG;MACjDC,KAAK,EAAEpB,YAAY,CAACa,IAAI,CAAC;MACzBC;IACF,CAAC;;IAED;IACA,IAAI,IAAI,CAACR,cAAc,IAAIS,IAAI,EAAE;MAC/B,IAAI;QACF,MAAMM,eAAe,GAAG,IAAI,CAACf,cAAc,CAAC;UAC1CK,KAAK,EAAEM,QAAQ;UACfE,gBAAgB;UAChBJ;QACF,CAAC,CAAC;QACF,IAAI,CAACL,yBAAyB,GAAGW,eAAe,IAAIF,gBAAgB;MACtE,CAAC,CAAC,OAAOR,KAAK,EAAE;QACdW,OAAO,CAACX,KAAK,CAAC,0CAA0C,EAAEA,KAAK,CAAC;QAChE,IAAI,CAACD,yBAAyB,GAAGS,gBAAgB;MACnD;IACF,CAAC,MAAM;MACL,IAAI,CAACT,yBAAyB,GAAGS,gBAAgB;IACnD;IAEA,IAAI,CAAChB,0BAA0B,CAAC,IAAI,CAACO,yBAAyB,CAAC;EACjE;;EAEA;AACF;AACA;EACEa,kBAAkBA,CAAA,EAAgC;IAChD,OAAO,IAAI,CAACf,gBAAgB;EAC9B;;EAEA;AACF;AACA;EACEgB,2BAA2BA,CAAA,EAAyC;IAClE,OAAO,IAAI,CAACd,yBAAyB;EACvC;AACF;AAACe,OAAA,CAAAxB,sBAAA,GAAAA,sBAAA","ignoreList":[]}
1
+ {"version":3,"names":["_BrowserPathHelper","require","ERROR_TITLES","NavigationErrorManager","onNavigationError","onFormattedNavigationError","setFormatCallback","callback","formatCallback","clearNavigationError","_navigationError","undefined","_formattedNavigationError","error","setNavigationError","code","message","path","statusCode","navError","statusCodeSuccess","defaultFormatted","title","customFormatted","kind","BrowserPathHelper","navigationErrorKind","console","getNavigationError","getFormattedNavigationError","exports"],"sourceRoot":"../../../../src","sources":["web/browser/NavigationErrorManager.ts"],"mappings":";;;;;;AAOA,IAAAA,kBAAA,GAAAC,OAAA;AAEA,MAAMC,YAAiD,GAAG;EACxD,eAAe,EAAE,eAAe;EAChC,YAAY,EAAE,cAAc;EAC5B,mBAAmB,EAAE,mBAAmB;EACxC,gBAAgB,EAAE,OAAO;EACzB,eAAe,EAAE,OAAO;EACxB,eAAe,EAAE,cAAc;EAC/B,SAAS,EAAE;AACb,CAAC;;AAED;AACA;AACA;AACA;AACO,MAAMC,sBAAsB,CAAC;EAKlC;EACAC,iBAAiB,GAAyCA,CAAA,KAAM,CAAC,CAAC;EAClEC,0BAA0B,GAEdA,CAAA,KAAM,CAAC,CAAC;;EAEpB;AACF;AACA;EACEC,iBAAiBA,CACfC,QAA6D,EACvD;IACN,IAAI,CAACC,cAAc,GAAGD,QAAQ;EAChC;;EAEA;AACF;AACA;EACEE,oBAAoBA,CAAA,EAAS;IAC3B,IACE,IAAI,CAACC,gBAAgB,KAAKC,SAAS,IACnC,IAAI,CAACC,yBAAyB,KAAKD,SAAS,EAC5C;MACA,IAAI,CAACD,gBAAgB,GAAGC,SAAS;MACjC,IAAI,CAACC,yBAAyB,GAAGD,SAAS;MAC1C,IAAI,CAACP,iBAAiB,CAAC;QAAES,KAAK,EAAEF;MAAU,CAAC,CAAC;MAC5C,IAAI,CAACN,0BAA0B,CAACM,SAAS,CAAC;IAC5C;EACF;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEG,kBAAkBA,CAChBC,IAAyB,EACzBC,OAAe,EACfC,IAAa,EACbC,UAAmB,EACb;IACN,MAAMC,QAAyB,GAAG;MAChCJ,IAAI;MACJC,OAAO;MACPE,UAAU;MACVE,iBAAiB,EAAET;IACrB,CAAC;IACD,IAAI,CAACD,gBAAgB,GAAGS,QAAQ;IAChC,IAAI,CAACf,iBAAiB,CAAC;MAAES,KAAK,EAAEM;IAAS,CAAC,CAAC;;IAE3C;IACA,MAAME,gBAA0C,GAAG;MACjDC,KAAK,EAAEpB,YAAY,CAACa,IAAI,CAAC;MACzBC;IACF,CAAC;;IAED;IACA,IAAI,IAAI,CAACR,cAAc,IAAIS,IAAI,EAAE;MAC/B,IAAI;QACF,MAAMM,eAAe,GAAG,IAAI,CAACf,cAAc,CAAC;UAC1CK,KAAK,EAAEM,QAAQ;UACfE,gBAAgB;UAChBJ,IAAI;UACJO,IAAI,EAAEC,oCAAiB,CAACC,mBAAmB,CAACT,IAAI;QAClD,CAAC,CAAC;QACF,IAAI,CAACL,yBAAyB,GAAGW,eAAe,IAAIF,gBAAgB;MACtE,CAAC,CAAC,OAAOR,KAAK,EAAE;QACdc,OAAO,CAACd,KAAK,CAAC,0CAA0C,EAAEA,KAAK,CAAC;QAChE,IAAI,CAACD,yBAAyB,GAAGS,gBAAgB;MACnD;IACF,CAAC,MAAM;MACL,IAAI,CAACT,yBAAyB,GAAGS,gBAAgB;IACnD;IAEA,IAAI,CAAChB,0BAA0B,CAAC,IAAI,CAACO,yBAAyB,CAAC;EACjE;;EAEA;AACF;AACA;EACEgB,kBAAkBA,CAAA,EAAgC;IAChD,OAAO,IAAI,CAAClB,gBAAgB;EAC9B;;EAEA;AACF;AACA;EACEmB,2BAA2BA,CAAA,EAAyC;IAClE,OAAO,IAAI,CAACjB,yBAAyB;EACvC;AACF;AAACkB,OAAA,CAAA3B,sBAAA,GAAAA,sBAAA","ignoreList":[]}
@@ -31,6 +31,12 @@ const BrowserPathHelper = exports.BrowserPathHelper = {
31
31
  CONTEXTUAL_TRACK_PARAM: '__trackId',
32
32
  /** Query parameter name for the tapped child's page position (tie-breaker) */
33
33
  CONTEXTUAL_INDEX_PARAM: '__index',
34
+ /**
35
+ * Whether a navigation error on this path came from a browse or a search
36
+ */
37
+ navigationErrorKind(path) {
38
+ return path.startsWith(`${this.SEARCH_PATH_PREFIX}?`) ? 'search' : 'browse';
39
+ },
34
40
  /**
35
41
  * Create a search path for a given query
36
42
  */
@@ -1 +1 @@
1
- {"version":3,"names":["BrowserPathHelper","exports","SEARCH_PATH_PREFIX","CONTEXTUAL_TRACK_PARAM","CONTEXTUAL_INDEX_PARAM","createSearchPath","query","encodedQuery","encodeURIComponent","isContextual","path","includes","stripTrackId","url","isFullUrl","startsWith","urlObj","URL","undefined","searchParams","delete","toString","search","pathname","paramPattern","RegExp","result","replace","build","parentPath","trackId","index","separator","indexParam","extractTrackId","extractParam","extractIndex","raw","test","Number","parseInt","param","get","match","decodeURIComponent","buildUrl","baseUrl","normalizedBase","normalizedPath"],"sourceRoot":"../../../../src","sources":["web/util/BrowserPathHelper.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["BrowserPathHelper","exports","SEARCH_PATH_PREFIX","CONTEXTUAL_TRACK_PARAM","CONTEXTUAL_INDEX_PARAM","navigationErrorKind","path","startsWith","createSearchPath","query","encodedQuery","encodeURIComponent","isContextual","includes","stripTrackId","url","isFullUrl","urlObj","URL","undefined","searchParams","delete","toString","search","pathname","paramPattern","RegExp","result","replace","build","parentPath","trackId","index","separator","indexParam","extractTrackId","extractParam","extractIndex","raw","test","Number","parseInt","param","get","match","decodeURIComponent","buildUrl","baseUrl","normalizedBase","normalizedPath"],"sourceRoot":"../../../../src","sources":["web/util/BrowserPathHelper.ts"],"mappings":"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","ignoreList":[]}