From 48ce2237930f6007f61e9403009d845ee0e8035b Mon Sep 17 00:00:00 2001 From: Amolith Date: Tue, 30 Jun 2026 16:50:45 -0600 Subject: [PATCH] Use text input for empty ranges 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. --- .../conversations/entities/Conversation.java | 12 ++--- .../entities/ConversationTest.java | 44 ++++++++++++++----- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index d99f082a23e2dd62415abd148c0cd4b47edf7df1..3a7488a75aa21524980580d0d168f21610ba2d33 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/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 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) { diff --git a/src/test/java/eu/siacs/conversations/entities/ConversationTest.java b/src/test/java/eu/siacs/conversations/entities/ConversationTest.java index 0ba456ab4e8f9ec47ef185f036fb3ac9c32f5f9f..c15a7a0ea190e2b42a669ed1d3b176d7db213a13 100644 --- a/src/test/java/eu/siacs/conversations/entities/ConversationTest.java +++ b/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(); }