sparkling-navigation 2.1.0-rc.24 → 2.1.0-rc.26

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.
@@ -32,6 +32,15 @@ android {
32
32
  kotlinOptions {
33
33
  jvmTarget = "11"
34
34
  }
35
+
36
+ testOptions {
37
+ unitTests.all {
38
+ it.extensions.configure(org.gradle.testing.jacoco.plugins.JacocoTaskExtension::class.java) {
39
+ isIncludeNoLocationClasses = true
40
+ excludes = listOf("jdk.internal.*")
41
+ }
42
+ }
43
+ }
35
44
  }
36
45
 
37
46
  dependencies {
@@ -39,13 +48,20 @@ dependencies {
39
48
  implementation(libs.androidx.core.ktx)
40
49
  implementation(libs.androidx.appcompat)
41
50
  testImplementation(libs.junit)
51
+ testImplementation("io.mockk:mockk:1.13.8")
52
+ testImplementation("org.robolectric:robolectric:4.11.1")
53
+ testImplementation("androidx.test:core:1.5.0")
42
54
  androidTestImplementation(libs.androidx.junit)
43
55
  androidTestImplementation(libs.androidx.espresso.core)
44
56
  val sparklingVersion =
45
57
  (findProperty("SPARKLING_ANDROID_SDK_VERSION") as? String)
46
58
  ?: System.getenv("SPARKLING_ANDROID_SDK_VERSION")
47
- ?: "2.1.0-rc.12"
48
- api("com.tiktok.sparkling:sparkling-method:$sparklingVersion")
59
+ ?: "2.1.0-rc.26"
60
+ if (rootProject.findProject(":sparkling-method") != null) {
61
+ api(project(":sparkling-method"))
62
+ } else {
63
+ api("com.tiktok.sparkling:sparkling-method:$sparklingVersion")
64
+ }
49
65
  }
50
66
 
51
67
  tasks.register<JacocoReport>("jacocoTestReport") {
@@ -2,7 +2,7 @@
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
4
 
5
- /** anycode-lint-ignore */
5
+ // anycode-lint-ignore
6
6
  package com.tiktok.sparkling.method.router.open
7
7
 
8
8
  import com.tiktok.sparkling.method.registry.core.annotation.IDLMethodName
@@ -14,8 +14,6 @@ import com.tiktok.sparkling.method.registry.core.model.idl.IDLMethodBaseModel
14
14
  import com.tiktok.sparkling.method.registry.core.model.idl.IDLMethodBaseParamModel
15
15
  import com.tiktok.sparkling.method.registry.core.model.idl.IDLMethodBaseResultModel
16
16
 
17
- /** anycode-lint-ignore */
18
-
19
17
  /**
20
18
  * GENERATED BY ANYCODE.
21
19
  * DO NOT MODIFY!!!
@@ -138,6 +138,7 @@ class RouterOpenMethod : AbsRouterOpenMethodIDL() {
138
138
  }
139
139
  }
140
140
 
141
+ @Suppress("ktlint:standard:enum-entry-name-case")
141
142
  enum class ReplaceType {
142
143
  alwaysCloseAfterOpen,
143
144
  alwaysCloseBeforeOpen,
@@ -0,0 +1,593 @@
1
+ package com.tiktok.sparkling.method.router
2
+
3
+ import android.content.Context
4
+ import com.tiktok.sparkling.method.registry.core.BridgePlatformType
5
+ import com.tiktok.sparkling.method.registry.core.IBridgeContext
6
+ import com.tiktok.sparkling.method.registry.core.IDLBridgeMethod
7
+ import com.tiktok.sparkling.method.registry.core.model.context.ContextProviderFactory
8
+ import com.tiktok.sparkling.method.registry.core.model.idl.CompletionBlock
9
+ import com.tiktok.sparkling.method.router.close.AbsRouterCloseMethodIDL
10
+ import com.tiktok.sparkling.method.router.close.RouterCloseMethod
11
+ import com.tiktok.sparkling.method.router.open.AbsRouterOpenMethodIDL
12
+ import com.tiktok.sparkling.method.router.open.ReplaceType
13
+ import com.tiktok.sparkling.method.router.open.RouterOpenMethod
14
+ import com.tiktok.sparkling.method.router.utils.AbsRouteOpenHandler
15
+ import com.tiktok.sparkling.method.router.utils.IHostRouterDepend
16
+ import com.tiktok.sparkling.method.router.utils.RouterProvider
17
+ import io.mockk.every
18
+ import io.mockk.mockk
19
+ import io.mockk.verify
20
+ import io.mockk.verifyOrder
21
+ import org.junit.After
22
+ import org.junit.Assert.assertEquals
23
+ import org.junit.Assert.assertFalse
24
+ import org.junit.Assert.assertNotNull
25
+ import org.junit.Assert.assertNull
26
+ import org.junit.Assert.assertSame
27
+ import org.junit.Assert.assertTrue
28
+ import org.junit.Before
29
+ import org.junit.Test
30
+ import org.junit.runner.RunWith
31
+ import org.robolectric.RobolectricTestRunner
32
+ import org.robolectric.annotation.Config
33
+
34
+ /**
35
+ * Extra unit tests for the sparkling-navigation module to cover edge cases not exercised
36
+ * by [RouterMethodUnitTest], including replace-type branches, exception handling,
37
+ * the default [IHostRouterDepend.openScheme] handler chain, and [AbsRouteOpenHandler] wiring.
38
+ */
39
+ @RunWith(RobolectricTestRunner::class)
40
+ @Config(manifest = Config.NONE, sdk = [33])
41
+ class RouterCoverageTest {
42
+ private lateinit var bridgeContext: IBridgeContext
43
+ private lateinit var context: Context
44
+
45
+ @Before
46
+ fun setUp() {
47
+ context = mockk(relaxed = true)
48
+ bridgeContext = mockk(relaxed = true)
49
+ every { bridgeContext.context } returns context
50
+ RouterProvider.hostRouterDepend = null
51
+ }
52
+
53
+ @After
54
+ fun tearDown() {
55
+ RouterProvider.hostRouterDepend = null
56
+ }
57
+
58
+ // region RouterOpenMethod ------------------------------------------------------------------
59
+
60
+ @Test
61
+ fun openMethodFailsWhenSDKContextNull() {
62
+ val method = RouterOpenMethod()
63
+ val params = openParams(scheme = "hybrid://lynx?bundle=a")
64
+ val callback = OpenCallbackRecorder()
65
+
66
+ method.handle(params, callback, BridgePlatformType.LYNX)
67
+
68
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
69
+ assertTrue(callback.failureMsg?.contains("Context not provided") == true)
70
+ }
71
+
72
+ @Test
73
+ fun openMethodFailsWhenInnerContextNull() {
74
+ val ctxLessBridge = mockk<IBridgeContext>(relaxed = true)
75
+ every { ctxLessBridge.context } returns null
76
+
77
+ val method = RouterOpenMethod().apply { setBridgeContext(ctxLessBridge) }
78
+ val params = openParams(scheme = "hybrid://lynx?bundle=a")
79
+ val callback = OpenCallbackRecorder()
80
+
81
+ method.handle(params, callback, BridgePlatformType.LYNX)
82
+
83
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
84
+ assertTrue(callback.failureMsg?.contains("Context not provided") == true)
85
+ }
86
+
87
+ @Test
88
+ fun openMethodFailsWhenRouterDependNotRegistered() {
89
+ // RouterProvider.hostRouterDepend stays null
90
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
91
+ val params = openParams(scheme = "hybrid://lynx?bundle=a")
92
+ val callback = OpenCallbackRecorder()
93
+
94
+ method.handle(params, callback, BridgePlatformType.LYNX)
95
+
96
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
97
+ assertTrue(callback.failureMsg?.contains("Router service not available") == true)
98
+ }
99
+
100
+ @Test
101
+ fun openMethodFailsWhenOpenSchemeReturnsFalse() {
102
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
103
+ every { hostRouter.openScheme(any(), any(), any(), any(), any()) } returns false
104
+ RouterProvider.hostRouterDepend = hostRouter
105
+
106
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
107
+ val callback = OpenCallbackRecorder()
108
+
109
+ method.handle(openParams(scheme = "hybrid://lynx?bundle=a"), callback, BridgePlatformType.LYNX)
110
+
111
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
112
+ assertTrue(callback.failureMsg?.contains("Failed to open scheme") == true)
113
+ }
114
+
115
+ @Test
116
+ fun openMethodSwallowsExceptionAndReportsFailure() {
117
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
118
+ every {
119
+ hostRouter.openScheme(any(), any(), any(), any(), any())
120
+ } throws RuntimeException("boom")
121
+ RouterProvider.hostRouterDepend = hostRouter
122
+
123
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
124
+ val callback = OpenCallbackRecorder()
125
+
126
+ method.handle(openParams(scheme = "hybrid://lynx?bundle=a"), callback, BridgePlatformType.LYNX)
127
+
128
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
129
+ }
130
+
131
+ @Test
132
+ fun openMethodReplaceAlwaysCloseBeforeOpenInvokesCloseFirst() {
133
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
134
+ every { hostRouter.closeView(any(), any(), any(), any()) } returns true
135
+ every { hostRouter.openScheme(any(), any(), any(), any(), any()) } returns true
136
+ RouterProvider.hostRouterDepend = hostRouter
137
+
138
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
139
+ val callback = OpenCallbackRecorder()
140
+
141
+ method.handle(
142
+ openParams(
143
+ scheme = "hybrid://lynx?bundle=a",
144
+ replace = true,
145
+ replaceType = ReplaceType.alwaysCloseBeforeOpen.name,
146
+ ),
147
+ callback,
148
+ BridgePlatformType.LYNX,
149
+ )
150
+
151
+ assertNotNull(callback.successResult)
152
+ verifyOrder {
153
+ hostRouter.closeView(bridgeContext, BridgePlatformType.LYNX, null, false)
154
+ hostRouter.openScheme(any(), any(), any(), BridgePlatformType.LYNX, any())
155
+ }
156
+ }
157
+
158
+ @Test
159
+ fun openMethodReplaceAlwaysCloseAfterOpenAlwaysClosesEvenWhenOpenFails() {
160
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
161
+ every { hostRouter.openScheme(any(), any(), any(), any(), any()) } returns false
162
+ every { hostRouter.closeView(any(), any(), any(), any()) } returns true
163
+ RouterProvider.hostRouterDepend = hostRouter
164
+
165
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
166
+ val callback = OpenCallbackRecorder()
167
+
168
+ method.handle(
169
+ openParams(
170
+ scheme = "hybrid://lynx?bundle=a",
171
+ replace = true,
172
+ replaceType = ReplaceType.alwaysCloseAfterOpen.name,
173
+ ),
174
+ callback,
175
+ BridgePlatformType.LYNX,
176
+ )
177
+
178
+ // open returned false so overall is failure, but close must still have been invoked
179
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
180
+ verify(exactly = 1) {
181
+ hostRouter.closeView(bridgeContext, BridgePlatformType.LYNX, null, false)
182
+ }
183
+ }
184
+
185
+ @Test
186
+ fun openMethodReplaceOnlyCloseAfterOpenSucceedSkipsCloseOnFailure() {
187
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
188
+ every { hostRouter.openScheme(any(), any(), any(), any(), any()) } returns false
189
+ RouterProvider.hostRouterDepend = hostRouter
190
+
191
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
192
+ val callback = OpenCallbackRecorder()
193
+
194
+ method.handle(
195
+ openParams(
196
+ scheme = "hybrid://lynx?bundle=a",
197
+ replace = true,
198
+ // default ReplaceType.onlyCloseAfterOpenSucceed when replaceType is null
199
+ replaceType = null,
200
+ ),
201
+ callback,
202
+ BridgePlatformType.LYNX,
203
+ )
204
+
205
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
206
+ verify(exactly = 0) { hostRouter.closeView(any(), any(), any(), any()) }
207
+ }
208
+
209
+ @Test
210
+ fun openMethodReplaceOnlyCloseAfterOpenSucceedClosesOnSuccess() {
211
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
212
+ every { hostRouter.openScheme(any(), any(), any(), any(), any()) } returns true
213
+ every { hostRouter.closeView(any(), any(), any(), any()) } returns true
214
+ RouterProvider.hostRouterDepend = hostRouter
215
+
216
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
217
+ val callback = OpenCallbackRecorder()
218
+
219
+ method.handle(
220
+ openParams(
221
+ scheme = "hybrid://lynx?bundle=a",
222
+ replace = true,
223
+ replaceType = ReplaceType.onlyCloseAfterOpenSucceed.name,
224
+ ),
225
+ callback,
226
+ BridgePlatformType.LYNX,
227
+ )
228
+
229
+ assertNotNull(callback.successResult)
230
+ verify(exactly = 1) {
231
+ hostRouter.closeView(bridgeContext, BridgePlatformType.LYNX, null, false)
232
+ }
233
+ }
234
+
235
+ @Test
236
+ fun openMethodReplaceSwallowsExceptionAndReportsFailure() {
237
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
238
+ every {
239
+ hostRouter.openScheme(any(), any(), any(), any(), any())
240
+ } throws IllegalStateException("kaboom")
241
+ RouterProvider.hostRouterDepend = hostRouter
242
+
243
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
244
+ val callback = OpenCallbackRecorder()
245
+
246
+ method.handle(
247
+ openParams(
248
+ scheme = "hybrid://lynx?bundle=a",
249
+ replace = true,
250
+ replaceType = ReplaceType.alwaysCloseBeforeOpen.name,
251
+ ),
252
+ callback,
253
+ BridgePlatformType.LYNX,
254
+ )
255
+
256
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
257
+ }
258
+
259
+ // endregion
260
+
261
+ // region RouterCloseMethod -----------------------------------------------------------------
262
+
263
+ @Test
264
+ fun closeMethodReportsCurrentContainerErrorWhenIdBlank() {
265
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
266
+ every { hostRouter.closeView(any(), any(), any(), any()) } returns false
267
+ RouterProvider.hostRouterDepend = hostRouter
268
+
269
+ val method = RouterCloseMethod().apply { setBridgeContext(bridgeContext) }
270
+ val params = mockk<AbsRouterCloseMethodIDL.IDLMethodCloseParamModel>(relaxed = true)
271
+ every { params.containerID } returns " "
272
+ every { params.animated } returns null
273
+
274
+ val callback = CloseCallbackRecorder()
275
+ method.handle(params, callback, BridgePlatformType.LYNX)
276
+
277
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
278
+ assertEquals("Failed to close current container", callback.failureMsg)
279
+ // animated default is true when null
280
+ verify(exactly = 1) {
281
+ hostRouter.closeView(bridgeContext, BridgePlatformType.LYNX, " ", true)
282
+ }
283
+ }
284
+
285
+ @Test
286
+ fun closeMethodReportsContainerSpecificErrorWhenIdProvided() {
287
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
288
+ every { hostRouter.closeView(any(), any(), any(), any()) } returns false
289
+ RouterProvider.hostRouterDepend = hostRouter
290
+
291
+ val method = RouterCloseMethod().apply { setBridgeContext(bridgeContext) }
292
+ val params = mockk<AbsRouterCloseMethodIDL.IDLMethodCloseParamModel>(relaxed = true)
293
+ every { params.containerID } returns "container-x"
294
+ every { params.animated } returns true
295
+
296
+ val callback = CloseCallbackRecorder()
297
+ method.handle(params, callback, BridgePlatformType.LYNX)
298
+
299
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
300
+ assertEquals("Failed to close container: container-x", callback.failureMsg)
301
+ }
302
+
303
+ @Test
304
+ fun closeMethodSwallowsExceptionAndReportsFailure() {
305
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
306
+ every {
307
+ hostRouter.closeView(any(), any(), any(), any())
308
+ } throws RuntimeException("close-boom")
309
+ RouterProvider.hostRouterDepend = hostRouter
310
+
311
+ val method = RouterCloseMethod().apply { setBridgeContext(bridgeContext) }
312
+ val params = mockk<AbsRouterCloseMethodIDL.IDLMethodCloseParamModel>(relaxed = true)
313
+ every { params.containerID } returns "container-y"
314
+ every { params.animated } returns true
315
+
316
+ val callback = CloseCallbackRecorder()
317
+ method.handle(params, callback, BridgePlatformType.LYNX)
318
+
319
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
320
+ }
321
+
322
+ // endregion
323
+
324
+ // region AbsRouteOpenHandler ---------------------------------------------------------------
325
+
326
+ @Test
327
+ fun absRouteOpenHandlerStoresNextAndExceptionHandlers() {
328
+ val handler = StubRouteOpenHandler()
329
+ val next = StubRouteOpenHandler()
330
+ val errorHandler = StubRouteOpenHandler()
331
+
332
+ assertNull(handler.nextHandler)
333
+ assertNull(handler.exceptionHandler)
334
+
335
+ handler.setNextHandler(next)
336
+ handler.setExceptionHandler(errorHandler)
337
+
338
+ assertSame(next, handler.nextHandler)
339
+ assertSame(errorHandler, handler.exceptionHandler)
340
+ }
341
+
342
+ // endregion
343
+
344
+ // region IHostRouterDepend default openScheme ----------------------------------------------
345
+
346
+ @Test
347
+ fun defaultOpenSchemeReturnsFalseWhenHandlerListEmpty() {
348
+ val depend =
349
+ object : IHostRouterDepend {
350
+ override fun closeView(
351
+ bridgeContext: IBridgeContext?,
352
+ type: BridgePlatformType,
353
+ containerID: String?,
354
+ animated: Boolean?,
355
+ ) = true
356
+ // empty handler list (default impl)
357
+ }
358
+
359
+ val result =
360
+ depend.openScheme(
361
+ bridgeContext,
362
+ "hybrid://lynx?bundle=a",
363
+ emptyMap(),
364
+ BridgePlatformType.LYNX,
365
+ context,
366
+ )
367
+ assertFalse(result)
368
+ }
369
+
370
+ @Test
371
+ fun defaultOpenSchemeReturnsTrueWhenFirstHandlerHandles() {
372
+ val handler =
373
+ StubRouteOpenHandler(
374
+ supported = listOf(BridgePlatformType.LYNX),
375
+ opener = { _, _, _ -> true },
376
+ )
377
+ val depend = makeDependWithHandlers(listOf(handler))
378
+
379
+ val result =
380
+ depend.openScheme(
381
+ bridgeContext,
382
+ "hybrid://lynx?bundle=a",
383
+ emptyMap(),
384
+ BridgePlatformType.LYNX,
385
+ context,
386
+ )
387
+
388
+ assertTrue(result)
389
+ assertEquals(1, handler.callCount)
390
+ }
391
+
392
+ @Test
393
+ fun defaultOpenSchemeFallsThroughWhenHandlerReturnsFalse() {
394
+ val first =
395
+ StubRouteOpenHandler(
396
+ supported = listOf(BridgePlatformType.LYNX),
397
+ opener = { _, _, _ -> false },
398
+ )
399
+ val second =
400
+ StubRouteOpenHandler(
401
+ supported = listOf(BridgePlatformType.LYNX),
402
+ opener = { _, _, _ -> true },
403
+ )
404
+ val depend = makeDependWithHandlers(listOf(first, second))
405
+
406
+ val result =
407
+ depend.openScheme(
408
+ bridgeContext,
409
+ "hybrid://lynx?bundle=a",
410
+ emptyMap(),
411
+ BridgePlatformType.LYNX,
412
+ context,
413
+ )
414
+
415
+ assertTrue(result)
416
+ assertEquals(1, first.callCount)
417
+ assertEquals(1, second.callCount)
418
+ }
419
+
420
+ @Test
421
+ fun defaultOpenSchemeSkipsHandlerWithUnsupportedPlatform() {
422
+ val webOnly =
423
+ StubRouteOpenHandler(
424
+ supported = listOf(BridgePlatformType.WEB),
425
+ opener = { _, _, _ -> true },
426
+ )
427
+ val lynxAll =
428
+ StubRouteOpenHandler(
429
+ supported = listOf(BridgePlatformType.ALL),
430
+ opener = { _, _, _ -> true },
431
+ )
432
+ val depend = makeDependWithHandlers(listOf(webOnly, lynxAll))
433
+
434
+ val result =
435
+ depend.openScheme(
436
+ bridgeContext,
437
+ "hybrid://lynx?bundle=a",
438
+ emptyMap(),
439
+ BridgePlatformType.LYNX,
440
+ context,
441
+ )
442
+
443
+ assertTrue(result)
444
+ assertEquals(0, webOnly.callCount)
445
+ assertEquals(1, lynxAll.callCount)
446
+ }
447
+
448
+ @Test
449
+ fun defaultOpenSchemeRoutesToExceptionHandlerWhenHandlerThrows() {
450
+ val throwing =
451
+ StubRouteOpenHandler(
452
+ supported = listOf(BridgePlatformType.LYNX),
453
+ opener = { _, _, _ -> throw RuntimeException("failed") },
454
+ )
455
+ val errorHandler =
456
+ StubRouteOpenHandler(
457
+ supported = listOf(BridgePlatformType.LYNX),
458
+ opener = { _, _, _ -> true },
459
+ )
460
+ val depend =
461
+ object : IHostRouterDepend {
462
+ override fun closeView(
463
+ bridgeContext: IBridgeContext?,
464
+ type: BridgePlatformType,
465
+ containerID: String?,
466
+ animated: Boolean?,
467
+ ) = true
468
+
469
+ override fun provideRouteOpenHandlerList(
470
+ contextProviderFactory: ContextProviderFactory?,
471
+ ) = listOf<AbsRouteOpenHandler>(throwing)
472
+
473
+ override fun provideRouteOpenExceptionHandler(
474
+ contextProviderFactory: ContextProviderFactory?,
475
+ ): AbsRouteOpenHandler = errorHandler
476
+ }
477
+
478
+ val result =
479
+ depend.openScheme(
480
+ bridgeContext,
481
+ "hybrid://lynx?bundle=a",
482
+ emptyMap(),
483
+ BridgePlatformType.LYNX,
484
+ context,
485
+ )
486
+
487
+ assertTrue(result)
488
+ assertEquals(1, throwing.callCount)
489
+ assertEquals(1, errorHandler.callCount)
490
+ }
491
+
492
+ // endregion
493
+
494
+ // region helpers ---------------------------------------------------------------------------
495
+
496
+ private fun openParams(
497
+ scheme: String,
498
+ replace: Boolean? = false,
499
+ replaceType: String? = null,
500
+ useSysBrowser: Boolean? = null,
501
+ ): AbsRouterOpenMethodIDL.IDLMethodOpenParamModel {
502
+ val params = mockk<AbsRouterOpenMethodIDL.IDLMethodOpenParamModel>(relaxed = true)
503
+ every { params.scheme } returns scheme
504
+ every { params.replace } returns replace
505
+ every { params.replaceType } returns replaceType
506
+ every { params.useSysBrowser } returns useSysBrowser
507
+ every { params.extra } returns null
508
+ return params
509
+ }
510
+
511
+ private fun makeDependWithHandlers(handlers: List<AbsRouteOpenHandler>) =
512
+ object : IHostRouterDepend {
513
+ override fun closeView(
514
+ bridgeContext: IBridgeContext?,
515
+ type: BridgePlatformType,
516
+ containerID: String?,
517
+ animated: Boolean?,
518
+ ) = true
519
+
520
+ override fun provideRouteOpenHandlerList(
521
+ contextProviderFactory: ContextProviderFactory?,
522
+ ) = handlers
523
+ }
524
+
525
+ private class StubRouteOpenHandler(
526
+ private val supported: List<BridgePlatformType> = listOf(BridgePlatformType.ALL),
527
+ private val opener: (String, Map<String, Any>, Context?) -> Boolean = { _, _, _ -> false },
528
+ ) : AbsRouteOpenHandler() {
529
+ var callCount: Int = 0
530
+ private set
531
+
532
+ override fun openScheme(
533
+ scheme: String,
534
+ extraInfo: Map<String, Any>,
535
+ context: Context?,
536
+ ): Boolean {
537
+ callCount++
538
+ return opener(scheme, extraInfo, context)
539
+ }
540
+
541
+ override fun getSupportPlatformTypeList(): List<BridgePlatformType> = supported
542
+ }
543
+
544
+ private class OpenCallbackRecorder : CompletionBlock<AbsRouterOpenMethodIDL.IDLMethodOpenResultModel> {
545
+ var successResult: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel? = null
546
+ var failureCode: Int? = null
547
+ var failureMsg: String? = null
548
+
549
+ override fun onSuccess(
550
+ result: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel,
551
+ msg: String,
552
+ ) {
553
+ successResult = result
554
+ }
555
+
556
+ override fun onFailure(
557
+ code: Int,
558
+ msg: String,
559
+ data: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel?,
560
+ ) {
561
+ failureCode = code
562
+ failureMsg = msg
563
+ }
564
+
565
+ override fun onRawSuccess(data: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel?) = Unit
566
+ }
567
+
568
+ private class CloseCallbackRecorder : CompletionBlock<AbsRouterCloseMethodIDL.IDLMethodCloseResultModel> {
569
+ var successResult: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel? = null
570
+ var failureCode: Int? = null
571
+ var failureMsg: String? = null
572
+
573
+ override fun onSuccess(
574
+ result: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel,
575
+ msg: String,
576
+ ) {
577
+ successResult = result
578
+ }
579
+
580
+ override fun onFailure(
581
+ code: Int,
582
+ msg: String,
583
+ data: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel?,
584
+ ) {
585
+ failureCode = code
586
+ failureMsg = msg
587
+ }
588
+
589
+ override fun onRawSuccess(data: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel?) = Unit
590
+ }
591
+
592
+ // endregion
593
+ }
@@ -1,20 +1,196 @@
1
- // Copyright (c) 2022 TikTok Pte. Ltd.
2
- // Licensed under the Apache License Version 2.0 that can be found in the
3
- // LICENSE file in the root directory of this source tree.
4
1
  package com.tiktok.sparkling.method.router
5
2
 
3
+ import android.content.Context
4
+ import com.tiktok.sparkling.method.registry.core.BridgePlatformType
5
+ import com.tiktok.sparkling.method.registry.core.IBridgeContext
6
+ import com.tiktok.sparkling.method.registry.core.IDLBridgeMethod
7
+ import com.tiktok.sparkling.method.registry.core.model.idl.CompletionBlock
8
+ import com.tiktok.sparkling.method.router.close.AbsRouterCloseMethodIDL
9
+ import com.tiktok.sparkling.method.router.close.RouterCloseMethod
10
+ import com.tiktok.sparkling.method.router.open.AbsRouterOpenMethodIDL
11
+ import com.tiktok.sparkling.method.router.open.RouterOpenMethod
12
+ import com.tiktok.sparkling.method.router.utils.IHostRouterDepend
13
+ import com.tiktok.sparkling.method.router.utils.RouterProvider
14
+ import io.mockk.every
15
+ import io.mockk.mockk
16
+ import io.mockk.verify
17
+ import org.junit.After
18
+ import org.junit.Assert.assertEquals
19
+ import org.junit.Assert.assertNotNull
20
+ import org.junit.Assert.assertNull
21
+ import org.junit.Assert.assertTrue
22
+ import org.junit.Before
6
23
  import org.junit.Test
24
+ import org.junit.runner.RunWith
25
+ import org.robolectric.RobolectricTestRunner
26
+ import org.robolectric.annotation.Config
7
27
 
8
- import org.junit.Assert.*
9
-
10
- /**
11
- * Example local unit test, which will execute on the development machine (host).
12
- *
13
- * See [testing documentation](http://d.android.com/tools/testing).
14
- */
28
+ @RunWith(RobolectricTestRunner::class)
29
+ @Config(manifest = Config.NONE, sdk = [33])
15
30
  class RouterMethodUnitTest {
31
+ private lateinit var bridgeContext: IBridgeContext
32
+ private lateinit var context: Context
33
+
34
+ @Before
35
+ fun setUp() {
36
+ context = mockk(relaxed = true)
37
+ bridgeContext = mockk(relaxed = true)
38
+ every { bridgeContext.context } returns context
39
+ RouterProvider.hostRouterDepend = null
40
+ }
41
+
42
+ @After
43
+ fun tearDown() {
44
+ RouterProvider.hostRouterDepend = null
45
+ }
46
+
47
+ @Test
48
+ fun openMethodFailsWhenSchemeBlank() {
49
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
50
+ val params = mockk<AbsRouterOpenMethodIDL.IDLMethodOpenParamModel>(relaxed = true)
51
+ every { params.scheme } returns " "
52
+ every { params.replace } returns false
53
+
54
+ val callback = OpenCallbackRecorder()
55
+ method.handle(params, callback, BridgePlatformType.LYNX)
56
+
57
+ assertTrue(callback.successResult == null)
58
+ assertEquals(IDLBridgeMethod.INVALID_PARAM, callback.failureCode)
59
+ assertTrue(callback.failureMsg?.contains("scheme") == true)
60
+ }
61
+
16
62
  @Test
17
- fun addition_isCorrect() {
18
- assertEquals(4, 2 + 2)
63
+ fun openMethodSucceedsWithValidScheme() {
64
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
65
+ every {
66
+ hostRouter.openScheme(any(), any(), any(), any(), any())
67
+ } returns true
68
+ RouterProvider.hostRouterDepend = hostRouter
69
+
70
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
71
+ val params = mockk<AbsRouterOpenMethodIDL.IDLMethodOpenParamModel>(relaxed = true)
72
+ every { params.scheme } returns "hybrid://lynxview?bundle=main.lynx.bundle"
73
+ every { params.replace } returns false
74
+ every { params.useSysBrowser } returns true
75
+ every { params.replaceType } returns null
76
+ every { params.extra } returns null
77
+
78
+ val callback = OpenCallbackRecorder()
79
+ method.handle(params, callback, BridgePlatformType.LYNX)
80
+
81
+ assertNotNull(callback.successResult)
82
+ assertNull(callback.failureCode)
83
+ verify(exactly = 1) {
84
+ hostRouter.openScheme(
85
+ bridgeContext,
86
+ "hybrid://lynxview?bundle=main.lynx.bundle",
87
+ any(),
88
+ BridgePlatformType.LYNX,
89
+ context,
90
+ )
91
+ }
92
+ }
93
+
94
+ @Test
95
+ fun openMethodFailsWhenReplaceTypeInvalid() {
96
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
97
+ RouterProvider.hostRouterDepend = hostRouter
98
+
99
+ val method = RouterOpenMethod().apply { setBridgeContext(bridgeContext) }
100
+ val params = mockk<AbsRouterOpenMethodIDL.IDLMethodOpenParamModel>(relaxed = true)
101
+ every { params.scheme } returns "hybrid://lynxview?bundle=main.lynx.bundle"
102
+ every { params.replace } returns true
103
+ every { params.replaceType } returns "not-a-valid-type"
104
+
105
+ val callback = OpenCallbackRecorder()
106
+ method.handle(params, callback, BridgePlatformType.LYNX)
107
+
108
+ assertEquals(IDLBridgeMethod.INVALID_PARAM, callback.failureCode)
109
+ assertTrue(callback.failureMsg?.contains("Invalid replaceType") == true)
110
+ }
111
+
112
+ @Test
113
+ fun closeMethodFailsWhenRouterNotRegistered() {
114
+ val method = RouterCloseMethod().apply { setBridgeContext(bridgeContext) }
115
+ val params = mockk<AbsRouterCloseMethodIDL.IDLMethodCloseParamModel>(relaxed = true)
116
+ every { params.containerID } returns "container-1"
117
+ every { params.animated } returns true
118
+
119
+ val callback = CloseCallbackRecorder()
120
+ method.handle(params, callback, BridgePlatformType.LYNX)
121
+
122
+ assertEquals(IDLBridgeMethod.FAIL, callback.failureCode)
123
+ assertTrue(callback.failureMsg?.contains("Router service not available") == true)
124
+ }
125
+
126
+ @Test
127
+ fun closeMethodSucceedsWhenRouterClosesView() {
128
+ val hostRouter = mockk<IHostRouterDepend>(relaxed = true)
129
+ every {
130
+ hostRouter.closeView(any(), any(), any(), any())
131
+ } returns true
132
+ RouterProvider.hostRouterDepend = hostRouter
133
+
134
+ val method = RouterCloseMethod().apply { setBridgeContext(bridgeContext) }
135
+ val params = mockk<AbsRouterCloseMethodIDL.IDLMethodCloseParamModel>(relaxed = true)
136
+ every { params.containerID } returns "container-2"
137
+ every { params.animated } returns false
138
+
139
+ val callback = CloseCallbackRecorder()
140
+ method.handle(params, callback, BridgePlatformType.LYNX)
141
+
142
+ assertNotNull(callback.successResult)
143
+ assertNull(callback.failureCode)
144
+ verify(exactly = 1) {
145
+ hostRouter.closeView(bridgeContext, BridgePlatformType.LYNX, "container-2", false)
146
+ }
147
+ }
148
+
149
+ private class OpenCallbackRecorder : CompletionBlock<AbsRouterOpenMethodIDL.IDLMethodOpenResultModel> {
150
+ var successResult: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel? = null
151
+ var failureCode: Int? = null
152
+ var failureMsg: String? = null
153
+
154
+ override fun onSuccess(
155
+ result: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel,
156
+ msg: String,
157
+ ) {
158
+ successResult = result
159
+ }
160
+
161
+ override fun onFailure(
162
+ code: Int,
163
+ msg: String,
164
+ data: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel?,
165
+ ) {
166
+ failureCode = code
167
+ failureMsg = msg
168
+ }
169
+
170
+ override fun onRawSuccess(data: AbsRouterOpenMethodIDL.IDLMethodOpenResultModel?) = Unit
171
+ }
172
+
173
+ private class CloseCallbackRecorder : CompletionBlock<AbsRouterCloseMethodIDL.IDLMethodCloseResultModel> {
174
+ var successResult: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel? = null
175
+ var failureCode: Int? = null
176
+ var failureMsg: String? = null
177
+
178
+ override fun onSuccess(
179
+ result: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel,
180
+ msg: String,
181
+ ) {
182
+ successResult = result
183
+ }
184
+
185
+ override fun onFailure(
186
+ code: Int,
187
+ msg: String,
188
+ data: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel?,
189
+ ) {
190
+ failureCode = code
191
+ failureMsg = msg
192
+ }
193
+
194
+ override fun onRawSuccess(data: AbsRouterCloseMethodIDL.IDLMethodCloseResultModel?) = Unit
19
195
  }
20
196
  }
@@ -17,7 +17,7 @@ export function getDevServerBaseURL() {
17
17
  }
18
18
  // Prefer the actual URL the native side used to load this bundle.
19
19
  // lynx.__globalProps.queryItems.url is set by the SDK from the scheme's
20
- // url= parameter (e.g. "http://192.168.1.100:5969/main.lynx.bundle").
20
+ // url= parameter (e.g. "http://127.0.0.1:5969/main.lynx.bundle").
21
21
  if (typeof lynx !== 'undefined' && ((_b = (_a = lynx === null || lynx === void 0 ? void 0 : lynx.__globalProps) === null || _a === void 0 ? void 0 : _a.queryItems) === null || _b === void 0 ? void 0 : _b.url)) {
22
22
  const pageUrl = lynx.__globalProps.queryItems.url;
23
23
  if (pageUrl.startsWith('http://') || pageUrl.startsWith('https://')) {
@@ -1 +1 @@
1
- {"version":3,"file":"devServer.js","sourceRoot":"","sources":["../../src/devServer.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,yEAAyE;AACzE,0DAA0D;AAM1D;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB;;IAC/B,IAAI,CAAC;QACD,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7C,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,kEAAkE;QAClE,wEAAwE;QACxE,sEAAsE;QACtE,IAAI,OAAO,IAAI,KAAK,WAAW,KAAI,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,aAAa,0CAAE,UAAU,0CAAE,GAAG,CAAA,EAAE,CAAC;YACtE,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC;YAClD,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClE,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBAC3C,IAAI,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACxC,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;gBAC/C,CAAC;YACL,CAAC;QACL,CAAC;QAED,iCAAiC;QACjC,IAAI,OAAO,uBAAuB,KAAK,QAAQ,IAAI,uBAAuB,EAAE,CAAC;YACzE,OAAO,uBAAuB,CAAC;QACnC,CAAC;IACL,CAAC;IAAC,WAAM,CAAC;QACL,wBAAwB;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"devServer.js","sourceRoot":"","sources":["../../src/devServer.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,yEAAyE;AACzE,0DAA0D;AAM1D;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB;;IAC/B,IAAI,CAAC;QACD,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7C,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,kEAAkE;QAClE,wEAAwE;QACxE,kEAAkE;QAClE,IAAI,OAAO,IAAI,KAAK,WAAW,KAAI,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,aAAa,0CAAE,UAAU,0CAAE,GAAG,CAAA,EAAE,CAAC;YACtE,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC;YAClD,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClE,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBAC3C,IAAI,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACxC,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;gBAC/C,CAAC;YACL,CAAC;QACL,CAAC;QAED,iCAAiC;QACjC,IAAI,OAAO,uBAAuB,KAAK,QAAQ,IAAI,uBAAuB,EAAE,CAAC;YACzE,OAAO,uBAAuB,CAAC;QACnC,CAAC;IACL,CAAC;IAAC,WAAM,CAAC;QACL,wBAAwB;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC"}
@@ -1 +1 @@
1
- {"fileNames":["../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../src/devServer.ts","../../../sparkling-method/dist/types.d.ts","../../../sparkling-method/dist/index.d.ts","../src/close/close.d.ts","../src/close/close.ts","../src/open/open.d.ts","../src/open/open.ts","../src/navigate/navigate.d.ts","../src/navigate/navigate.ts","../index.ts","../../../../node_modules/.pnpm/@jest+expect-utils@29.7.0/node_modules/@jest/expect-utils/build/index.d.ts","../../../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/index.d.ts","../../../../node_modules/.pnpm/@sinclair+typebox@0.27.10/node_modules/@sinclair/typebox/typebox.d.ts","../../../../node_modules/.pnpm/@jest+schemas@29.6.3/node_modules/@jest/schemas/build/index.d.ts","../../../../node_modules/.pnpm/pretty-format@29.7.0/node_modules/pretty-format/build/index.d.ts","../../../../node_modules/.pnpm/jest-diff@29.7.0/node_modules/jest-diff/build/index.d.ts","../../../../node_modules/.pnpm/jest-matcher-utils@29.7.0/node_modules/jest-matcher-utils/build/index.d.ts","../../../../node_modules/.pnpm/expect@29.7.0/node_modules/expect/build/index.d.ts","../../../../node_modules/.pnpm/@types+jest@29.5.14/node_modules/@types/jest/index.d.ts"],"fileIdsList":[[27],[29,32],[25,31],[29],[26,30],[28],[18,19,20,21,22,23],[17,18],[20],[15,21,22],[15,17,20],[16]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"4fc8c80e3f83b51aa0be32aca09faa0a00968109a8b2f9a1172999ae422f6a58","signature":"51d6a96a88f06928d2bf9400f47a225ab2ca51b0c290f5c5aeb232b04f0aca9d"},"f7633d7fe074d6a4f1d215bbc7a3b27fc0419a5a3bfbbe054ac07f3dbbc68487","bdddec6a06096ab2fc1460e59d0e8c1c954ffe72290bee32bc1ecce28ebe9393","582fe2e974682a46dcee0b08fac869d96c56ec3421ad48633f6289f497acc3fd",{"version":"437d1d1bf67ea36dbf6f48e75dc75f7385e58e91d5a0acc493cbc1cec23c59fa","signature":"f421088758adc5b04e44c1548f2d7ac1cebbdda08458c8e493a490002c8e6f7c"},"a078d8603e9ef86dbed7ba0ff50276734c4cefbc5e64a6ab07144911ff701e3b",{"version":"ad4667e00d7fce51bded3e210d1f271e651ea5516cea8ba121201bc1614e022f","signature":"dd1d64b74f46208aecdee3727641eff5c0262826752e37a19ca6b2eb474d45af"},"e3963382b8a425258f14e665e7a0d39b024f1e71b7c9200096cdc9604ddf5f94",{"version":"712aba6de5cf5a6724f0ecdc5ec1176d35e7ed1071e36fb69d696dabfb1e7088","signature":"e902e6d4f2877e301fa4cf11b50a18c6a1c078bd34edecb15ae62459310004d0"},{"version":"f4ef2dc4d2182f03365773c1c488eb01ebd07186a7d7e3c51eea5eed6c0d5252","signature":"4acdb0a03d91ac0b94410f47337f1dba8f947a74300a305e1e6a14fef3933d07"},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"e1028394c1cf96d5d057ecc647e31e457b919092f882ed0c7092152b077fed9d","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1}],"root":[15,19,21,23,24],"options":{"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":false,"esModuleInterop":true,"module":5,"outDir":"./","skipLibCheck":true,"sourceMap":true,"strict":true,"target":2},"referencedMap":[[28,1],[33,2],[32,3],[30,4],[31,5],[29,6],[24,7],[19,8],[22,9],[23,10],[21,11],[17,12]],"latestChangedDtsFile":"./index.d.ts","version":"5.9.3"}
1
+ {"fileNames":["../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../src/devServer.ts","../../../sparkling-method/dist/types.d.ts","../../../sparkling-method/dist/index.d.ts","../src/close/close.d.ts","../src/close/close.ts","../src/open/open.d.ts","../src/open/open.ts","../src/navigate/navigate.d.ts","../src/navigate/navigate.ts","../index.ts","../../../../node_modules/.pnpm/@jest+expect-utils@29.7.0/node_modules/@jest/expect-utils/build/index.d.ts","../../../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/index.d.ts","../../../../node_modules/.pnpm/@sinclair+typebox@0.27.10/node_modules/@sinclair/typebox/typebox.d.ts","../../../../node_modules/.pnpm/@jest+schemas@29.6.3/node_modules/@jest/schemas/build/index.d.ts","../../../../node_modules/.pnpm/pretty-format@29.7.0/node_modules/pretty-format/build/index.d.ts","../../../../node_modules/.pnpm/jest-diff@29.7.0/node_modules/jest-diff/build/index.d.ts","../../../../node_modules/.pnpm/jest-matcher-utils@29.7.0/node_modules/jest-matcher-utils/build/index.d.ts","../../../../node_modules/.pnpm/expect@29.7.0/node_modules/expect/build/index.d.ts","../../../../node_modules/.pnpm/@types+jest@29.5.14/node_modules/@types/jest/index.d.ts"],"fileIdsList":[[27],[29,32],[25,31],[29],[26,30],[28],[18,19,20,21,22,23],[17,18],[20],[15,21,22],[15,17,20],[16]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"a4a7d5010a7e00e0249b9862f3ca68e90f7b5b7d2fb713f5d995ffc9b157e0e7","signature":"51d6a96a88f06928d2bf9400f47a225ab2ca51b0c290f5c5aeb232b04f0aca9d"},"f7633d7fe074d6a4f1d215bbc7a3b27fc0419a5a3bfbbe054ac07f3dbbc68487","bdddec6a06096ab2fc1460e59d0e8c1c954ffe72290bee32bc1ecce28ebe9393","582fe2e974682a46dcee0b08fac869d96c56ec3421ad48633f6289f497acc3fd",{"version":"437d1d1bf67ea36dbf6f48e75dc75f7385e58e91d5a0acc493cbc1cec23c59fa","signature":"f421088758adc5b04e44c1548f2d7ac1cebbdda08458c8e493a490002c8e6f7c"},"a078d8603e9ef86dbed7ba0ff50276734c4cefbc5e64a6ab07144911ff701e3b",{"version":"ad4667e00d7fce51bded3e210d1f271e651ea5516cea8ba121201bc1614e022f","signature":"dd1d64b74f46208aecdee3727641eff5c0262826752e37a19ca6b2eb474d45af"},"e3963382b8a425258f14e665e7a0d39b024f1e71b7c9200096cdc9604ddf5f94",{"version":"712aba6de5cf5a6724f0ecdc5ec1176d35e7ed1071e36fb69d696dabfb1e7088","signature":"e902e6d4f2877e301fa4cf11b50a18c6a1c078bd34edecb15ae62459310004d0"},{"version":"f4ef2dc4d2182f03365773c1c488eb01ebd07186a7d7e3c51eea5eed6c0d5252","signature":"4acdb0a03d91ac0b94410f47337f1dba8f947a74300a305e1e6a14fef3933d07"},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"e1028394c1cf96d5d057ecc647e31e457b919092f882ed0c7092152b077fed9d","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1}],"root":[15,19,21,23,24],"options":{"composite":true,"declaration":true,"declarationMap":true,"emitDeclarationOnly":false,"esModuleInterop":true,"module":5,"outDir":"./","skipLibCheck":true,"sourceMap":true,"strict":true,"target":2},"referencedMap":[[28,1],[33,2],[32,3],[30,4],[31,5],[29,6],[24,7],[19,8],[22,9],[23,10],[21,11],[17,12]],"latestChangedDtsFile":"./index.d.ts","version":"5.9.3"}
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'Sparkling-Router'
3
- s.version = "2.1.0-rc.24"
3
+ s.version = "2.1.0-rc.26"
4
4
  s.summary = "iOS navigation router SDK for Sparkling"
5
5
  s.description = "iOS navigation router SDK for Sparkling"
6
6
  s.license = "Apache-2.0"
@@ -26,6 +26,13 @@ Pod::Spec.new do |s|
26
26
  ]
27
27
  end
28
28
 
29
+ s.test_spec 'Tests' do |tests|
30
+ tests.requires_app_host = false
31
+ tests.source_files = [
32
+ 'SparklingMethodTests/**/*.{h,m,swift}',
33
+ ]
34
+ end
35
+
29
36
  s.dependency 'SparklingMethod/Core'
30
37
  s.dependency 'SparklingMethod/DIProvider'
31
38
  s.dependency 'Mantle', '~> 2.2.0'
@@ -0,0 +1,119 @@
1
+ // Copyright 2025 The Sparkling Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import XCTest
6
+ import SparklingMethod
7
+ @testable import Sparkling_Router
8
+
9
+ class SPKRouterTest: XCTestCase {
10
+
11
+ // MARK: - CloseMethod Tests
12
+
13
+ func testCloseMethodName() {
14
+ let method = CloseMethod()
15
+ XCTAssertEqual(method.methodName, "router.close")
16
+ XCTAssertEqual(CloseMethod.methodName(), "router.close")
17
+ }
18
+
19
+ func testCloseMethodModels() {
20
+ let method = CloseMethod()
21
+ XCTAssertTrue(method.paramsModelClass is CloseMethodParamModel.Type)
22
+ XCTAssertTrue(method.resultModelClass is EmptyMethodModelClass.Type)
23
+ }
24
+
25
+ func testCloseMethodParamModelValidation() {
26
+ let paramModel = CloseMethodParamModel()
27
+
28
+ XCTAssertTrue(type(of: paramModel).requiredKeyPaths()?.isEmpty ?? false)
29
+
30
+ paramModel.containerID = "test-container"
31
+ XCTAssertNoThrow(try paramModel.validate())
32
+ }
33
+
34
+ func testCloseMethodParamModelJSONMapping() {
35
+ let paramModel = CloseMethodParamModel()
36
+ paramModel.containerID = "test-container"
37
+ paramModel.animated = true
38
+
39
+ XCTAssertEqual(type(of: paramModel).jsonKeyPathsByPropertyKey() as? [String: String], [
40
+ "containerID": "containerID",
41
+ "animated": "animated"
42
+ ])
43
+ }
44
+
45
+ // MARK: - OpenMethod Tests
46
+
47
+ func testOpenMethodName() {
48
+ let method = OpenMethod()
49
+ XCTAssertEqual(method.methodName, "router.open")
50
+ XCTAssertEqual(OpenMethod.methodName(), "router.open")
51
+ }
52
+
53
+ func testOpenMethodModels() {
54
+ let method = OpenMethod()
55
+ XCTAssertTrue(method.paramsModelClass is OpenMethodParamModel.Type)
56
+ XCTAssertTrue(method.resultModelClass is EmptyMethodModelClass.Type)
57
+ }
58
+
59
+ func testOpenMethodParamModelValidation() throws {
60
+ XCTAssertThrowsError(try OpenMethodParamModel(dictionary: [:]))
61
+
62
+ let paramModel = try XCTUnwrap(try OpenMethodParamModel.from(dict: ["scheme": "https://example.com"]))
63
+ paramModel.scheme = "https://example.com"
64
+ XCTAssertNoThrow(try paramModel.validate())
65
+ }
66
+
67
+ func testOpenMethodParamModelJSONMapping() {
68
+ let paramModel = OpenMethodParamModel()
69
+
70
+ XCTAssertEqual(type(of: paramModel).jsonKeyPathsByPropertyKey() as? [String: String], [
71
+ "scheme": "scheme",
72
+ "replace": "replace",
73
+ "replaceType": "replaceType",
74
+ "useSysBrowser": "useSysBrowser",
75
+ "animated": "animated",
76
+ "interceptor": "interceptor",
77
+ "extra": "extra"
78
+ ])
79
+ }
80
+
81
+ func testOpenMethodParamModelDefaultValues() {
82
+ let paramModel = OpenMethodParamModel()
83
+ paramModel.scheme = "test-scheme"
84
+
85
+ XCTAssertFalse(paramModel.replace)
86
+ XCTAssertFalse(paramModel.useSysBrowser)
87
+ XCTAssertFalse(paramModel.animated)
88
+ }
89
+
90
+ func testOpenMethodParamModelAllFields() {
91
+ let paramModel = OpenMethodParamModel()
92
+ paramModel.scheme = "test-scheme"
93
+ paramModel.replace = true
94
+ paramModel.replaceType = "all"
95
+ paramModel.useSysBrowser = true
96
+ paramModel.animated = true
97
+ paramModel.interceptor = "test-interceptor"
98
+ let extraDict = NSDictionary(dictionary: ["key": "value"])
99
+ paramModel.extra = extraDict
100
+
101
+ XCTAssertEqual(paramModel.scheme, "test-scheme")
102
+ XCTAssertTrue(paramModel.replace)
103
+ XCTAssertEqual(paramModel.replaceType, "all")
104
+ XCTAssertTrue(paramModel.useSysBrowser)
105
+ XCTAssertTrue(paramModel.animated)
106
+ XCTAssertEqual(paramModel.interceptor, "test-interceptor")
107
+ XCTAssertEqual(paramModel.extra, extraDict)
108
+ }
109
+
110
+ func testOpenMethodParamModelFromDictParsesExtraObject() throws {
111
+ let dict: [String: Any] = [
112
+ "scheme": "hybrid://lynxview_page?bundle=a.lynx.bundle",
113
+ "extra": ["foo": "bar"],
114
+ ]
115
+ let model = try XCTUnwrap(try OpenMethodParamModel.from(dict: dict))
116
+ XCTAssertNotNil(model.extra)
117
+ XCTAssertEqual(model.extra?["foo"] as? String, "bar")
118
+ }
119
+ }
@@ -29,7 +29,7 @@
29
29
  }
30
30
  },
31
31
  "android": {
32
- "packageName": "com.tiktok.sparkling.methods.router",
32
+ "packageName": "com.tiktok.sparkling.method.router",
33
33
  "className": "RouterMethod",
34
34
  "buildGradle": "android/build.gradle.kts"
35
35
  },
@@ -43,4 +43,4 @@
43
43
  "entryPoint": "index.ts",
44
44
  "bundleName": "router.bundle"
45
45
  }
46
- }
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkling-navigation",
3
- "version": "2.1.0-rc.24",
3
+ "version": "2.1.0-rc.26",
4
4
  "homepage": "https://tiktok.github.io/sparkling/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,7 +24,7 @@
24
24
  "license": "Apache-2.0",
25
25
  "description": "none",
26
26
  "dependencies": {
27
- "sparkling-method": "2.1.0-rc.24"
27
+ "sparkling-method": "2.1.0-rc.26"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/jest": "^29.5.12",
package/src/devServer.ts CHANGED
@@ -22,7 +22,7 @@ export function getDevServerBaseURL(): string | undefined {
22
22
 
23
23
  // Prefer the actual URL the native side used to load this bundle.
24
24
  // lynx.__globalProps.queryItems.url is set by the SDK from the scheme's
25
- // url= parameter (e.g. "http://192.168.1.100:5969/main.lynx.bundle").
25
+ // url= parameter (e.g. "http://127.0.0.1:5969/main.lynx.bundle").
26
26
  if (typeof lynx !== 'undefined' && lynx?.__globalProps?.queryItems?.url) {
27
27
  const pageUrl = lynx.__globalProps.queryItems.url;
28
28
  if (pageUrl.startsWith('http://') || pageUrl.startsWith('https://')) {