fix: Slippage Selector Issues

- add various error/warning messages according to Figma (where it makes
sense)
- letters or more than 2 decimals are caught by the internal validator
so those combinations are impossible to enter
- fix marking the custom value as (in)valid
- fix selecting "Use default" after typing a custom value
- fix resetting to predefined value after typing a custom value that
matches one of the predefined ones
- reject typing thousands separator
- add regression QML tests for the above fixes

Fixes #15017
This commit is contained in:
Lukáš Tinkl 2024-08-30 21:00:39 +02:00 committed by Lukáš Tinkl
parent e698fd3e34
commit 5cf4592c3b
5 changed files with 169 additions and 53 deletions

View File

@ -45,6 +45,12 @@ SplitView {
text: "Valid: %1".arg(slippageSelector.valid ? "true" : "false")
}
Label {
Layout.fillWidth: true
font.weight: Font.Medium
text: "Edited: %1".arg(slippageSelector.isEdited ? "true" : "false")
}
ColumnLayout {
Repeater {
model: [0, 0.1, 0.5, 0.24, 0.8, 120.84]

View File

@ -87,10 +87,19 @@ Item {
mouseClick(firstButton)
tryCompare(controlUnderTest, "value", firstButton.value)
verify(controlUnderTest.valid)
tryCompare(customButton, "visible", true)
tryCompare(customInput, "visible", false)
}
function test_setCustomValue() {
const theValue = 1.42
function test_setCustomValue_data() {
return [
{tag: "valid", value: 1.42, valid: true},
{tag: "invalid", value: 111.42, valid: false},
]
}
function test_setCustomValue(data) {
const theValue = data.value
verify(!!controlUnderTest)
verify(controlUnderTest.valid)
@ -100,9 +109,77 @@ Item {
verify(!!customInput)
tryCompare(customInput, "cursorVisible", true)
tryCompare(customInput, "value", theValue)
tryCompare(customInput, "text", customInput.asString)
verify(controlUnderTest.value, theValue)
verify(controlUnderTest.valid)
compare(controlUnderTest.valid, data.valid)
}
function test_setCustomValueAndReset_data() {
return [
{tag: "valid", value: 1.42, valid: true, isDefault: false},
{tag: "default", value: 0.5, valid: true, isDefault: true},
{tag: "invalid", value: 111.42, valid: false, isDefault: false},
]
}
function test_setCustomValueAndReset(data) {
verify(!!controlUnderTest)
let defaultValue = NaN
let defaultButton = null
const isDefault = data.isDefault
// get the default (checked/selected) button and value
const buttonsRepeater = findChild(controlUnderTest, "buttonsRepeater")
verify(!!buttonsRepeater)
for (let i = 0; i < buttonsRepeater.count; i++) {
const button = buttonsRepeater.itemAt(i)
if (button && button.checked) {
defaultButton = button
defaultValue = button.value
break
}
}
verify(!!defaultButton)
verify(defaultValue !== NaN)
// verify that by default, the custom button is visible, and custom input not
const customButton = findChild(controlUnderTest, "customButton")
verify(!!customButton)
tryCompare(customButton, "visible", true)
const customInput = findChild(controlUnderTest, "customInput")
verify(!!customInput)
tryCompare(customInput, "visible", false)
tryCompare(controlUnderTest, "valid", true)
// assign a new (custom) value
const theValue = data.value
controlUnderTest.value = theValue
tryCompare(controlUnderTest, "valid", data.valid)
// verify the custom input has the new value (and text), and Custom button is not visible
tryCompare(customInput, "visible", !isDefault)
tryCompare(customInput, "activeFocus", !isDefault)
tryCompare(customButton, "visible", isDefault)
if (!isDefault) {
tryCompare(customInput, "value", theValue)
tryCompare(customInput, "text", customInput.asString)
}
// call reset()
controlUnderTest.reset()
// verify that after reset, the Custom button is back
tryCompare(customInput, "visible", false)
tryCompare(customInput, "activeFocus", false)
tryCompare(customButton, "visible", true)
// verify that the button with default value is selected again
tryCompare(defaultButton, "checked", true)
tryCompare(controlUnderTest, "value", defaultValue)
tryCompare(controlUnderTest, "valid", true)
}
function test_resetDefaults() {

View File

@ -16,6 +16,8 @@ RowLayout {
spacing: 8
signal buttonClicked()
Repeater {
id: repeater
@ -34,7 +36,10 @@ RowLayout {
checked: value === root.value
text: model[root.textRole]
onClicked: root.value = value
onClicked: {
root.value = value
root.buttonClicked()
}
}
}
}

View File

@ -47,8 +47,10 @@ TextField {
}
Keys.onPressed: (event) => {
// additionally accept dot (.) and convert it to the correct decimal point char
if (event.key === Qt.Key_Period) {
// reject group (thousands) separator
if (event.text === Qt.locale(root.locale).groupSeparator) {
event.accepted = true
} else if (event.key === Qt.Key_Period) { // additionally accept dot (.) and convert it to the correct decimal point char
root.insert(root.cursorPosition, Qt.locale(root.locale).decimalPoint)
event.accepted = true
} else if (event.modifiers === Qt.NoModifier && event.key >= Qt.Key_A && event.key <= Qt.Key_Z) {

View File

@ -2,24 +2,29 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
Control {
id: root
property double value: d.defaultValue
readonly property double defaultValue: d.defaultValue
readonly property bool valid: customInput.activeFocus && customInput.valid
|| buttons.value !== null
readonly property bool valid: (customInput.activeFocus && customInput.valid) || buttons.value !== null
readonly property bool isEdited: root.value !== d.defaultValue
property double valueTooHighThreshold: 5
function reset() {
value = d.defaultValue
customInput.focus = false
customButton.visible = true
}
onValueChanged: {
if (d.internalUpdate)
return false
return
const custom = !d.values.includes(value)
@ -53,56 +58,77 @@ Control {
}
background: null
contentItem: RowLayout {
spacing: buttons.spacing
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: buttons.spacing
StatusButtonRow {
id: buttons
model: ListModel {}
StatusButtonRow {
id: buttons
model: ListModel {}
Binding on value {
value: customInput.activeFocus ? null : root.value
}
Binding on value {
value: customInput.activeFocus ? null : root.value
}
onValueChanged: {
if (value === null)
return
onValueChanged: {
if (value === null)
return
d.update(value)
}
}
StatusButton {
id: customButton
objectName: "customButton"
Layout.minimumWidth: 130
text: qsTr("Custom")
onClicked: {
visible = false
customInput.clear()
customInput.forceActiveFocus()
}
}
CurrencyAmountInput {
id: customInput
objectName: "customInput"
Layout.minimumWidth: customButton.Layout.minimumWidth
visible: !customButton.visible
minValue: 0.01
maxValue: 100.0
currencySymbol: d.customSymbol
onValueChanged: d.update(value)
onFocusChanged: {
if (focus && valid)
d.update(value)
else if (!valid)
clear()
}
onButtonClicked: customButton.visible = true
}
StatusButton {
id: customButton
objectName: "customButton"
Layout.minimumWidth: 130
text: qsTr("Custom")
onClicked: {
visible = false
customInput.clear()
customInput.forceActiveFocus()
}
}
CurrencyAmountInput {
id: customInput
objectName: "customInput"
Layout.minimumWidth: customButton.Layout.minimumWidth
visible: !customButton.visible
minValue: 0.01
maxValue: 100.0
maximumLength: 6 // 3 integral + separator + 2 decimals (e.g. "999.99")
currencySymbol: d.customSymbol
onValueChanged: d.update(value)
}
}
StatusBaseText {
Layout.alignment: Qt.AlignRight
visible: customInput.visible && !customInput.valid
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
text: {
if (customInput.length === 0)
return qsTr("Enter a slippage value")
if (customInput.value === 0)
return qsTr("Slippage should be more than 0")
return qsTr("Invalid value")
}
}
StatusBaseText {
Layout.alignment: Qt.AlignRight
visible: customInput.visible && customInput.valid && customInput.value > root.valueTooHighThreshold
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.warningColor1
text: qsTr("Slippage may be higher than necessary")
}
}
}