From 6aca09aceda8fd1848cd161348debf3af53efe15 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 23 Jul 2024 12:53:07 +0200 Subject: [PATCH 1/3] Make clickable react to NumPadEnter and Spacebar, too Adding a bunch of tests, too, to ensure clickable responds to Enter (existing behaviour, not tested), NumPadEnter, and Spacebar. Testing that toggleable and selectable have the same behaviour, since they both use clickable under the hood. --- .../compose/foundation/Clickable.skiko.kt | 5 +- .../compose/foundation/ClickableTest.kt | 1052 +++++++++++++++++ 2 files changed, 1054 insertions(+), 3 deletions(-) create mode 100644 compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/Clickable.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/Clickable.skiko.kt index 0dc50e3469adf..3b9ede84f233a 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/Clickable.skiko.kt +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/Clickable.skiko.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type -import androidx.compose.ui.node.CompositionLocalConsumerModifierNode import androidx.compose.ui.node.DelegatableNode // TODO(https://github.com/JetBrains/compose-multiplatform/issues/3341): support isComposeRootInScrollableContainer @@ -32,7 +31,7 @@ internal actual fun DelegatableNode } internal actual val KeyEvent.isPress: Boolean - get() = type == KeyDown && key == Key.Enter + get() = type == KeyDown && (key == Key.Enter || key == Key.NumPadEnter || key == Key.Spacebar) internal actual val KeyEvent.isClick: Boolean - get() = type == KeyUp && key == Key.Enter + get() = type == KeyUp && (key == Key.Enter || key == Key.NumPadEnter || key == Key.Spacebar) diff --git a/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt new file mode 100644 index 0000000000000..f9534211a997a --- /dev/null +++ b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt @@ -0,0 +1,1052 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.toggleable +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performKeyInput +import androidx.compose.ui.test.runSkikoComposeUiTest +import androidx.compose.ui.test.withKeyDown +import androidx.compose.ui.unit.dp +import kotlin.test.Test + +@OptIn(ExperimentalTestApi::class) +class ClickableTest { + @Test + fun trigger_clickable_with_enter_key() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + } + + @Test + fun trigger_clickable_with_enter_key_and_shift() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.ShiftLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.ShiftRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_enter_key_and_alt() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.AltLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.AltRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_enter_key_and_ctrl() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.CtrlLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.CtrlRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_enter_key_and_meta() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.MetaLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.MetaRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_numpad_enter_key() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + } + + @Test + fun trigger_clickable_with_numpad_enter_key_and_shift() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.ShiftLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.ShiftRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_numpad_enter_key_and_alt() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.AltLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.AltRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_numpad_enter_key_and_ctrl() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.CtrlLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.CtrlRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_numpad_enter_key_and_meta() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.MetaLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.MetaRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_spacebar_key() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + } + + @Test + fun trigger_clickable_with_spacebar_key_and_shift() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.ShiftLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.ShiftRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_spacebar_key_and_alt() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.AltLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.AltRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_spacebar_key_and_ctrl() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.CtrlLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.CtrlRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_clickable_with_spacebar_key_and_meta() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .clickable { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.MetaLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(1) + + onNodeWithTag("my-clickable").performKeyInput { + withKeyDown(Key.MetaRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(2) + } + + @Test + fun trigger_toggleable_with_enter_key_too() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .toggleable(false) { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.Enter) + keyUp(Key.Enter) + + withKeyDown(Key.ShiftLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.ShiftRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.CtrlLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.CtrlRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.AltLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.AltRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.MetaLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.MetaRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(9) + } + + @Test + fun trigger_selectable_with_enter_key_too() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .selectable(false) { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.Enter) + keyUp(Key.Enter) + + withKeyDown(Key.ShiftLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.ShiftRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.CtrlLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.CtrlRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.AltLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.AltRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.MetaLeft) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + withKeyDown(Key.MetaRight) { + keyDown(Key.Enter) + keyUp(Key.Enter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(9) + } + + @Test + fun trigger_toggleable_with_numpad_enter_key_too() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .toggleable(false) { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + + withKeyDown(Key.ShiftLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.ShiftRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.CtrlLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.CtrlRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.AltLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.AltRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.MetaLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.MetaRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(9) + } + + @Test + fun trigger_selectable_with_numpad_enter_key_too() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .selectable(false) { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + + withKeyDown(Key.ShiftLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.ShiftRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.CtrlLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.CtrlRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.AltLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.AltRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.MetaLeft) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + withKeyDown(Key.MetaRight) { + keyDown(Key.NumPadEnter) + keyUp(Key.NumPadEnter) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(9) + } + + @Test + fun trigger_toggleable_with_spacebar_key_too() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .toggleable(false) { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + + withKeyDown(Key.ShiftLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.ShiftRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.CtrlLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.CtrlRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.AltLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.AltRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.MetaLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.MetaRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(9) + } + + @Test + fun trigger_selectable_with_spacebar_key_too() = runSkikoComposeUiTest { + val focusRequester = FocusRequester() + var clicksCount = 0 + var focused = false + + setContent { + Box( + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { focused = it.isFocused } + .selectable(false) { clicksCount++ } + .size(10.dp, 20.dp) + .testTag("my-clickable") + ) + } + + waitForIdle() + focusRequester.requestFocus() + + waitForIdle() + assertThat(focused, "Component is focused").isTrue() + + onNodeWithTag("my-clickable").performKeyInput { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + + withKeyDown(Key.ShiftLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.ShiftRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.CtrlLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.CtrlRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.AltLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.AltRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.MetaLeft) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + withKeyDown(Key.MetaRight) { + keyDown(Key.Spacebar) + keyUp(Key.Spacebar) + } + } + + waitForIdle() + assertThat(clicksCount).isEqualTo(9) + } +} From 3d36b676d43da2ed257ffd06b8c4350d71b398a3 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 23 Jul 2024 14:16:34 +0200 Subject: [PATCH 2/3] Rename ClickableTest to ClickableKeyTest --- .../{MouseClickableTest.kt => MouseClickableKeyTest.kt} | 2 +- .../foundation/{ClickableTest.kt => ClickableKeyTest.kt} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/{MouseClickableTest.kt => MouseClickableKeyTest.kt} (99%) rename compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/{ClickableTest.kt => ClickableKeyTest.kt} (99%) diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableKeyTest.kt similarity index 99% rename from compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableTest.kt rename to compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableKeyTest.kt index efc6bb5630016..4b71198c78bb7 100644 --- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableTest.kt +++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableKeyTest.kt @@ -34,7 +34,7 @@ import org.junit.Test @Suppress("DEPRECATION") @OptIn(ExperimentalFoundationApi::class) -class MouseClickableTest { +class MouseClickableKeyTest { @Test fun click() = ImageComposeScene( width = 100, diff --git a/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableKeyTest.kt similarity index 99% rename from compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt rename to compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableKeyTest.kt index f9534211a997a..ce9bebaa6dc8b 100644 --- a/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableTest.kt +++ b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/ClickableKeyTest.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.unit.dp import kotlin.test.Test @OptIn(ExperimentalTestApi::class) -class ClickableTest { +class ClickableKeyTest { @Test fun trigger_clickable_with_enter_key() = runSkikoComposeUiTest { val focusRequester = FocusRequester() From fd786506d311278d9a0ac7c2487d75a65acb0c96 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Tue, 23 Jul 2024 14:35:47 +0200 Subject: [PATCH 3/3] Undo accidental renaming --- .../{MouseClickableKeyTest.kt => MouseClickableTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/{MouseClickableKeyTest.kt => MouseClickableTest.kt} (99%) diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableKeyTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableTest.kt similarity index 99% rename from compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableKeyTest.kt rename to compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableTest.kt index 4b71198c78bb7..efc6bb5630016 100644 --- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableKeyTest.kt +++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/MouseClickableTest.kt @@ -34,7 +34,7 @@ import org.junit.Test @Suppress("DEPRECATION") @OptIn(ExperimentalFoundationApi::class) -class MouseClickableKeyTest { +class MouseClickableTest { @Test fun click() = ImageComposeScene( width = 100,