diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 9483c111..1ea993f2 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -31,6 +31,7 @@ class SeedsMenuView(View): LOAD = "Load a seed" + CREATE = "Create a seed" def __init__(self): super().__init__() @@ -49,7 +50,7 @@ def run(self): button_data = [] for seed in self.seeds: button_data.append((seed["fingerprint"], SeedSignerIconConstants.FINGERPRINT)) - button_data.append("Load a seed") + button_data.extend([self.LOAD, self.CREATE]) selected_menu_num = self.run_screen( ButtonListScreen, @@ -61,9 +62,14 @@ def run(self): if len(self.seeds) > 0 and selected_menu_num < len(self.seeds): return Destination(SeedOptionsView, view_args={"seed_num": selected_menu_num}) + # accessed relative to len(self.seeds) which may have changed elif selected_menu_num == len(self.seeds): return Destination(LoadSeedView) + elif selected_menu_num == len(self.seeds) + 1: + from .tools_views import ToolsMenuView + return Destination(ToolsMenuView, view_args={"new_seeds_only": True}) + elif selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) @@ -184,7 +190,8 @@ def run(self): if self.settings.get_value(SettingsConstants.SETTING__ELECTRUM_SEEDS) == SettingsConstants.OPTION__ENABLED: button_data.append(self.TYPE_ELECTRUM) - button_data.append(self.CREATE) + if len(self.controller.storage.seeds) == 0: + button_data.append(self.CREATE) selected_menu_num = self.run_screen( ButtonListScreen, @@ -213,8 +220,7 @@ def run(self): elif button_data[selected_menu_num] == self.CREATE: from .tools_views import ToolsMenuView - return Destination(ToolsMenuView) - + return Destination(ToolsMenuView, view_args={"new_seeds_only": True}) class SeedMnemonicEntryView(View): diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index eabb1171..7c781483 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -32,12 +32,18 @@ class ToolsMenuView(View): ADDRESS_EXPLORER = "Address Explorer" VERIFY_ADDRESS = "Verify address" + def __init__(self, new_seeds_only: bool = False): + super().__init__() + self.new_seeds_only = new_seeds_only + def run(self): - button_data = [self.IMAGE, self.DICE, self.KEYBOARD, self.ADDRESS_EXPLORER, self.VERIFY_ADDRESS] + button_data = [self.IMAGE, self.DICE, self.KEYBOARD] + if not self.new_seeds_only: + button_data.extend([self.ADDRESS_EXPLORER, self.VERIFY_ADDRESS]) selected_menu_num = self.run_screen( ButtonListScreen, - title="Tools", + title="Tools" if not self.new_seeds_only else "Create A Seed", is_button_text_centered=False, button_data=button_data ) @@ -54,10 +60,10 @@ def run(self): elif button_data[selected_menu_num] == self.KEYBOARD: return Destination(ToolsCalcFinalWordNumWordsView) - elif button_data[selected_menu_num] == self.ADDRESS_EXPLORER: + elif not self.new_seeds_only and button_data[selected_menu_num] == self.ADDRESS_EXPLORER: return Destination(ToolsAddressExplorerSelectSourceView) - elif button_data[selected_menu_num] == self.VERIFY_ADDRESS: + elif not self.new_seeds_only and button_data[selected_menu_num] == self.VERIFY_ADDRESS: from seedsigner.views.scan_views import ScanAddressView return Destination(ScanAddressView) @@ -193,12 +199,13 @@ def run(self): TWENTY_FOUR = f"24 words ({mnemonic_generation.DICE__NUM_ROLLS__24WORD} rolls)" button_data = [TWELVE, TWENTY_FOUR] - selected_menu_num = ButtonListScreen( + selected_menu_num = self.run_screen( + ButtonListScreen, title="Mnemonic Length", is_bottom_list=True, is_button_text_centered=True, button_data=button_data, - ).display() + ) if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index 4c0c092a..22189fc8 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -172,6 +172,7 @@ def add_op_return_to_psbt(psbt: PSBT, raw_payload_data: bytes): "Seed Views": [ seed_views.SeedsMenuView, seed_views.LoadSeedView, + (tools_views.ToolsMenuView, dict(new_seeds_only=True), "ToolsViaCreateASeed"), seed_views.SeedMnemonicEntryView, seed_views.SeedMnemonicInvalidView, seed_views.SeedFinalizeView, diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index f6ef501d..c9847dba 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -9,7 +9,7 @@ from seedsigner.models.settings import Settings, SettingsConstants from seedsigner.models.seed import ElectrumSeed, Seed from seedsigner.views.view import ErrorView, MainMenuView, OptionDisabledView, View, NetworkMismatchErrorView -from seedsigner.views import seed_views, scan_views, settings_views +from seedsigner.views import seed_views, scan_views, settings_views, tools_views def load_seed_into_decoder(view: scan_views.ScanView): @@ -408,6 +408,83 @@ def test_discard_seed_flow(self): ) + def test_create_first_seed_via_seeds(self): + # From SeedMenu into "Create a Seed", then foreach valid option: in-out-next + 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.CREATE), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.IMAGE), + FlowStep(tools_views.ToolsImageEntropyLivePreviewView, screen_return_value=RET_CODE__BACK_BUTTON, is_redirect=True), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.DICE), + FlowStep(tools_views.ToolsDiceEntropyMnemonicLengthView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.KEYBOARD), + FlowStep(tools_views.ToolsCalcFinalWordNumWordsView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(tools_views.ToolsMenuView) + ]) + + # From SeedMenu into "Create a Seed", Address Explorer not available + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: + 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.CREATE), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.ADDRESS_EXPLORER), + ]) + + # From SeedMenu into "Create a Seed", Verify Address not available + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: + 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.CREATE), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.VERIFY_ADDRESS), + ]) + + + def test_create_additional_seed_via_seeds(self): + # Load a finalized Seed into the Controller + mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() + self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) + self.controller.storage.finalize_pending_seed() + + # From SeedMenu into "Create a Seed", then foreach valid option: in-out-next + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, button_data_selection=seed_views.SeedsMenuView.CREATE), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.IMAGE), + FlowStep(tools_views.ToolsImageEntropyLivePreviewView, screen_return_value=RET_CODE__BACK_BUTTON, is_redirect=True), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.DICE), + FlowStep(tools_views.ToolsDiceEntropyMnemonicLengthView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.KEYBOARD), + FlowStep(tools_views.ToolsCalcFinalWordNumWordsView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(tools_views.ToolsMenuView) + ]) + + # From SeedMenu into "Load a Seed", Create a seed not available + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, button_data_selection=seed_views.SeedsMenuView.LOAD), + FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.CREATE), + ]) + + # From SeedMenu into "Create a Seed", Address Explorer not available + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, button_data_selection=seed_views.SeedsMenuView.CREATE), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.ADDRESS_EXPLORER), + ]) + + # From SeedMenu into "Create a Seed", Verify Address not available + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, button_data_selection=seed_views.SeedsMenuView.CREATE), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.VERIFY_ADDRESS), + ]) + class TestMessageSigningFlows(FlowTest): MAINNET_DERIVATION_PATH = "m/84h/0h/0h/0/0" @@ -664,3 +741,4 @@ def expect_unsupported_derivation(load_message: Callable): self.settings.set_value(SettingsConstants.SETTING__NETWORK, SettingsConstants.MAINNET) expect_unsupported_derivation(self.load_custom_derivation_into_decoder) + diff --git a/tests/test_flows_tools.py b/tests/test_flows_tools.py index 588859be..58025c75 100644 --- a/tests/test_flows_tools.py +++ b/tests/test_flows_tools.py @@ -245,3 +245,15 @@ def load_descriptor_into_decoder(view: scan_views.ScanView): FlowStep(seed_views.AddressVerificationSuccessView), ]) + + def test_create_seed_via_tools_has_other_tools(self): + """ + When Navigating to ToolsMenuView (also used for "Create a seed") from MainMenu, + other tools exist; Into AddressExplorer, then back, then into VerifyAddress + """ + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.TOOLS), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.ADDRESS_EXPLORER), + FlowStep(tools_views.ToolsAddressExplorerSelectSourceView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.VERIFY_ADDRESS), + ])