Invalid arguments for TextFieldValue and TextRange
Updated TextRange to require values to be positive.
Updated TextField to require ranges to be less or equal to text.length.
Test: TreeHugger
Bug: 158795058
Change-Id: Ie14e261ea57692ad40cf4887e9a9c45150be9310
diff --git a/ui/ui-text-core/api/0.1.0-dev14.txt b/ui/ui-text-core/api/0.1.0-dev14.txt
index ca89418..74a797f 100644
--- a/ui/ui-text-core/api/0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/0.1.0-dev14.txt
@@ -633,7 +633,7 @@
}
@androidx.compose.Immutable public final class TextRange {
- ctor public TextRange(int start, int end);
+ ctor public TextRange(@IntRange(from=null) int start, @IntRange(from=null) int end);
method public int component1();
method public int component2();
method public operator boolean contains(androidx.ui.text.TextRange other);
diff --git a/ui/ui-text-core/api/current.txt b/ui/ui-text-core/api/current.txt
index ca89418..74a797f 100644
--- a/ui/ui-text-core/api/current.txt
+++ b/ui/ui-text-core/api/current.txt
@@ -633,7 +633,7 @@
}
@androidx.compose.Immutable public final class TextRange {
- ctor public TextRange(int start, int end);
+ ctor public TextRange(@IntRange(from=null) int start, @IntRange(from=null) int end);
method public int component1();
method public int component2();
method public operator boolean contains(androidx.ui.text.TextRange other);
diff --git a/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt b/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
index ca89418..74a797f 100644
--- a/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/public_plus_experimental_0.1.0-dev14.txt
@@ -633,7 +633,7 @@
}
@androidx.compose.Immutable public final class TextRange {
- ctor public TextRange(int start, int end);
+ ctor public TextRange(@IntRange(from=null) int start, @IntRange(from=null) int end);
method public int component1();
method public int component2();
method public operator boolean contains(androidx.ui.text.TextRange other);
diff --git a/ui/ui-text-core/api/public_plus_experimental_current.txt b/ui/ui-text-core/api/public_plus_experimental_current.txt
index ca89418..74a797f 100644
--- a/ui/ui-text-core/api/public_plus_experimental_current.txt
+++ b/ui/ui-text-core/api/public_plus_experimental_current.txt
@@ -633,7 +633,7 @@
}
@androidx.compose.Immutable public final class TextRange {
- ctor public TextRange(int start, int end);
+ ctor public TextRange(@IntRange(from=null) int start, @IntRange(from=null) int end);
method public int component1();
method public int component2();
method public operator boolean contains(androidx.ui.text.TextRange other);
diff --git a/ui/ui-text-core/api/restricted_0.1.0-dev14.txt b/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
index ca89418..74a797f 100644
--- a/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
+++ b/ui/ui-text-core/api/restricted_0.1.0-dev14.txt
@@ -633,7 +633,7 @@
}
@androidx.compose.Immutable public final class TextRange {
- ctor public TextRange(int start, int end);
+ ctor public TextRange(@IntRange(from=null) int start, @IntRange(from=null) int end);
method public int component1();
method public int component2();
method public operator boolean contains(androidx.ui.text.TextRange other);
diff --git a/ui/ui-text-core/api/restricted_current.txt b/ui/ui-text-core/api/restricted_current.txt
index ca89418..74a797f 100644
--- a/ui/ui-text-core/api/restricted_current.txt
+++ b/ui/ui-text-core/api/restricted_current.txt
@@ -633,7 +633,7 @@
}
@androidx.compose.Immutable public final class TextRange {
- ctor public TextRange(int start, int end);
+ ctor public TextRange(@IntRange(from=null) int start, @IntRange(from=null) int end);
method public int component1();
method public int component2();
method public operator boolean contains(androidx.ui.text.TextRange other);
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
index e6ee3b4..8445c03 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/input/TextFieldValue.kt
@@ -91,10 +91,12 @@
*
* @param text the text will be rendered
* @param selection the selection range. If the selection is collapsed, it represents cursor
- * location. Do not specify outside of the text buffer.
+ * location. Selection range must be within the bounds of the [text], otherwise an exception will be
+ * thrown.
* @param composition A composition range visible to IME. If null, there is no composition range.
- * If non-null, the composition range must be valid range in the given text. For description of
- * composition please check [W3C IME Composition](https://www.w3.org/TR/ime-api/#ime-composition).
+ * Composition range must be within the bounds of the [text], otherwise an exception will be
+ * thrown. For description of composition please check [W3C IME Composition](https://www.w3
+ * .org/TR/ime-api/#ime-composition).
*/
@Immutable
data class TextFieldValue(
@@ -105,6 +107,21 @@
@Stable
val composition: TextRange? = null
) {
+ init {
+ // TextRange end is exclusive therefore can be at the end of the text
+ require(selection.end <= text.length) {
+ "Selection is out of bounds. [selection: $selection, text.length = ${text.length}]"
+ }
+
+ // TextRange end is exclusive therefore can be at the end of the text
+ composition?.let {
+ require(composition.end <= text.length) {
+ "Composition is out of bounds. " +
+ "[composition: $selection, text.length = ${text.length}]"
+ }
+ }
+ }
+
companion object {
/**
* The default [Saver] implementation for [TextFieldValue].
diff --git a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
index 4dd1d07..39479b2 100644
--- a/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
+++ b/ui/ui-text-core/src/commonMain/kotlin/androidx/ui/text/TextRange.kt
@@ -17,6 +17,7 @@
package androidx.ui.text
import androidx.compose.Immutable
+import androidx.ui.util.annotation.IntRange
fun CharSequence.substring(range: TextRange): String = this.substring(range.min, range.max)
@@ -25,11 +26,13 @@
* (exclusive). [end] can be smaller than [start] and in those cases [min] and [max] can be
* used in order to fetch the values.
*
- * @param start the inclusive start offset of the range.
- * @param end the exclusive end offset of the range
+ * @param start the inclusive start offset of the range. Must be non-negative, otherwise an
+ * exception will be thrown.
+ * @param end the exclusive end offset of the range. Must be non-negative, otherwise an
+ * exception will be thrown.
*/
@Immutable
-data class TextRange(val start: Int, val end: Int) {
+data class TextRange(@IntRange(from = 0) val start: Int, @IntRange(from = 0) val end: Int) {
/** The minimum offset of the range. */
val min: Int get() = if (start > end) end else start
@@ -46,6 +49,15 @@
*/
val length: Int get() = max - min
+ init {
+ require(start >= 0) {
+ "start cannot be negative. [start: $start]"
+ }
+ require(end >= 0) {
+ "end cannot negative. [end: $end]"
+ }
+ }
+
/**
* Returns true if the given range has intersection with this range
*/
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/input/TextFieldValueTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/input/TextFieldValueTest.kt
new file mode 100644
index 0000000..eece874
--- /dev/null
+++ b/ui/ui-text-core/src/test/java/androidx/ui/input/TextFieldValueTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 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.ui.input
+
+import androidx.test.filters.SmallTest
+import androidx.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class TextFieldValueTest {
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throws_exception_for_negative_selection() {
+ TextFieldValue(text = "", selection = TextRange(-1))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throws_exception_for_outofbounds_selection() {
+ TextFieldValue(text = "a", selection = TextRange("a".length + 1))
+ }
+
+ @Test
+ fun accepts_selection_end_equal_to_text_length() {
+ val text = "a"
+ val textRange = TextRange(text.length)
+ val textFieldValue = TextFieldValue(text = text, selection = textRange)
+
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.selection).isEqualTo(textRange)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throws_exception_for_negative_composition() {
+ TextFieldValue(text = "", composition = TextRange(-1))
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun throws_exception_for_outofbounds_composition() {
+ TextFieldValue(text = "a", composition = TextRange("a".length + 1))
+ }
+
+ @Test
+ fun accepts_composition_end_equal_to_text_length() {
+ val text = "a"
+ val textRange = TextRange(text.length)
+
+ val textFieldValue = TextFieldValue(text = text, composition = textRange)
+
+ assertThat(textFieldValue.text).isEqualTo(text)
+ assertThat(textFieldValue.composition).isEqualTo(textRange)
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-text-core/src/test/java/androidx/ui/text/TextRangeTest.kt b/ui/ui-text-core/src/test/java/androidx/ui/text/TextRangeTest.kt
index c061aec..47b3f90 100644
--- a/ui/ui-text-core/src/test/java/androidx/ui/text/TextRangeTest.kt
+++ b/ui/ui-text-core/src/test/java/androidx/ui/text/TextRangeTest.kt
@@ -117,4 +117,14 @@
assertThat(2 in TextRange(0, 2)).isFalse()
assertThat(3 in TextRange(0, 2)).isFalse()
}
+
+ @Test(expected = IllegalArgumentException::class)
+ fun test_does_not_accept_negative_value_for_start() {
+ TextRange(-1, 1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun test_does_not_accept_negative_value_for_end() {
+ TextRange(1, -1)
+ }
}
\ No newline at end of file