@shortkitsdk/react-native 0.2.38 → 0.2.39
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.kts +2 -2
- package/android/libs/shortkit-release.aar +0 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +19 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +1 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHost.kt +29 -25
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +37 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +1 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +1 -1
- package/android/src/test/java/com/shortkit/reactnative/ReactCarouselOverlayHostLifecycleTest.kt +19 -0
- package/android/src/test/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHostLifecycleTest.kt +22 -0
- package/android/src/test/java/com/shortkit/reactnative/ShortKitBridgeLongPressTest.kt +148 -0
- package/ios/ShortKitBridge.swift +2 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3 -3
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +3 -3
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3 -3
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +3 -3
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +5 -5
- package/package.json +1 -1
package/android/build.gradle.kts
CHANGED
|
@@ -37,8 +37,8 @@ dependencies {
|
|
|
37
37
|
implementation("com.facebook.react:react-android")
|
|
38
38
|
// Composite build in sample/android/settings.gradle substitutes this with
|
|
39
39
|
// live android_sdk source automatically (dev.shortkit:shortkit → :shortkit project).
|
|
40
|
-
implementation("dev.shortkit:shortkit:0.2.37")
|
|
41
|
-
|
|
40
|
+
// implementation("dev.shortkit:shortkit:0.2.37")
|
|
41
|
+
implementation(files("libs/shortkit-release.aar"))
|
|
42
42
|
// Transitive deps needed when using local AAR (Maven artifact bundles these automatically)
|
|
43
43
|
implementation("androidx.media3:media3-exoplayer:1.5.1")
|
|
44
44
|
implementation("androidx.media3:media3-exoplayer-hls:1.5.1")
|
|
Binary file
|
|
@@ -15,6 +15,8 @@ import kotlinx.coroutines.CoroutineScope
|
|
|
15
15
|
import kotlinx.coroutines.Dispatchers
|
|
16
16
|
import kotlinx.coroutines.Job
|
|
17
17
|
import kotlinx.coroutines.SupervisorJob
|
|
18
|
+
import kotlinx.coroutines.cancel
|
|
19
|
+
import kotlinx.coroutines.isActive
|
|
18
20
|
import kotlinx.coroutines.launch
|
|
19
21
|
import kotlinx.coroutines.withContext
|
|
20
22
|
import kotlinx.serialization.encodeToString
|
|
@@ -52,10 +54,20 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
|
|
|
52
54
|
private var surface: ReactSurface? = null
|
|
53
55
|
private var pendingProps: Bundle? = null
|
|
54
56
|
private var isInLayoutPass: Boolean = false
|
|
55
|
-
private
|
|
57
|
+
private var ioScope: CoroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
56
58
|
private var pendingWriteJob: Job? = null
|
|
57
59
|
private var configureGeneration: Int = 0
|
|
58
60
|
|
|
61
|
+
/** Test-only accessor for ioScope. */
|
|
62
|
+
@androidx.annotation.VisibleForTesting
|
|
63
|
+
internal val ioScopeForTest: CoroutineScope get() = ioScope
|
|
64
|
+
|
|
65
|
+
/** Test-only triggers for window lifecycle (protected in View). */
|
|
66
|
+
@androidx.annotation.VisibleForTesting
|
|
67
|
+
internal fun callOnDetachedFromWindow() = onDetachedFromWindow()
|
|
68
|
+
@androidx.annotation.VisibleForTesting
|
|
69
|
+
internal fun callOnAttachedToWindow() = onAttachedToWindow()
|
|
70
|
+
|
|
59
71
|
/** Unique identifier for this overlay instance, used for event routing. */
|
|
60
72
|
val surfaceId: String = java.util.UUID.randomUUID().toString()
|
|
61
73
|
|
|
@@ -405,6 +417,9 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
|
|
|
405
417
|
|
|
406
418
|
override fun onAttachedToWindow() {
|
|
407
419
|
super.onAttachedToWindow()
|
|
420
|
+
if (!ioScope.isActive) {
|
|
421
|
+
ioScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
422
|
+
}
|
|
408
423
|
|
|
409
424
|
val parentView = parent as? android.view.View
|
|
410
425
|
if (parentView != null && parentView.width > 0 && parentView.height > 0 && (width == 0 || height == 0)) {
|
|
@@ -467,11 +482,14 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
|
|
|
467
482
|
|
|
468
483
|
public override fun onDetachedFromWindow() {
|
|
469
484
|
super.onDetachedFromWindow()
|
|
485
|
+
layoutHandler.removeCallbacks(layoutRunnable)
|
|
470
486
|
pendingWriteJob?.cancel()
|
|
471
487
|
pendingWriteJob = null
|
|
488
|
+
ioScope.cancel()
|
|
472
489
|
// Non-destructive: surface survives cell recycle and view transitions.
|
|
473
490
|
// iOS parity: ReactCarouselOverlayHost.swift has no willMove/didMoveToWindow
|
|
474
491
|
// override — surface stops only in deinit. Mirrors the non-destructive
|
|
475
492
|
// contract enforced on ReactVideoCarouselOverlayHost.
|
|
493
|
+
// ioScope is recreated in onAttachedToWindow if needed.
|
|
476
494
|
}
|
|
477
495
|
}
|
|
@@ -300,6 +300,7 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
|
|
|
300
300
|
|
|
301
301
|
override fun onDetachedFromWindow() {
|
|
302
302
|
super.onDetachedFromWindow()
|
|
303
|
+
layoutHandler.removeCallbacks(layoutRunnable)
|
|
303
304
|
// Cancel flow subscriptions to avoid emitting events for off-screen cells.
|
|
304
305
|
// Do NOT stop the surface — keep JS alive so subscriptions persist across
|
|
305
306
|
// RecyclerView detach/reattach cycles. This matches iOS behavior where the
|
|
@@ -156,6 +156,17 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
156
156
|
|
|
157
157
|
private var flowScope: CoroutineScope? = null
|
|
158
158
|
|
|
159
|
+
/** Test-only read-write accessor for flowScope. Write path lets tests inject a scope
|
|
160
|
+
* without needing a full ShortKitPlayer mock. */
|
|
161
|
+
@androidx.annotation.VisibleForTesting
|
|
162
|
+
internal var flowScopeForTest: CoroutineScope?
|
|
163
|
+
get() = flowScope
|
|
164
|
+
set(value) { flowScope = value }
|
|
165
|
+
|
|
166
|
+
/** Test-only trigger for onDetachedFromWindow (protected in View). */
|
|
167
|
+
@androidx.annotation.VisibleForTesting
|
|
168
|
+
internal fun callOnDetachedFromWindow() = onDetachedFromWindow()
|
|
169
|
+
|
|
159
170
|
// ------------------------------------------------------------------
|
|
160
171
|
// Fabric layout workaround
|
|
161
172
|
// ------------------------------------------------------------------
|
|
@@ -277,9 +288,10 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
277
288
|
putBoolean("isActive", false)
|
|
278
289
|
putString("playerState", "idle")
|
|
279
290
|
putBoolean("isMuted", cachedIsMuted)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
291
|
+
val initialIdx = item.clampedInitialPageIndex
|
|
292
|
+
if (item.videos.indices.contains(initialIdx)) {
|
|
293
|
+
putString("activeVideo", ShortKitBridge.serializeContentItemToJSON(item.videos[initialIdx]))
|
|
294
|
+
putInt("activeVideoIndex", initialIdx)
|
|
283
295
|
}
|
|
284
296
|
}
|
|
285
297
|
|
|
@@ -299,11 +311,12 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
299
311
|
putBoolean("isActive", false)
|
|
300
312
|
putString("playerState", "idle")
|
|
301
313
|
putBoolean("isMuted", cachedIsMuted)
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
val initialIdx = item.clampedInitialPageIndex
|
|
315
|
+
putInt("activeVideoIndex", initialIdx)
|
|
316
|
+
if (item.videos.indices.contains(initialIdx)) {
|
|
304
317
|
putString(
|
|
305
318
|
"activeVideo",
|
|
306
|
-
ShortKitBridge.serializeContentItemToJSON(item.videos
|
|
319
|
+
ShortKitBridge.serializeContentItemToJSON(item.videos[initialIdx]),
|
|
307
320
|
)
|
|
308
321
|
} else {
|
|
309
322
|
putNull("activeVideo")
|
|
@@ -633,26 +646,17 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
633
646
|
|
|
634
647
|
public override fun onDetachedFromWindow() {
|
|
635
648
|
super.onDetachedFromWindow()
|
|
636
|
-
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
649
|
+
layoutHandler.removeCallbacks(layoutRunnable)
|
|
650
|
+
// Cancel flow subscriptions so StateFlow collectors don't retain this host
|
|
651
|
+
// after permanent cell eviction. onAttachedToWindow re-subscribes via the
|
|
652
|
+
// existing `if (flowScope == null)` guard; attach(player) calls
|
|
653
|
+
// resubscribeToPlayer unconditionally, which is the canonical re-entry point.
|
|
641
654
|
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
// gating out every updateActiveVideo emit.
|
|
648
|
-
//
|
|
649
|
-
// Player flow subscriptions are idempotently re-established by
|
|
650
|
-
// attach() -> resubscribeToPlayer (which cancels the prior scope
|
|
651
|
-
// on entry) when the cell rebinds its player on next visit.
|
|
652
|
-
//
|
|
653
|
-
// True teardown happens via release() (called by the cell on
|
|
654
|
-
// permanent disposal) or implicitly when the host is GC'd along
|
|
655
|
-
// with the cell.
|
|
655
|
+
// Do NOT call stopTimeCoalescing() here — the time coalescer is owned
|
|
656
|
+
// exclusively by activatePlayback/deactivatePlayback. Stopping it on detach
|
|
657
|
+
// breaks the kept-attached-zone scenario where activatePlayback won't re-fire.
|
|
658
|
+
flowScope?.cancel()
|
|
659
|
+
flowScope = null
|
|
656
660
|
}
|
|
657
661
|
|
|
658
662
|
}
|
|
@@ -33,6 +33,7 @@ import com.shortkit.sdk.feed.ShortKitRefreshState
|
|
|
33
33
|
import com.shortkit.sdk.config.VideoOverlayMode
|
|
34
34
|
import com.shortkit.sdk.feed.FeedPreload
|
|
35
35
|
import com.shortkit.sdk.feed.ShortKitFeedFragment
|
|
36
|
+
import com.shortkit.sdk.feed.VideoCarouselCellLongPressState
|
|
36
37
|
import kotlinx.coroutines.CoroutineScope
|
|
37
38
|
import kotlinx.coroutines.Dispatchers
|
|
38
39
|
import kotlinx.coroutines.SupervisorJob
|
|
@@ -446,6 +447,7 @@ class ShortKitBridge(
|
|
|
446
447
|
videos = videoInputs,
|
|
447
448
|
title = itemObj.optString("title", null),
|
|
448
449
|
description = itemObj.optString("description", null),
|
|
450
|
+
initialPageIndex = itemObj.optInt("initialPageIndex", -1).takeIf { it >= 0 },
|
|
449
451
|
author = itemObj.optString("author", null),
|
|
450
452
|
section = itemObj.optString("section", null),
|
|
451
453
|
articleUrl = itemObj.optString("articleUrl", null),
|
|
@@ -551,6 +553,13 @@ class ShortKitBridge(
|
|
|
551
553
|
private var shortKit: ShortKit? = null
|
|
552
554
|
private var scope: CoroutineScope? = null
|
|
553
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Factory for [WritableMap] instances. Overridden in unit tests with
|
|
558
|
+
* `{ JavaOnlyMap() }` to avoid the JNI initialisation that
|
|
559
|
+
* [Arguments.createMap] requires. Production code always uses the default.
|
|
560
|
+
*/
|
|
561
|
+
internal var createMap: () -> WritableMap = { Arguments.createMap() }
|
|
562
|
+
|
|
554
563
|
/** Preload handles keyed by UUID, consumed by feed views via preloadId prop. */
|
|
555
564
|
var preloadHandles = mutableMapOf<String, FeedPreload>()
|
|
556
565
|
private set
|
|
@@ -733,6 +742,11 @@ class ShortKitBridge(
|
|
|
733
742
|
fragment.onVideoCarouselCellTap = { payload ->
|
|
734
743
|
emitVideoCarouselCellTap(payload.id, payload.index, payload.pageIndex)
|
|
735
744
|
}
|
|
745
|
+
fragment.onVideoCarouselCellLongPress = { payload ->
|
|
746
|
+
emitVideoCarouselCellLongPress(
|
|
747
|
+
payload.id, payload.index, payload.pageIndex, payload.state
|
|
748
|
+
)
|
|
749
|
+
}
|
|
736
750
|
// Per-feed transition event. Fires once per transition, bound to this
|
|
737
751
|
// fragment. Replaces the global player.feedTransition.collect pattern
|
|
738
752
|
// (which fanned every feed's transitions out to every mounted
|
|
@@ -934,6 +948,29 @@ class ShortKitBridge(
|
|
|
934
948
|
emitEventOnMain("onVideoCarouselCellTap", params)
|
|
935
949
|
}
|
|
936
950
|
|
|
951
|
+
/**
|
|
952
|
+
* Emit `onVideoCarouselCellLongPress` with the active surface's feedId.
|
|
953
|
+
* Mirrors iOS ShortKitBridge.emitOnMain("onVideoCarouselCellLongPress").
|
|
954
|
+
* State enum is lowercased so JS receives "began" | "ended" | "cancelled"
|
|
955
|
+
* matching the iOS bridge output.
|
|
956
|
+
*/
|
|
957
|
+
internal fun emitVideoCarouselCellLongPress(
|
|
958
|
+
itemId: String,
|
|
959
|
+
index: Int,
|
|
960
|
+
pageIndex: Int,
|
|
961
|
+
state: VideoCarouselCellLongPressState,
|
|
962
|
+
) {
|
|
963
|
+
val feedId = activeSurfaceFeedId()
|
|
964
|
+
val params = createMap().apply {
|
|
965
|
+
putString("feedId", feedId)
|
|
966
|
+
putString("id", itemId)
|
|
967
|
+
putInt("index", index)
|
|
968
|
+
putInt("pageIndex", pageIndex)
|
|
969
|
+
putString("state", state.name.lowercase())
|
|
970
|
+
}
|
|
971
|
+
emitEventOnMain("onVideoCarouselCellLongPress", params)
|
|
972
|
+
}
|
|
973
|
+
|
|
937
974
|
// ------------------------------------------------------------------
|
|
938
975
|
// Player commands
|
|
939
976
|
// ------------------------------------------------------------------
|
|
@@ -205,6 +205,7 @@ class ShortKitFeedView(context: Context) : FrameLayout(context) {
|
|
|
205
205
|
synchronized(ShortKitBridge.staticPendingFeedViews) {
|
|
206
206
|
ShortKitBridge.staticPendingFeedViews.remove(this)
|
|
207
207
|
}
|
|
208
|
+
handler.removeCallbacks(constrainRunnable)
|
|
208
209
|
// Mirror iOS willMove(toWindow:) at react_native_sdk/ios/ShortKitFeedView.swift:91-99 —
|
|
209
210
|
// suspend only if leaving the window AND the prop says we should be active.
|
|
210
211
|
// When active=false the host has already driven the fragment into deactivate
|
|
@@ -352,7 +352,7 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
352
352
|
"onVideoCarouselCellTap" -> emitOnVideoCarouselCellTap(params)
|
|
353
353
|
"onVideoCarouselItemChanged" -> emitOnVideoCarouselItemChanged(params)
|
|
354
354
|
"onCarouselActiveVideoCompleted" -> emitOnCarouselActiveVideoCompleted(params)
|
|
355
|
-
"
|
|
355
|
+
"onVideoCarouselCellLongPress" -> emitOnVideoCarouselCellLongPress(params)
|
|
356
356
|
else -> {
|
|
357
357
|
android.util.Log.w("SK:Module", "sendEvent: unknown event name '$name', using legacy emitter")
|
|
358
358
|
reactApplicationContext
|
package/android/src/test/java/com/shortkit/reactnative/ReactCarouselOverlayHostLifecycleTest.kt
CHANGED
|
@@ -8,7 +8,9 @@ import com.facebook.react.interfaces.TaskInterface
|
|
|
8
8
|
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
9
9
|
import com.shortkit.sdk.model.CarouselImage
|
|
10
10
|
import com.shortkit.sdk.model.ImageCarouselItem
|
|
11
|
+
import kotlinx.coroutines.isActive
|
|
11
12
|
import org.junit.Assert.assertEquals
|
|
13
|
+
import org.junit.Assert.assertFalse
|
|
12
14
|
import org.junit.Assert.assertNotNull
|
|
13
15
|
import org.junit.Assert.assertTrue
|
|
14
16
|
import org.junit.Test
|
|
@@ -133,4 +135,21 @@ class ReactCarouselOverlayHostLifecycleTest {
|
|
|
133
135
|
assertTrue("surface must remain running after resetState", surface.isRunning)
|
|
134
136
|
assertEquals("resetState must not call surface.stop()", stopsBefore, surface.stopCount)
|
|
135
137
|
}
|
|
138
|
+
|
|
139
|
+
@Test
|
|
140
|
+
fun `onDetachedFromWindow cancels ioScope and it is recreated on reattach`() {
|
|
141
|
+
val ctx = ApplicationProvider.getApplicationContext<Context>()
|
|
142
|
+
val host = ReactCarouselOverlayHost(ctx)
|
|
143
|
+
|
|
144
|
+
assertTrue("ioScope must be active initially", host.ioScopeForTest.isActive)
|
|
145
|
+
|
|
146
|
+
host.callOnDetachedFromWindow()
|
|
147
|
+
|
|
148
|
+
assertFalse("ioScope must be cancelled after detach", host.ioScopeForTest.isActive)
|
|
149
|
+
|
|
150
|
+
// Simulate reattach
|
|
151
|
+
host.callOnAttachedToWindow()
|
|
152
|
+
|
|
153
|
+
assertTrue("ioScope must be active again after reattach", host.ioScopeForTest.isActive)
|
|
154
|
+
}
|
|
136
155
|
}
|
package/android/src/test/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHostLifecycleTest.kt
CHANGED
|
@@ -8,9 +8,14 @@ import com.facebook.react.interfaces.TaskInterface
|
|
|
8
8
|
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
9
9
|
import com.shortkit.sdk.model.ContentItem
|
|
10
10
|
import com.shortkit.sdk.model.VideoCarouselItem
|
|
11
|
+
import kotlinx.coroutines.CoroutineScope
|
|
12
|
+
import kotlinx.coroutines.Dispatchers
|
|
13
|
+
import kotlinx.coroutines.SupervisorJob
|
|
14
|
+
import kotlinx.coroutines.isActive
|
|
11
15
|
import org.junit.Assert.assertEquals
|
|
12
16
|
import org.junit.Assert.assertFalse
|
|
13
17
|
import org.junit.Assert.assertNotNull
|
|
18
|
+
import org.junit.Assert.assertNull
|
|
14
19
|
import org.junit.Assert.assertTrue
|
|
15
20
|
import org.junit.Test
|
|
16
21
|
import org.junit.runner.RunWith
|
|
@@ -175,4 +180,21 @@ class ReactVideoCarouselOverlayHostLifecycleTest {
|
|
|
175
180
|
assertTrue("isActive untouched", host.isActive)
|
|
176
181
|
assertNotNull("currentCarouselItem untouched", host.currentCarouselItem)
|
|
177
182
|
}
|
|
183
|
+
|
|
184
|
+
@Test
|
|
185
|
+
fun `onDetachedFromWindow cancels flowScope`() {
|
|
186
|
+
val ctx = ApplicationProvider.getApplicationContext<Context>()
|
|
187
|
+
val host = ReactVideoCarouselOverlayHost(ctx)
|
|
188
|
+
|
|
189
|
+
// Inject a live scope directly via the writable test accessor.
|
|
190
|
+
val injected = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
191
|
+
host.flowScopeForTest = injected
|
|
192
|
+
|
|
193
|
+
assertTrue("injected scope must be active before detach", injected.isActive)
|
|
194
|
+
|
|
195
|
+
host.callOnDetachedFromWindow()
|
|
196
|
+
|
|
197
|
+
assertNull("flowScope must be null after detach", host.flowScopeForTest)
|
|
198
|
+
assertFalse("injected scope must be cancelled after detach", injected.isActive)
|
|
199
|
+
}
|
|
178
200
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
package com.shortkit.reactnative
|
|
2
|
+
|
|
3
|
+
import androidx.test.core.app.ApplicationProvider
|
|
4
|
+
import com.facebook.react.bridge.JavaOnlyMap
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
import com.shortkit.sdk.feed.VideoCarouselCellLongPressState
|
|
7
|
+
import org.junit.Assert.assertEquals
|
|
8
|
+
import org.junit.Test
|
|
9
|
+
import org.junit.runner.RunWith
|
|
10
|
+
import org.robolectric.RobolectricTestRunner
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* JVM unit tests for [ShortKitBridge.emitVideoCarouselCellLongPress].
|
|
14
|
+
*
|
|
15
|
+
* Verifies the event name, state-string lowercasing for all three enum values,
|
|
16
|
+
* and the full payload shape (feedId, id, index, pageIndex).
|
|
17
|
+
*
|
|
18
|
+
* The [JavaOnlyMap] seam avoids the JNI initialisation that
|
|
19
|
+
* [com.facebook.react.bridge.Arguments.createMap] requires in host tests
|
|
20
|
+
* (same pattern used in ReactVideoCarouselOverlayHostEmitTest).
|
|
21
|
+
*/
|
|
22
|
+
@RunWith(RobolectricTestRunner::class)
|
|
23
|
+
class ShortKitBridgeLongPressTest {
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Construct a minimal [ShortKitBridge] with a captured [emitEvent] lambda.
|
|
27
|
+
* [JavaOnlyMap] is injected via the [ShortKitBridge.createMap] seam so
|
|
28
|
+
* [Arguments.createMap] (which requires JNI) is never called in tests.
|
|
29
|
+
*/
|
|
30
|
+
private fun makeBridge(
|
|
31
|
+
onEmit: (String, WritableMap) -> Unit,
|
|
32
|
+
): ShortKitBridge {
|
|
33
|
+
val bridge = ShortKitBridge(
|
|
34
|
+
apiKey = "pk_test_unit",
|
|
35
|
+
context = ApplicationProvider.getApplicationContext(),
|
|
36
|
+
hasLoadingView = false,
|
|
37
|
+
clientAppName = null,
|
|
38
|
+
clientAppVersion = null,
|
|
39
|
+
customDimensionsJSON = null,
|
|
40
|
+
debugPanel = false,
|
|
41
|
+
serverTracingEnabled = false,
|
|
42
|
+
consoleTracingEnabled = false,
|
|
43
|
+
emitEvent = onEmit,
|
|
44
|
+
)
|
|
45
|
+
// Override the map factory before any emit call so the bridge never
|
|
46
|
+
// reaches Arguments.createMap() in emitVideoCarouselCellLongPress.
|
|
47
|
+
bridge.createMap = { JavaOnlyMap() }
|
|
48
|
+
return bridge
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ------------------------------------------------------------------
|
|
52
|
+
// State-string lowercasing
|
|
53
|
+
// ------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
@Test
|
|
56
|
+
fun emitsBegan() {
|
|
57
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
58
|
+
val bridge = makeBridge { name, params -> emitted.add(name to params) }
|
|
59
|
+
|
|
60
|
+
bridge.emitVideoCarouselCellLongPress(
|
|
61
|
+
itemId = "item-1",
|
|
62
|
+
index = 0,
|
|
63
|
+
pageIndex = 0,
|
|
64
|
+
state = VideoCarouselCellLongPressState.BEGAN,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
assertEquals(1, emitted.size)
|
|
68
|
+
assertEquals("onVideoCarouselCellLongPress", emitted[0].first)
|
|
69
|
+
assertEquals("began", emitted[0].second.getString("state"))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
fun emitsEnded() {
|
|
74
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
75
|
+
val bridge = makeBridge { name, params -> emitted.add(name to params) }
|
|
76
|
+
|
|
77
|
+
bridge.emitVideoCarouselCellLongPress(
|
|
78
|
+
itemId = "item-1",
|
|
79
|
+
index = 0,
|
|
80
|
+
pageIndex = 0,
|
|
81
|
+
state = VideoCarouselCellLongPressState.ENDED,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
assertEquals(1, emitted.size)
|
|
85
|
+
assertEquals("onVideoCarouselCellLongPress", emitted[0].first)
|
|
86
|
+
assertEquals("ended", emitted[0].second.getString("state"))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Test
|
|
90
|
+
fun emitsCancelled() {
|
|
91
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
92
|
+
val bridge = makeBridge { name, params -> emitted.add(name to params) }
|
|
93
|
+
|
|
94
|
+
bridge.emitVideoCarouselCellLongPress(
|
|
95
|
+
itemId = "item-1",
|
|
96
|
+
index = 0,
|
|
97
|
+
pageIndex = 0,
|
|
98
|
+
state = VideoCarouselCellLongPressState.CANCELLED,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assertEquals(1, emitted.size)
|
|
102
|
+
assertEquals("onVideoCarouselCellLongPress", emitted[0].first)
|
|
103
|
+
assertEquals("cancelled", emitted[0].second.getString("state"))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ------------------------------------------------------------------
|
|
107
|
+
// Payload shape
|
|
108
|
+
// ------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
@Test
|
|
111
|
+
fun emitsCorrectPayloadFields() {
|
|
112
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
113
|
+
val bridge = makeBridge { name, params -> emitted.add(name to params) }
|
|
114
|
+
|
|
115
|
+
bridge.emitVideoCarouselCellLongPress(
|
|
116
|
+
itemId = "car-1",
|
|
117
|
+
index = 3,
|
|
118
|
+
pageIndex = 2,
|
|
119
|
+
state = VideoCarouselCellLongPressState.BEGAN,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
val params = emitted[0].second
|
|
123
|
+
assertEquals("car-1", params.getString("id"))
|
|
124
|
+
assertEquals(3, params.getInt("index"))
|
|
125
|
+
assertEquals(2, params.getInt("pageIndex"))
|
|
126
|
+
assertEquals("began", params.getString("state"))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Test
|
|
130
|
+
fun emitsFeedId() {
|
|
131
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
132
|
+
val bridge = makeBridge { name, params -> emitted.add(name to params) }
|
|
133
|
+
|
|
134
|
+
bridge.emitVideoCarouselCellLongPress(
|
|
135
|
+
itemId = "item-1",
|
|
136
|
+
index = 0,
|
|
137
|
+
pageIndex = 0,
|
|
138
|
+
state = VideoCarouselCellLongPressState.BEGAN,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
// activeSurfaceFeedId() returns "" when no active surface is registered
|
|
142
|
+
// (no ShortKitFeedFragment has been attached in this unit-test environment).
|
|
143
|
+
// The important guarantee is that the "feedId" key is present in the map,
|
|
144
|
+
// matching the iOS bridge payload shape.
|
|
145
|
+
val params = emitted[0].second
|
|
146
|
+
assertEquals("", params.getString("feedId"))
|
|
147
|
+
}
|
|
148
|
+
}
|
package/ios/ShortKitBridge.swift
CHANGED
|
@@ -208,7 +208,8 @@ import ShortKitSDK
|
|
|
208
208
|
customDimensions: dimensions,
|
|
209
209
|
debugPanelEnabled: debugPanel,
|
|
210
210
|
serverTracingEnabled: serverTracingEnabled,
|
|
211
|
-
consoleTracingEnabled: consoleTracingEnabled
|
|
211
|
+
consoleTracingEnabled: consoleTracingEnabled,
|
|
212
|
+
poolDebugEnabled: false
|
|
212
213
|
)
|
|
213
214
|
self.shortKit = sdk
|
|
214
215
|
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
<key>CFBundlePackageType</key>
|
|
12
12
|
<string>FMWK</string>
|
|
13
13
|
<key>CFBundleVersion</key>
|
|
14
|
-
<string>0.2.
|
|
14
|
+
<string>0.2.39</string>
|
|
15
15
|
<key>CFBundleShortVersionString</key>
|
|
16
|
-
<string>0.2.
|
|
16
|
+
<string>0.2.39</string>
|
|
17
17
|
<key>MinimumOSVersion</key>
|
|
18
18
|
<string>16.0</string>
|
|
19
19
|
</dict>
|
|
@@ -52850,14 +52850,14 @@
|
|
|
52850
52850
|
{
|
|
52851
52851
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/Feed\/VideoCarouselCell.swift",
|
|
52852
52852
|
"kind": "BooleanLiteral",
|
|
52853
|
-
"offset":
|
|
52853
|
+
"offset": 45026,
|
|
52854
52854
|
"length": 4,
|
|
52855
52855
|
"value": "true"
|
|
52856
52856
|
},
|
|
52857
52857
|
{
|
|
52858
52858
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/Feed\/VideoCarouselCell.swift",
|
|
52859
52859
|
"kind": "IntegerLiteral",
|
|
52860
|
-
"offset":
|
|
52860
|
+
"offset": 52291,
|
|
52861
52861
|
"length": 1,
|
|
52862
52862
|
"value": "0"
|
|
52863
52863
|
},
|
|
@@ -53517,7 +53517,7 @@
|
|
|
53517
53517
|
"kind": "StringLiteral",
|
|
53518
53518
|
"offset": 154,
|
|
53519
53519
|
"length": 8,
|
|
53520
|
-
"value": "\"0.2.
|
|
53520
|
+
"value": "\"0.2.39\""
|
|
53521
53521
|
},
|
|
53522
53522
|
{
|
|
53523
53523
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/ShortKit.swift",
|
|
Binary file
|
package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
</data>
|
|
11
11
|
<key>Info.plist</key>
|
|
12
12
|
<data>
|
|
13
|
-
|
|
13
|
+
DfvOpn/tL7HwpstLyexoHO9tmlc=
|
|
14
14
|
</data>
|
|
15
15
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json</key>
|
|
16
16
|
<data>
|
|
17
|
-
|
|
17
|
+
AhyuvP9Jp9aQmkTfkyn18bZWgYE=
|
|
18
18
|
</data>
|
|
19
19
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface</key>
|
|
20
20
|
<data>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<dict>
|
|
51
51
|
<key>hash2</key>
|
|
52
52
|
<data>
|
|
53
|
-
|
|
53
|
+
NxZxhP9cPb/bnY8osKT6QvOK/iH3xHbDQNIHRYJGfQ0=
|
|
54
54
|
</data>
|
|
55
55
|
</dict>
|
|
56
56
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface</key>
|
package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
<key>CFBundlePackageType</key>
|
|
12
12
|
<string>FMWK</string>
|
|
13
13
|
<key>CFBundleVersion</key>
|
|
14
|
-
<string>0.2.
|
|
14
|
+
<string>0.2.39</string>
|
|
15
15
|
<key>CFBundleShortVersionString</key>
|
|
16
|
-
<string>0.2.
|
|
16
|
+
<string>0.2.39</string>
|
|
17
17
|
<key>MinimumOSVersion</key>
|
|
18
18
|
<string>16.0</string>
|
|
19
19
|
</dict>
|
|
@@ -52850,14 +52850,14 @@
|
|
|
52850
52850
|
{
|
|
52851
52851
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/Feed\/VideoCarouselCell.swift",
|
|
52852
52852
|
"kind": "BooleanLiteral",
|
|
52853
|
-
"offset":
|
|
52853
|
+
"offset": 45026,
|
|
52854
52854
|
"length": 4,
|
|
52855
52855
|
"value": "true"
|
|
52856
52856
|
},
|
|
52857
52857
|
{
|
|
52858
52858
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/Feed\/VideoCarouselCell.swift",
|
|
52859
52859
|
"kind": "IntegerLiteral",
|
|
52860
|
-
"offset":
|
|
52860
|
+
"offset": 52291,
|
|
52861
52861
|
"length": 1,
|
|
52862
52862
|
"value": "0"
|
|
52863
52863
|
},
|
|
@@ -53517,7 +53517,7 @@
|
|
|
53517
53517
|
"kind": "StringLiteral",
|
|
53518
53518
|
"offset": 154,
|
|
53519
53519
|
"length": 8,
|
|
53520
|
-
"value": "\"0.2.
|
|
53520
|
+
"value": "\"0.2.39\""
|
|
53521
53521
|
},
|
|
53522
53522
|
{
|
|
53523
53523
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/ShortKit.swift",
|
|
@@ -52850,14 +52850,14 @@
|
|
|
52850
52850
|
{
|
|
52851
52851
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/Feed\/VideoCarouselCell.swift",
|
|
52852
52852
|
"kind": "BooleanLiteral",
|
|
52853
|
-
"offset":
|
|
52853
|
+
"offset": 45026,
|
|
52854
52854
|
"length": 4,
|
|
52855
52855
|
"value": "true"
|
|
52856
52856
|
},
|
|
52857
52857
|
{
|
|
52858
52858
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/Feed\/VideoCarouselCell.swift",
|
|
52859
52859
|
"kind": "IntegerLiteral",
|
|
52860
|
-
"offset":
|
|
52860
|
+
"offset": 52291,
|
|
52861
52861
|
"length": 1,
|
|
52862
52862
|
"value": "0"
|
|
52863
52863
|
},
|
|
@@ -53517,7 +53517,7 @@
|
|
|
53517
53517
|
"kind": "StringLiteral",
|
|
53518
53518
|
"offset": 154,
|
|
53519
53519
|
"length": 8,
|
|
53520
|
-
"value": "\"0.2.
|
|
53520
|
+
"value": "\"0.2.39\""
|
|
53521
53521
|
},
|
|
53522
53522
|
{
|
|
53523
53523
|
"filePath": "\/Users\/michaelseleman\/shortkit\/swift_sdk\/Sources\/ShortKit\/ShortKit.swift",
|
package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK
CHANGED
|
Binary file
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
</data>
|
|
11
11
|
<key>Info.plist</key>
|
|
12
12
|
<data>
|
|
13
|
-
|
|
13
|
+
DfvOpn/tL7HwpstLyexoHO9tmlc=
|
|
14
14
|
</data>
|
|
15
15
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
|
|
16
16
|
<data>
|
|
17
|
-
|
|
17
|
+
AhyuvP9Jp9aQmkTfkyn18bZWgYE=
|
|
18
18
|
</data>
|
|
19
19
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
|
|
20
20
|
<data>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
</data>
|
|
31
31
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
|
32
32
|
<data>
|
|
33
|
-
|
|
33
|
+
AhyuvP9Jp9aQmkTfkyn18bZWgYE=
|
|
34
34
|
</data>
|
|
35
35
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
|
36
36
|
<data>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
<dict>
|
|
67
67
|
<key>hash2</key>
|
|
68
68
|
<data>
|
|
69
|
-
|
|
69
|
+
NxZxhP9cPb/bnY8osKT6QvOK/iH3xHbDQNIHRYJGfQ0=
|
|
70
70
|
</data>
|
|
71
71
|
</dict>
|
|
72
72
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<dict>
|
|
95
95
|
<key>hash2</key>
|
|
96
96
|
<data>
|
|
97
|
-
|
|
97
|
+
NxZxhP9cPb/bnY8osKT6QvOK/iH3xHbDQNIHRYJGfQ0=
|
|
98
98
|
</data>
|
|
99
99
|
</dict>
|
|
100
100
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|