Skip to content

Commit e98eb8e

Browse files
authored
fix: CHALLENGE_CLOSED should be emitted only once (#178)
1 parent df374cc commit e98eb8e

File tree

5 files changed

+82
-19
lines changed

5 files changed

+82
-19
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
# 4.1.2
4+
5+
- Fix: double call with CHALLENGE_CLOSED error
6+
- Fix: broken retryPredicate config
7+
38
# 4.1.1
49

510
- Fix: back button should cancel hCaptcha in compose-sdk

compose-sdk/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ android {
2020
// See https://developer.android.com/studio/publish/versioning
2121
// versionCode must be integer and be incremented by one for every new update
2222
// android system uses this to prevent downgrades
23-
versionCode 46
23+
versionCode 47
2424

2525
// version number visible to the user
2626
// should follow semantic versioning (See https://semver.org)
27-
versionName "4.1.1"
27+
versionName "4.1.2"
2828

2929
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3030
consumerProguardFiles "consumer-rules.pro"

compose-sdk/src/main/java/com/hcaptcha/sdk/HCaptchaCompose.kt

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.runtime.setValue
1717
import androidx.compose.ui.Alignment
1818
import androidx.compose.ui.Modifier
1919
import androidx.compose.ui.platform.LocalContext
20+
import androidx.compose.ui.platform.testTag
2021
import androidx.compose.ui.unit.dp
2122
import androidx.compose.ui.viewinterop.AndroidView
2223
import androidx.compose.ui.window.Dialog
@@ -36,38 +37,35 @@ public fun HCaptchaCompose(config: HCaptchaConfig, onResult: (HCaptchaResponse)
3637
helper.value = HCaptchaWebViewHelper(
3738
handler, context, config, internalConfig, verifier, this
3839
)
39-
4040
}
4141
}
4242
var dismissed by remember { mutableStateOf(false) }
4343

44-
HCaptchaLog.d("HCaptchaCompose($config)")
45-
46-
DisposableEffect(dismissed) {
47-
onDispose {
48-
if (dismissed) {
49-
verifier.onFailure(HCaptchaException(HCaptchaError.CHALLENGE_CLOSED));
50-
helper.value?.destroy()
51-
}
52-
}
44+
val onDismissRequest: () -> Unit = {
45+
dismissed = true
46+
verifier.onFailure(HCaptchaException(HCaptchaError.CHALLENGE_CLOSED));
47+
helper.value?.destroy()
5348
}
5449

50+
HCaptchaLog.d("HCaptchaCompose($config)")
51+
5552
if (config.hideDialog) {
5653
AndroidView(
5754
modifier = Modifier.size(0.dp),
5855
factory = { preloadedWebView }
5956
)
6057
} else if (!dismissed) {
6158
Dialog(
62-
onDismissRequest = { dismissed = true }
59+
onDismissRequest = onDismissRequest
6360
) {
6461
Column(
6562
modifier = Modifier
63+
.testTag("dialogRoot")
6664
.fillMaxSize()
6765
.clickable(
6866
interactionSource = MutableInteractionSource(),
6967
indication = null,
70-
onClick = { dismissed = true }
68+
onClick = onDismissRequest
7169
),
7270
verticalArrangement = Arrangement.Center,
7371
horizontalAlignment = Alignment.CenterHorizontally

sdk/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ android {
2828
// See https://developer.android.com/studio/publish/versioning
2929
// versionCode must be integer and be incremented by one for every new update
3030
// android system uses this to prevent downgrades
31-
versionCode 46
31+
versionCode 47
3232

3333
// version number visible to the user
3434
// should follow semantic versioning (See https://semver.org)
35-
versionName "4.1.1"
35+
versionName "4.1.2"
3636

3737
buildConfigField 'String', 'VERSION_NAME', "\"${defaultConfig.versionName}_${defaultConfig.versionCode}\""
3838

test/src/androidTest/java/com/hcaptcha/sdk/compose/HCaptchaComposeTest.kt

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf
99
import androidx.compose.runtime.remember
1010
import androidx.compose.runtime.setValue
1111
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.geometry.Offset
1213
import androidx.compose.ui.semantics.contentDescription
1314
import androidx.compose.ui.semantics.semantics
1415
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -17,6 +18,7 @@ import com.hcaptcha.sdk.HCaptchaConfig
1718
import com.hcaptcha.sdk.HCaptchaError
1819
import com.hcaptcha.sdk.HCaptchaResponse
1920
import com.hcaptcha.sdk.HCaptchaSize
21+
import androidx.test.espresso.Espresso.pressBack
2022
import kotlinx.coroutines.delay
2123
import kotlinx.coroutines.runBlocking
2224
import org.junit.Rule
@@ -37,7 +39,9 @@ class HCaptchaComposeTest {
3739
@get:Rule
3840
val composeTestRule = createComposeRule()
3941

40-
fun setContent(siteKey: String = SITE_KEY, passiveSiteKey: Boolean = false) {
42+
private fun setContent(siteKey: String = SITE_KEY,
43+
passiveSiteKey: Boolean = false,
44+
size: HCaptchaSize = HCaptchaSize.INVISIBLE) {
4145
composeTestRule.setContent {
4246
var text by remember { mutableStateOf("<init>") }
4347
Column {
@@ -47,15 +51,19 @@ class HCaptchaComposeTest {
4751
.builder()
4852
.siteKey(siteKey)
4953
.diagnosticLog(true)
50-
.size(HCaptchaSize.INVISIBLE)
54+
.size(size)
5155
.hideDialog(passiveSiteKey)
5256
.build()) { result ->
5357
when (result) {
5458
is HCaptchaResponse.Success -> {
5559
text = result.token
5660
}
5761
is HCaptchaResponse.Failure -> {
58-
text = result.error.name
62+
if (result.error.name == text) {
63+
text += "2"
64+
} else {
65+
text = result.error.name
66+
}
5967
}
6068
else -> {}
6169
}
@@ -93,4 +101,56 @@ class HCaptchaComposeTest {
93101
composeTestRule.onNodeWithContentDescription(resultContentDescription)
94102
.assertTextEquals(TEST_TOKEN)
95103
}
104+
105+
@Test
106+
fun passiveVisualChallengeCanceled() {
107+
setContent("00000000-0000-0000-0000-000000000000", false)
108+
109+
runBlocking { delay(timeout) }
110+
111+
composeTestRule.onNodeWithTag("dialogRoot").performTouchInput {
112+
val x = this.width / 2f
113+
val y = this.height * 0.9f
114+
115+
click(Offset(x, y))
116+
}
117+
118+
runBlocking { delay(timeout / 2) }
119+
120+
composeTestRule.onNodeWithContentDescription(resultContentDescription)
121+
.assertTextContains(HCaptchaError.CHALLENGE_CLOSED.name)
122+
}
123+
124+
@Test
125+
fun passiveCheckboxCanceled() {
126+
setContent("00000000-0000-0000-0000-000000000000", false, HCaptchaSize.COMPACT)
127+
128+
runBlocking { delay(timeout) }
129+
130+
composeTestRule.onNodeWithTag("dialogRoot").performTouchInput {
131+
val x = this.width / 2f
132+
val y = this.height * 0.9f
133+
134+
click(Offset(x, y))
135+
}
136+
137+
runBlocking { delay(timeout / 2) }
138+
139+
composeTestRule.onNodeWithContentDescription(resultContentDescription)
140+
.assertTextContains(HCaptchaError.CHALLENGE_CLOSED.name)
141+
}
142+
143+
@Test
144+
fun passiveCheckboxCanceledWithBack() {
145+
setContent("00000000-0000-0000-0000-000000000000", false, HCaptchaSize.COMPACT)
146+
147+
runBlocking { delay(timeout) }
148+
149+
pressBack()
150+
151+
runBlocking { delay(timeout / 2) }
152+
153+
composeTestRule.onNodeWithContentDescription(resultContentDescription)
154+
.assertTextContains(HCaptchaError.CHALLENGE_CLOSED.name)
155+
}
96156
}

0 commit comments

Comments
 (0)