Skip to content

Commit b4f17fa

Browse files
committed
test: add tests for keying mechanism
1 parent 0fd25c1 commit b4f17fa

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.activityresult
9+
10+
import android.app.Activity
11+
import android.os.Bundle
12+
import androidx.activity.result.ActivityResultRegistry
13+
import androidx.activity.result.ActivityResultRegistryOwner
14+
import androidx.activity.result.contract.ActivityResultContract
15+
import androidx.activity.result.contract.ActivityResultContracts.GetContent
16+
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
17+
import androidx.core.app.ActivityOptionsCompat
18+
import com.facebook.react.bridge.ReactApplicationContext
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.assertj.core.api.Assertions.assertThatThrownBy
21+
import org.junit.Before
22+
import org.junit.Test
23+
import org.junit.runner.RunWith
24+
import org.mockito.kotlin.mock
25+
import org.mockito.kotlin.whenever
26+
import org.robolectric.Robolectric
27+
import org.robolectric.RobolectricTestRunner
28+
29+
/**
30+
* Covers the registration keying scheme: owner-scoped by default so two independent modules can use
31+
* the same stock contract, with an extra-key overload -- appended to that scope, not replacing it --
32+
* for one owner needing several launchers of the same contract class.
33+
*/
34+
@RunWith(RobolectricTestRunner::class)
35+
class ReactActivityResultCallerImplTest {
36+
37+
/** Records the keys handed to [ActivityResultRegistry.register] and never starts anything. */
38+
private class RecordingRegistry : ActivityResultRegistry() {
39+
override fun <I, O> onLaunch(
40+
requestCode: Int,
41+
contract: ActivityResultContract<I, O>,
42+
input: I,
43+
options: ActivityOptionsCompat?,
44+
): Unit = Unit
45+
46+
/** [onSaveInstanceState] is the only public window into the registry's key table. */
47+
val registeredKeys: List<String>
48+
get() =
49+
Bundle()
50+
.also { onSaveInstanceState(it) }
51+
.getStringArrayList("KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS")
52+
.orEmpty()
53+
}
54+
55+
class TestActivity : Activity(), ActivityResultRegistryOwner {
56+
override val activityResultRegistry: ActivityResultRegistry = RecordingRegistry()
57+
}
58+
59+
/** Two distinct owner classes, standing in for two unrelated third-party modules. */
60+
private class ModuleA
61+
62+
private class ModuleB
63+
64+
private lateinit var registry: RecordingRegistry
65+
private lateinit var reactContext: ReactApplicationContext
66+
private lateinit var caller: ReactActivityResultCallerImpl
67+
68+
private val moduleA = ModuleA()
69+
private val moduleB = ModuleB()
70+
71+
private val moduleAName = ModuleA::class.java.name
72+
private val moduleBName = ModuleB::class.java.name
73+
private val getContentName = GetContent::class.java.name
74+
75+
@Before
76+
fun setUp() {
77+
val activity = Robolectric.buildActivity(TestActivity::class.java).create().get()
78+
registry = activity.activityResultRegistry as RecordingRegistry
79+
reactContext = mock<ReactApplicationContext>()
80+
whenever(reactContext.currentActivity).thenReturn(activity)
81+
caller = ReactActivityResultCallerImpl(reactContext)
82+
}
83+
84+
@Test
85+
fun twoOwnersMayRegisterTheSameStockContract() {
86+
caller.registerForActivityResult(moduleA, GetContent()) {}
87+
caller.registerForActivityResult(moduleB, GetContent()) {}
88+
89+
assertThat(registry.registeredKeys)
90+
.containsExactlyInAnyOrder(
91+
"$moduleAName:$getContentName", "$moduleBName:$getContentName")
92+
}
93+
94+
@Test
95+
fun oneOwnerRegisteringTheSameContractTwiceThrows() {
96+
caller.registerForActivityResult(moduleA, GetContent()) {}
97+
98+
assertThatThrownBy { caller.registerForActivityResult(moduleA, GetContent()) {} }
99+
.isInstanceOf(IllegalStateException::class.java)
100+
.hasMessageContaining("registerForActivityResult(owner, \"someName\", contract, callback)")
101+
}
102+
103+
@Test
104+
fun oneOwnerMayRegisterDifferentContractClasses() {
105+
caller.registerForActivityResult(moduleA, GetContent()) {}
106+
caller.registerForActivityResult(moduleA, RequestPermission()) {}
107+
108+
assertThat(registry.registeredKeys)
109+
.containsExactlyInAnyOrder(
110+
"$moduleAName:$getContentName", "$moduleAName:${RequestPermission::class.java.name}")
111+
}
112+
113+
@Test
114+
fun extraKeysAllowTwoLaunchersOfOneContract() {
115+
caller.registerForActivityResult(moduleA, "avatar", GetContent()) {}
116+
caller.registerForActivityResult(moduleA, "banner", GetContent()) {}
117+
118+
assertThat(registry.registeredKeys)
119+
.containsExactlyInAnyOrder(
120+
"$moduleAName:$getContentName:avatar", "$moduleAName:$getContentName:banner")
121+
}
122+
123+
/** The owner-and-contract scope is still applied, so a shared key across owners is safe. */
124+
@Test
125+
fun theSameExtraKeyFromTwoOwnersDoesNotCollide() {
126+
caller.registerForActivityResult(moduleA, "pick", GetContent()) {}
127+
caller.registerForActivityResult(moduleB, "pick", GetContent()) {}
128+
129+
assertThat(registry.registeredKeys)
130+
.containsExactlyInAnyOrder(
131+
"$moduleAName:$getContentName:pick", "$moduleBName:$getContentName:pick")
132+
}
133+
134+
@Test
135+
fun duplicateExtraKeyForOneOwnerThrows() {
136+
caller.registerForActivityResult(moduleA, "avatar", GetContent()) {}
137+
138+
assertThatThrownBy { caller.registerForActivityResult(moduleA, "avatar", GetContent()) {} }
139+
.isInstanceOf(IllegalStateException::class.java)
140+
.hasMessageContaining("$moduleAName:$getContentName:avatar")
141+
.hasMessageContaining("unique among this owner's launchers")
142+
}
143+
144+
@Test
145+
fun aNonModuleOwnerKeysTheSameWayAModuleDoes() {
146+
class MediaHelper
147+
148+
val helper = MediaHelper()
149+
caller.registerForActivityResult(helper, GetContent()) {}
150+
151+
assertThat(registry.registeredKeys)
152+
.containsExactly("${MediaHelper::class.java.name}:$getContentName")
153+
}
154+
155+
@Test
156+
fun unregisteringFreesTheKeyForReuse() {
157+
val launcher = caller.registerForActivityResult(moduleA, GetContent()) {}
158+
launcher.unregister()
159+
160+
caller.registerForActivityResult(moduleA, GetContent()) {}
161+
162+
assertThat(registry.registeredKeys).containsExactly("$moduleAName:$getContentName")
163+
}
164+
}

0 commit comments

Comments
 (0)