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:
parent
e698fd3e34
commit
5cf4592c3b
|
@ -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]
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue