v0.4.8: seven UX fixes for the BAL inheritance plugin

- #04: Raw/Date selector now reappears on the WILL/HEIR tabs when switching to
  ADVANCED (and when a raw value is pushed from the wizard), keeping the current
  value/editor. New BalTimeEditWidget.apply_user_type_visibility() wired into
  WillSettingsWidget for both the locktime and check-alive boxes.
- #3: Building Will report window minimum height 500 -> 450 px.
- #5: 'User Type' setting moved to the bottom, above 'Rebroadcast transactions'.
- #6: enabling ADVANCED now requires typing 'at My Risk' (case-insensitive);
  wrong phrase or cancel reverts to BASIC. QInputDialog added to common import.
- #7a: on CHECK with an emptied wallet, an extra reassuring line is shown on
  'Checking your will': 'Inheritance already executed (on blockchain)' in GREEN
  (CONFIRMED) or 'Inheritance in mempool (waiting confirmation)' in ORANGE
  (MEMPOOL). New _executed_inheritance_status() helper.
- #7b: 'Balance is too low, or CheckAlive is in the past. Skipped' (space added)
  recoloured to ORANGE instead of red.
- Reset button renamed 'Reset setting' -> 'Reset to Default Setting'.
- Added tests/test_group_h_v048.py (8 tests). Version 0.4.7 -> 0.4.8.
  Full suite: 266 passed; ruff clean; CHANGELOG #24; memory updated (#04 done).
This commit is contained in:
2026-06-28 23:02:40 -04:00
parent 0f2b3a4146
commit 33e8a7533d
11 changed files with 425 additions and 38 deletions

127
tests/test_group_h_v048.py Normal file
View File

@@ -0,0 +1,127 @@
"""
Tests for the v0.4.8 changes (Group H).
These tests cover the small, GUI-free DECISION LOGIC introduced in v0.4.8,
without importing the Qt widgets (which need PyQt6 + an Electrum window). For
each behaviour we reproduce the exact rule the production code uses, so the
tests stay fast and headless while still pinning the contract.
Covered behaviour:
* #7a - "executed inheritance" detection used to show a reassuring note on the
"Checking your will" row when the wallet is empty because the inheritance was
already executed (CONFIRMED) or is on its way (MEMPOOL). The rule:
CONFIRMED takes precedence over MEMPOOL, and only when neither is present is
the will treated as "really changed" (None).
* #6 - the "at My Risk" confirmation gate for enabling ADVANCED mode. The
typed phrase is accepted case-insensitively; anything else (or a cancel)
must keep the user in BASIC.
Run:
PYTHONPATH=electrum-src python3 -m pytest tests/test_group_h_v048.py -q
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
# ------------------------------------------------------------------ #
# Mocks
# ------------------------------------------------------------------ #
class FakeWillItem:
"""Minimal will item exposing only ``get_status`` like the real WillItem."""
def __init__(self, statuses):
# ``statuses`` is a set of status names that are True for this item.
self._statuses = set(statuses)
def get_status(self, status):
return status in self._statuses
def executed_inheritance_status(willitems):
"""GUI-free copy of ``BalDialog._executed_inheritance_status``.
Mirrors the production rule exactly (see bal/gui/qt/dialogs.py):
* return "CONFIRMED" as soon as any item is confirmed on-chain;
* otherwise return "MEMPOOL" if any item is in the mempool;
* otherwise return None.
"""
has_mempool = False
for witem in willitems.values():
if witem.get_status("CONFIRMED"):
return "CONFIRMED"
if witem.get_status("MEMPOOL"):
has_mempool = True
return "MEMPOOL" if has_mempool else None
def advanced_phrase_ok(text, accepted):
"""GUI-free copy of the ADVANCED gate check (see on_user_type_change).
Returns True only when ``text`` equals the confirmation phrase ignoring
surrounding whitespace and letter case. ``accepted`` is False when the input
dialog was cancelled, which must never enable ADVANCED.
"""
if not accepted:
return False
return text.strip().lower() == "at my risk"
# ------------------------------------------------------------------ #
# #7a - executed-inheritance detection
# ------------------------------------------------------------------ #
def test_executed_status_confirmed_wins():
"""A confirmed transaction reports CONFIRMED even if a mempool one exists."""
willitems = {
"a": FakeWillItem({"MEMPOOL"}),
"b": FakeWillItem({"CONFIRMED"}),
}
assert executed_inheritance_status(willitems) == "CONFIRMED"
def test_executed_status_mempool_only():
"""With only a mempool transaction the status is MEMPOOL."""
willitems = {"a": FakeWillItem({"MEMPOOL"})}
assert executed_inheritance_status(willitems) == "MEMPOOL"
def test_executed_status_none_when_neither():
"""No confirmed/mempool item -> None (the will really has to be rebuilt)."""
willitems = {"a": FakeWillItem({"VALID"})}
assert executed_inheritance_status(willitems) is None
def test_executed_status_empty():
"""No will items at all -> None."""
assert executed_inheritance_status({}) is None
# ------------------------------------------------------------------ #
# #6 - "at My Risk" ADVANCED gate
# ------------------------------------------------------------------ #
def test_advanced_phrase_exact():
"""The exact phrase as shown in the prompt is accepted."""
assert advanced_phrase_ok("at My Risk", True) is True
def test_advanced_phrase_case_insensitive():
"""Any capitalisation is accepted (case-insensitive)."""
for variant in ("at my risk", "AT MY RISK", "At My Risk", " at my risk "):
assert advanced_phrase_ok(variant, True) is True
def test_advanced_phrase_wrong_text():
"""A wrong phrase keeps the user in BASIC."""
assert advanced_phrase_ok("at your risk", True) is False
assert advanced_phrase_ok("", True) is False
def test_advanced_phrase_cancelled():
"""Cancelling the dialog (accepted=False) never enables ADVANCED."""
assert advanced_phrase_ok("at My Risk", False) is False