我在可组合项中有一个 Switch:
Switch(
checked = false,
modifier = Modifier.testTag("mySwitch")
)
我正在尝试通过组合单元测试来验证它:
composeTestRule.onAllNodesWithTag("mySwitch")
.assertAll(isToggleable() and isOff())
但是它失败了,但出现以下异常:
java.lang.AssertionError: Failed to assertAll((ToggleableState is defined) && (ToggleableState = 'Off'))
Found '1' nodes not matching:
1) Node #8 at (l=955.0, t=387.0, r=1054.0, b=450.0)px, Tag: 'switch'
Has 4 siblings
Selector used: 'TestTag = 'mySwitch''
显然该开关既不可切换也不能“开/关”。我还分别检查了
assertIsToggleable
和 assertIsOff
,但都失败了。
我已经验证 Switch 对于测试中使用的 UI 状态是可见的。
为什么我的测试失败?应该可以轻松测试一个该死的 Switch。开关正是“可切换”的定义。那么我该如何测试它,我应该使用自定义语义属性吗?
免责声明:这个问题与这个问题不同。我想验证 Switch 状态,而不是点击它(我稍后会尝试)
我的测试代码毕竟是正确的。
assertIs[Not]Toggleable
、assertIsOff
、assertIsOn
和 assertAll(isToggleable())
应该可以在 Switch 上使用。但由于 Compose 1.2.1 中的错误(或奇怪的未记录功能),除非您将回调作为参数传递,否则 Switch 不会被标记为“可切换”。
我通过检查
Switch.kt
的源代码发现了这一点。这是导致问题的部分:
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun Switch(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
...
) {
...
val toggleableModifier =
if (onCheckedChange != null) {
Modifier.toggleable(
value = checked,
onValueChange = onCheckedChange,
enabled = enabled,
role = Role.Switch,
interactionSource = interactionSource,
indication = null
)
} else {
Modifier
}
如您所见,除非您传递可选参数
onCheckedChange
,否则 Switch 不会变为可切换。这反过来会导致所有类似 isToggleable
的断言抛出异常,并且 isOn
和 isOff
断言也会发生同样的情况,因为它们还要求节点是“可切换的”。
因此,将虚拟
onCheckedChange
lambda 传递给测试代码中的开关解决了问题:
Switch(checked=True,onCheckedChange={})
如果您不想提供
onCheckedChange
参数,您可以使用以下内容:
@Composable
fun SemanticSwitch(
checked: Boolean,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: SwitchColors = SwitchDefaults.colors()
) {
Switch(
modifier = modifier.semantics {
toggleableState = if(checked) ToggleableState.On else ToggleableState.Off
},
checked = checked,
onCheckedChange = null,
enabled = enabled,
interactionSource = interactionSource,
colors = colors
)
}
或者如果您只想应用修改器:
@Composable
fun Modifier.applyCheckedSemantics(checked: Boolean): Modifier = this.semantics {
toggleableState = if(checked) ToggleableState.On else ToggleableState.Off
}
用途:
Switch(
modifier = Modifier
.testTag("mySwitch")
.applyCheckedSemantics(switchChecked),
checked = switchChecked,
onCheckedChange = null
)
您可以在测试中使用
.assertIsOn()
。
private val checked = mutableStateOf(false)
@Before
fun launchContent() {
composeTestRule.setContent {
SharedComponentsTheme {
Switch(
checked = checked.value,
onCheckedChange = { checked.value = !checked.value }
)
}
}
}
@Test
fun testSwitchOn() {
checked.value = false
composeTestRule.onNodeWithTag("SWITCH_TAG")
.performTouchInput { click() }
.assertIsOn()
}
在代码中,您需要将
toggleableState
设置为 On
或 Off
import androidx.compose.ui.state.ToggleableState.On
import androidx.compose.ui.state.ToggleableState.Off
import androidx.compose.ui.semantics.Role.Switch
@Composable
fun Switch(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
) {
androidx.compose.material3.Switch(
modifier = Modifier
.clearAndSetSemantics {
testTag = "SWITCH_TAG",
role = Switch,
toggleableState = if (checked) {
On
} else {
Off
}
},
checked = checked,
onCheckedChange = { onCheckedChange(it) }
)
}