What changed, and why it matters
This commit only reorganizes test files. It moves QML-related unit tests from the tests/ folder into a new tests/qml/ subfolder and updates import paths accordingly. No production code, user-facing behavior, or security-sensitive logic was changed.
No security action needed. This is a test-only refactoring commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is a pure file relocation: tests/qt_util.py and three test_qml_*.py files were moved into tests/qml/, with relative imports adjusted (e.g., ‘from tests.qt_util import …’ becomes ‘from .qt_util import …’, and ‘from . import ElectrumTestCase’ becomes ‘from .. import ElectrumTestCase’). Content of the test files is essentially identical. No application code was modified.
Changed components
tests/qml/__init__.pytests/qml/qt_util.pytests/qml/test_qml_qeconfig.pytests/qml/test_qml_qetransactionlistmodel.pytests/qml/test_qml_types.pyInspect captured patch +450 / −447
diff --git a/tests/qml/__init__.py b/tests/qml/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qml/qt_util.py b/tests/qml/qt_util.py
new file mode 100644
index 0000000..cad939e
--- /dev/null
+++ b/tests/qml/qt_util.py
@@ -0,0 +1,98 @@
+import threading
+import traceback
+import unittest
+from functools import wraps, partial
+from unittest import SkipTest
+
+from PyQt6.QtCore import QCoreApplication, QMetaObject, Qt, pyqtSlot, QObject
+
+
+class TestQCoreApplication(QCoreApplication):
+ @pyqtSlot()
+ def doInvoke(self):
+ getattr(self._instance, self._method)()
+
+
+class QEventReceiver(QObject):
+ def __init__(self, *signals):
+ super().__init__()
+ self.received = []
+ self.signals = []
+ for signal in signals:
+ self.signals.append(signal)
+ signal.connect(partial(self.doReceive, signal))
+
+ # intentionally no pyqtSlot decorator, to catch all
+ def doReceive(self, signal, *args):
+ self.received.append((signal, args))
+
+ def receivedForSignal(self, signal):
+ return list(filter(lambda x: x[0] == signal, self.received))
+
+ def clear(self):
+ self.received.clear()
+
+
+class QETestCase(unittest.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.app = None
+ self._e = None
+ self._testcase_event = threading.Event()
+ self._app_ready_event = threading.Event()
+
+ def start_qt_task():
+ try:
+ assert self.app is None
+ self.app = TestQCoreApplication([])
+ self._app_ready_event.set()
+ self.app.exec()
+ self.app = None
+ except Exception as e:
+ print(f'Problem starting QCoreApplication: {str(e)}')
+
+ self._qt_thread = threading.Thread(target=start_qt_task)
+ self._qt_thread.start()
+
+ def tearDown(self):
+ self.app.exit()
+ if self._qt_thread.is_alive():
+ self._qt_thread.join()
+
+
+def qt_test(func):
+ @wraps(func)
+ def decorator(self, *args):
+ if threading.current_thread().name == 'MainThread':
+ res = self._app_ready_event.wait(3)
+ if not res:
+ raise Exception('app not ready in time')
+ self._testcase_event.clear()
+ self.app._instance = self
+ self.app._method = func.__name__
+ QMetaObject.invokeMethod(self.app, 'doInvoke', Qt.ConnectionType.QueuedConnection)
+ res = self._testcase_event.wait(15)
+ if not res:
+ self._e = Exception('testcase timed out')
+ if self._e:
+ print("".join(traceback.format_exception(self._e)))
+ # deallocate stored exception from qt thread otherwise we SEGV garbage collector
+ # instead, re-create using the exception message, special casing AssertionError and SkipTest
+ e = None
+ if isinstance(self._e, AssertionError):
+ e = AssertionError(str(self._e))
+ elif isinstance(self._e, SkipTest):
+ e = SkipTest(str(self._e))
+ else:
+ e = Exception(str(self._e))
+ self._e = None
+ raise e
+ return
+ try:
+ func(self, *args)
+ except Exception as e:
+ self._e = e
+ finally:
+ self._testcase_event.set()
+ return decorator
diff --git a/tests/qml/test_qml_qeconfig.py b/tests/qml/test_qml_qeconfig.py
new file mode 100644
index 0000000..c6de315
--- /dev/null
+++ b/tests/qml/test_qml_qeconfig.py
@@ -0,0 +1,140 @@
+from typing import TYPE_CHECKING
+
+from electrum import SimpleConfig
+from electrum.gui.qml.qeconfig import QEConfig
+
+from .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())
diff --git a/tests/qml/test_qml_qetransactionlistmodel.py b/tests/qml/test_qml_qetransactionlistmodel.py
new file mode 100644
index 0000000..e71638a
--- /dev/null
+++ b/tests/qml/test_qml_qetransactionlistmodel.py
@@ -0,0 +1,71 @@
+from datetime import datetime
+from unittest.mock import patch
+
+from electrum.gui.qml.qetransactionlistmodel import QETransactionListModel
+
+from .. import ElectrumTestCase
+
+
+class TestQETransactionListModel(ElectrumTestCase):
+
+ def test_get_section_by_timestamp(self):
+ f = QETransactionListModel.get_section_by_timestamp
+
+ mock_today = datetime(2023, 6, 15, 0, 0, 0, 0)
+ with patch('electrum.gui.qml.qetransactionlistmodel.datetime') as mock_dt:
+ mock_dt.today.return_value = mock_today
+ mock_dt.fromtimestamp = datetime.fromtimestamp
+
+ today_ts = datetime(2023, 6, 15, 10, 30, 0).timestamp()
+ self.assertEqual(f(today_ts), 'today')
+
+ today_edge_ts = datetime(2023, 6, 15, 0, 0, 1).timestamp()
+ self.assertEqual(f(today_edge_ts), 'today')
+
+ yesterday_ts = datetime(2023, 6, 14, 15, 0, 0).timestamp()
+ self.assertEqual(f(yesterday_ts), 'yesterday')
+
+ yesterday_edge_ts = datetime(2023, 6, 13, 23, 59, 59).timestamp()
+ self.assertEqual(f(yesterday_edge_ts), 'lastweek')
+
+ lastweek_ts = datetime(2023, 6, 12, 12, 0, 0).timestamp()
+ self.assertEqual(f(lastweek_ts), 'lastweek')
+
+ lastweek_boundary_ts = datetime(2023, 6, 8, 12, 0, 0).timestamp()
+ self.assertEqual(f(lastweek_boundary_ts), 'lastweek')
+
+ lastmonth_ts = datetime(2023, 6, 5, 9, 0, 0).timestamp()
+ self.assertEqual(f(lastmonth_ts), 'lastmonth')
+
+ lastmonth_boundary_ts = datetime(2023, 5, 15, 8, 0, 0).timestamp()
+ self.assertEqual(f(lastmonth_boundary_ts), 'lastmonth')
+
+ older_ts = datetime(2023, 5, 14, 10, 0, 0).timestamp()
+ self.assertEqual(f(older_ts), 'older')
+
+ much_older_ts = datetime(2022, 1, 1, 0, 0, 0).timestamp()
+ self.assertEqual(f(much_older_ts), 'older')
+
+ def test_format_date_by_section(self):
+ f = QETransactionListModel.format_date_by_section
+
+ test_date = datetime(2023, 6, 15, 14, 30, 45)
+
+ result = f('today', test_date)
+ self.assertEqual(result, '14:30')
+
+ result = f('yesterday', test_date)
+ self.assertEqual(result, '14:30')
+
+ result = f('lastweek', test_date)
+ self.assertEqual(result, 'Thu, 14:30')
+
+ result = f('lastmonth', test_date)
+ self.assertEqual(result, 'Thu 15, 14:30')
+
+ result = f('older', test_date)
+ self.assertEqual(result, '2023-06-15 14:30')
+
+ result = f('unknown_section', test_date)
+ self.assertEqual(result, '2023-06-15 14:30')
+
diff --git a/tests/qml/test_qml_types.py b/tests/qml/test_qml_types.py
new file mode 100644
index 0000000..b578888
--- /dev/null
+++ b/tests/qml/test_qml_types.py
@@ -0,0 +1,141 @@
+import shutil
+import tempfile
+
+from electrum import SimpleConfig
+from electrum.gui.qml.qetypes import QEAmount
+from electrum.invoices import Invoice, LN_EXPIRY_NEVER
+from electrum.transaction import PartialTxOutput
+
+from .qt_util import QETestCase, QEventReceiver, qt_test
+
+
+class WalletMock:
+ def __init__(self, electrum_path):
+ self.config = SimpleConfig({
+ 'electrum_path': electrum_path,
+ 'decimal_point': 5
+ })
+ self.contacts = None
+
+
+class TestTypes(QETestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.electrum_path = tempfile.mkdtemp()
+ self.wallet = WalletMock(self.electrum_path)
+
+ def tearDown(self):
+ super().tearDown()
+ shutil.rmtree(self.electrum_path)
+
+ @qt_test
+ def test_qeamount(self):
+ a = QEAmount()
+ self.assertTrue(a.isEmpty)
+ a_er = QEventReceiver(a.valueChanged)
+ a.satsInt = 1
+ self.assertTrue(bool(a_er.received))
+ self.assertFalse(a.isEmpty)
+ self.assertEqual('1', a.satsStr)
+
+ a_er.clear()
+ a.clear()
+ self.assertTrue(a.isEmpty)
+ self.assertTrue(bool(a_er.received))
+ self.assertEqual('0', a.satsStr)
+
+ a.clear()
+ a_er.clear()
+ a.isMax = True
+ self.assertTrue(a.isMax)
+ self.assertFalse(a.isEmpty)
+ self.assertTrue(bool(a_er.received))
+ self.assertEqual('0', a.satsStr)
+
+ a.clear()
+ a_er.clear()
+ a.msatsInt = 1
+ self.assertTrue(bool(a_er.received))
+ self.assertFalse(a.isEmpty)
+ self.assertEqual('1', a.msatsStr)
+
+ @qt_test
+ def test_qeamount_copy(self):
+ a = QEAmount()
+ b = QEAmount()
+ b.satsInt = 1
+ c = QEAmount()
+ c.msatsInt = 1
+ d = QEAmount()
+ d.isMax = True
+
+ t = QEAmount()
+ t_er = QEventReceiver(t.valueChanged)
+
+ t.copyFrom(a)
+ self.assertTrue(t.isEmpty)
+ self.assertEqual(0, len(t_er.received))
+
+ t.clear()
+ t_er.clear()
+ t.copyFrom(b)
+ self.assertFalse(t.isEmpty)
+ self.assertEqual(t.satsInt, 1)
+ self.assertEqual(1, len(t_er.received))
+
+ t.clear()
+ t_er.clear()
+ t.copyFrom(c)
+ self.assertFalse(t.isEmpty)
+ self.assertEqual(t.msatsInt, 1)
+ self.assertEqual(1, len(t_er.received))
+
+ t.clear()
+ t_er.clear()
+ t.copyFrom(d)
+ self.assertFalse(t.isEmpty)
+ self.assertTrue(t.isMax)
+ self.assertEqual(1, len(t_er.received))
+
+ @qt_test
+ def test_qeamount_frominvoice(self):
+ amount_sat = 10_000
+ outputs = [PartialTxOutput.from_address_and_value('bc1qj3zx2zc4rpv3npzmznxhdxzn0wm7pzqp8p2293', amount_sat)]
+ invoice = Invoice(
+ amount_msat=amount_sat * 1000,
+ message="mymsg",
+ time=1692716965,
+ exp=LN_EXPIRY_NEVER,
+ outputs=outputs,
+ bip70=None,
+ height=0,
+ lightning_invoice=None,
+ )
+ a = QEAmount(from_invoice=invoice)
+ self.assertEqual(10_000, a.satsInt)
+ self.assertEqual(10_000_000, a.msatsInt)
+ self.assertFalse(a.isMax)
+
+ outputs = [PartialTxOutput.from_address_and_value('bc1qj3zx2zc4rpv3npzmznxhdxzn0wm7pzqp8p2293', '!')]
+ invoice = Invoice(
+ amount_msat='!',
+ message="mymsg",
+ time=1692716965,
+ exp=LN_EXPIRY_NEVER,
+ outputs=outputs,
+ bip70=None,
+ height=0,
+ lightning_invoice=None,
+ )
+ a = QEAmount(from_invoice=invoice)
+ self.assertTrue(a.isMax)
+ self.assertEqual(0, a.satsInt)
+ self.assertEqual(0, a.msatsInt)
+
+ bolt11 = 'lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj'
+ invoice = Invoice.from_bech32(bolt11)
+ a = QEAmount(from_invoice=invoice)
+ self.assertEqual(2_000_000, a.satsInt)
+ self.assertEqual(2_000_000_000, a.msatsInt)
+ self.assertFalse(a.isMax)
diff --git a/tests/qt_util.py b/tests/qt_util.py
deleted file mode 100644
index cad939e..0000000
--- a/tests/qt_util.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import threading
-import traceback
-import unittest
-from functools import wraps, partial
-from unittest import SkipTest
-
-from PyQt6.QtCore import QCoreApplication, QMetaObject, Qt, pyqtSlot, QObject
-
-
-class TestQCoreApplication(QCoreApplication):
- @pyqtSlot()
- def doInvoke(self):
- getattr(self._instance, self._method)()
-
-
-class QEventReceiver(QObject):
- def __init__(self, *signals):
- super().__init__()
- self.received = []
- self.signals = []
- for signal in signals:
- self.signals.append(signal)
- signal.connect(partial(self.doReceive, signal))
-
- # intentionally no pyqtSlot decorator, to catch all
- def doReceive(self, signal, *args):
- self.received.append((signal, args))
-
- def receivedForSignal(self, signal):
- return list(filter(lambda x: x[0] == signal, self.received))
-
- def clear(self):
- self.received.clear()
-
-
-class QETestCase(unittest.TestCase):
-
- def setUp(self):
- super().setUp()
- self.app = None
- self._e = None
- self._testcase_event = threading.Event()
- self._app_ready_event = threading.Event()
-
- def start_qt_task():
- try:
- assert self.app is None
- self.app = TestQCoreApplication([])
- self._app_ready_event.set()
- self.app.exec()
- self.app = None
- except Exception as e:
- print(f'Problem starting QCoreApplication: {str(e)}')
-
- self._qt_thread = threading.Thread(target=start_qt_task)
- self._qt_thread.start()
-
- def tearDown(self):
- self.app.exit()
- if self._qt_thread.is_alive():
- self._qt_thread.join()
-
-
-def qt_test(func):
- @wraps(func)
- def decorator(self, *args):
- if threading.current_thread().name == 'MainThread':
- res = self._app_ready_event.wait(3)
- if not res:
- raise Exception('app not ready in time')
- self._testcase_event.clear()
- self.app._instance = self
- self.app._method = func.__name__
- QMetaObject.invokeMethod(self.app, 'doInvoke', Qt.ConnectionType.QueuedConnection)
- res = self._testcase_event.wait(15)
- if not res:
- self._e = Exception('testcase timed out')
- if self._e:
- print("".join(traceback.format_exception(self._e)))
- # deallocate stored exception from qt thread otherwise we SEGV garbage collector
- # instead, re-create using the exception message, special casing AssertionError and SkipTest
- e = None
- if isinstance(self._e, AssertionError):
- e = AssertionError(str(self._e))
- elif isinstance(self._e, SkipTest):
- e = SkipTest(str(self._e))
- else:
- e = Exception(str(self._e))
- self._e = None
- raise e
- return
- try:
- func(self, *args)
- except Exception as e:
- self._e = e
- finally:
- self._testcase_event.set()
- return decorator
diff --git a/tests/test_qml_qeconfig.py b/tests/test_qml_qeconfig.py
deleted file mode 100644
index f58956b..0000000
--- a/tests/test_qml_qeconfig.py
+++ /dev/null
@@ -1,138 +0,0 @@
-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())
diff --git a/tests/test_qml_qetransactionlistmodel.py b/tests/test_qml_qetransactionlistmodel.py
deleted file mode 100644
index 7c2b8af..0000000
--- a/tests/test_qml_qetransactionlistmodel.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from datetime import datetime
-from unittest.mock import patch
-
-from electrum.gui.qml.qetransactionlistmodel import QETransactionListModel
-
-from . import ElectrumTestCase
-
-
-class TestQETransactionListModel(ElectrumTestCase):
-
- def test_get_section_by_timestamp(self):
- f = QETransactionListModel.get_section_by_timestamp
-
- mock_today = datetime(2023, 6, 15, 0, 0, 0, 0)
- with patch('electrum.gui.qml.qetransactionlistmodel.datetime') as mock_dt:
- mock_dt.today.return_value = mock_today
- mock_dt.fromtimestamp = datetime.fromtimestamp
-
- today_ts = datetime(2023, 6, 15, 10, 30, 0).timestamp()
- self.assertEqual(f(today_ts), 'today')
-
- today_edge_ts = datetime(2023, 6, 15, 0, 0, 1).timestamp()
- self.assertEqual(f(today_edge_ts), 'today')
-
- yesterday_ts = datetime(2023, 6, 14, 15, 0, 0).timestamp()
- self.assertEqual(f(yesterday_ts), 'yesterday')
-
- yesterday_edge_ts = datetime(2023, 6, 13, 23, 59, 59).timestamp()
- self.assertEqual(f(yesterday_edge_ts), 'lastweek')
-
- lastweek_ts = datetime(2023, 6, 12, 12, 0, 0).timestamp()
- self.assertEqual(f(lastweek_ts), 'lastweek')
-
- lastweek_boundary_ts = datetime(2023, 6, 8, 12, 0, 0).timestamp()
- self.assertEqual(f(lastweek_boundary_ts), 'lastweek')
-
- lastmonth_ts = datetime(2023, 6, 5, 9, 0, 0).timestamp()
- self.assertEqual(f(lastmonth_ts), 'lastmonth')
-
- lastmonth_boundary_ts = datetime(2023, 5, 15, 8, 0, 0).timestamp()
- self.assertEqual(f(lastmonth_boundary_ts), 'lastmonth')
-
- older_ts = datetime(2023, 5, 14, 10, 0, 0).timestamp()
- self.assertEqual(f(older_ts), 'older')
-
- much_older_ts = datetime(2022, 1, 1, 0, 0, 0).timestamp()
- self.assertEqual(f(much_older_ts), 'older')
-
- def test_format_date_by_section(self):
- f = QETransactionListModel.format_date_by_section
-
- test_date = datetime(2023, 6, 15, 14, 30, 45)
-
- result = f('today', test_date)
- self.assertEqual(result, '14:30')
-
- result = f('yesterday', test_date)
- self.assertEqual(result, '14:30')
-
- result = f('lastweek', test_date)
- self.assertEqual(result, 'Thu, 14:30')
-
- result = f('lastmonth', test_date)
- self.assertEqual(result, 'Thu 15, 14:30')
-
- result = f('older', test_date)
- self.assertEqual(result, '2023-06-15 14:30')
-
- result = f('unknown_section', test_date)
- self.assertEqual(result, '2023-06-15 14:30')
-
diff --git a/tests/test_qml_types.py b/tests/test_qml_types.py
deleted file mode 100644
index 30f6df8..0000000
--- a/tests/test_qml_types.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import shutil
-import tempfile
-
-from electrum import SimpleConfig
-from electrum.gui.qml.qetypes import QEAmount
-from electrum.invoices import Invoice, LN_EXPIRY_NEVER
-from tests.qt_util import QETestCase, QEventReceiver, qt_test
-from electrum.transaction import PartialTxOutput
-
-
-class WalletMock:
- def __init__(self, electrum_path):
- self.config = SimpleConfig({
- 'electrum_path': electrum_path,
- 'decimal_point': 5
- })
- self.contacts = None
-
-
-class TestTypes(QETestCase):
-
- def setUp(self):
- super().setUp()
- self.electrum_path = tempfile.mkdtemp()
- self.wallet = WalletMock(self.electrum_path)
-
- def tearDown(self):
- super().tearDown()
- shutil.rmtree(self.electrum_path)
-
- @qt_test
- def test_qeamount(self):
- a = QEAmount()
- self.assertTrue(a.isEmpty)
- a_er = QEventReceiver(a.valueChanged)
- a.satsInt = 1
- self.assertTrue(bool(a_er.received))
- self.assertFalse(a.isEmpty)
- self.assertEqual('1', a.satsStr)
-
- a_er.clear()
- a.clear()
- self.assertTrue(a.isEmpty)
- self.assertTrue(bool(a_er.received))
- self.assertEqual('0', a.satsStr)
-
- a.clear()
- a_er.clear()
- a.isMax = True
- self.assertTrue(a.isMax)
- self.assertFalse(a.isEmpty)
- self.assertTrue(bool(a_er.received))
- self.assertEqual('0', a.satsStr)
-
- a.clear()
- a_er.clear()
- a.msatsInt = 1
- self.assertTrue(bool(a_er.received))
- self.assertFalse(a.isEmpty)
- self.assertEqual('1', a.msatsStr)
-
- @qt_test
- def test_qeamount_copy(self):
- a = QEAmount()
- b = QEAmount()
- b.satsInt = 1
- c = QEAmount()
- c.msatsInt = 1
- d = QEAmount()
- d.isMax = True
-
- t = QEAmount()
- t_er = QEventReceiver(t.valueChanged)
-
- t.copyFrom(a)
- self.assertTrue(t.isEmpty)
- self.assertEqual(0, len(t_er.received))
-
- t.clear()
- t_er.clear()
- t.copyFrom(b)
- self.assertFalse(t.isEmpty)
- self.assertEqual(t.satsInt, 1)
- self.assertEqual(1, len(t_er.received))
-
- t.clear()
- t_er.clear()
- t.copyFrom(c)
- self.assertFalse(t.isEmpty)
- self.assertEqual(t.msatsInt, 1)
- self.assertEqual(1, len(t_er.received))
-
- t.clear()
- t_er.clear()
- t.copyFrom(d)
- self.assertFalse(t.isEmpty)
- self.assertTrue(t.isMax)
- self.assertEqual(1, len(t_er.received))
-
- @qt_test
- def test_qeamount_frominvoice(self):
- amount_sat = 10_000
- outputs = [PartialTxOutput.from_address_and_value('bc1qj3zx2zc4rpv3npzmznxhdxzn0wm7pzqp8p2293', amount_sat)]
- invoice = Invoice(
- amount_msat=amount_sat * 1000,
- message="mymsg",
- time=1692716965,
- exp=LN_EXPIRY_NEVER,
- outputs=outputs,
- bip70=None,
- height=0,
- lightning_invoice=None,
- )
- a = QEAmount(from_invoice=invoice)
- self.assertEqual(10_000, a.satsInt)
- self.assertEqual(10_000_000, a.msatsInt)
- self.assertFalse(a.isMax)
-
- outputs = [PartialTxOutput.from_address_and_value('bc1qj3zx2zc4rpv3npzmznxhdxzn0wm7pzqp8p2293', '!')]
- invoice = Invoice(
- amount_msat='!',
- message="mymsg",
- time=1692716965,
- exp=LN_EXPIRY_NEVER,
- outputs=outputs,
- bip70=None,
- height=0,
- lightning_invoice=None,
- )
- a = QEAmount(from_invoice=invoice)
- self.assertTrue(a.isMax)
- self.assertEqual(0, a.satsInt)
- self.assertEqual(0, a.msatsInt)
-
- bolt11 = 'lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj'
- invoice = Invoice.from_bech32(bolt11)
- a = QEAmount(from_invoice=invoice)
- self.assertEqual(2_000_000, a.satsInt)
- self.assertEqual(2_000_000_000, a.msatsInt)
- self.assertFalse(a.isMax)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.