Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Filip Pavlis | bc81dcd | 2020-10-21 19:25:11 +0100 | [diff] [blame] | 17 | package androidx.compose.ui.test |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 18 | |
Louis Pullen-Freilich | ddda7be | 2020-07-17 18:28:12 +0100 | [diff] [blame] | 19 | import androidx.compose.foundation.ExperimentalFoundationApi |
Matvei Malkov | 575d0df | 2020-10-28 22:15:09 +0300 | [diff] [blame] | 20 | import androidx.compose.foundation.text.BasicTextField |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 21 | import androidx.compose.foundation.text.KeyboardActions |
Siyamed Sinir | 8951653 | 2020-10-30 19:09:45 -0700 | [diff] [blame] | 22 | import androidx.compose.foundation.text.KeyboardOptions |
Adam Powell | 94867223 | 2020-07-28 12:06:47 -0700 | [diff] [blame] | 23 | import androidx.compose.runtime.Composable |
| 24 | import androidx.compose.runtime.mutableStateOf |
| 25 | import androidx.compose.runtime.remember |
| 26 | import androidx.compose.ui.Modifier |
| 27 | import androidx.compose.ui.platform.testTag |
Filip Pavlis | 2bf7891 | 2020-10-26 18:01:47 +0000 | [diff] [blame] | 28 | import androidx.compose.ui.test.junit4.createComposeRule |
Matvei Malkov | 575d0df | 2020-10-28 22:15:09 +0300 | [diff] [blame] | 29 | import androidx.compose.ui.test.util.BoundaryNode |
| 30 | import androidx.compose.ui.test.util.expectErrorMessageStartsWith |
Louis Pullen-Freilich | ab19475 | 2020-07-21 22:21:22 +0100 | [diff] [blame] | 31 | import androidx.compose.ui.text.input.ImeAction |
Matvei Malkov | 575d0df | 2020-10-28 22:15:09 +0300 | [diff] [blame] | 32 | import androidx.test.ext.junit.runners.AndroidJUnit4 |
Adam Powell | 94867223 | 2020-07-28 12:06:47 -0700 | [diff] [blame] | 33 | import androidx.test.filters.MediumTest |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 34 | import com.google.common.truth.Truth.assertThat |
| 35 | import org.junit.Rule |
| 36 | import org.junit.Test |
| 37 | import org.junit.runner.RunWith |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 38 | |
| 39 | @MediumTest |
Jelle Fresen | 828e83b | 2020-10-20 12:01:28 +0100 | [diff] [blame] | 40 | @RunWith(AndroidJUnit4::class) |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 41 | class TextActionsTest { |
| 42 | |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 43 | private val fieldTag = "Field" |
| 44 | |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 45 | @get:Rule |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 46 | val rule = createComposeRule() |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 47 | |
| 48 | @Composable |
Matvei Malkov | 790b311 | 2020-07-15 14:09:37 +0100 | [diff] [blame] | 49 | @OptIn(ExperimentalFoundationApi::class) |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 50 | fun TextFieldUi( |
Siyamed Sinir | 1cd8bb9 | 2021-01-08 01:41:47 -0800 | [diff] [blame] | 51 | imeAction: ImeAction = ImeAction.Default, |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 52 | keyboardActions: KeyboardActions = KeyboardActions.Default, |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 53 | textCallback: (String) -> Unit = {} |
| 54 | ) { |
Siyamed Sinir | 663ecc0 | 2020-11-16 22:51:52 -0800 | [diff] [blame] | 55 | val state = remember { mutableStateOf("") } |
Matvei Malkov | 575d0df | 2020-10-28 22:15:09 +0300 | [diff] [blame] | 56 | BasicTextField( |
Alexandre Elias | 5c8252f | 2020-05-27 15:52:40 -0700 | [diff] [blame] | 57 | modifier = Modifier.testTag(fieldTag), |
| 58 | value = state.value, |
Siyamed Sinir | 8951653 | 2020-10-30 19:09:45 -0700 | [diff] [blame] | 59 | keyboardOptions = KeyboardOptions(imeAction = imeAction), |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 60 | keyboardActions = keyboardActions, |
Alexandre Elias | 5c8252f | 2020-05-27 15:52:40 -0700 | [diff] [blame] | 61 | onValueChange = { |
| 62 | state.value = it |
Siyamed Sinir | 663ecc0 | 2020-11-16 22:51:52 -0800 | [diff] [blame] | 63 | textCallback(it) |
Alexandre Elias | 5c8252f | 2020-05-27 15:52:40 -0700 | [diff] [blame] | 64 | } |
| 65 | ) |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | @Test |
| 69 | fun sendText_clearText() { |
| 70 | var lastSeenText = "" |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 71 | rule.setContent { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 72 | TextFieldUi { |
| 73 | lastSeenText = it |
| 74 | } |
| 75 | } |
| 76 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 77 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 78 | .performTextInput("Hello!") |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 79 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 80 | rule.runOnIdle { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 81 | assertThat(lastSeenText).isEqualTo("Hello!") |
| 82 | } |
| 83 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 84 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | a038234 | 2021-02-09 14:56:23 +0000 | [diff] [blame] | 85 | .performTextClearance() |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 86 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 87 | rule.runOnIdle { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 88 | assertThat(lastSeenText).isEqualTo("") |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | @Test |
| 93 | fun sendTextTwice_shouldAppend() { |
| 94 | var lastSeenText = "" |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 95 | rule.setContent { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 96 | TextFieldUi { |
| 97 | lastSeenText = it |
| 98 | } |
| 99 | } |
| 100 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 101 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 102 | .performTextInput("Hello ") |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 103 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 104 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | a038234 | 2021-02-09 14:56:23 +0000 | [diff] [blame] | 105 | .performTextInput("world!") |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 106 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 107 | rule.runOnIdle { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 108 | assertThat(lastSeenText).isEqualTo("Hello world!") |
| 109 | } |
| 110 | } |
| 111 | |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 112 | // @Test - not always appends, seems to be flaky |
| 113 | fun sendTextTwice_shouldAppend_ver2() { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 114 | var lastSeenText = "" |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 115 | rule.setContent { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 116 | TextFieldUi { |
| 117 | lastSeenText = it |
| 118 | } |
| 119 | } |
| 120 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 121 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 122 | .performTextInput("Hello") |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 123 | |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 124 | // This helps. So there must be some timing issue. |
| 125 | // Thread.sleep(3000) |
| 126 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 127 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | a038234 | 2021-02-09 14:56:23 +0000 | [diff] [blame] | 128 | .performTextInput(" world!") |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 129 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 130 | rule.runOnIdle { |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 131 | assertThat(lastSeenText).isEqualTo("Hello world!") |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | @Test |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 136 | fun replaceText() { |
| 137 | var lastSeenText = "" |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 138 | rule.setContent { |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 139 | TextFieldUi { |
| 140 | lastSeenText = it |
| 141 | } |
| 142 | } |
| 143 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 144 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 145 | .performTextInput("Hello") |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 146 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 147 | rule.runOnIdle { |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 148 | assertThat(lastSeenText).isEqualTo("Hello") |
| 149 | } |
| 150 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 151 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | a038234 | 2021-02-09 14:56:23 +0000 | [diff] [blame] | 152 | .performTextReplacement("world") |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 153 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 154 | rule.runOnIdle { |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 155 | assertThat(lastSeenText).isEqualTo("world") |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | @Test |
| 160 | fun sendImeAction_search() { |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 161 | var actionPerformed = false |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 162 | rule.setContent { |
Jeff Gaston | ad2fc53 | 2020-09-18 15:10:46 -0400 | [diff] [blame] | 163 | TextFieldUi( |
| 164 | imeAction = ImeAction.Search, |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 165 | keyboardActions = KeyboardActions(onSearch = { actionPerformed = true }) |
Jeff Gaston | ad2fc53 | 2020-09-18 15:10:46 -0400 | [diff] [blame] | 166 | ) |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 167 | } |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 168 | assertThat(actionPerformed).isFalse() |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 169 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 170 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 171 | .performImeAction() |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 172 | |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 173 | rule.runOnIdle { |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 174 | assertThat(actionPerformed).isTrue() |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 175 | } |
| 176 | } |
| 177 | |
| 178 | @Test |
| 179 | fun sendImeAction_actionNotDefined_shouldFail() { |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 180 | var actionPerformed = false |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 181 | rule.setContent { |
Jeff Gaston | ad2fc53 | 2020-09-18 15:10:46 -0400 | [diff] [blame] | 182 | TextFieldUi( |
Siyamed Sinir | 1cd8bb9 | 2021-01-08 01:41:47 -0800 | [diff] [blame] | 183 | imeAction = ImeAction.Default, |
Ralston Da Silva | 4b76bcb | 2021-02-03 14:55:03 -0800 | [diff] [blame] | 184 | keyboardActions = KeyboardActions { actionPerformed = true } |
Jeff Gaston | ad2fc53 | 2020-09-18 15:10:46 -0400 | [diff] [blame] | 185 | ) |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 186 | } |
Ralston Da Silva | 82855d6 | 2021-02-03 00:11:06 -0800 | [diff] [blame] | 187 | assertThat(actionPerformed).isFalse() |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 188 | |
Jeff Gaston | ad2fc53 | 2020-09-18 15:10:46 -0400 | [diff] [blame] | 189 | expectErrorMessageStartsWith( |
| 190 | "" + |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 191 | "Failed to perform IME action as current node does not specify any.\n" + |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 192 | "Semantics of the node:" |
| 193 | ) { |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 194 | rule.onNodeWithTag(fieldTag) |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 195 | .performImeAction() |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 196 | } |
| 197 | } |
| 198 | |
| 199 | @Test |
| 200 | fun sendImeAction_inputNotSupported_shouldFail() { |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 201 | rule.setContent { |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 202 | BoundaryNode(testTag = "node") |
| 203 | } |
| 204 | |
Jeff Gaston | ad2fc53 | 2020-09-18 15:10:46 -0400 | [diff] [blame] | 205 | expectErrorMessageStartsWith( |
| 206 | "" + |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 207 | "Failed to perform IME action.\n" + |
Filip Pavlis | 13bdbe7 | 2020-11-06 17:35:47 +0000 | [diff] [blame] | 208 | "Failed to assert the following: (SetText is defined)\n" + |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 209 | "Semantics of the node:" |
| 210 | ) { |
Filip Pavlis | a76b5bd7 | 2020-09-03 12:54:27 +0100 | [diff] [blame] | 211 | rule.onNodeWithTag("node") |
Filip Pavlis | 659ea72 | 2020-07-13 14:14:32 +0100 | [diff] [blame] | 212 | .performImeAction() |
Filip Pavlis | 66f9c89 | 2020-05-14 12:00:08 +0100 | [diff] [blame] | 213 | } |
| 214 | } |
Filip Pavlis | 5b2a140 | 2020-05-11 15:12:36 +0100 | [diff] [blame] | 215 | } |