expo-router 55.0.3 → 55.0.5
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/android/build.gradle +2 -2
- package/build/getReactNavigationConfig.d.ts +1 -1
- package/build/getReactNavigationConfig.d.ts.map +1 -1
- package/build/getReactNavigationConfig.js +4 -4
- package/build/getReactNavigationConfig.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/ExpoRouter.podspec +6 -0
- package/ios/LinkPreview/LinkPreviewNativeNavigation.swift +10 -14
- package/ios/LinkPreview/RNScreensTabCompat.swift +42 -0
- package/ios/Tests/RNScreensTabCompatTests.swift +218 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.module → 55.0.5/expo.modules.router-55.0.5.module} +7 -7
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.module.md5 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.pom → 55.0.5/expo.modules.router-55.0.5.pom} +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.5/expo.modules.router-55.0.5.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/maven-metadata.xml.sha512 +1 -1
- package/package.json +5 -5
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.module.md5 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/router/expo.modules.router/55.0.3/expo.modules.router-55.0.3.pom.sha512 +0 -1
- package/plugin/tsconfig.tsbuildinfo +0 -1
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3-sources.jar → 55.0.5/expo.modules.router-55.0.5-sources.jar} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3-sources.jar.md5 → 55.0.5/expo.modules.router-55.0.5-sources.jar.md5} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3-sources.jar.sha1 → 55.0.5/expo.modules.router-55.0.5-sources.jar.sha1} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3-sources.jar.sha256 → 55.0.5/expo.modules.router-55.0.5-sources.jar.sha256} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3-sources.jar.sha512 → 55.0.5/expo.modules.router-55.0.5-sources.jar.sha512} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.aar → 55.0.5/expo.modules.router-55.0.5.aar} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.aar.md5 → 55.0.5/expo.modules.router-55.0.5.aar.md5} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.aar.sha1 → 55.0.5/expo.modules.router-55.0.5.aar.sha1} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.aar.sha256 → 55.0.5/expo.modules.router-55.0.5.aar.sha256} +0 -0
- /package/local-maven-repo/expo/modules/router/expo.modules.router/{55.0.3/expo.modules.router-55.0.3.aar.sha512 → 55.0.5/expo.modules.router-55.0.5.aar.sha512} +0 -0
package/android/build.gradle
CHANGED
|
@@ -4,13 +4,13 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'expo.modules.router'
|
|
7
|
-
version = '55.0.
|
|
7
|
+
version = '55.0.5'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.router"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
versionCode 1
|
|
13
|
-
versionName "55.0.
|
|
13
|
+
versionName "55.0.5"
|
|
14
14
|
}
|
|
15
15
|
lintOptions {
|
|
16
16
|
abortOnError false
|
|
@@ -7,7 +7,7 @@ export type Screen = string | {
|
|
|
7
7
|
};
|
|
8
8
|
export declare function parseRouteSegments(segments: string): string;
|
|
9
9
|
export declare function getReactNavigationScreensConfig(nodes: RouteNode[], metaOnly: boolean): Record<string, Screen>;
|
|
10
|
-
export declare function getReactNavigationConfig(
|
|
10
|
+
export declare function getReactNavigationConfig(routeTree: RouteNode | null, metaOnly: boolean): {
|
|
11
11
|
initialRouteName: undefined;
|
|
12
12
|
screens: Record<string, Screen>;
|
|
13
13
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getReactNavigationConfig.d.ts","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,MAAM,GACd,MAAM,GACN;IACE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsBN,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc3D;AAoCD,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB;AAED,wBAAgB,wBAAwB,CAAC,
|
|
1
|
+
{"version":3,"file":"getReactNavigationConfig.d.ts","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,MAAM,GACd,MAAM,GACN;IACE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAsBN,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc3D;AAoCD,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,SAAS,EAAE,EAClB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIxB;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO;;;EAatF"}
|
|
@@ -71,15 +71,15 @@ function convertRouteNodeToScreen(node, metaOnly) {
|
|
|
71
71
|
function getReactNavigationScreensConfig(nodes, metaOnly) {
|
|
72
72
|
return Object.fromEntries(nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)]));
|
|
73
73
|
}
|
|
74
|
-
function getReactNavigationConfig(
|
|
74
|
+
function getReactNavigationConfig(routeTree, metaOnly) {
|
|
75
75
|
const config = {
|
|
76
76
|
initialRouteName: undefined,
|
|
77
|
-
screens: getReactNavigationScreensConfig(
|
|
77
|
+
screens: routeTree ? getReactNavigationScreensConfig(routeTree.children, metaOnly) : {},
|
|
78
78
|
};
|
|
79
|
-
if (
|
|
79
|
+
if (routeTree?.initialRouteName) {
|
|
80
80
|
// We're using LinkingOptions the generic type is `object` instead of a proper ParamList.
|
|
81
81
|
// So we need to cast the initialRouteName to `any` to avoid type errors.
|
|
82
|
-
config.initialRouteName =
|
|
82
|
+
config.initialRouteName = routeTree.initialRouteName;
|
|
83
83
|
}
|
|
84
84
|
return config;
|
|
85
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getReactNavigationConfig.js","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":";;AAgCA,gDAcC;AAoCD,0EAOC;AAED,
|
|
1
|
+
{"version":3,"file":"getReactNavigationConfig.js","sourceRoot":"","sources":["../src/getReactNavigationConfig.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RouteNode } from './Route';\nimport { matchDynamicName } from './matchers';\n\nexport type Screen =\n | string\n | {\n path: string;\n screens: Record<string, Screen>;\n _route?: RouteNode;\n initialRouteName?: string;\n };\n\n// `[page]` -> `:page`\n// `page` -> `page`\nfunction convertDynamicRouteToReactNavigation(segment: string): string {\n // NOTE(EvanBacon): To support shared routes we preserve group segments.\n if (segment === 'index') {\n return '';\n }\n if (segment === '+not-found') {\n return '*not-found';\n }\n const dynamicName = matchDynamicName(segment);\n if (dynamicName && !dynamicName.deep) {\n return `:${dynamicName.name}`;\n } else if (dynamicName?.deep) {\n return '*' + dynamicName.name;\n } else {\n return segment;\n }\n}\n\nexport function parseRouteSegments(segments: string): string {\n return (\n // NOTE(EvanBacon): When there are nested routes without layouts\n // the node.route will be something like `app/home/index`\n // this needs to be split to ensure each segment is parsed correctly.\n segments\n .split('/')\n // Convert each segment to a React Navigation format.\n .map(convertDynamicRouteToReactNavigation)\n // Remove any empty paths from groups or index routes.\n .filter(Boolean)\n // Join to return as a path.\n .join('/')\n );\n}\n\nfunction convertRouteNodeToScreen(node: RouteNode, metaOnly: boolean): Screen {\n const path = parseRouteSegments(node.route);\n if (!node.children.length) {\n if (!metaOnly) {\n return {\n path,\n screens: {},\n _route: node,\n };\n }\n return path;\n }\n const screens = getReactNavigationScreensConfig(node.children, metaOnly);\n\n const screen: Screen = {\n path,\n screens,\n };\n\n if (node.initialRouteName) {\n // NOTE(EvanBacon): This is bad because it forces all Layout Routes\n // to be loaded into memory. We should move towards a system where\n // the initial route name is either loaded asynchronously in the Layout Route\n // or defined via a file system convention.\n screen.initialRouteName = node.initialRouteName;\n }\n\n if (!metaOnly) {\n screen._route = node;\n }\n\n return screen;\n}\n\nexport function getReactNavigationScreensConfig(\n nodes: RouteNode[],\n metaOnly: boolean\n): Record<string, Screen> {\n return Object.fromEntries(\n nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)] as const)\n );\n}\n\nexport function getReactNavigationConfig(routeTree: RouteNode | null, metaOnly: boolean) {\n const config = {\n initialRouteName: undefined,\n screens: routeTree ? getReactNavigationScreensConfig(routeTree.children, metaOnly) : {},\n };\n\n if (routeTree?.initialRouteName) {\n // We're using LinkingOptions the generic type is `object` instead of a proper ParamList.\n // So we need to cast the initialRouteName to `any` to avoid type errors.\n config.initialRouteName = routeTree.initialRouteName as any;\n }\n\n return config;\n}\n"]}
|
package/expo-module.config.json
CHANGED
package/ios/ExpoRouter.podspec
CHANGED
|
@@ -36,10 +36,16 @@ Pod::Spec.new do |s|
|
|
|
36
36
|
'OTHER_SWIFT_FLAGS' => "$(inherited) #{compiler_flags}",
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
s.exclude_files = 'Tests/**/*'
|
|
40
|
+
|
|
39
41
|
if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
|
|
40
42
|
s.source_files = "**/*.h"
|
|
41
43
|
s.vendored_frameworks = "#{s.name}.xcframework"
|
|
42
44
|
else
|
|
43
45
|
s.source_files = "**/*.{h,m,swift,mm,cpp}"
|
|
44
46
|
end
|
|
47
|
+
|
|
48
|
+
s.test_spec 'Tests' do |test_spec|
|
|
49
|
+
test_spec.source_files = 'Tests/**/*.{m,swift}'
|
|
50
|
+
end
|
|
45
51
|
end
|
|
@@ -48,12 +48,12 @@ internal class LinkPreviewNativeNavigation {
|
|
|
48
48
|
guard let stackOrTabView else {
|
|
49
49
|
return
|
|
50
50
|
}
|
|
51
|
-
if
|
|
51
|
+
if RNScreensTabCompat.isTabScreen(stackOrTabView) {
|
|
52
52
|
let newTabKeys = tabPath?.path.map { $0.newTabKey } ?? []
|
|
53
53
|
// The order is important here. findStackViewWithScreenIdInSubViews must be called
|
|
54
54
|
// even if screenId is nil to compute the tabChangeCommands.
|
|
55
55
|
if let stackView = findStackViewWithScreenIdInSubViews(
|
|
56
|
-
screenId: screenId, tabKeys: newTabKeys, rootView:
|
|
56
|
+
screenId: screenId, tabKeys: newTabKeys, rootView: stackOrTabView), let screenId {
|
|
57
57
|
setPreloadedView(stackView: stackView, screenId: screenId)
|
|
58
58
|
}
|
|
59
59
|
} else if let stackView = stackOrTabView as? RNSScreenStackView, let screenId {
|
|
@@ -150,8 +150,7 @@ internal class LinkPreviewNativeNavigation {
|
|
|
150
150
|
if let result =
|
|
151
151
|
enumeratedViews
|
|
152
152
|
.first(where: { _, view in
|
|
153
|
-
guard let
|
|
154
|
-
else {
|
|
153
|
+
guard let tabKey = RNScreensTabCompat.tabKey(from: view) else {
|
|
155
154
|
return false
|
|
156
155
|
}
|
|
157
156
|
return tabKeys.contains(tabKey)
|
|
@@ -162,13 +161,10 @@ internal class LinkPreviewNativeNavigation {
|
|
|
162
161
|
}
|
|
163
162
|
|
|
164
163
|
private func getTabBarControllerFromTabView(view: UIView) -> UITabBarController? {
|
|
165
|
-
if let
|
|
166
|
-
return
|
|
164
|
+
if let tabBarController = RNScreensTabCompat.tabBarController(fromTabScreen: view) {
|
|
165
|
+
return tabBarController
|
|
167
166
|
}
|
|
168
|
-
|
|
169
|
-
return tabHostView.controller as? UITabBarController
|
|
170
|
-
}
|
|
171
|
-
return nil
|
|
167
|
+
return RNScreensTabCompat.tabBarController(fromTabHost: view)
|
|
172
168
|
}
|
|
173
169
|
|
|
174
170
|
private func findStackViewWithScreenIdOrTabBarController(
|
|
@@ -182,10 +178,10 @@ internal class LinkPreviewNativeNavigation {
|
|
|
182
178
|
if view.screenIds.contains(screenId) {
|
|
183
179
|
return view
|
|
184
180
|
}
|
|
185
|
-
} else if let
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
} else if let nextView = nextResponder as? UIView,
|
|
182
|
+
let tabKey = RNScreensTabCompat.tabKey(from: nextView),
|
|
183
|
+
tabKeys.contains(tabKey) {
|
|
184
|
+
return nextView
|
|
189
185
|
}
|
|
190
186
|
currentResponder = nextResponder
|
|
191
187
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Instead of casting to concrete class names (which break on renames),
|
|
4
|
+
/// we detect tab views by checking `responds(to:)` for expected selectors
|
|
5
|
+
/// and read properties via KVC.
|
|
6
|
+
enum RNScreensTabCompat {
|
|
7
|
+
private static let tabKeyName = "tabKey"
|
|
8
|
+
private static let controllerName = "controller"
|
|
9
|
+
private static let reactViewControllerName = "reactViewController"
|
|
10
|
+
|
|
11
|
+
private static let tabKeySelector = NSSelectorFromString(tabKeyName)
|
|
12
|
+
private static let controllerSelector = NSSelectorFromString(controllerName)
|
|
13
|
+
private static let reactViewControllerSelector = NSSelectorFromString(reactViewControllerName)
|
|
14
|
+
|
|
15
|
+
// MARK: - Type check
|
|
16
|
+
|
|
17
|
+
/// A view is a tab screen if it has a `tabKey` property — specific to RNScreens tab views.
|
|
18
|
+
static func isTabScreen(_ view: UIView) -> Bool {
|
|
19
|
+
view.responds(to: tabKeySelector)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// MARK: - Property access via KVC
|
|
23
|
+
|
|
24
|
+
static func tabKey(from view: UIView) -> String? {
|
|
25
|
+
guard view.responds(to: tabKeySelector) else { return nil }
|
|
26
|
+
return view.value(forKey: tabKeyName) as? String
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Calls `reactViewController()` dynamically via `perform(_:)`, then returns `.tabBarController`.
|
|
30
|
+
static func tabBarController(fromTabScreen view: UIView) -> UITabBarController? {
|
|
31
|
+
guard isTabScreen(view),
|
|
32
|
+
view.responds(to: reactViewControllerSelector)
|
|
33
|
+
else { return nil }
|
|
34
|
+
let vc = view.perform(reactViewControllerSelector)?.takeUnretainedValue() as? UIViewController
|
|
35
|
+
return vc?.tabBarController
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static func tabBarController(fromTabHost view: UIView) -> UITabBarController? {
|
|
39
|
+
guard view.responds(to: controllerSelector) else { return nil }
|
|
40
|
+
return view.value(forKey: controllerName) as? UITabBarController
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
@testable import ExpoRouter
|
|
5
|
+
|
|
6
|
+
// MARK: - Mock views
|
|
7
|
+
|
|
8
|
+
/// Mock tab screen: has @objc tabKey property, mimicking RNScreens tab screen views.
|
|
9
|
+
private class MockTabScreenView: UIView {
|
|
10
|
+
@objc var tabKey: String?
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Mock tab host: has @objc controller property, mimicking RNScreens tab host views.
|
|
14
|
+
private class MockTabHostView: UIView {
|
|
15
|
+
@objc var controller: UIViewController?
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Mock tab host with a non-UIViewController controller property.
|
|
19
|
+
private class MockTabHostWithBadController: UIView {
|
|
20
|
+
@objc var controller: NSObject? = NSObject()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Mock view with a reactViewController() method that returns a UIViewController.
|
|
24
|
+
private class MockTabScreenWithReactVC: UIView {
|
|
25
|
+
@objc var tabKey: String?
|
|
26
|
+
private var _reactViewController: UIViewController?
|
|
27
|
+
|
|
28
|
+
func configure(reactViewController: UIViewController) {
|
|
29
|
+
_reactViewController = reactViewController
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@objc override func reactViewController() -> UIViewController? {
|
|
33
|
+
return _reactViewController
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// MARK: - Unit tests
|
|
38
|
+
|
|
39
|
+
@Suite("RNScreensTabCompat unit tests")
|
|
40
|
+
struct RNScreensTabCompatUnitTests {
|
|
41
|
+
|
|
42
|
+
@Suite("isTabScreen")
|
|
43
|
+
struct IsTabScreen {
|
|
44
|
+
@Test
|
|
45
|
+
func `detects mock tab screen`() {
|
|
46
|
+
let tabScreen = MockTabScreenView()
|
|
47
|
+
#expect(RNScreensTabCompat.isTabScreen(tabScreen))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
func `rejects plain UIView`() {
|
|
52
|
+
let plainView = UIView()
|
|
53
|
+
#expect(!RNScreensTabCompat.isTabScreen(plainView))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test
|
|
57
|
+
func `rejects mock tab host`() {
|
|
58
|
+
let tabHost = MockTabHostView()
|
|
59
|
+
#expect(!RNScreensTabCompat.isTabScreen(tabHost))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Suite("tabKey")
|
|
64
|
+
struct TabKey {
|
|
65
|
+
@Test
|
|
66
|
+
func `reads value`() {
|
|
67
|
+
let tabScreen = MockTabScreenView()
|
|
68
|
+
tabScreen.tabKey = "home"
|
|
69
|
+
#expect(RNScreensTabCompat.tabKey(from: tabScreen) == "home")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
func `returns nil for nil tab key`() {
|
|
74
|
+
let tabScreen = MockTabScreenView()
|
|
75
|
+
tabScreen.tabKey = nil
|
|
76
|
+
#expect(RNScreensTabCompat.tabKey(from: tabScreen) == nil)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test
|
|
80
|
+
func `returns nil for plain UIView`() {
|
|
81
|
+
let plainView = UIView()
|
|
82
|
+
#expect(RNScreensTabCompat.tabKey(from: plainView) == nil)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Suite("tabBarController(fromTabHost:)")
|
|
87
|
+
struct TabBarControllerFromTabHost {
|
|
88
|
+
@Test
|
|
89
|
+
func `returns tab bar controller`() {
|
|
90
|
+
let tabHost = MockTabHostView()
|
|
91
|
+
let tabBarController = UITabBarController()
|
|
92
|
+
tabHost.controller = tabBarController
|
|
93
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabHost: tabHost) === tabBarController)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
func `returns nil for non-tab bar controller`() {
|
|
98
|
+
let tabHost = MockTabHostView()
|
|
99
|
+
tabHost.controller = UIViewController()
|
|
100
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabHost: tabHost) == nil)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Test
|
|
104
|
+
func `returns nil for nil controller`() {
|
|
105
|
+
let tabHost = MockTabHostView()
|
|
106
|
+
tabHost.controller = nil
|
|
107
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabHost: tabHost) == nil)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@Test
|
|
111
|
+
func `returns nil for non-UIViewController type`() {
|
|
112
|
+
let tabHost = MockTabHostWithBadController()
|
|
113
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabHost: tabHost) == nil)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Test
|
|
117
|
+
func `returns nil for plain UIView`() {
|
|
118
|
+
let plainView = UIView()
|
|
119
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabHost: plainView) == nil)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Suite("tabBarController(fromTabScreen:)")
|
|
124
|
+
struct TabBarControllerFromTabScreen {
|
|
125
|
+
@Test
|
|
126
|
+
func `returns tab bar controller via reactViewController`() {
|
|
127
|
+
let tabBarController = UITabBarController()
|
|
128
|
+
let childVC = UIViewController()
|
|
129
|
+
tabBarController.viewControllers = [childVC]
|
|
130
|
+
|
|
131
|
+
let mockView = MockTabScreenWithReactVC()
|
|
132
|
+
mockView.tabKey = "tab1"
|
|
133
|
+
mockView.configure(reactViewController: childVC)
|
|
134
|
+
childVC.view.addSubview(mockView)
|
|
135
|
+
|
|
136
|
+
let result = RNScreensTabCompat.tabBarController(fromTabScreen: mockView)
|
|
137
|
+
#expect(result === tabBarController)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Test
|
|
141
|
+
func `returns nil when reactViewController returns nil`() {
|
|
142
|
+
let mockView = MockTabScreenWithReactVC()
|
|
143
|
+
mockView.tabKey = "tab1"
|
|
144
|
+
// Don't configure — reactViewController() returns nil
|
|
145
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabScreen: mockView) == nil)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
func `returns nil when no reactViewController method`() {
|
|
150
|
+
// MockTabScreenView has tabKey but no reactViewController() method
|
|
151
|
+
let mockView = MockTabScreenView()
|
|
152
|
+
mockView.tabKey = "tab1"
|
|
153
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabScreen: mockView) == nil)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@Test
|
|
157
|
+
func `returns nil for plain UIView`() {
|
|
158
|
+
let plainView = UIView()
|
|
159
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabScreen: plainView) == nil)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Test
|
|
163
|
+
func `returns nil when not in tab bar controller`() {
|
|
164
|
+
let navController = UINavigationController()
|
|
165
|
+
let childVC = UIViewController()
|
|
166
|
+
navController.viewControllers = [childVC]
|
|
167
|
+
|
|
168
|
+
let mockView = MockTabScreenWithReactVC()
|
|
169
|
+
mockView.tabKey = "tab1"
|
|
170
|
+
mockView.configure(reactViewController: childVC)
|
|
171
|
+
childVC.view.addSubview(mockView)
|
|
172
|
+
|
|
173
|
+
#expect(RNScreensTabCompat.tabBarController(fromTabScreen: mockView) == nil)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// MARK: - Integration tests (RNScreens API contract)
|
|
179
|
+
|
|
180
|
+
@Suite("RNScreens API contract")
|
|
181
|
+
struct RNScreensAPIContractTests {
|
|
182
|
+
|
|
183
|
+
@Test
|
|
184
|
+
func `tab screen class responds to tabKey`() throws {
|
|
185
|
+
let cls = NSClassFromString("RNSTabsScreenComponentView")
|
|
186
|
+
?? NSClassFromString("RNSBottomTabsScreenComponentView")
|
|
187
|
+
guard let cls else {
|
|
188
|
+
Issue.record("No tab screen class found — neither RNSTabsScreenComponentView nor RNSBottomTabsScreenComponentView")
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
let view = try #require((cls as? UIView.Type)?.init(), "Failed to instantiate tab screen class")
|
|
192
|
+
#expect(view.responds(to: NSSelectorFromString("tabKey")))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@Test
|
|
196
|
+
func `tab host class responds to controller`() throws {
|
|
197
|
+
let cls = NSClassFromString("RNSTabsHostComponentView")
|
|
198
|
+
?? NSClassFromString("RNSBottomTabsHostComponentView")
|
|
199
|
+
guard let cls else {
|
|
200
|
+
Issue.record("No tab host class found — neither RNSTabsHostComponentView nor RNSBottomTabsHostComponentView")
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
let view = try #require((cls as? UIView.Type)?.init(), "Failed to instantiate tab host class")
|
|
204
|
+
#expect(view.responds(to: NSSelectorFromString("controller")))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Test
|
|
208
|
+
func `tab screen class responds to reactViewController`() throws {
|
|
209
|
+
let cls = NSClassFromString("RNSTabsScreenComponentView")
|
|
210
|
+
?? NSClassFromString("RNSBottomTabsScreenComponentView")
|
|
211
|
+
guard let cls else {
|
|
212
|
+
Issue.record("No tab screen class found")
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
let view = try #require((cls as? UIView.Type)?.init(), "Failed to instantiate tab screen class")
|
|
216
|
+
#expect(view.responds(to: NSSelectorFromString("reactViewController")))
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"component": {
|
|
4
4
|
"group": "expo.modules.router",
|
|
5
5
|
"module": "expo.modules.router",
|
|
6
|
-
"version": "55.0.
|
|
6
|
+
"version": "55.0.5",
|
|
7
7
|
"attributes": {
|
|
8
8
|
"org.gradle.status": "release"
|
|
9
9
|
}
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
{
|
|
27
|
-
"name": "expo.modules.router-55.0.
|
|
28
|
-
"url": "expo.modules.router-55.0.
|
|
27
|
+
"name": "expo.modules.router-55.0.5.aar",
|
|
28
|
+
"url": "expo.modules.router-55.0.5.aar",
|
|
29
29
|
"size": 13684,
|
|
30
30
|
"sha512": "096e87bdf28a3c152ae7522330d6395eb967a309de7a888cc9e2654bbed45dc52d89fc7153bda0870732d72a1d938c0508be24b0b213c592e4dd61e3b105f1f2",
|
|
31
31
|
"sha256": "3bdd36a13fbff4fb9fa0e431e2f71ab69c3d52d2fd72e9dad006249c6d447dcb",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
],
|
|
61
61
|
"files": [
|
|
62
62
|
{
|
|
63
|
-
"name": "expo.modules.router-55.0.
|
|
64
|
-
"url": "expo.modules.router-55.0.
|
|
63
|
+
"name": "expo.modules.router-55.0.5.aar",
|
|
64
|
+
"url": "expo.modules.router-55.0.5.aar",
|
|
65
65
|
"size": 13684,
|
|
66
66
|
"sha512": "096e87bdf28a3c152ae7522330d6395eb967a309de7a888cc9e2654bbed45dc52d89fc7153bda0870732d72a1d938c0508be24b0b213c592e4dd61e3b105f1f2",
|
|
67
67
|
"sha256": "3bdd36a13fbff4fb9fa0e431e2f71ab69c3d52d2fd72e9dad006249c6d447dcb",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
},
|
|
81
81
|
"files": [
|
|
82
82
|
{
|
|
83
|
-
"name": "expo.modules.router-55.0.
|
|
84
|
-
"url": "expo.modules.router-55.0.
|
|
83
|
+
"name": "expo.modules.router-55.0.5-sources.jar",
|
|
84
|
+
"url": "expo.modules.router-55.0.5-sources.jar",
|
|
85
85
|
"size": 2326,
|
|
86
86
|
"sha512": "a1a1537b57bc0d66efbf2bdfd5f514684400b9598e549eaf53c528d1bf32ff42b7826e74fb6e115f8ec0490b9e06ffbb3c3efe9f2f703652c13291ef82b0fa03",
|
|
87
87
|
"sha256": "2add0f802e663eb9b5f90e086dca0e0e337a1442e15fb372a05945e01ee0d289",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
7f1015ac9bfa1fb23cf80ef3a93d7936
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
795c084bbde2560a4d31bbb8120ab9e3d8871ad3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bd938df8799536e5bad775a74e93fb22bfed780edb67ec90c224265d3de22894
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
181c67244df19ea37ff62d66345b8284555e4d864e72467a900d708ce0baf2a8883c793f1b88c0598bb6107547286181c9c27cf71c09d87bc1ac634cf40dfa13
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<modelVersion>4.0.0</modelVersion>
|
|
10
10
|
<groupId>expo.modules.router</groupId>
|
|
11
11
|
<artifactId>expo.modules.router</artifactId>
|
|
12
|
-
<version>55.0.
|
|
12
|
+
<version>55.0.5</version>
|
|
13
13
|
<packaging>aar</packaging>
|
|
14
14
|
<name>expo.modules.router</name>
|
|
15
15
|
<url>https://github.com/expo/expo</url>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e97db10758542ec585326c3219ed474f
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
412af68f3b0de7e1fe04f4d80a97a59d3f21cbad
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
7c0c7fe3c2b1497588f1ca29d731cf8b9ef31d37738a4b78a0d1360d346bff86
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
223324ba2cf6eaa09fe1c9c7b8fcb5b60b9d8942926b66303034db4acbbcfbdfcfe48343172042381f9fd98e557121a00231393b514ae71173c3d3ada31ab477
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
<groupId>expo.modules.router</groupId>
|
|
4
4
|
<artifactId>expo.modules.router</artifactId>
|
|
5
5
|
<versioning>
|
|
6
|
-
<latest>55.0.
|
|
7
|
-
<release>55.0.
|
|
6
|
+
<latest>55.0.5</latest>
|
|
7
|
+
<release>55.0.5</release>
|
|
8
8
|
<versions>
|
|
9
|
-
<version>55.0.
|
|
9
|
+
<version>55.0.5</version>
|
|
10
10
|
</versions>
|
|
11
|
-
<lastUpdated>
|
|
11
|
+
<lastUpdated>20260311141659</lastUpdated>
|
|
12
12
|
</versioning>
|
|
13
13
|
</metadata>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
30335fecbcab7d367223533228dabb85
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
0152624c82ad683b9f5143c521d4a4bbbde4594b
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
603335e2005f23bb5072fead26e065ebcf2b51e441325e751da4e66dbb58f26a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
9f737311a339e4245c099a3dcc70849dcdb329bbe9441b7f8ccb932d35ec400c3f58ef758070bc34b4a2b40d555fa139ee32eadb5cf0b22eb1687923134984cd
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-router",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.5",
|
|
4
4
|
"description": "Expo Router is a file-based router for React Native and web applications.",
|
|
5
5
|
"author": "650 Industries, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -143,10 +143,10 @@
|
|
|
143
143
|
"client-only": "^0.0.1",
|
|
144
144
|
"debug": "^4.3.4",
|
|
145
145
|
"escape-string-regexp": "^4.0.0",
|
|
146
|
-
"expo-glass-effect": "^55.0.
|
|
147
|
-
"expo-image": "^55.0.
|
|
146
|
+
"expo-glass-effect": "^55.0.8",
|
|
147
|
+
"expo-image": "^55.0.6",
|
|
148
148
|
"expo-server": "^55.0.6",
|
|
149
|
-
"expo-symbols": "^55.0.
|
|
149
|
+
"expo-symbols": "^55.0.5",
|
|
150
150
|
"fast-deep-equal": "^3.1.3",
|
|
151
151
|
"invariant": "^2.2.4",
|
|
152
152
|
"nanoid": "^3.3.8",
|
|
@@ -160,5 +160,5 @@
|
|
|
160
160
|
"use-latest-callback": "^0.2.1",
|
|
161
161
|
"vaul": "^1.1.2"
|
|
162
162
|
},
|
|
163
|
-
"gitHead": "
|
|
163
|
+
"gitHead": "bcdd2c239f8a92cdf5140e35cde768352630acd6"
|
|
164
164
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
dcff82ba1cd5c5dd8b91118d94889523
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
59d22fd3b2a09b409814f02180c86cd228bc8b3d
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3409f52bea4c41fddd03ab99a0ecefc0fc9e2aa4c89fa17c84c23f823fe00f37
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3d212a0d2fac1a63ab3ac17b1ebae05e95d4c4e6a7281dc90e7aa7755082ba0534bd746fc69e8e7a1bdd0f7c92e6c55f3e5342694d6151d8d7f060983bc22d9b
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
6d2a83488fc401645471e646e24c9d67
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2e08b11a632d6dd6e965271fa67584a72d421278
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
18b1a4968d3848c5b29a13f8a5f3c02d52cebf864d04a6fff475252e8b1d2ca9
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ed74b55712827d3be2b41bee0400f652a18b21b20b2fe35c562de840d32c2807f8bbe4a30b293260a7ba4b5b3c2a944638bc9049244eb6a36f7fff4d7243a1ee
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["./src/index.ts"],"version":"5.8.3"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|