diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/DesktopDefaultSelectionTests.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/DesktopDefaultSelectionTests.kt new file mode 100644 index 0000000000000..8fae7f041e39e --- /dev/null +++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/DesktopDefaultSelectionTests.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023 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.text.selection + +import androidx.compose.foundation.text.KeyMapping +import androidx.compose.foundation.text.defaultSkikoKeyMapping +import androidx.compose.foundation.text.overriddenDefaultKeyMapping + +internal class DesktopDefaultSelectionTests : + CommonSelectionTests( + DefaultKeyboardActions, defaultSkikoKeyMapping + ) { + + override fun setPlatformDefaultKeyMapping(value: KeyMapping) { + overriddenDefaultKeyMapping = value + } +} diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionMacosTests.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionMacosTests.kt new file mode 100644 index 0000000000000..bad2bdeb2f013 --- /dev/null +++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/DesktopSelectionMacosTests.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023 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.text.selection + +import androidx.compose.foundation.text.KeyMapping +import androidx.compose.foundation.text.createMacosDefaultKeyMapping +import androidx.compose.foundation.text.overriddenDefaultKeyMapping + +internal class DesktopSelectionMacosTests : + CommonSelectionTests( + MacosKeyboardActions, createMacosDefaultKeyMapping() + ) { + + override fun setPlatformDefaultKeyMapping(value: KeyMapping) { + overriddenDefaultKeyMapping = value + } +} diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/SelectionTests.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/SelectionTests.kt deleted file mode 100644 index 947f8d71a1557..0000000000000 --- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/text/selection/SelectionTests.kt +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2023 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.text.selection - -import androidx.compose.foundation.DesktopPlatform -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyMapping -import androidx.compose.foundation.text.createPlatformDefaultKeyMapping -import androidx.compose.foundation.text.overriddenDefaultKeyMapping -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.test.* -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.TextFieldValue -import com.google.common.truth.Truth -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Rule -import org.junit.Test - - -class SelectionTests { - - @get:Rule - val rule = createComposeRule() - - @After - fun restoreRealDesktopPlatform() { - overriddenDefaultKeyMapping = null - } - - private fun setPlatformDefaultKeyMapping(value: KeyMapping) { - overriddenDefaultKeyMapping = value - } - - suspend fun SemanticsNodeInteraction.waitAndCheck(check: () -> Unit): SemanticsNodeInteraction { - rule.awaitIdle() - check() - return this - } - - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.textFieldSemanticInteraction(initialValue: String = "", semanticNodeContext: suspend SemanticsNodeInteraction.(state: MutableState) -> SemanticsNodeInteraction) = - runBlocking { - setPlatformDefaultKeyMapping(createPlatformDefaultKeyMapping(this@textFieldSemanticInteraction)) - val state = mutableStateOf(TextFieldValue(initialValue)) - - rule.setContent { - BasicTextField( - value = state.value, - onValueChange = { state.value = it }, - modifier = Modifier.testTag("textField") - ) - } - rule.awaitIdle() - val textField = rule.onNodeWithTag("textField") - textField.performMouseInput { - click(Offset(0f, 0f)) - } - - rule.awaitIdle() - textField.assertIsFocused() - - Truth.assertThat(state.value.selection).isEqualTo(TextRange(0, 0)) - - semanticNodeContext.invoke(textField, state) - } - - - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.selectLineStart(keyboardInteraction: KeyInjectionScope.() -> Unit) { - textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { state -> - performKeyInput { - pressKey(Key.DirectionRight) - pressKey(Key.DirectionDown) - } - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) - } - .performKeyInput(keyboardInteraction) - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 7)) - } - } - } - - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.selectTextStart(keyboardInteraction: KeyInjectionScope.() -> Unit) { - textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { state -> - performKeyInput { - pressKey(Key.DirectionRight) - pressKey(Key.DirectionDown) - }.waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) - } - performKeyInput(keyboardInteraction) - .waitAndCheck { Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 0)) } - } - } - - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.selectTextEnd(keyboardInteraction: KeyInjectionScope.() -> Unit) { - textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { state -> - performKeyInput { - pressKey(Key.DirectionRight) - pressKey(Key.DirectionDown) - } - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) - } - .performKeyInput(keyboardInteraction) - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 34)) - } - } - } - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.selectLineEnd(keyboardInteraction: KeyInjectionScope.() -> Unit) { - textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { state -> - performKeyInput { - pressKey(Key.DirectionRight) - pressKey(Key.DirectionDown) - }.waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) - } - .performKeyInput(keyboardInteraction) - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(8, 13)) - } - } - } - - @Test - fun `Select till line start with DesktopPlatform-Windows`() = runBlocking { - DesktopPlatform.Windows.selectLineStart { - keyDown(Key.ShiftLeft) - pressKey(Key.MoveHome) - keyUp(Key.ShiftLeft) - } - } - - @Test - fun `Select till text start with DesktopPlatform-Windows`() = runBlocking { - DesktopPlatform.Windows.selectTextStart { - keyDown(Key.CtrlLeft) - keyDown(Key.ShiftLeft) - pressKey(Key.MoveHome) - keyUp(Key.ShiftLeft) - keyUp(Key.CtrlLeft) - } - } - - @Test - fun `Select till line end with DesktopPlatform-Windows`() = runBlocking { - DesktopPlatform.Windows.selectLineEnd { - keyDown(Key.ShiftLeft) - pressKey(Key.MoveEnd) - keyUp(Key.ShiftLeft) - } - } - - @Test - fun `Select till text end with DesktopPlatform-Windows`() = runBlocking { - DesktopPlatform.Windows.selectTextEnd { - keyDown(Key.CtrlLeft) - keyDown(Key.ShiftLeft) - pressKey(Key.MoveEnd) - keyUp(Key.ShiftLeft) - keyUp(Key.CtrlLeft) - } - } - - - @Test - fun `Select till line start with DesktopPlatform-MacOs`() = runBlocking { - DesktopPlatform.MacOS.selectLineStart() { - keyDown(Key.ShiftLeft) - keyDown(Key.MetaLeft) - pressKey(Key.DirectionLeft) - keyUp(Key.ShiftLeft) - keyUp(Key.MetaLeft) - } - } - - @Test - fun `Select till text start with DesktopPlatform-MacOs`() = runBlocking { - DesktopPlatform.MacOS.selectTextStart { - keyDown(Key.ShiftLeft) - pressKey(Key.Home) - keyUp(Key.ShiftLeft) - } - } - - @Test - fun `Select till line end with DesktopPlatform-Macos`() = runBlocking { - DesktopPlatform.MacOS.selectLineEnd { - keyDown(Key.ShiftLeft) - keyDown(Key.MetaLeft) - pressKey(Key.DirectionRight) - keyUp(Key.ShiftLeft) - keyUp(Key.MetaLeft) - } - } - - @Test - fun `Select till text end with DesktopPlatform-Macos`() = runBlocking { - DesktopPlatform.MacOS.selectTextEnd { - keyDown(Key.ShiftLeft) - pressKey(Key.MoveEnd) - keyUp(Key.ShiftLeft) - } - } - - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.deleteAllFromKeyBoard( - initialText: String, deleteAllInteraction: KeyInjectionScope.() -> Unit - ) { - textFieldSemanticInteraction(initialText) { state -> - performKeyInput(deleteAllInteraction).waitAndCheck { Truth.assertThat(state.value.text).isEqualTo("") } - } - } - - - @Test - fun `Delete backwards on an empty line with DesktopPlatform-Windows`() { - DesktopPlatform.Windows.deleteAllFromKeyBoard("") { - keyDown(Key.CtrlLeft) - keyDown(Key.Backspace) - } - } - - @Test - fun `Delete backwards on an empty line with DesktopPlatform-Macos`() { - DesktopPlatform.MacOS.deleteAllFromKeyBoard("") { - keyDown(Key.MetaLeft) - keyDown(Key.Delete) - } - } - - @OptIn(ExperimentalTestApi::class) - private fun DesktopPlatform.selectAllTest(selectAllInteraction: KeyInjectionScope.() -> Unit) { - textFieldSemanticInteraction("Select this text") { state -> - performKeyInput(selectAllInteraction) - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(0, 16)) - } - .performKeyInput { keyDown(Key.Delete) } - .waitAndCheck { - Truth.assertThat(state.value.selection).isEqualTo(TextRange(0, 0)) - Truth.assertThat(state.value.text).isEqualTo("") - } - } - } - - @Test - fun `Select all with DesktopPlatform-Windows`() = runBlocking { - DesktopPlatform.Windows.selectAllTest { - keyDown(Key.CtrlLeft) - pressKey(Key.A) - keyUp(Key.CtrlLeft) - } - } - - @Test - fun `Select all with DesktopPlatform-Macos`() = runBlocking { - DesktopPlatform.MacOS.selectAllTest { - keyDown(Key.MetaLeft) - pressKey(Key.A) - keyUp(Key.MetaLeft) - } - } -} diff --git a/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/text/selection/CommonSelectionTest.kt b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/text/selection/CommonSelectionTest.kt new file mode 100644 index 0000000000000..800e06144d51f --- /dev/null +++ b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/text/selection/CommonSelectionTest.kt @@ -0,0 +1,168 @@ +/* + * 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.text.selection + +import androidx.compose.foundation.assertThat +import androidx.compose.foundation.isEqualTo +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyMapping +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.assertIsFocused +import androidx.compose.ui.test.click +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performKeyInput +import androidx.compose.ui.test.performMouseInput +import androidx.compose.ui.test.pressKey +import androidx.compose.ui.test.runComposeUiTest +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue +import kotlin.test.Test + +internal abstract class CommonSelectionTests( + val keyboardActions: KeyboardActions, + private val keyMapping: KeyMapping +) : KeyboardActions by keyboardActions { + + abstract fun setPlatformDefaultKeyMapping(value: KeyMapping) + + @OptIn(ExperimentalTestApi::class) + private fun textFieldSemanticInteraction( + initialValue: String = "", + runAction: ComposeUiTest.(node: SemanticsNodeInteraction, state: MutableState) -> Unit + ) = + runComposeUiTest { + setPlatformDefaultKeyMapping(keyMapping) + val state = mutableStateOf(TextFieldValue(initialValue)) + + waitForIdle() + + setContent { + BasicTextField( + value = state.value, + onValueChange = { state.value = it }, + modifier = Modifier.testTag("textField") + ) + } + + onNodeWithTag("textField").apply { + performMouseInput { + click(Offset(0f, 0f)) + } + + assertIsFocused() + + assertThat(state.value.selection).isEqualTo(TextRange(0, 0)) + + runAction(this, state) + } + } + + + @OptIn(ExperimentalTestApi::class) + @Test + fun selectLineStart() { + textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { node, state -> + node.performKeyInput { + pressKey(Key.DirectionRight) + pressKey(Key.DirectionDown) + } + assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) + + node.performKeyInput { this.selectLineStart() } + + assertThat(state.value.selection).isEqualTo(TextRange(8, 7)) + } + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun selectTextStart() { + textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { node, state -> + node.performKeyInput { + pressKey(Key.DirectionRight) + pressKey(Key.DirectionDown) + } + + assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) + + node.performKeyInput { this.selectTextStart() } + + assertThat(state.value.selection).isEqualTo(TextRange(8, 0)) + } + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun selectTextEnd() { + textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { node, state -> + node.performKeyInput { + pressKey(Key.DirectionRight) + pressKey(Key.DirectionDown) + } + assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) + + node.performKeyInput { this.selectTextEnd() } + assertThat(state.value.selection).isEqualTo(TextRange(8, 34)) + } + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun selectLineEnd() { + textFieldSemanticInteraction("line 1\nline 2\nline 3\nline 4\nline 5") { node, state -> + node.performKeyInput { + pressKey(Key.DirectionRight) + pressKey(Key.DirectionDown) + } + + assertThat(state.value.selection).isEqualTo(TextRange(8, 8)) + + node.performKeyInput { this.selectLineEnd() } + assertThat(state.value.selection).isEqualTo(TextRange(8, 13)) + } + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun deleteAll() { + textFieldSemanticInteraction("") { node, state -> + node.performKeyInput { this.deleteAll() } + assertThat(state.value.text).isEqualTo("") + } + } + + @OptIn(ExperimentalTestApi::class) + @Test + fun selectAll() { + textFieldSemanticInteraction("Select this text") { node, state -> + node.performKeyInput { this.selectAll() } + assertThat(state.value.selection).isEqualTo(TextRange(0, 16)) + + node.performKeyInput { keyDown(Key.Delete) } + assertThat(state.value.selection).isEqualTo(TextRange(0, 0)) + assertThat(state.value.text).isEqualTo("") + } + } +} diff --git a/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/text/selection/KeyboardActions.kt b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/text/selection/KeyboardActions.kt new file mode 100644 index 0000000000000..ece9f91226466 --- /dev/null +++ b/compose/foundation/foundation/src/skikoTest/kotlin/androidx/compose/foundation/text/selection/KeyboardActions.kt @@ -0,0 +1,116 @@ +/* + * 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.text.selection + +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.KeyInjectionScope +import androidx.compose.ui.test.pressKey + +interface KeyboardActions { + fun KeyInjectionScope.selectAll() + fun KeyInjectionScope.selectLineStart() + fun KeyInjectionScope.selectTextStart() + fun KeyInjectionScope.selectLineEnd() + fun KeyInjectionScope.selectTextEnd() + fun KeyInjectionScope.deleteAll() +} + +object DefaultKeyboardActions : KeyboardActions { + override fun KeyInjectionScope.selectAll() { + keyDown(Key.CtrlLeft) + pressKey(Key.A) + keyUp(Key.CtrlLeft) + } + + override fun KeyInjectionScope.selectLineStart() { + keyDown(Key.ShiftLeft) + pressKey(Key.MoveHome) + keyUp(Key.ShiftLeft) + } + + override fun KeyInjectionScope.selectTextStart() { + keyDown(Key.CtrlLeft) + keyDown(Key.ShiftLeft) + pressKey(Key.MoveHome) + keyUp(Key.ShiftLeft) + keyUp(Key.CtrlLeft) + } + + override fun KeyInjectionScope.selectLineEnd() { + keyDown(Key.ShiftLeft) + pressKey(Key.MoveEnd) + keyUp(Key.ShiftLeft) + } + + override fun KeyInjectionScope.selectTextEnd() { + keyDown(Key.CtrlLeft) + keyDown(Key.ShiftLeft) + pressKey(Key.MoveEnd) + keyUp(Key.ShiftLeft) + keyUp(Key.CtrlLeft) + } + + override fun KeyInjectionScope.deleteAll() { + keyDown(Key.CtrlLeft) + keyDown(Key.Backspace) + } + + override fun toString() = "Win" +} + +object MacosKeyboardActions : KeyboardActions { + override fun KeyInjectionScope.selectAll() { + keyDown(Key.MetaLeft) + pressKey(Key.A) + keyUp(Key.MetaLeft) + } + + override fun KeyInjectionScope.selectLineStart() { + keyDown(Key.ShiftLeft) + keyDown(Key.MetaLeft) + pressKey(Key.DirectionLeft) + keyUp(Key.ShiftLeft) + keyUp(Key.MetaLeft) + } + + override fun KeyInjectionScope.selectTextStart() { + keyDown(Key.ShiftLeft) + pressKey(Key.Home) + keyUp(Key.ShiftLeft) + } + + override fun KeyInjectionScope.selectLineEnd() { + keyDown(Key.ShiftLeft) + keyDown(Key.MetaLeft) + pressKey(Key.DirectionRight) + keyUp(Key.ShiftLeft) + keyUp(Key.MetaLeft) + } + + override fun KeyInjectionScope.selectTextEnd() { + keyDown(Key.ShiftLeft) + pressKey(Key.MoveEnd) + keyUp(Key.ShiftLeft) + } + + override fun KeyInjectionScope.deleteAll() { + keyDown(Key.MetaLeft) + keyDown(Key.Delete) + } + + override fun toString() = "MacOS" +} \ No newline at end of file diff --git a/compose/foundation/foundation/src/wasmJsTest/kotlin/androidx.compose.foundation.text.selection/WasmSelectionTests.kt b/compose/foundation/foundation/src/wasmJsTest/kotlin/androidx.compose.foundation.text.selection/WasmSelectionTests.kt new file mode 100644 index 0000000000000..f24eff9d4bbb8 --- /dev/null +++ b/compose/foundation/foundation/src/wasmJsTest/kotlin/androidx.compose.foundation.text.selection/WasmSelectionTests.kt @@ -0,0 +1,39 @@ +/* + * 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.text.selection + +import androidx.compose.foundation.text.KeyMapping +import androidx.compose.foundation.text.createPlatformDefaultKeyMapping +import org.jetbrains.skiko.hostOs +import kotlin.test.Test +import org.jetbrains.skiko.OS + +private val ResolvedKeyboardActions + get() = when (hostOs) { + OS.MacOS -> MacosKeyboardActions + else -> DefaultKeyboardActions + } + +internal class WasmSelectionTests : + CommonSelectionTests( + ResolvedKeyboardActions, + createPlatformDefaultKeyMapping(hostOs) + ) { + + override fun setPlatformDefaultKeyMapping(value: KeyMapping) { + } +} diff --git a/compose/foundation/foundation/src/webCommonW3C/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.js.kt b/compose/foundation/foundation/src/webCommonW3C/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.js.kt index 36cf957bc6285..ee83d5660887e 100644 --- a/compose/foundation/foundation/src/webCommonW3C/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.js.kt +++ b/compose/foundation/foundation/src/webCommonW3C/kotlin/androidx/compose/foundation/text/TextFieldKeyInput.js.kt @@ -18,13 +18,12 @@ package androidx.compose.foundation.text import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.isMetaPressed - -import org.jetbrains.skiko.* +import org.jetbrains.skiko.SkikoKeyboardEventKind +import org.jetbrains.skiko.SkikoPlatformKeyboardEvent actual val KeyEvent.isTypedEvent: Boolean get() = nativeKeyEvent.kind == SkikoKeyboardEventKind.DOWN && !isMetaPressed && nativeKeyEvent.platform?.isPrintable() == true - private fun SkikoPlatformKeyboardEvent.isPrintable(): Boolean { - return key.firstOrNull()?.toChar()?.toString() == key + return key.firstOrNull()?.toString() == key } diff --git a/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/InputDispatcher.wasmMain.kt b/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/InputDispatcher.wasmMain.kt index f2ec9aa63bbbc..71b069d849bcb 100644 --- a/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/InputDispatcher.wasmMain.kt +++ b/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/InputDispatcher.wasmMain.kt @@ -13,13 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:OptIn(ExperimentalTestApi::class) package androidx.compose.ui.test import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType +import org.jetbrains.skiko.SkikoInputModifiers +import org.jetbrains.skiko.SkikoKey +import org.jetbrains.skiko.SkikoKeyboardEvent +import org.jetbrains.skiko.SkikoKeyboardEventKind +import org.w3c.dom.events.KeyboardEvent +import org.w3c.dom.events.KeyboardEventInit /** * The [KeyEvent] is usually created by the system. This function creates an instance of @@ -27,6 +32,65 @@ import androidx.compose.ui.input.key.KeyEventType */ internal actual fun keyEvent( key: Key, keyEventType: KeyEventType, modifiers: Int -): KeyEvent = TODO() +): KeyEvent { + val nativeCode = key.keyCode.toInt() -internal actual fun Int.updatedKeyboardModifiers(key: Key, down: Boolean): Int = this // TODO: implement updatedKeyboardModifiers + val kind = when (keyEventType) { + KeyEventType.KeyUp -> SkikoKeyboardEventKind.UP + KeyEventType.KeyDown -> SkikoKeyboardEventKind.DOWN + else -> SkikoKeyboardEventKind.UNKNOWN + } + + return KeyEvent( + SkikoKeyboardEvent( + kind = kind, + key = SkikoKey.valueOf(nativeCode), + modifiers = SkikoInputModifiers(modifiers), + platform = KeyboardEvent( + when (kind) { + SkikoKeyboardEventKind.DOWN -> "keydown" + SkikoKeyboardEventKind.UP -> "keyup" + else -> "keypress" + }, KeyboardEventInit( + key = if (key.isPrintable()) nativeCode.toChar().toString() else "Unknown" + ) + ) + ) + ) +} + +// Basically this is what we've tried to avoid in isTypedEvent implementation, but in tests it's totally fine +// Just add any non-printable key you need for tests here +private val NonPrintableKeys = setOf( + Key.AltLeft, + Key.MetaLeft, + Key.ShiftLeft, + Key.CtrlLeft, + Key.Delete, + Key.DirectionLeft, + Key.DirectionRight, + Key.DirectionDown, + Key.DirectionUp, + Key.Home, + Key.MoveEnd +) + +private fun Key.isPrintable(): Boolean { + return !NonPrintableKeys.contains(this) +} + +internal actual fun Int.updatedKeyboardModifiers(key: Key, down: Boolean): Int { + val mask = when (key) { + Key.ShiftLeft, Key.ShiftRight -> SkikoInputModifiers.SHIFT.value + Key.CtrlLeft, Key.CtrlRight -> SkikoInputModifiers.CONTROL.value + Key.MetaLeft, Key.MetaRight -> SkikoInputModifiers.META.value + Key.AltLeft, Key.AltRight -> SkikoInputModifiers.ALT.value + else -> null + } + + return if (mask != null) { + if (down) this or mask else this xor mask + } else { + this + } +} \ No newline at end of file