feat(StatusQ): AmountValidator for handling user-provided fp numbers
This commit is contained in:
parent
3202413501
commit
b9790c9797
|
@ -0,0 +1,86 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Validators 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
||||
TextField {
|
||||
id: textField
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
validator: AmountValidator {
|
||||
decimalPoint: buttonGroup.checkedButton.decimalPoint
|
||||
|
||||
maxIntegralDigits: maxIntegralDigitsSpinBox.value
|
||||
maxDecimalDigits: maxDecimalDigitsSpinBox.value
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
text: `acceptableInput: ${textField.acceptableInput}`
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: buttonGroup
|
||||
|
||||
buttons: radioButtonsRow.children
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: radioButtonsRow
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
RadioButton {
|
||||
checked: true
|
||||
text: "period (.)"
|
||||
|
||||
readonly property string decimalPoint: "."
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
text: "comma (,)"
|
||||
|
||||
readonly property string decimalPoint: ","
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Label {
|
||||
text: "Max number of integral digits:"
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: maxIntegralDigitsSpinBox
|
||||
value: 10
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Label {
|
||||
text: "Max number of decimal digits:"
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: maxDecimalDigitsSpinBox
|
||||
|
||||
value: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Validators
|
|
@ -0,0 +1,175 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import QtTest 1.15
|
||||
|
||||
import StatusQ.Validators 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Component {
|
||||
id: componentUnderTest
|
||||
|
||||
TextField {
|
||||
validator: AmountValidator {
|
||||
id: validator
|
||||
|
||||
decimalPoint: "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property TextField textField
|
||||
|
||||
TestCase {
|
||||
name: "AmountValidator"
|
||||
when: windowShown
|
||||
|
||||
function type(key, times = 1) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
keyPress(key)
|
||||
keyRelease(key)
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
textField = createTemporaryObject(componentUnderTest, root)
|
||||
}
|
||||
|
||||
function test_empty() {
|
||||
compare(textField.acceptableInput, false)
|
||||
}
|
||||
|
||||
function test_decimalPointOnly() {
|
||||
textField.forceActiveFocus()
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, false)
|
||||
compare(textField.text, ".")
|
||||
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, false)
|
||||
compare(textField.text, ".")
|
||||
|
||||
textField.text = ""
|
||||
|
||||
type(Qt.Key_Comma)
|
||||
|
||||
compare(textField.acceptableInput, false)
|
||||
compare(textField.text, ".")
|
||||
|
||||
type(Qt.Key_Comma)
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, false)
|
||||
compare(textField.text, ".")
|
||||
|
||||
textField.text = ""
|
||||
textField.validator.decimalPoint = ","
|
||||
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, false)
|
||||
compare(textField.text, ",")
|
||||
|
||||
type(Qt.Key_Comma)
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, false)
|
||||
compare(textField.text, ",")
|
||||
}
|
||||
|
||||
function test_decimalPointWithDigits() {
|
||||
textField.forceActiveFocus()
|
||||
type(Qt.Key_1)
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, "1.")
|
||||
|
||||
type(Qt.Key_1)
|
||||
type(Qt.Key_Period)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, "1.1")
|
||||
|
||||
textField.text = ""
|
||||
type(Qt.Key_Period)
|
||||
type(Qt.Key_1)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, ".1")
|
||||
}
|
||||
|
||||
function test_maxIntegralDigits() {
|
||||
textField.forceActiveFocus()
|
||||
textField.validator.maxIntegralDigits = 2
|
||||
|
||||
type(Qt.Key_1)
|
||||
type(Qt.Key_1)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, "11")
|
||||
|
||||
type(Qt.Key_2)
|
||||
type(Qt.Key_2)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, "11")
|
||||
|
||||
type(Qt.Key_Period)
|
||||
type(Qt.Key_3)
|
||||
type(Qt.Key_3)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, "11.33")
|
||||
}
|
||||
|
||||
function test_maxDecimalDigits() {
|
||||
textField.forceActiveFocus()
|
||||
textField.validator.maxDecimalDigits = 2
|
||||
|
||||
type(Qt.Key_Period)
|
||||
type(Qt.Key_1)
|
||||
type(Qt.Key_1)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, ".11")
|
||||
|
||||
type(Qt.Key_2)
|
||||
type(Qt.Key_2)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, ".11")
|
||||
|
||||
textField.cursorPosition = 0
|
||||
|
||||
type(Qt.Key_2)
|
||||
type(Qt.Key_2)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, "22.11")
|
||||
}
|
||||
|
||||
function test_trimmingDecimalDigits() {
|
||||
textField.text = ".11"
|
||||
textField.forceActiveFocus()
|
||||
textField.validator.maxDecimalDigits = 2
|
||||
textField.cursorPosition = 1
|
||||
|
||||
type(Qt.Key_2)
|
||||
type(Qt.Key_2)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, ".22")
|
||||
|
||||
type(Qt.Key_3)
|
||||
type(Qt.Key_3)
|
||||
|
||||
compare(textField.acceptableInput, true)
|
||||
compare(textField.text, ".22")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import StatusQ 0.1
|
||||
|
||||
/*!
|
||||
\qmltype AmountValidator
|
||||
\inherits GenericValidator
|
||||
\inqmlmodule StatusQ.Validators
|
||||
\brief Validator validating amounts provided by the user.
|
||||
|
||||
The validator do following checks:
|
||||
|
||||
- marks empty input and consisting only of decimal point as Intermediate
|
||||
- limits allowed char set - digits and (only when maxDecimalDigits is not 0)
|
||||
two decimal point characters are available (".", ",")
|
||||
- replaces entered decimal point to the one provied via decimalPoint property
|
||||
- blocks attemps of entering more then one decimal point char
|
||||
- limits number of integral part specified by maxIntegralDigits
|
||||
- trims number of decimal part specified by maxDecimalDigits
|
||||
- numbers starting or ending with decimal point like .1 or 1. are considered
|
||||
as valid input
|
||||
*/
|
||||
GenericValidator {
|
||||
id: root
|
||||
|
||||
property string decimalPoint: "."
|
||||
property int maxIntegralDigits: 10
|
||||
property int maxDecimalDigits: 10
|
||||
|
||||
validate: {
|
||||
if (input.length === 0)
|
||||
return GenericValidator.Intermediate
|
||||
|
||||
const charSetRegex = root.maxDecimalDigits > 0 ? /^[0-9\.\,]*$/
|
||||
: /^[0-9]*$/
|
||||
const validCharSet = charSetRegex.test(input)
|
||||
|
||||
if (!validCharSet)
|
||||
return GenericValidator.Invalid
|
||||
|
||||
const delocalized = input.replace(/,/g, ".")
|
||||
|
||||
if (delocalized === ".")
|
||||
return {
|
||||
state: GenericValidator.Intermediate,
|
||||
output: root.decimalPoint
|
||||
}
|
||||
|
||||
const pointsCount = (delocalized.match(/\./g) || []).length
|
||||
|
||||
if (pointsCount > 1)
|
||||
return GenericValidator.Invalid
|
||||
|
||||
const [integral, decimal] = pointsCount ? delocalized.split(".")
|
||||
: [delocalized, ""]
|
||||
|
||||
if (integral.length > root.maxIntegralDigits)
|
||||
return GenericValidator.Invalid
|
||||
|
||||
if (pointsCount === 0)
|
||||
return GenericValidator.Acceptable
|
||||
|
||||
const decimalTrimmed = decimal.slice(0, root.maxDecimalDigits)
|
||||
const localized = [integral, decimalTrimmed].join(root.decimalPoint)
|
||||
|
||||
return {
|
||||
state: GenericValidator.Acceptable,
|
||||
output: localized
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module StatusQ.Validators
|
||||
|
||||
AmountValidator 0.1 AmountValidator.qml
|
|
@ -249,5 +249,7 @@
|
|||
<file>StatusQ/Popups/statusModal/StatusImageWithTitle.qml</file>
|
||||
<file>StatusQ/Popups/statusModal/StatusModalFooter.qml</file>
|
||||
<file>StatusQ/Popups/statusModal/StatusModalHeader.qml</file>
|
||||
<file>StatusQ/Validators/qmldir</file>
|
||||
<file>StatusQ/Validators/AmountValidator.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Loading…
Reference in New Issue