[go: nahoru, domu]

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