如何在Jetpack Compose中测试注解字符串的超链接点击事件?
核心问题原因
performClick() 默认会点击匹配到的控件的中心位置,而链接只是整个 Annotated String 的一部分,控件中心恰好不在链接的可点击区域范围内,自然无法触发对应的点击事件。
解决方案:自定义点击动作,精准命中链接区域
你需要先拿到文本的布局信息,定位到目标链接对应的文本范围,再计算该范围的中心坐标执行点击,具体实现如下:
- 先定义两个工具方法:匹配带指定URL注解的文本节点、点击指定URL注解对应的区域
// 匹配包含指定链接注解的文本节点 fun hasAnnotatedStringWithUrl(url: String): SemanticsMatcher { return SemanticsMatcher("Has annotated string with URL $url") { node -> node.config.getOrNull(SemanticsProperties.Text)?.any { annotatedString -> annotatedString.getStringAnnotations( tag = "URL", // 此处tag要和生产代码里给链接加的注解tag完全一致 start = 0, end = annotatedString.length ).any { it.item == url } } == true } } // 点击文本中指定URL对应的区域 fun clickOnUrl(url: String): SemanticsAction<Unit> { return SemanticsAction("Click on URL $url") { node -> val textLayoutResult = node.config.getOrNull(SemanticsProperties.TextLayoutResult) ?: throw IllegalStateException("No TextLayoutResult found for node") val annotatedString = node.config[SemanticsProperties.Text].first() // 定位目标URL对应的注解范围 val urlAnnotation = annotatedString.getStringAnnotations( tag = "URL", start = 0, end = annotatedString.length ).first { it.item == url } // 计算注解文本的中心坐标 val startOffset = urlAnnotation.start val endOffset = urlAnnotation.end val startBoundingBox = textLayoutResult.getBoundingBox(startOffset) val endBoundingBox = textLayoutResult.getBoundingBox(endOffset - 1) val clickX = (startBoundingBox.left + endBoundingBox.right) / 2 val clickY = (startBoundingBox.top + startBoundingBox.bottom) / 2 // 执行精准点击 node.performTouchInput { click(Offset(clickX, clickY)) } } }
- 调整测试用例即可正常运行
@Test fun it_should_open_terms_of_service_link() { val termsLink = getString(R.string.settings_terms_link) try { Intents.init() stubAnyIntent() composeTestRule.onNode(hasAnnotatedStringWithUrl(termsLink)).perform(clickOnUrl(termsLink)) assertLinkWasOpened(termsLink) } finally { Intents.release() } }
注意事项
- 确保生产代码中给链接加
StringAnnotation时用的tag和测试代码里的tag完全一致,常规实现都会用"URL"作为tag,如果你自定义了tag记得同步修改 - 默认情况下Compose的Text组件会自动暴露
TextLayoutResult到语义树,如果你的控件开启了自定义语义合并,需要确保该属性没有被覆盖
内容的提问来源于stack exchange,提问作者John A Qualls
相关产品推荐
相关产品推荐

