Merge pull request #900 from PROWLERx15/fix-seed-views

[Bugfix] Back Navigation in Seed Entry Views
This commit is contained in:
Nick Klockenga
2026-06-09 22:10:44 -04:00
committed by GitHub
2 changed files with 79 additions and 7 deletions
+6 -7
View File
@@ -226,11 +226,13 @@ class SeedMnemonicEntryView(View):
)
if ret == RET_CODE__BACK_BUTTON:
if self.cur_word_index > 0:
return Destination(BackStackView)
else:
# This handles two possible scenarios:
# 1. Backing out of the first word cancels the mnemonic entry process;
# return to whichever `View` routed us here initially.
# 2. Backing out of the current word returns to the previous word.
if self.cur_word_index == 0:
self.controller.storage.discard_pending_mnemonic()
return Destination(MainMenuView)
return Destination(BackStackView)
# ret will be our new mnemonic word
self.controller.storage.update_pending_mnemonic(ret, self.cur_word_index)
@@ -339,9 +341,6 @@ class SeedFinalizeView(View):
elif button_data[selected_menu_num] == self.PASSPHRASE:
return Destination(SeedAddPassphraseView)
elif selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
class SeedAddPassphraseView(View):
+73
View File
@@ -495,6 +495,79 @@ class TestSeedFlows(FlowTest):
class TestSeedEntryBackFlows(FlowTest):
"""
Tests for every BACK exit scenario from SeedMnemonicEntryView and related views.
A naive BackStackView swap can leave resume_main_flow dangling, causing
auto-redirects on stale flow state. These tests verify that BACK navigation
returns to the correct parent view AND that no flow state leaks.
"""
def test_back_from_seed_entry_first_word(self):
"""
Pressing BACK on the first word of mnemonic entry should return to
the View that initiated the mnemonic entry process.
"""
for seed_type in [seed_views.LoadSeedView.TYPE_12WORD, seed_views.LoadSeedView.TYPE_24WORD]:
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS),
FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView
FlowStep(seed_views.LoadSeedView, button_data_selection=seed_type),
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON),
FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView
])
BaseTest.reset_controller()
def test_back_from_seed_entry_mid_word(self):
"""
Pressing BACK from a middle word (eg. word #2) should return to the
previous SeedMnemonicEntryView (eg. word #1) via the back stack.
"""
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS),
FlowStep(seed_views.SeedsMenuView, is_redirect=True),
FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD),
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value="abandon"), # word #1
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), # BACK from word #2
FlowStep(seed_views.SeedMnemonicEntryView), # Returns to word #1
])
# Verify we're back on word #1: word at index 0 should still be set
# from the previous entry, while word at index 1 should be unset.
assert self.controller.storage.get_pending_mnemonic_word(0) == "abandon"
assert self.controller.storage.get_pending_mnemonic_word(1) is None
def test_back_from_seed_entry_via_seed_select(self):
"""
Backing out of mnemonic entry during an active flow must preserve
`resume_main_flow` so the user remains within that flow.
"""
from seedsigner.controller import Controller
from seedsigner.models.settings import SettingsConstants
self.settings.set_value(SettingsConstants.SETTING__MESSAGE_SIGNING, SettingsConstants.OPTION__ENABLED)
def load_signmessage_into_decoder(view):
view.decoder.add_data("signmessage m/84h/0h/0h/0/0 ascii:test message")
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN),
FlowStep(scan_views.ScanView, before_run=load_signmessage_into_decoder),
FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True),
FlowStep(seed_views.SeedSelectSeedView, button_data_selection=seed_views.SeedSelectSeedView.TYPE_12WORD),
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), # BACK on first word
FlowStep(seed_views.SeedSelectSeedView), # Should return here, in the sign message flow
])
# Verify resume_main_flow is still set — user is still in the sign message flow
assert self.controller.resume_main_flow == Controller.FLOW__SIGN_MESSAGE
class TestMessageSigningFlows(FlowTest):
MAINNET_DERIVATION_PATH = "m/84h/0h/0h/0/0"
TESTNET_DERIVATION_PATH = "m/84h/1h/0h/0/0"