feat(StatusQ): AmountValidator for handling user-provided fp numbers

This commit is contained in:
Michał Cieślak 2024-07-17 17:28:09 +02:00 committed by Michał
parent 3202413501
commit b9790c9797
5 changed files with 335 additions and 0 deletions

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,3 @@
module StatusQ.Validators
AmountValidator 0.1 AmountValidator.qml

View File

@ -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>