Use text input for empty ranges

Amolith created

Treat empty or missing ranged numeric values as ineligible for slider
rendering. The existing numeric datatype handling on text fields keeps
these fields editable without implying the minimum value is selected.

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java     | 12 
src/test/java/eu/siacs/conversations/entities/ConversationTest.java | 44 
2 files changed, 36 insertions(+), 20 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -1880,13 +1880,9 @@ public class Conversation extends AbstractEntity
         if (min == null || max == null || min >= max) return null;
 
         final String value = firstValue(field);
-        final Float parsedValue;
-        if (value == null || value.equals("")) {
-            parsedValue = null;
-        } else {
-            parsedValue = parseFloat(value);
-            if (parsedValue == null || parsedValue < min || parsedValue > max) return null;
-        }
+        if (value == null || value.equals("")) return null;
+        final Float parsedValue = parseFloat(value);
+        if (parsedValue == null || parsedValue < min || parsedValue > max) return null;
 
         final List<Float> options = optionValues(field);
         if (options == null) return null;
@@ -1902,7 +1898,7 @@ public class Conversation extends AbstractEntity
             }
         }
         if (step == null || (step > 0f && !landsOnStep(max, min, step))) return null;
-        return parsedValue == null || step == 0f || landsOnStep(parsedValue, min, step) ? step : null;
+        return step == 0f || landsOnStep(parsedValue, min, step) ? step : null;
     }
 
     static String formatSliderValue(final float value, final String datatype) {

src/test/java/eu/siacs/conversations/entities/ConversationTest.java 🔗

@@ -211,6 +211,15 @@ public class ConversationTest {
         Assert.assertEquals(Float.valueOf(0f), Conversation.steppedSliderStep(field));
     }
 
+    @Test
+    public void steppedSliderStepRejectsMissingOrMalformedRangeBounds() {
+        final var missingMin = sliderField("xs:integer", null, "10", "5");
+        final var malformedMax = sliderField("xs:integer", "0", "not-a-number", "5");
+
+        Assert.assertNull(Conversation.steppedSliderStep(missingMin));
+        Assert.assertNull(Conversation.steppedSliderStep(malformedMax));
+    }
+
     @Test
     public void formatSliderValueDoesNotClampLargeIntegerDatatypesToInt() {
         Assert.assertEquals("3000000000", Conversation.formatSliderValue(3_000_000_000f, "xs:long"));
@@ -268,23 +277,34 @@ public class ConversationTest {
     }
 
     @Test
-    public void sliderFieldViewHolderBindsEmptyValueWithoutCrashing() {
-        final var context = new ContextThemeWrapper(
-                RuntimeEnvironment.getApplication(),
-                com.google.android.material.R.style.Theme_MaterialComponents_DayNight_NoActionBar);
+    public void rangedNumericFieldWithBadBoundsFallsBackToTextInput() {
         final var session = withOccupantId.pagerAdapter.new CommandSession(
                 "test", "node", mock(XmppConnectionService.class));
         try {
-            final var binding = CommandSliderFieldBinding.inflate(LayoutInflater.from(context));
-            final var holder = session.new SliderFieldViewHolder(binding);
-            final var field = sliderField("xs:integer", "0", "70", "", "0", "35", "70");
-            final var item = session.new Field(
-                    eu.siacs.conversations.xmpp.forms.Field.parse(field),
-                    session.TYPE_SLIDER_FIELD);
+            session.responseElement = new Element("x", Namespace.DATA);
+            session.responseElement.setAttribute("type", "form");
+            final var missingMin = sliderField("xs:integer", null, "10", "5");
+            final var malformedMax = sliderField("xs:integer", "0", "not-a-number", "5");
 
-            holder.bind(item);
+            Assert.assertEquals(session.TYPE_TEXT_FIELD, session.mkField(missingMin).viewType);
+            Assert.assertEquals(session.TYPE_TEXT_FIELD, session.mkField(malformedMax).viewType);
+        } finally {
+            session.loadingTimer.cancel();
+        }
+    }
+
+    @Test
+    public void rangedNumericFieldWithEmptyValueFallsBackToTextInput() {
+        final var session = withOccupantId.pagerAdapter.new CommandSession(
+                "test", "node", mock(XmppConnectionService.class));
+        try {
+            session.responseElement = new Element("x", Namespace.DATA);
+            session.responseElement.setAttribute("type", "form");
+            final var emptyValue = sliderField("xs:integer", "0", "70", "", "0", "35", "70");
+            final var missingValue = sliderField("xs:integer", "0", "70", null, "0", "35", "70");
 
-            Assert.assertEquals(0f, binding.slider.getValue(), 0.0001f);
+            Assert.assertEquals(session.TYPE_TEXT_FIELD, session.mkField(emptyValue).viewType);
+            Assert.assertEquals(session.TYPE_TEXT_FIELD, session.mkField(missingValue).viewType);
         } finally {
             session.loadingTimer.cancel();
         }