@clerk/expo-native-components 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +106 -2
- package/android/build.gradle +152 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt +241 -0
- package/android/src/main/java/expo/modules/clerk/ClerkComposeNativeViewHost.kt +148 -0
- package/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt +1077 -0
- package/android/src/main/java/expo/modules/clerk/ClerkUserButtonViewModule.kt +156 -0
- package/android/src/main/java/expo/modules/clerk/ClerkUserProfileCustomPageState.kt +120 -0
- package/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt +315 -0
- package/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt +326 -0
- package/android/src/test/java/expo/modules/clerk/ClerkUserProfileCustomPageStateTest.kt +163 -0
- package/app.plugin.d.ts +5 -0
- package/app.plugin.js +333 -0
- package/dist/AuthView.d.ts +35 -0
- package/dist/AuthView.d.ts.map +1 -0
- package/dist/AuthView.js +79 -0
- package/dist/AuthView.js.map +1 -0
- package/dist/AuthView.types.d.ts +62 -0
- package/dist/AuthView.types.d.ts.map +1 -0
- package/dist/AuthView.types.js +0 -0
- package/dist/EmbeddedNavigation.types.d.ts +24 -0
- package/dist/EmbeddedNavigation.types.d.ts.map +1 -0
- package/dist/EmbeddedNavigation.types.js +0 -0
- package/dist/UserButton.d.ts +38 -0
- package/dist/UserButton.d.ts.map +1 -0
- package/dist/UserButton.js +68 -0
- package/dist/UserButton.js.map +1 -0
- package/dist/UserProfileCustomPages.d.ts +81 -0
- package/dist/UserProfileCustomPages.d.ts.map +1 -0
- package/dist/UserProfileCustomPages.js +140 -0
- package/dist/UserProfileCustomPages.js.map +1 -0
- package/dist/UserProfileView.d.ts +76 -0
- package/dist/UserProfileView.d.ts.map +1 -0
- package/dist/UserProfileView.js +101 -0
- package/dist/UserProfileView.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +29 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/specs/NativeClerkAuthView.android.d.ts +15 -0
- package/dist/specs/NativeClerkAuthView.android.d.ts.map +1 -0
- package/dist/specs/NativeClerkAuthView.android.js +8 -0
- package/dist/specs/NativeClerkAuthView.android.js.map +1 -0
- package/dist/specs/NativeClerkAuthView.d.ts +15 -0
- package/dist/specs/NativeClerkAuthView.d.ts.map +1 -0
- package/dist/specs/NativeClerkAuthView.js +9 -0
- package/dist/specs/NativeClerkAuthView.js.map +1 -0
- package/dist/specs/NativeClerkModule.d.ts +4 -0
- package/dist/specs/NativeClerkModule.d.ts.map +1 -0
- package/dist/specs/NativeClerkModule.js +8 -0
- package/dist/specs/NativeClerkModule.js.map +1 -0
- package/dist/specs/NativeClerkModule.types.d.ts +11 -0
- package/dist/specs/NativeClerkModule.types.d.ts.map +1 -0
- package/dist/specs/NativeClerkModule.types.js +0 -0
- package/dist/specs/NativeClerkModule.web.d.ts +4 -0
- package/dist/specs/NativeClerkModule.web.d.ts.map +1 -0
- package/dist/specs/NativeClerkModule.web.js +7 -0
- package/dist/specs/NativeClerkModule.web.js.map +1 -0
- package/dist/specs/NativeClerkUserButtonView.android.d.ts +6 -0
- package/dist/specs/NativeClerkUserButtonView.android.d.ts.map +1 -0
- package/dist/specs/NativeClerkUserButtonView.android.js +8 -0
- package/dist/specs/NativeClerkUserButtonView.android.js.map +1 -0
- package/dist/specs/NativeClerkUserButtonView.d.ts +6 -0
- package/dist/specs/NativeClerkUserButtonView.d.ts.map +1 -0
- package/dist/specs/NativeClerkUserButtonView.js +9 -0
- package/dist/specs/NativeClerkUserButtonView.js.map +1 -0
- package/dist/specs/NativeClerkUserProfileView.android.d.ts +13 -0
- package/dist/specs/NativeClerkUserProfileView.android.d.ts.map +1 -0
- package/dist/specs/NativeClerkUserProfileView.android.js +8 -0
- package/dist/specs/NativeClerkUserProfileView.android.js.map +1 -0
- package/dist/specs/NativeClerkUserProfileView.d.ts +13 -0
- package/dist/specs/NativeClerkUserProfileView.d.ts.map +1 -0
- package/dist/specs/NativeClerkUserProfileView.js +9 -0
- package/dist/specs/NativeClerkUserProfileView.js.map +1 -0
- package/dist/useAuthViewState.d.ts +12 -0
- package/dist/useAuthViewState.d.ts.map +1 -0
- package/dist/useAuthViewState.js +76 -0
- package/dist/useAuthViewState.js.map +1 -0
- package/dist/utils/native-module.d.ts +3 -0
- package/dist/utils/native-module.d.ts.map +1 -0
- package/dist/utils/native-module.js +14 -0
- package/dist/utils/native-module.js.map +1 -0
- package/expo-module.config.json +15 -0
- package/ios/ClerkAppDelegateSubscriber.swift +16 -0
- package/ios/ClerkAuthNativeView.swift +157 -0
- package/ios/ClerkExpo.podspec +67 -0
- package/ios/ClerkExpoModule.swift +315 -0
- package/ios/ClerkNativeBridge.swift +1575 -0
- package/ios/ClerkNativeViewHost.swift +204 -0
- package/ios/ClerkUserButtonNativeView.swift +34 -0
- package/ios/ClerkUserProfileNativeView.swift +92 -0
- package/ios/Tests/ClerkAuthNativeViewPaperTests.swift +30 -0
- package/ios/Tests/ClerkNativeBridgeTests.swift +185 -0
- package/ios/Tests/ClerkNativeViewHostTests.swift +66 -0
- package/ios/Tests/ClerkUserProfileCustomPageStateTests.swift +311 -0
- package/package.json +79 -3
- package/react-native.config.js +8 -0
- package/src/specs/NativeClerkAuthView.android.ts +15 -0
- package/src/specs/NativeClerkAuthView.ts +19 -0
- package/src/specs/NativeClerkModule.ts +6 -0
- package/src/specs/NativeClerkModule.types.ts +9 -0
- package/src/specs/NativeClerkModule.web.ts +3 -0
- package/src/specs/NativeClerkUserButtonView.android.ts +9 -0
- package/src/specs/NativeClerkUserButtonView.ts +11 -0
- package/src/specs/NativeClerkUserProfileView.android.ts +13 -0
- package/src/specs/NativeClerkUserProfileView.ts +17 -0
- package/src/specs/__tests__/native-view.web.test.ts +43 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
@testable import ClerkExpo
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import XCTest
|
|
4
|
+
|
|
5
|
+
final class ClerkUserProfileCustomPageStateTests: XCTestCase {
|
|
6
|
+
@MainActor
|
|
7
|
+
func testNavigationPathCanBeRestoredIntoANewLiveHost() {
|
|
8
|
+
let state = ClerkUserProfileCustomPageState()
|
|
9
|
+
let navigationPath = makeNavigationPath("billing")
|
|
10
|
+
|
|
11
|
+
state.navigationPathDidChange(navigationPath)
|
|
12
|
+
|
|
13
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@MainActor
|
|
17
|
+
func testKeepingTheSameNavigationDepthDoesNotDismissThePage() {
|
|
18
|
+
let state = ClerkUserProfileCustomPageState()
|
|
19
|
+
var events: [String] = []
|
|
20
|
+
state.setPageEventHandler { type, path in
|
|
21
|
+
events.append("\(type):\(path)")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
25
|
+
state.navigationDepthDidChange(1)
|
|
26
|
+
|
|
27
|
+
XCTAssertEqual(events, ["presented:billing"])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@MainActor
|
|
31
|
+
func testIncreasingTheNavigationDepthDoesNotDismissThePage() {
|
|
32
|
+
let state = ClerkUserProfileCustomPageState()
|
|
33
|
+
var events: [String] = []
|
|
34
|
+
state.setPageEventHandler { type, path in
|
|
35
|
+
events.append("\(type):\(path)")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
39
|
+
state.navigationDepthDidChange(2)
|
|
40
|
+
|
|
41
|
+
XCTAssertEqual(events, ["presented:billing"])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@MainActor
|
|
45
|
+
func testDecreasingTheNavigationDepthDismissesThePageOnce() {
|
|
46
|
+
let state = ClerkUserProfileCustomPageState()
|
|
47
|
+
var events: [String] = []
|
|
48
|
+
state.setPageEventHandler { type, path in
|
|
49
|
+
events.append("\(type):\(path)")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
53
|
+
state.navigationDepthDidChange(0)
|
|
54
|
+
state.navigationDepthDidChange(0)
|
|
55
|
+
|
|
56
|
+
XCTAssertEqual(events, ["presented:billing", "dismissed:billing"])
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@MainActor
|
|
60
|
+
func testInitialUserObservationKeepsTheRetainedPath() {
|
|
61
|
+
let state = ClerkUserProfileCustomPageState()
|
|
62
|
+
state.navigationPathDidChange(makeNavigationPath("billing"))
|
|
63
|
+
|
|
64
|
+
state.userDidChange(to: "user_1")
|
|
65
|
+
|
|
66
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@MainActor
|
|
70
|
+
func testUserIdentityChangeInvalidatesNavigationAndDismissesThePageOnce() {
|
|
71
|
+
let state = ClerkUserProfileCustomPageState()
|
|
72
|
+
var events: [String] = []
|
|
73
|
+
state.setPageEventHandler { type, path in
|
|
74
|
+
events.append("\(type):\(path)")
|
|
75
|
+
}
|
|
76
|
+
state.userDidChange(to: "user_1")
|
|
77
|
+
state.navigationPathDidChange(makeNavigationPath("billing"))
|
|
78
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
79
|
+
|
|
80
|
+
state.userDidChange(to: nil)
|
|
81
|
+
state.userDidChange(to: nil)
|
|
82
|
+
|
|
83
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 0)
|
|
84
|
+
XCTAssertEqual(events, ["presented:billing", "dismissed:billing"])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@MainActor
|
|
88
|
+
func testKeepingTheSameUserPreservesNavigation() {
|
|
89
|
+
let state = ClerkUserProfileCustomPageState()
|
|
90
|
+
state.userDidChange(to: "user_1")
|
|
91
|
+
state.navigationPathDidChange(makeNavigationPath("billing"))
|
|
92
|
+
|
|
93
|
+
state.userDidChange(to: "user_1")
|
|
94
|
+
|
|
95
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 1)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@MainActor
|
|
99
|
+
func testUserIdentityChangeInvalidatesTheEntireUserButtonStack() {
|
|
100
|
+
let state = ClerkUserProfileCustomPageState()
|
|
101
|
+
var events: [String] = []
|
|
102
|
+
state.setPageEventHandler { type, path in
|
|
103
|
+
events.append("\(type):\(path)")
|
|
104
|
+
}
|
|
105
|
+
state.userDidChange(to: "user_1")
|
|
106
|
+
state.pageDidPresent(path: "billing")
|
|
107
|
+
state.navigate(action: "push", routeKey: "preferences")
|
|
108
|
+
state.pageDidDismiss(path: "billing")
|
|
109
|
+
state.pageDidPresent(path: "preferences")
|
|
110
|
+
events.removeAll()
|
|
111
|
+
|
|
112
|
+
state.userDidChange(to: "user_2")
|
|
113
|
+
state.userDidChange(to: "user_2")
|
|
114
|
+
|
|
115
|
+
XCTAssertEqual(events, ["dismissed:preferences", "dismissed:billing"])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@MainActor
|
|
119
|
+
func testRemovingThePresentedCustomPageInvalidatesNavigation() {
|
|
120
|
+
let state = ClerkUserProfileCustomPageState()
|
|
121
|
+
var events: [String] = []
|
|
122
|
+
state.setPageEventHandler { type, path in
|
|
123
|
+
events.append("\(type):\(path)")
|
|
124
|
+
}
|
|
125
|
+
state.navigationPathDidChange(makeNavigationPath("billing"))
|
|
126
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
127
|
+
|
|
128
|
+
state.reconcileCustomPagePaths(["preferences"])
|
|
129
|
+
|
|
130
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 0)
|
|
131
|
+
XCTAssertEqual(events, ["presented:billing", "dismissed:billing"])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@MainActor
|
|
135
|
+
func testRemovingAnEarlierCustomPageInvalidatesTheRetainedStack() {
|
|
136
|
+
let state = ClerkUserProfileCustomPageState()
|
|
137
|
+
var events: [String] = []
|
|
138
|
+
state.setPageEventHandler { type, path in
|
|
139
|
+
events.append("\(type):\(path)")
|
|
140
|
+
}
|
|
141
|
+
state.navigationPathDidChange(makeNavigationPath("billing", "preferences"))
|
|
142
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
143
|
+
state.pageDidPresent(path: "preferences", navigationDepth: 2)
|
|
144
|
+
|
|
145
|
+
state.reconcileCustomPagePaths(["preferences"])
|
|
146
|
+
|
|
147
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 0)
|
|
148
|
+
XCTAssertEqual(
|
|
149
|
+
events,
|
|
150
|
+
["presented:billing", "presented:preferences", "dismissed:preferences", "dismissed:billing"]
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@MainActor
|
|
155
|
+
func testRemovingAnEarlierUserButtonPageInvalidatesTheRetainedStack() {
|
|
156
|
+
let state = ClerkUserProfileCustomPageState()
|
|
157
|
+
var events: [String] = []
|
|
158
|
+
state.setPageEventHandler { type, path in
|
|
159
|
+
events.append("\(type):\(path)")
|
|
160
|
+
}
|
|
161
|
+
state.pageDidPresent(path: "billing")
|
|
162
|
+
state.navigate(action: "push", routeKey: "preferences")
|
|
163
|
+
state.pageDidDismiss(path: "billing")
|
|
164
|
+
state.pageDidPresent(path: "preferences")
|
|
165
|
+
events.removeAll()
|
|
166
|
+
|
|
167
|
+
state.reconcileCustomPagePaths(["preferences"])
|
|
168
|
+
|
|
169
|
+
XCTAssertEqual(events, ["dismissed:preferences", "dismissed:billing"])
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@MainActor
|
|
173
|
+
func testClosingUserButtonProfileDismissesCoveredPages() {
|
|
174
|
+
var pendingResets: [ClerkUserProfileCustomPageState.InactiveResetAction] = []
|
|
175
|
+
let state = ClerkUserProfileCustomPageState { pendingResets.append($0) }
|
|
176
|
+
var events: [String] = []
|
|
177
|
+
state.setPageEventHandler { type, path in
|
|
178
|
+
events.append("\(type):\(path)")
|
|
179
|
+
}
|
|
180
|
+
state.pageDidPresent(path: "billing")
|
|
181
|
+
state.navigate(action: "push", routeKey: "preferences")
|
|
182
|
+
state.pageDidDismiss(path: "billing")
|
|
183
|
+
state.pageDidPresent(path: "preferences")
|
|
184
|
+
events.removeAll()
|
|
185
|
+
|
|
186
|
+
state.pageDidDismiss(path: "preferences")
|
|
187
|
+
XCTAssertEqual(pendingResets.count, 1)
|
|
188
|
+
pendingResets.removeFirst()()
|
|
189
|
+
|
|
190
|
+
XCTAssertEqual(events, ["dismissed:preferences", "dismissed:billing"])
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@MainActor
|
|
194
|
+
func testReturningToCoveredUserButtonPageKeepsItRetained() {
|
|
195
|
+
var pendingResets: [ClerkUserProfileCustomPageState.InactiveResetAction] = []
|
|
196
|
+
let state = ClerkUserProfileCustomPageState { pendingResets.append($0) }
|
|
197
|
+
var events: [String] = []
|
|
198
|
+
state.setPageEventHandler { type, path in
|
|
199
|
+
events.append("\(type):\(path)")
|
|
200
|
+
}
|
|
201
|
+
state.pageDidPresent(path: "billing")
|
|
202
|
+
state.navigate(action: "push", routeKey: "preferences")
|
|
203
|
+
state.pageDidDismiss(path: "billing")
|
|
204
|
+
state.pageDidPresent(path: "preferences")
|
|
205
|
+
events.removeAll()
|
|
206
|
+
|
|
207
|
+
state.pageDidDismiss(path: "preferences")
|
|
208
|
+
state.pageDidPresent(path: "billing")
|
|
209
|
+
XCTAssertEqual(pendingResets.count, 1)
|
|
210
|
+
pendingResets.removeFirst()()
|
|
211
|
+
state.reconcileCustomPagePaths(["billing"])
|
|
212
|
+
|
|
213
|
+
XCTAssertEqual(events, ["dismissed:preferences", "presented:billing"])
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@MainActor
|
|
217
|
+
func testPushingAUserButtonPathAlreadyInTheStackDoesNotCollapseLaterPaths() {
|
|
218
|
+
let state = ClerkUserProfileCustomPageState()
|
|
219
|
+
var events: [String] = []
|
|
220
|
+
state.setPageEventHandler { type, path in
|
|
221
|
+
events.append("\(type):\(path)")
|
|
222
|
+
}
|
|
223
|
+
state.pageDidPresent(path: "billing")
|
|
224
|
+
state.navigate(action: "push", routeKey: "preferences")
|
|
225
|
+
state.pageDidDismiss(path: "billing")
|
|
226
|
+
state.pageDidPresent(path: "preferences")
|
|
227
|
+
events.removeAll()
|
|
228
|
+
|
|
229
|
+
state.navigate(action: "push", routeKey: "billing")
|
|
230
|
+
state.reconcileCustomPagePaths(["billing", "preferences"])
|
|
231
|
+
|
|
232
|
+
XCTAssertEqual(events, [])
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@MainActor
|
|
236
|
+
func testPoppingMultipleCustomPagesDismissesEachRetainedPage() {
|
|
237
|
+
let state = ClerkUserProfileCustomPageState()
|
|
238
|
+
var events: [String] = []
|
|
239
|
+
state.setPageEventHandler { type, path in
|
|
240
|
+
events.append("\(type):\(path)")
|
|
241
|
+
}
|
|
242
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
243
|
+
state.pageDidPresent(path: "preferences", navigationDepth: 2)
|
|
244
|
+
|
|
245
|
+
state.navigationDepthDidChange(0)
|
|
246
|
+
|
|
247
|
+
XCTAssertEqual(
|
|
248
|
+
events,
|
|
249
|
+
["presented:billing", "presented:preferences", "dismissed:preferences", "dismissed:billing"]
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@MainActor
|
|
254
|
+
func testPoppedCustomPagesAreNotIncludedInLaterReconciliation() {
|
|
255
|
+
let state = ClerkUserProfileCustomPageState()
|
|
256
|
+
state.navigationPathDidChange(makeNavigationPath("billing", "preferences"))
|
|
257
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
258
|
+
state.pageDidPresent(path: "preferences", navigationDepth: 2)
|
|
259
|
+
|
|
260
|
+
state.navigationPathDidChange(makeNavigationPath("billing"))
|
|
261
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
262
|
+
state.reconcileCustomPagePaths(["billing"])
|
|
263
|
+
|
|
264
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 1)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@MainActor
|
|
268
|
+
func testKeepingThePresentedCustomPagePreservesNavigation() {
|
|
269
|
+
let state = ClerkUserProfileCustomPageState()
|
|
270
|
+
state.navigationPathDidChange(makeNavigationPath("billing"))
|
|
271
|
+
state.pageDidPresent(path: "billing", navigationDepth: 1)
|
|
272
|
+
|
|
273
|
+
state.reconcileCustomPagePaths(["billing", "preferences"])
|
|
274
|
+
|
|
275
|
+
XCTAssertEqual(state.navigationPathForRestoration().count, 1)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
func testCustomPageLabelComesFromTheMatchingRow() {
|
|
279
|
+
let rows = parseUserProfileCustomPages(
|
|
280
|
+
"""
|
|
281
|
+
[{"path":"billing","label":"Billing details","icon":"billing","placement":{"type":"sectionEnd","section":"profile"}}]
|
|
282
|
+
""",
|
|
283
|
+
pageCount: 1
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
XCTAssertEqual(userProfileCustomPageLabel(for: "billing", rows: rows), "Billing details")
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func testPushOnlyDestinationsKeepTheirTitleWithoutCreatingRows() {
|
|
290
|
+
let pages = parseUserProfileCustomPages(
|
|
291
|
+
"""
|
|
292
|
+
[
|
|
293
|
+
{"path":"billing","label":"Billing","icon":"billing","placement":{"type":"sectionEnd","section":"profile"}},
|
|
294
|
+
{"path":"invoice-details","label":"Invoice details","icon":"settings","placement":{"type":"sectionEnd","section":"profile"},"showAsRow":false}
|
|
295
|
+
]
|
|
296
|
+
""",
|
|
297
|
+
pageCount: 2
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
XCTAssertEqual(pages.filter(\.shouldShowAsRow).map(\.path), ["billing"])
|
|
301
|
+
XCTAssertEqual(userProfileCustomPageLabel(for: "invoice-details", rows: pages), "Invoice details")
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private func makeNavigationPath(_ routes: String...) -> NavigationPath {
|
|
305
|
+
var navigationPath = NavigationPath()
|
|
306
|
+
for route in routes {
|
|
307
|
+
navigationPath.append(route)
|
|
308
|
+
}
|
|
309
|
+
return navigationPath
|
|
310
|
+
}
|
|
311
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clerk/expo-native-components",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Native Clerk components and native SDK integration for Expo, powered by clerk-ios and clerk-android",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"react-native",
|
|
8
|
+
"expo",
|
|
9
|
+
"clerk",
|
|
10
|
+
"auth",
|
|
11
|
+
"authentication",
|
|
12
|
+
"native"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://clerk.com/",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/clerk/javascript/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/clerk/javascript.git",
|
|
21
|
+
"directory": "packages/expo-native-components"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Clerk",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./app.plugin.js": "./app.plugin.js",
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"source": "./src/index.ts",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"android",
|
|
39
|
+
"ios",
|
|
40
|
+
"src/specs",
|
|
41
|
+
"expo-module.config.json",
|
|
42
|
+
"react-native.config.js",
|
|
43
|
+
"app.plugin.js",
|
|
44
|
+
"app.plugin.d.ts"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"tslib": "2.8.1",
|
|
48
|
+
"@clerk/react": "^6.17.7"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@expo/config-plugins": "^54.0.4",
|
|
52
|
+
"react-native": "^0.86.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"expo": ">=54 <58",
|
|
56
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
57
|
+
"react-native": ">=0.75"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=20.9.0"
|
|
61
|
+
},
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
},
|
|
65
|
+
"codegenConfig": {
|
|
66
|
+
"name": "ClerkExpoSpec",
|
|
67
|
+
"type": "all",
|
|
68
|
+
"jsSrcsDir": "src/specs"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "tsdown",
|
|
72
|
+
"build:declarations": "tsc -p tsconfig.declarations.json",
|
|
73
|
+
"clean": "rimraf ./dist",
|
|
74
|
+
"dev": "tsdown --watch",
|
|
75
|
+
"dev:pub": "pnpm dev -- --env.publish",
|
|
76
|
+
"format": "node ../../scripts/format-package.mjs",
|
|
77
|
+
"format:check": "node ../../scripts/format-package.mjs --check",
|
|
78
|
+
"lint": "eslint src",
|
|
79
|
+
"test": "vitest run",
|
|
80
|
+
"test:watch": "vitest watch"
|
|
81
|
+
}
|
|
6
82
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import type { NativeSyntheticEvent, ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type AuthEvent = Readonly<{ type: string }>;
|
|
5
|
+
|
|
6
|
+
interface NativeProps extends ViewProps {
|
|
7
|
+
mode?: string;
|
|
8
|
+
isDismissible?: boolean;
|
|
9
|
+
logoMaxHeight?: number;
|
|
10
|
+
hostBackButton?: boolean;
|
|
11
|
+
onAuthEvent?: (event: NativeSyntheticEvent<AuthEvent>) => void;
|
|
12
|
+
onHostBack?: (event: NativeSyntheticEvent<object>) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default requireNativeView<NativeProps>('ClerkAuthView');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import type { NativeSyntheticEvent, ViewProps } from 'react-native';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
type AuthEvent = Readonly<{ type: string }>;
|
|
6
|
+
|
|
7
|
+
interface NativeProps extends ViewProps {
|
|
8
|
+
mode?: string;
|
|
9
|
+
isDismissible?: boolean;
|
|
10
|
+
logoMaxHeight?: number;
|
|
11
|
+
hostBackButton?: boolean;
|
|
12
|
+
onAuthEvent?: (event: NativeSyntheticEvent<AuthEvent>) => void;
|
|
13
|
+
onHostBack?: (event: NativeSyntheticEvent<object>) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const NativeClerkAuthView =
|
|
17
|
+
Platform.OS === 'ios' || Platform.OS === 'android' ? requireNativeView<NativeProps>('ClerkAuthView') : null;
|
|
18
|
+
|
|
19
|
+
export default NativeClerkAuthView;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type NativeAuthFlowState = {
|
|
2
|
+
isLoaded: boolean;
|
|
3
|
+
isAuthFlowComplete: boolean;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export interface Spec {
|
|
7
|
+
addListener?(eventName: string, listener?: (...args: unknown[]) => void): { remove: () => void };
|
|
8
|
+
getAuthFlowState?(): Promise<NativeAuthFlowState>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
// Codegen requires an interface declaration in the iOS spec; keep the Android
|
|
5
|
+
// view prop shape identical for the shared React wrapper.
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
7
|
+
interface NativeProps extends ViewProps {}
|
|
8
|
+
|
|
9
|
+
export default requireNativeView<NativeProps>('ClerkUserButtonView');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
6
|
+
interface NativeProps extends ViewProps {}
|
|
7
|
+
|
|
8
|
+
const NativeClerkUserButtonView =
|
|
9
|
+
Platform.OS === 'ios' || Platform.OS === 'android' ? requireNativeView<NativeProps>('ClerkUserButtonView') : null;
|
|
10
|
+
|
|
11
|
+
export default NativeClerkUserButtonView;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import type { NativeSyntheticEvent, ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type ProfileEvent = Readonly<{ type: string }>;
|
|
5
|
+
|
|
6
|
+
interface NativeProps extends ViewProps {
|
|
7
|
+
isDismissible?: boolean;
|
|
8
|
+
hostBackButton?: boolean;
|
|
9
|
+
onProfileEvent?: (event: NativeSyntheticEvent<ProfileEvent>) => void;
|
|
10
|
+
onHostBack?: (event: NativeSyntheticEvent<object>) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default requireNativeView<NativeProps>('ClerkUserProfileView');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import type { NativeSyntheticEvent, ViewProps } from 'react-native';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
type ProfileEvent = Readonly<{ type: string }>;
|
|
6
|
+
|
|
7
|
+
interface NativeProps extends ViewProps {
|
|
8
|
+
isDismissible?: boolean;
|
|
9
|
+
hostBackButton?: boolean;
|
|
10
|
+
onProfileEvent?: (event: NativeSyntheticEvent<ProfileEvent>) => void;
|
|
11
|
+
onHostBack?: (event: NativeSyntheticEvent<object>) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NativeClerkUserProfileView =
|
|
15
|
+
Platform.OS === 'ios' || Platform.OS === 'android' ? requireNativeView<NativeProps>('ClerkUserProfileView') : null;
|
|
16
|
+
|
|
17
|
+
export default NativeClerkUserProfileView;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
requireNativeView: vi.fn(() => {
|
|
5
|
+
throw new Error('requireNativeView should not be called on web');
|
|
6
|
+
}),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('expo', () => ({
|
|
10
|
+
requireNativeView: mocks.requireNativeView,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('react-native', () => ({
|
|
14
|
+
Platform: {
|
|
15
|
+
OS: 'web',
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
async function importNativeViews() {
|
|
20
|
+
const [authView, userProfileView, userButtonView] = await Promise.all([
|
|
21
|
+
import('../NativeClerkAuthView'),
|
|
22
|
+
import('../NativeClerkUserProfileView'),
|
|
23
|
+
import('../NativeClerkUserButtonView'),
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
return [authView.default, userProfileView.default, userButtonView.default] as const;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('native view specs on web', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.resetModules();
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('do not require native views at import time', async () => {
|
|
36
|
+
const [authView, userProfileView, userButtonView] = await importNativeViews();
|
|
37
|
+
|
|
38
|
+
expect(authView).toBeNull();
|
|
39
|
+
expect(userProfileView).toBeNull();
|
|
40
|
+
expect(userButtonView).toBeNull();
|
|
41
|
+
expect(mocks.requireNativeView).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
});
|