Merge pull request #10321 from accumulator/qml_msat_precision
qml millisat precision fix
This commit is contained in:
@@ -181,7 +181,9 @@ ElDialog {
|
||||
font.pixelSize: constants.fontSizeXLarge
|
||||
font.family: FixedFont
|
||||
font.bold: true
|
||||
text: Config.formatSats(invoice.amount, false)
|
||||
text: invoice.invoiceType == Invoice.LightningInvoice
|
||||
? Config.formatMilliSats(invoice.amount, false)
|
||||
: Config.formatSats(invoice.amount, false)
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -223,12 +225,13 @@ ElDialog {
|
||||
Layout.preferredWidth: amountFontMetrics.advanceWidth('0') * 14 + leftPadding + rightPadding
|
||||
fiatfield: amountFiat
|
||||
readOnly: amountMax.checked
|
||||
msatPrecision: invoice.invoiceType == Invoice.LightningInvoice
|
||||
color: readOnly
|
||||
? Material.accentColor
|
||||
: Material.foreground
|
||||
onTextAsSatsChanged: {
|
||||
if (!amountMax.checked)
|
||||
invoice.amountOverride.satsInt = textAsSats.satsInt
|
||||
invoice.amountOverride.copyFrom(textAsSats)
|
||||
}
|
||||
Connections {
|
||||
target: invoice.amountOverride
|
||||
|
||||
@@ -7,12 +7,13 @@ TextField {
|
||||
id: amount
|
||||
|
||||
required property TextField fiatfield
|
||||
property bool msatPrecision: false
|
||||
|
||||
font.family: FixedFont
|
||||
placeholderText: qsTr('Amount')
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: Config.btcAmountRegex
|
||||
regularExpression: msatPrecision ? Config.btcAmountRegexMsat : Config.btcAmountRegex
|
||||
}
|
||||
|
||||
property Amount textAsSats
|
||||
|
||||
@@ -94,14 +94,22 @@ class QEConfig(AuthMixin, QObject):
|
||||
|
||||
@pyqtProperty('QRegularExpression', notify=baseUnitChanged)
|
||||
def btcAmountRegex(self):
|
||||
return self._btcAmountRegex()
|
||||
|
||||
@pyqtProperty('QRegularExpression', notify=baseUnitChanged)
|
||||
def btcAmountRegexMsat(self):
|
||||
return self._btcAmountRegex(3)
|
||||
|
||||
def _btcAmountRegex(self, extra_precision: int = 0):
|
||||
decimal_point = base_unit_name_to_decimal_point(self.config.get_base_unit())
|
||||
max_digits_before_dp = (
|
||||
len(str(TOTAL_COIN_SUPPLY_LIMIT_IN_BTC))
|
||||
+ (base_unit_name_to_decimal_point("BTC") - decimal_point))
|
||||
exp = '[0-9]{0,%d}' % max_digits_before_dp
|
||||
exp = '^[0-9]{0,%d}' % max_digits_before_dp
|
||||
decimal_point += extra_precision
|
||||
if decimal_point > 0:
|
||||
exp += '\\.'
|
||||
exp += '[0-9]{0,%d}' % decimal_point
|
||||
exp += '(\\.[0-9]{0,%d})?' % decimal_point
|
||||
exp += '$'
|
||||
return QRegularExpression(exp)
|
||||
|
||||
thousandsSeparatorChanged = pyqtSignal()
|
||||
@@ -358,13 +366,6 @@ class QEConfig(AuthMixin, QObject):
|
||||
else:
|
||||
return self.config.format_amount(msats/1000, precision=precision)
|
||||
|
||||
# TODO delegate all this to config.py/util.py
|
||||
def decimal_point(self):
|
||||
return self.config.BTC_AMOUNTS_DECIMAL_POINT
|
||||
|
||||
def max_precision(self):
|
||||
return self.decimal_point() + 0 # self.extra_precision
|
||||
|
||||
@pyqtSlot(str, result=QEAmount)
|
||||
def unitsToSats(self, unitAmount):
|
||||
self._amount = QEAmount()
|
||||
@@ -373,18 +374,13 @@ class QEConfig(AuthMixin, QObject):
|
||||
except Exception:
|
||||
return self._amount
|
||||
|
||||
# scale it to max allowed precision, make it an int
|
||||
max_prec_amount = int(pow(10, self.max_precision()) * x)
|
||||
# if the max precision is simply what unit conversion allows, just return
|
||||
if self.max_precision() == self.decimal_point():
|
||||
self._amount = QEAmount(amount_sat=max_prec_amount)
|
||||
return self._amount
|
||||
self._logger.debug('fallthrough')
|
||||
# otherwise, scale it back to the expected unit
|
||||
#amount = Decimal(max_prec_amount) / Decimal(pow(10, self.max_precision()-self.decimal_point()))
|
||||
#return int(amount) #Decimal(amount) if not self.is_int else int(amount)
|
||||
sat_max_precision = self.config.BTC_AMOUNTS_DECIMAL_POINT
|
||||
msat_max_precision = self.config.BTC_AMOUNTS_DECIMAL_POINT + 3
|
||||
sat_max_prec_amount = int(pow(10, sat_max_precision) * x)
|
||||
msat_max_prec_amount = int(pow(10, msat_max_precision) * x)
|
||||
self._amount = QEAmount(amount_sat=sat_max_prec_amount, amount_msat=msat_max_prec_amount)
|
||||
return self._amount
|
||||
|
||||
@pyqtSlot('quint64', result=float)
|
||||
def satsToUnits(self, satoshis):
|
||||
return satoshis / pow(10, self.config.decimal_point)
|
||||
return satoshis / pow(10, self.config.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import copy
|
||||
import threading
|
||||
from enum import IntEnum
|
||||
from typing import Optional, Dict, Any
|
||||
from typing import Optional, Dict, Any, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, pyqtEnum, QTimer
|
||||
@@ -327,23 +327,10 @@ class QEInvoice(QObject, QtEventListener):
|
||||
PR_UNKNOWN: _('Invoice has unknown status'),
|
||||
}[_status]
|
||||
|
||||
if self.invoiceType == QEInvoice.Type.LightningInvoice:
|
||||
if status in [PR_UNPAID, PR_FAILED]:
|
||||
if self.get_max_spendable_lightning() >= amount.satsInt:
|
||||
lnaddr = self._effectiveInvoice._lnaddr
|
||||
if lnaddr.amount and amount.satsInt < lnaddr.amount * COIN:
|
||||
self.userinfo = _('Cannot pay less than the amount specified in the invoice')
|
||||
elif not self.address or self.get_max_spendable_onchain() < amount.satsInt:
|
||||
# TODO: for onchain: validate address? subtract fee?
|
||||
self.userinfo = _('Insufficient balance')
|
||||
else:
|
||||
self.userinfo = userinfo_for_invoice_status(status)
|
||||
elif self.invoiceType == QEInvoice.Type.OnchainInvoice:
|
||||
if status in [PR_UNPAID, PR_FAILED]:
|
||||
if not ((amount.isMax and self.get_max_spendable_onchain() > 0) or (self.get_max_spendable_onchain() >= amount.satsInt)):
|
||||
self.userinfo = _('Insufficient balance')
|
||||
else:
|
||||
self.userinfo = userinfo_for_invoice_status(status)
|
||||
if status in [PR_UNPAID, PR_FAILED]:
|
||||
x, self.userinfo = self.check_can_pay_amount(amount)
|
||||
else:
|
||||
self.userinfo = userinfo_for_invoice_status(status)
|
||||
|
||||
def determine_can_pay(self):
|
||||
self.canPay = False
|
||||
@@ -364,24 +351,25 @@ class QEInvoice(QObject, QtEventListener):
|
||||
if amount.isEmpty and status == PR_UNPAID: # unspecified amount
|
||||
return
|
||||
|
||||
if status in [PR_UNPAID, PR_FAILED]:
|
||||
self.canPay, x = self.check_can_pay_amount(amount)
|
||||
|
||||
def check_can_pay_amount(self, amount: QEAmount) -> Tuple[bool, Optional[str]]:
|
||||
assert self.status in [PR_UNPAID, PR_FAILED]
|
||||
if self.invoiceType == QEInvoice.Type.LightningInvoice:
|
||||
if status in [PR_UNPAID, PR_FAILED]:
|
||||
if self.get_max_spendable_lightning() >= amount.satsInt:
|
||||
lnaddr = self._effectiveInvoice._lnaddr
|
||||
if not (lnaddr.amount and amount.satsInt < lnaddr.amount * COIN):
|
||||
self.canPay = True
|
||||
elif self.address and self.get_max_spendable_onchain() > amount.satsInt:
|
||||
# TODO: validate address?
|
||||
# TODO: subtract fee?
|
||||
self.canPay = True
|
||||
if self.get_max_spendable_lightning() * 1000 >= amount.msatsInt:
|
||||
lnaddr = self._effectiveInvoice._lnaddr
|
||||
if lnaddr.amount and amount.msatsInt < lnaddr.amount * COIN * 1000:
|
||||
return False, _('Cannot pay less than the amount specified in the invoice')
|
||||
else:
|
||||
return True, None
|
||||
elif self.address and self.get_max_spendable_onchain() > amount.satsInt:
|
||||
return True, None
|
||||
elif self.invoiceType == QEInvoice.Type.OnchainInvoice:
|
||||
if status in [PR_UNPAID, PR_FAILED]:
|
||||
if amount.isMax and self.get_max_spendable_onchain() > 0:
|
||||
# TODO: dust limit?
|
||||
self.canPay = True
|
||||
elif self.get_max_spendable_onchain() >= amount.satsInt:
|
||||
# TODO: subtract fee?
|
||||
self.canPay = True
|
||||
if (amount.isMax and self.get_max_spendable_onchain() > 0) or (self.get_max_spendable_onchain() >= amount.satsInt):
|
||||
return True, None
|
||||
|
||||
return False, _('Insufficient balance')
|
||||
|
||||
@pyqtSlot()
|
||||
def payLightningInvoice(self):
|
||||
@@ -395,7 +383,7 @@ class QEInvoice(QObject, QtEventListener):
|
||||
if self.amount.isEmpty:
|
||||
if self.amountOverride.isEmpty:
|
||||
raise Exception('can not pay 0 amount')
|
||||
amount_msat = self.amountOverride.satsInt * 1000
|
||||
amount_msat = self.amountOverride.msatsInt
|
||||
|
||||
self._wallet.pay_lightning_invoice(self._effectiveInvoice, amount_msat)
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ class QEAmount(QObject):
|
||||
self._is_max = False
|
||||
self.valueChanged.emit()
|
||||
|
||||
@pyqtSlot('QVariant')
|
||||
def copyFrom(self, amount):
|
||||
if not amount:
|
||||
self._logger.warning('copyFrom with None argument. assuming 0') # TODO
|
||||
|
||||
@@ -1005,7 +1005,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
return self.config.format_fee_rate(fee_rate)
|
||||
|
||||
def get_decimal_point(self):
|
||||
return self.config.get_decimal_point()
|
||||
return self.config.BTC_AMOUNTS_DECIMAL_POINT
|
||||
|
||||
def base_unit(self):
|
||||
return self.config.get_base_unit()
|
||||
|
||||
@@ -97,7 +97,7 @@ class SettingsDialog(QDialog, QtEventListener):
|
||||
nz_label = HelpLabel.from_configvar(self.config.cv.BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT)
|
||||
nz = QSpinBox()
|
||||
nz.setMinimum(0)
|
||||
nz.setMaximum(self.config.decimal_point)
|
||||
nz.setMaximum(self.config.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
nz.setValue(self.config.num_zeros)
|
||||
if not self.config.cv.BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT.is_modifiable():
|
||||
for w in [nz, nz_label]: w.setEnabled(False)
|
||||
@@ -205,7 +205,7 @@ class SettingsDialog(QDialog, QtEventListener):
|
||||
if self.config.get_base_unit() == unit_result:
|
||||
return
|
||||
self.config.set_base_unit(unit_result)
|
||||
nz.setMaximum(self.config.decimal_point)
|
||||
nz.setMaximum(self.config.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
self.app.refresh_tabs_signal.emit()
|
||||
self.app.update_status_signal.emit()
|
||||
self.app.refresh_amount_edits_signal.emit()
|
||||
|
||||
@@ -615,7 +615,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
|
||||
x = Decimal(text)
|
||||
except Exception:
|
||||
return None
|
||||
power = pow(10, self.config.get_decimal_point())
|
||||
power = pow(10, self.config.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
return int(power * x)
|
||||
|
||||
def read_invoice(self):
|
||||
|
||||
@@ -557,7 +557,7 @@ class PaymentIdentifier(Logger):
|
||||
raise Exception("Amount is empty")
|
||||
if parse_max_spend(x):
|
||||
return x
|
||||
p = pow(10, self.config.get_decimal_point())
|
||||
p = pow(10, self.config.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
try:
|
||||
return int(p * Decimal(x))
|
||||
except InvalidOperation:
|
||||
|
||||
@@ -518,7 +518,7 @@ class BitBox02Client(HardwareClientBase):
|
||||
|
||||
format_unit = bitbox02.btc.BTCSignInitRequest.FormatUnit.DEFAULT
|
||||
# Base unit is configured to be "sat":
|
||||
if self.config.get_decimal_point() == 0:
|
||||
if self.config.BTC_AMOUNTS_DECIMAL_POINT == 0:
|
||||
format_unit = bitbox02.btc.BTCSignInitRequest.FormatUnit.SAT
|
||||
|
||||
sigs = self.bitbox02_device.btc_sign(
|
||||
|
||||
@@ -324,11 +324,11 @@ class TrezorPlugin(HW_PluginBase):
|
||||
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
||||
|
||||
def get_trezor_amount_unit(self):
|
||||
if self.config.decimal_point == 0:
|
||||
if self.config.BTC_AMOUNTS_DECIMAL_POINT == 0:
|
||||
return AmountUnit.SATOSHI
|
||||
elif self.config.decimal_point == 2:
|
||||
elif self.config.BTC_AMOUNTS_DECIMAL_POINT == 2:
|
||||
return AmountUnit.MICROBITCOIN
|
||||
elif self.config.decimal_point == 5:
|
||||
elif self.config.BTC_AMOUNTS_DECIMAL_POINT == 5:
|
||||
return AmountUnit.MILLIBITCOIN
|
||||
else:
|
||||
return AmountUnit.BITCOIN
|
||||
|
||||
@@ -217,12 +217,10 @@ class SimpleConfig(Logger):
|
||||
self._check_dependent_keys()
|
||||
|
||||
# units and formatting
|
||||
# FIXME is this duplication (dp, nz, post_sat, thou_sep) due to performance reasons??
|
||||
self.decimal_point = self.BTC_AMOUNTS_DECIMAL_POINT
|
||||
try:
|
||||
decimal_point_to_base_unit_name(self.decimal_point)
|
||||
decimal_point_to_base_unit_name(self.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
except UnknownBaseUnit:
|
||||
self.decimal_point = DECIMAL_POINT_DEFAULT
|
||||
self.BTC_AMOUNTS_DECIMAL_POINT = DECIMAL_POINT_DEFAULT
|
||||
self.num_zeros = self.BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT
|
||||
self.amt_precision_post_satoshi = self.BTC_AMOUNTS_PREC_POST_SAT
|
||||
self.amt_add_thousands_sep = self.BTC_AMOUNTS_ADD_THOUSANDS_SEP
|
||||
@@ -530,7 +528,7 @@ class SimpleConfig(Logger):
|
||||
return format_satoshis(
|
||||
amount_sat,
|
||||
num_zeros=self.num_zeros,
|
||||
decimal_point=self.decimal_point,
|
||||
decimal_point=self.BTC_AMOUNTS_DECIMAL_POINT,
|
||||
is_diff=is_diff,
|
||||
whitespaces=whitespaces,
|
||||
precision=precision,
|
||||
@@ -545,15 +543,11 @@ class SimpleConfig(Logger):
|
||||
return format_fee_satoshis(fee_rate/1000, num_zeros=self.num_zeros) + f" {util.UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE}"
|
||||
|
||||
def get_base_unit(self):
|
||||
return decimal_point_to_base_unit_name(self.decimal_point)
|
||||
return decimal_point_to_base_unit_name(self.BTC_AMOUNTS_DECIMAL_POINT)
|
||||
|
||||
def set_base_unit(self, unit):
|
||||
assert unit in base_units.keys()
|
||||
self.decimal_point = base_unit_name_to_decimal_point(unit)
|
||||
self.BTC_AMOUNTS_DECIMAL_POINT = self.decimal_point
|
||||
|
||||
def get_decimal_point(self):
|
||||
return self.decimal_point
|
||||
self.BTC_AMOUNTS_DECIMAL_POINT = base_unit_name_to_decimal_point(unit)
|
||||
|
||||
def get_nostr_relays(self) -> Sequence[str]:
|
||||
relays = []
|
||||
|
||||
138
tests/test_qml_qeconfig.py
Normal file
138
tests/test_qml_qeconfig.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from electrum import SimpleConfig
|
||||
from electrum.gui.qml.qeconfig import QEConfig
|
||||
from tests.qt_util import QETestCase, qt_test
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt6.QtCore import QRegularExpression
|
||||
|
||||
|
||||
class TestConfig(QETestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
QEConfig(SimpleConfig())
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.q: QEConfig = QEConfig.instance
|
||||
# raise Exception() # NOTE: exceptions in setUp() will block the test
|
||||
|
||||
@qt_test
|
||||
def test_satstounits(self):
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 5
|
||||
self.assertEqual(self.q.satsToUnits(100_000), 1.0)
|
||||
self.assertEqual(self.q.satsToUnits(1), 0.00001)
|
||||
self.assertEqual(self.q.satsToUnits(0.001), 0.00000001)
|
||||
|
||||
@qt_test
|
||||
def test_unitstosats(self):
|
||||
qa = self.q.unitsToSats('')
|
||||
self.assertTrue(qa.isEmpty)
|
||||
qa = self.q.unitsToSats('0')
|
||||
self.assertTrue(qa.isEmpty)
|
||||
qa = self.q.unitsToSats('0.000')
|
||||
self.assertTrue(qa.isEmpty)
|
||||
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 5
|
||||
|
||||
qa = self.q.unitsToSats('1')
|
||||
self.assertFalse(qa.isEmpty)
|
||||
self.assertEqual(qa.satsInt, 100_000)
|
||||
self.assertEqual(qa.msatsInt, 100_000_000)
|
||||
|
||||
qa = self.q.unitsToSats('1.001')
|
||||
self.assertFalse(qa.isEmpty)
|
||||
self.assertEqual(qa.satsInt, 100_100)
|
||||
self.assertEqual(qa.msatsInt, 100_100_000)
|
||||
|
||||
qa = self.q.unitsToSats('1.000001')
|
||||
self.assertFalse(qa.isEmpty)
|
||||
self.assertEqual(qa.satsInt, 100_000)
|
||||
self.assertEqual(qa.msatsInt, 100_000_100)
|
||||
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 0
|
||||
|
||||
qa = self.q.unitsToSats('1.001')
|
||||
self.assertFalse(qa.isEmpty)
|
||||
self.assertEqual(qa.satsInt, 1)
|
||||
self.assertEqual(qa.msatsInt, 1001)
|
||||
|
||||
qa = self.q.unitsToSats('1.0001') # outside msat precision
|
||||
self.assertFalse(qa.isEmpty)
|
||||
self.assertEqual(qa.satsInt, 1)
|
||||
self.assertEqual(qa.msatsInt, 1000)
|
||||
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 8
|
||||
|
||||
qa = self.q.unitsToSats('0.00000001001')
|
||||
self.assertFalse(qa.isEmpty)
|
||||
self.assertEqual(qa.satsInt, 1)
|
||||
self.assertEqual(qa.msatsInt, 1001)
|
||||
|
||||
@qt_test
|
||||
def test_btc_amount_regexes(self):
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 8
|
||||
|
||||
a: 'QRegularExpression' = self.q.btcAmountRegex
|
||||
b: 'QRegularExpression' = self.q.btcAmountRegexMsat
|
||||
|
||||
self.assertTrue(a.isValid())
|
||||
self.assertTrue(b.isValid())
|
||||
|
||||
self.assertTrue(a.match('1').hasMatch())
|
||||
self.assertTrue(a.match('1.').hasMatch())
|
||||
self.assertTrue(a.match('1.00000000').hasMatch())
|
||||
self.assertFalse(a.match('1.000000000').hasMatch())
|
||||
self.assertTrue(a.match('21000000').hasMatch())
|
||||
self.assertFalse(a.match('121000000').hasMatch())
|
||||
|
||||
self.assertTrue(b.match('1').hasMatch())
|
||||
self.assertTrue(b.match('1.').hasMatch())
|
||||
self.assertTrue(b.match('1.00000000').hasMatch())
|
||||
self.assertTrue(b.match('1.00000000000').hasMatch())
|
||||
self.assertFalse(b.match('1.000000000000').hasMatch())
|
||||
self.assertTrue(b.match('21000000').hasMatch())
|
||||
self.assertFalse(b.match('121000000').hasMatch())
|
||||
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 5
|
||||
|
||||
a: 'QRegularExpression' = self.q.btcAmountRegex
|
||||
b: 'QRegularExpression' = self.q.btcAmountRegexMsat
|
||||
|
||||
self.assertTrue(a.isValid())
|
||||
self.assertTrue(b.isValid())
|
||||
|
||||
self.assertTrue(a.match('1').hasMatch())
|
||||
self.assertTrue(a.match('1.').hasMatch())
|
||||
self.assertTrue(a.match('1.00000').hasMatch())
|
||||
self.assertFalse(a.match('1.000000').hasMatch())
|
||||
self.assertTrue(a.match('21000000000').hasMatch())
|
||||
self.assertFalse(a.match('121000000000').hasMatch())
|
||||
|
||||
self.assertTrue(b.match('1').hasMatch())
|
||||
self.assertTrue(b.match('1.').hasMatch())
|
||||
self.assertTrue(b.match('1.0000000').hasMatch())
|
||||
self.assertTrue(b.match('1.00000000').hasMatch())
|
||||
self.assertFalse(b.match('1.000000000000').hasMatch())
|
||||
self.assertTrue(b.match('21000000000').hasMatch())
|
||||
self.assertFalse(b.match('121000000000').hasMatch())
|
||||
|
||||
self.q.config.BTC_AMOUNTS_DECIMAL_POINT = 0
|
||||
|
||||
a: 'QRegularExpression' = self.q.btcAmountRegex
|
||||
b: 'QRegularExpression' = self.q.btcAmountRegexMsat
|
||||
|
||||
self.assertTrue(a.isValid())
|
||||
self.assertTrue(b.isValid())
|
||||
|
||||
self.assertTrue(a.match('1').hasMatch())
|
||||
self.assertFalse(a.match('1.').hasMatch())
|
||||
self.assertTrue(a.match('2100000000000000').hasMatch())
|
||||
self.assertFalse(a.match('12100000000000000').hasMatch())
|
||||
|
||||
self.assertTrue(b.match('1').hasMatch())
|
||||
self.assertTrue(b.match('1.').hasMatch())
|
||||
self.assertTrue(b.match('1.000').hasMatch())
|
||||
self.assertFalse(b.match('1.0000').hasMatch())
|
||||
self.assertTrue(b.match('2100000000000000').hasMatch())
|
||||
self.assertFalse(b.match('12100000000000000').hasMatch())
|
||||
Reference in New Issue
Block a user