mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
PM-13471 Remove instances deprecated ClickableText (#4076)
This commit is contained in:
parent
bde47d7919
commit
4756040c4a
6 changed files with 325 additions and 137 deletions
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -27,10 +26,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.onClick
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
@ -40,8 +36,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.auth.feature.checkemail.handlers.rememberCheckEmailHandler
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.ClickableTextHighlight
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.createAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.createClickableAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
|
@ -52,8 +50,6 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
|||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
private const val TAG_URL = "URL"
|
||||
|
||||
/**
|
||||
* Top level composable for the check email screen.
|
||||
*/
|
||||
|
@ -282,54 +278,34 @@ private fun CheckEmailLegacyContent(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val goBackAnnotatedString = createAnnotatedString(
|
||||
val goBackAnnotatedString = createClickableAnnotatedString(
|
||||
mainString = stringResource(
|
||||
id = R.string.no_email_go_back_to_edit_your_email_address,
|
||||
),
|
||||
highlights = listOf(stringResource(id = R.string.go_back)),
|
||||
tag = TAG_URL,
|
||||
highlights = listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = stringResource(id = R.string.go_back),
|
||||
onTextClick = onChangeEmailClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
ClickableText(
|
||||
Text(
|
||||
text = goBackAnnotatedString,
|
||||
onClick = {
|
||||
goBackAnnotatedString
|
||||
.getStringAnnotations(TAG_URL, it, it)
|
||||
.firstOrNull()?.let {
|
||||
onChangeEmailClick()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.semantics {
|
||||
role = Role.Button
|
||||
onClick {
|
||||
onChangeEmailClick()
|
||||
true
|
||||
}
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
val logInAnnotatedString = createAnnotatedString(
|
||||
val logInAnnotatedString = createClickableAnnotatedString(
|
||||
mainString = stringResource(
|
||||
id = R.string.or_log_in_you_may_already_have_an_account,
|
||||
),
|
||||
highlights = listOf(stringResource(id = R.string.log_in)),
|
||||
tag = TAG_URL,
|
||||
highlights = listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = stringResource(id = R.string.log_in),
|
||||
onTextClick = onLoginClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
ClickableText(
|
||||
Text(
|
||||
text = logInAnnotatedString,
|
||||
onClick = {
|
||||
logInAnnotatedString
|
||||
.getStringAnnotations(TAG_URL, it, it)
|
||||
.firstOrNull()?.let {
|
||||
onLoginClick()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.semantics {
|
||||
role = Role.Button
|
||||
onClick {
|
||||
onLoginClick()
|
||||
true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.material3.ripple
|
||||
|
@ -41,9 +41,6 @@ import androidx.compose.ui.semantics.testTag
|
|||
import androidx.compose.ui.semantics.toggleableState
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
@ -59,9 +56,10 @@ import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationEv
|
|||
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationEvent.NavigateToTerms
|
||||
import com.x8bit.bitwarden.ui.auth.feature.startregistration.handlers.StartRegistrationHandler
|
||||
import com.x8bit.bitwarden.ui.auth.feature.startregistration.handlers.rememberStartRegistrationHandler
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.ClickableTextHighlight
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.createAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.createClickableAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
|
@ -313,54 +311,22 @@ private fun TermsAndPrivacyText(
|
|||
) {
|
||||
val strTerms = stringResource(id = R.string.terms_of_service)
|
||||
val strPrivacy = stringResource(id = R.string.privacy_policy)
|
||||
val annotatedLinkString: AnnotatedString = buildAnnotatedString {
|
||||
val strTermsAndPrivacy = stringResource(
|
||||
id = R.string.by_continuing_you_agree_to_the_terms_of_service_and_privacy_policy,
|
||||
)
|
||||
val startIndexTerms = strTermsAndPrivacy.indexOf(strTerms)
|
||||
val endIndexTerms = startIndexTerms + strTerms.length
|
||||
val startIndexPrivacy = strTermsAndPrivacy.indexOf(strPrivacy)
|
||||
val endIndexPrivacy = startIndexPrivacy + strPrivacy.length
|
||||
append(strTermsAndPrivacy)
|
||||
addStyle(
|
||||
style = SpanStyle(
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
fontSize = BitwardenTheme.typography.bodyMedium.fontSize,
|
||||
val strTermsAndPrivacy = stringResource(
|
||||
id = R.string.by_continuing_you_agree_to_the_terms_of_service_and_privacy_policy,
|
||||
)
|
||||
val annotatedLinkString: AnnotatedString = createClickableAnnotatedString(
|
||||
mainString = strTermsAndPrivacy,
|
||||
highlights = listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = strTerms,
|
||||
onTextClick = onTermsClick,
|
||||
),
|
||||
start = 0,
|
||||
end = strTermsAndPrivacy.length,
|
||||
)
|
||||
addStyle(
|
||||
style = SpanStyle(
|
||||
color = BitwardenTheme.colorScheme.text.interaction,
|
||||
fontSize = BitwardenTheme.typography.bodyMedium.fontSize,
|
||||
fontWeight = FontWeight.Bold,
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = strPrivacy,
|
||||
onTextClick = onPrivacyPolicyClick,
|
||||
),
|
||||
start = startIndexTerms,
|
||||
end = endIndexTerms,
|
||||
)
|
||||
addStyle(
|
||||
style = SpanStyle(
|
||||
color = BitwardenTheme.colorScheme.text.interaction,
|
||||
fontSize = BitwardenTheme.typography.bodyMedium.fontSize,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
start = startIndexPrivacy,
|
||||
end = endIndexPrivacy,
|
||||
)
|
||||
addStringAnnotation(
|
||||
tag = TAG_URL,
|
||||
annotation = strTerms,
|
||||
start = startIndexTerms,
|
||||
end = endIndexTerms,
|
||||
)
|
||||
addStringAnnotation(
|
||||
tag = TAG_URL,
|
||||
annotation = strPrivacy,
|
||||
start = startIndexPrivacy,
|
||||
end = endIndexPrivacy,
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
@ -387,23 +353,11 @@ private fun TermsAndPrivacyText(
|
|||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val termsUrl = stringResource(id = R.string.terms_of_service)
|
||||
ClickableText(
|
||||
Text(
|
||||
text = annotatedLinkString,
|
||||
style = BitwardenTheme.typography.bodyMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
onClick = {
|
||||
annotatedLinkString
|
||||
.getStringAnnotations(TAG_URL, it, it)
|
||||
.firstOrNull()?.let { stringAnnotation ->
|
||||
if (stringAnnotation.item == termsUrl) {
|
||||
onTermsClick()
|
||||
} else {
|
||||
onPrivacyPolicyClick()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -419,14 +373,13 @@ private fun ReceiveMarketingEmailsSwitch(
|
|||
val unsubscribeString = stringResource(id = R.string.unsubscribe)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
val annotatedLinkString = createAnnotatedString(
|
||||
val annotatedLinkString = createClickableAnnotatedString(
|
||||
mainString = stringResource(id = R.string.get_emails_from_bitwarden_for_announcements_advices_and_research_opportunities_unsubscribe_any_time),
|
||||
highlights = listOf(unsubscribeString),
|
||||
tag = TAG_URL,
|
||||
highlightStyle = SpanStyle(
|
||||
color = BitwardenTheme.colorScheme.text.interaction,
|
||||
fontSize = BitwardenTheme.typography.bodyMedium.fontSize,
|
||||
fontWeight = FontWeight.Bold,
|
||||
highlights = listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = unsubscribeString,
|
||||
onTextClick = onUnsubscribeClick,
|
||||
),
|
||||
),
|
||||
)
|
||||
Row(
|
||||
|
@ -464,16 +417,9 @@ private fun ReceiveMarketingEmailsSwitch(
|
|||
colors = bitwardenSwitchColors(),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
ClickableText(
|
||||
Text(
|
||||
text = annotatedLinkString,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
onClick = {
|
||||
annotatedLinkString
|
||||
.getStringAnnotations(TAG_URL, it, it)
|
||||
.firstOrNull()?.let {
|
||||
onUnsubscribeClick()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
@ -151,19 +153,100 @@ fun createAnnotatedString(
|
|||
end = mainString.length,
|
||||
)
|
||||
for (highlightString in highlights) {
|
||||
val startIndexUnsubscribe = mainString.indexOf(highlightString, ignoreCase = true)
|
||||
val endIndexUnsubscribe = startIndexUnsubscribe + highlightString.length
|
||||
val startIndex = mainString.indexOf(highlightString, ignoreCase = true)
|
||||
val endIndex = startIndex + highlightString.length
|
||||
addStyle(
|
||||
style = highlightStyle,
|
||||
start = startIndexUnsubscribe,
|
||||
end = endIndexUnsubscribe,
|
||||
start = startIndex,
|
||||
end = endIndex,
|
||||
)
|
||||
addStringAnnotation(
|
||||
tag = tag,
|
||||
annotation = highlightString,
|
||||
start = startIndexUnsubscribe,
|
||||
end = endIndexUnsubscribe,
|
||||
start = startIndex,
|
||||
end = endIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an [AnnotatedString] with highlighted parts that can be clicked.
|
||||
* @param mainString the full string to be processed.
|
||||
* @param highlights list of [ClickableTextHighlight]s to be annotated within the [mainString].
|
||||
* If a highlighted text is repeated in the [mainString], you must choose which instance to use
|
||||
* by setting the [ClickableTextHighlight.instance] property. Only one instance of the text will
|
||||
* be annotated.
|
||||
*/
|
||||
@Composable
|
||||
fun createClickableAnnotatedString(
|
||||
mainString: String,
|
||||
highlights: List<ClickableTextHighlight>,
|
||||
style: SpanStyle = SpanStyle(
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
fontSize = BitwardenTheme.typography.bodyMedium.fontSize,
|
||||
),
|
||||
highlightStyle: SpanStyle = SpanStyle(
|
||||
color = BitwardenTheme.colorScheme.text.interaction,
|
||||
fontSize = BitwardenTheme.typography.bodyMedium.fontSize,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
): AnnotatedString {
|
||||
return buildAnnotatedString {
|
||||
append(mainString)
|
||||
addStyle(
|
||||
style = style,
|
||||
start = 0,
|
||||
end = mainString.length,
|
||||
)
|
||||
for (highlight in highlights) {
|
||||
val text = highlight.textToHighlight
|
||||
val startIndex = when (highlight.instance) {
|
||||
ClickableTextHighlight.Instance.FIRST -> {
|
||||
mainString.indexOf(text, ignoreCase = true)
|
||||
}
|
||||
|
||||
ClickableTextHighlight.Instance.LAST -> {
|
||||
mainString.lastIndexOf(text, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
val endIndex = startIndex + highlight.textToHighlight.length
|
||||
val link = LinkAnnotation.Clickable(
|
||||
tag = highlight.textToHighlight,
|
||||
styles = TextLinkStyles(
|
||||
style = highlightStyle,
|
||||
),
|
||||
) {
|
||||
highlight.onTextClick.invoke()
|
||||
}
|
||||
addLink(
|
||||
link,
|
||||
start = startIndex,
|
||||
end = endIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models text that should be highlighted with and associated with a click action.
|
||||
* @property textToHighlight the text to highlight and associate with click action.
|
||||
* @property onTextClick the click action to perform when the text is clicked.
|
||||
* @property instance to denote if there are multiple instances of the [textToHighlight] in the
|
||||
* [AnnotatedString] which should be highlighted.
|
||||
*/
|
||||
data class ClickableTextHighlight(
|
||||
val textToHighlight: String,
|
||||
val onTextClick: () -> Unit,
|
||||
val instance: Instance = Instance.FIRST,
|
||||
) {
|
||||
/**
|
||||
* To denote if a [ClickableTextHighlight.textToHighlight] should highlight the
|
||||
* first instance of the text or the last instance.
|
||||
* "If you ain't first, you're last" == true
|
||||
*/
|
||||
enum class Instance {
|
||||
FIRST,
|
||||
LAST,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.checkemail
|
||||
|
||||
import androidx.compose.ui.semantics.SemanticsActions
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.performSemanticsAction
|
||||
import androidx.compose.ui.test.printToLog
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.util.assertLinkAnnotationIsAppliedAndInvokeClickAction
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
|
@ -90,10 +91,17 @@ class CheckEmailScreenTest : BaseComposeTest() {
|
|||
@Test
|
||||
fun `go back and update email text click should send ChangeEmailClick action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = false)
|
||||
composeTestRule
|
||||
.onNodeWithText("No email? Go back to edit your email address.")
|
||||
.performScrollTo()
|
||||
.performSemanticsAction(SemanticsActions.OnClick)
|
||||
composeTestRule.onRoot().printToLog("oh shit")
|
||||
val mainString = "No email? Go back to edit your email address."
|
||||
val linkText = "Go back"
|
||||
val expectedStart = mainString.indexOf(linkText)
|
||||
val expectedEnd = expectedStart + linkText.length
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString = mainString,
|
||||
highLightText = linkText,
|
||||
expectedStart = expectedStart,
|
||||
expectedEnd = expectedEnd,
|
||||
)
|
||||
|
||||
verify { viewModel.trySendAction(CheckEmailAction.ChangeEmailClick) }
|
||||
}
|
||||
|
@ -101,10 +109,16 @@ class CheckEmailScreenTest : BaseComposeTest() {
|
|||
@Test
|
||||
fun `already have account text click should send ChangeEmailClick action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = false)
|
||||
composeTestRule
|
||||
.onNodeWithText("Or log in, you may already have an account.")
|
||||
.performScrollTo()
|
||||
.performSemanticsAction(SemanticsActions.OnClick)
|
||||
val mainString = "Or log in, you may already have an account."
|
||||
val linkText = "log in"
|
||||
val expectedStart = mainString.indexOf(linkText)
|
||||
val expectedEnd = expectedStart + linkText.length
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString = mainString,
|
||||
highLightText = linkText,
|
||||
expectedStart = expectedStart,
|
||||
expectedEnd = expectedEnd,
|
||||
)
|
||||
|
||||
verify { viewModel.trySendAction(CheckEmailAction.LoginClick) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package com.x8bit.bitwarden.ui.platform.base.util
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.util.assertLinkAnnotationIsAppliedAndInvokeClickAction
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ClickableAnnotatedStringTest : BaseComposeTest() {
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `clickable annotated string should add Clickable LinkAnnotation to highlighted string`() {
|
||||
var textClickCalled = false
|
||||
val mainString = "This is me testing the thing."
|
||||
val highLightText = "testing"
|
||||
composeTestRule.setContent {
|
||||
val annotatedString = createClickableAnnotatedString(
|
||||
mainString,
|
||||
listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = highLightText,
|
||||
onTextClick = { textClickCalled = true },
|
||||
),
|
||||
),
|
||||
)
|
||||
Text(text = annotatedString)
|
||||
}
|
||||
val expectedStart = mainString.indexOf(highLightText)
|
||||
val expectedEnd = expectedStart + highLightText.length
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString,
|
||||
highLightText,
|
||||
expectedStart,
|
||||
expectedEnd,
|
||||
)
|
||||
assertTrue(textClickCalled)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `clickable annotated string should add multiple Clickable LinkAnnotations to highlighted string`() {
|
||||
val mainString = "This is me testing the thing."
|
||||
val highLightText1 = "testing"
|
||||
val highlightText2 = "thing"
|
||||
composeTestRule.setContent {
|
||||
val annotatedString = createClickableAnnotatedString(
|
||||
mainString,
|
||||
listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = highLightText1,
|
||||
onTextClick = {},
|
||||
),
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = highlightText2,
|
||||
onTextClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
Text(text = annotatedString)
|
||||
}
|
||||
val expectedStart1 = mainString.indexOf(highLightText1)
|
||||
val expectedEnd1 = expectedStart1 + highLightText1.length
|
||||
val expectedStart2 = mainString.indexOf(highlightText2)
|
||||
val expectedEnd2 = expectedStart2 + highlightText2.length
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString,
|
||||
highLightText1,
|
||||
expectedStart1,
|
||||
expectedEnd1,
|
||||
)
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString,
|
||||
highlightText2,
|
||||
expectedStart2,
|
||||
expectedEnd2,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `clickable annotated string should add annotation to first instance of highlighted string`() {
|
||||
val mainString = "Testing 1,2,3 testing"
|
||||
val highLightText = "testing"
|
||||
composeTestRule.setContent {
|
||||
val annotatedString = createClickableAnnotatedString(
|
||||
mainString,
|
||||
listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = highLightText,
|
||||
onTextClick = {},
|
||||
instance = ClickableTextHighlight.Instance.FIRST,
|
||||
),
|
||||
),
|
||||
)
|
||||
Text(text = annotatedString)
|
||||
}
|
||||
// indexOf returns the index of the first instance.
|
||||
val expectedStart = mainString.indexOf(highLightText)
|
||||
val expectedEnd = expectedStart + highLightText.length
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString,
|
||||
highLightText,
|
||||
expectedStart,
|
||||
expectedEnd,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `clickable annotated string should add annotation to last instance of highlighted string`() {
|
||||
val mainString = "Testing 1,2,3 testing"
|
||||
val highLightText = "testing"
|
||||
composeTestRule.setContent {
|
||||
val annotatedString = createClickableAnnotatedString(
|
||||
mainString,
|
||||
listOf(
|
||||
ClickableTextHighlight(
|
||||
textToHighlight = highLightText,
|
||||
onTextClick = {},
|
||||
instance = ClickableTextHighlight.Instance.FIRST,
|
||||
),
|
||||
),
|
||||
)
|
||||
Text(text = annotatedString)
|
||||
}
|
||||
// indexOf returns the index of the first instance.
|
||||
val expectedStart = mainString.lastIndexOf(highLightText)
|
||||
val expectedEnd = expectedStart + highLightText.length
|
||||
composeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString,
|
||||
highLightText,
|
||||
expectedStart,
|
||||
expectedEnd,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.test.hasScrollToNodeAction
|
|||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onFirst
|
||||
|
@ -21,6 +22,9 @@ import androidx.compose.ui.test.onNodeWithContentDescription
|
|||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performScrollToNode
|
||||
import androidx.compose.ui.test.printToString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
/**
|
||||
|
@ -173,3 +177,32 @@ fun SemanticsNodeInteraction.performCustomAccessibilityAction(label: String) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to assert link annotation is applied to the given text in
|
||||
* the [mainString] and invoke click action if it is found.
|
||||
*/
|
||||
fun ComposeTestRule.assertLinkAnnotationIsAppliedAndInvokeClickAction(
|
||||
mainString: String,
|
||||
highLightText: String,
|
||||
expectedStart: Int,
|
||||
expectedEnd: Int,
|
||||
) {
|
||||
this
|
||||
.onNodeWithText(mainString)
|
||||
.fetchSemanticsNode()
|
||||
.config
|
||||
.getOrNull(SemanticsProperties.Text)
|
||||
?.let { text ->
|
||||
text.forEach {
|
||||
it.getLinkAnnotations(expectedStart, expectedEnd)
|
||||
.forEach { annotationRange ->
|
||||
val annotation = annotationRange.item as? LinkAnnotation.Clickable
|
||||
val tag = annotation?.tag
|
||||
assertNotNull(tag)
|
||||
assertTrue(highLightText.equals(tag, ignoreCase = true))
|
||||
annotation?.linkInteractionListener?.onClick(annotation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue