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/StatusImageWithTitle.qml</file>
|
||||||
<file>StatusQ/Popups/statusModal/StatusModalFooter.qml</file>
|
<file>StatusQ/Popups/statusModal/StatusModalFooter.qml</file>
|
||||||
<file>StatusQ/Popups/statusModal/StatusModalHeader.qml</file>
|
<file>StatusQ/Popups/statusModal/StatusModalHeader.qml</file>
|
||||||
|
<file>StatusQ/Validators/qmldir</file>
|
||||||
|
<file>StatusQ/Validators/AmountValidator.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
Loading…
Reference in New Issue