diff --git a/ren_browser/app.py b/ren_browser/app.py index 24dfb3f..97ebcf3 100644 --- a/ren_browser/app.py +++ b/ren_browser/app.py @@ -93,40 +93,41 @@ async def main(page: Page): def reload_reticulum(page: Page, on_complete=None): """Hot reload Reticulum with updated configuration. - + Args: page: Flet page instance on_complete: Optional callback to run when reload is complete - + """ + def reload_thread(): import time - + try: global RNS_INSTANCE - + if RNS_INSTANCE: try: RNS_INSTANCE.exit_handler() print("RNS exit handler completed") except Exception as e: print(f"Warning during RNS shutdown: {e}") - + RNS.Reticulum._Reticulum__instance = None - + RNS.Transport.destinations = [] - + RNS_INSTANCE = None print("RNS instance cleared") - + time.sleep(0.5) - + # Initialize storage system storage = initialize_storage(page) - + # Get Reticulum config directory from storage manager config_dir = storage.get_reticulum_config_path() - + # Ensure any saved config is written to filesystem before RNS init try: saved_config = storage.load_config() @@ -136,27 +137,28 @@ def reload_reticulum(page: Page, on_complete=None): config_file_path.write_text(saved_config, encoding="utf-8") except Exception as e: print(f"Warning: Failed to write config file: {e}") - + try: # Re-initialize Reticulum import ren_browser.logs + ren_browser.logs.setup_rns_logging() RNS_INSTANCE = RNS.Reticulum(str(config_dir)) - + # Success if on_complete: on_complete(True, None) - + except Exception as e: print(f"Error reinitializing Reticulum: {e}") if on_complete: on_complete(False, str(e)) - + except Exception as e: print(f"Error during reload: {e}") if on_complete: on_complete(False, str(e)) - + page.run_thread(reload_thread) @@ -172,10 +174,17 @@ def run(): help="Select renderer (plaintext or micron)", ) parser.add_argument( - "-w", "--web", action="store_true", help="Launch in web browser mode", + "-w", + "--web", + action="store_true", + help="Launch in web browser mode", ) parser.add_argument( - "-p", "--port", type=int, default=None, help="Port for web server", + "-p", + "--port", + type=int, + default=None, + help="Port for web server", ) parser.add_argument( "-c", diff --git a/ren_browser/renderer/micron.py b/ren_browser/renderer/micron.py index 1ef8fd9..9b1f62f 100644 --- a/ren_browser/renderer/micron.py +++ b/ren_browser/renderer/micron.py @@ -22,7 +22,7 @@ def hex_to_rgb(hex_color: str) -> str: def parse_micron_line(line: str) -> list: """Parse a single line of micron markup into styled text spans. - + Returns list of dicts with 'text', 'bold', 'italic', 'underline', 'color', 'bgcolor'. """ spans = [] @@ -37,14 +37,16 @@ def parse_micron_line(line: str) -> list: while i < len(line): if line[i] == "`" and i + 1 < len(line): if current_text: - spans.append({ - "text": current_text, - "bold": bold, - "italic": italic, - "underline": underline, - "color": color, - "bgcolor": bgcolor, - }) + spans.append( + { + "text": current_text, + "bold": bold, + "italic": italic, + "underline": underline, + "color": color, + "bgcolor": bgcolor, + } + ) current_text = "" tag = line[i + 1] @@ -59,13 +61,13 @@ def parse_micron_line(line: str) -> list: underline = not underline i += 2 elif tag == "F" and i + 5 <= len(line): - color = hex_to_rgb(line[i+2:i+5]) + color = hex_to_rgb(line[i + 2 : i + 5]) i += 5 elif tag == "f": color = None i += 2 elif tag == "B" and i + 5 <= len(line): - bgcolor = hex_to_rgb(line[i+2:i+5]) + bgcolor = hex_to_rgb(line[i + 2 : i + 5]) i += 5 elif tag == "b": bgcolor = None @@ -85,21 +87,23 @@ def parse_micron_line(line: str) -> list: i += 1 if current_text: - spans.append({ - "text": current_text, - "bold": bold, - "italic": italic, - "underline": underline, - "color": color, - "bgcolor": bgcolor, - }) + spans.append( + { + "text": current_text, + "bold": bold, + "italic": italic, + "underline": underline, + "color": color, + "bgcolor": bgcolor, + } + ) return spans def render_micron(content: str, on_link_click=None) -> ft.Control: """Render micron markup content to a Flet control. - + Falls back to plaintext renderer if parsing fails. Args: @@ -119,7 +123,7 @@ def render_micron(content: str, on_link_click=None) -> ft.Control: def _render_micron_internal(content: str, on_link_click=None) -> ft.Control: """Internal micron rendering implementation. - + Args: content: Micron markup content to render. on_link_click: Optional callback function(url) called when a link is clicked. @@ -186,23 +190,23 @@ def _render_micron_internal(content: str, on_link_click=None) -> ft.Control: if "`[" in line: row_controls = [] - remaining = line last_end = 0 - + for link_match in re.finditer(r"`\[([^`]*)`([^\]]*)\]", line): - before = line[last_end:link_match.start()] + before = line[last_end : link_match.start()] if before: before_spans = parse_micron_line(before) for span in before_spans: row_controls.append(create_text_span(span)) - + label = link_match.group(1) url = link_match.group(2) - + def make_link_handler(link_url): def handler(e): if on_link_click: on_link_click(link_url) + return handler row_controls.append( @@ -215,9 +219,9 @@ def _render_micron_internal(content: str, on_link_click=None) -> ft.Control: on_click=make_link_handler(url), ), ) - + last_end = link_match.end() - + after = line[last_end:] if after: after_spans = parse_micron_line(after) diff --git a/ren_browser/storage/storage.py b/ren_browser/storage/storage.py index ad8dda5..11932db 100644 --- a/ren_browser/storage/storage.py +++ b/ren_browser/storage/storage.py @@ -51,9 +51,7 @@ class StorageManager: elif "APPDATA" in os.environ: # Windows storage_dir = pathlib.Path(os.environ["APPDATA"]) / "ren_browser" elif "XDG_CONFIG_HOME" in os.environ: # Linux XDG standard - storage_dir = ( - pathlib.Path(os.environ["XDG_CONFIG_HOME"]) / "ren_browser" - ) + storage_dir = pathlib.Path(os.environ["XDG_CONFIG_HOME"]) / "ren_browser" else: storage_dir = pathlib.Path.home() / ".ren_browser" @@ -126,7 +124,8 @@ class StorageManager: if self.page and hasattr(self.page, "client_storage"): self.page.client_storage.set("ren_browser_config", config_content) self.page.client_storage.set( - "ren_browser_config_error", f"File save failed: {error}", + "ren_browser_config_error", + f"File save failed: {error}", ) return True @@ -193,7 +192,8 @@ class StorageManager: if self.page and hasattr(self.page, "client_storage"): self.page.client_storage.set( - "ren_browser_bookmarks", json.dumps(bookmarks), + "ren_browser_bookmarks", + json.dumps(bookmarks), ) return True @@ -258,7 +258,9 @@ class StorageManager: json.dump(settings, f, indent=2) if self.page and hasattr(self.page, "client_storage"): - self.page.client_storage.set("ren_browser_settings", json.dumps(settings)) + self.page.client_storage.set( + "ren_browser_settings", json.dumps(settings) + ) return True except Exception: @@ -270,7 +272,7 @@ class StorageManager: "horizontal_scroll": False, "page_bgcolor": "#000000", } - + try: settings_path = self._storage_dir / "settings.json" if settings_path.exists(): diff --git a/ren_browser/tabs/tabs.py b/ren_browser/tabs/tabs.py index c090824..091da5f 100644 --- a/ren_browser/tabs/tabs.py +++ b/ren_browser/tabs/tabs.py @@ -32,10 +32,10 @@ class TabsManager: self.page = page self.page.on_resize = self._on_resize self.manager = SimpleNamespace(tabs=[], index=0) - + storage = get_storage_manager(page) self.settings = storage.load_app_settings() - + self.tab_bar = ft.Container( content=ft.Row( spacing=6, @@ -60,7 +60,9 @@ class TabsManager: self._on_tab_go(None, 0) default_content = ( - render_micron("Welcome to Ren Browser", on_link_click=handle_link_click_home) + render_micron( + "Welcome to Ren Browser", on_link_click=handle_link_click_home + ) if app_module.RENDERER == "micron" else render_plaintext("Welcome to Ren Browser") ) @@ -96,16 +98,16 @@ class TabsManager: self.settings = settings bgcolor = settings.get("page_bgcolor", "#000000") self.content_container.bgcolor = bgcolor - + horizontal_scroll = settings.get("horizontal_scroll", False) scroll_mode = ft.ScrollMode.ALWAYS if horizontal_scroll else ft.ScrollMode.AUTO - + for tab in self.manager.tabs: if "content" in tab and hasattr(tab["content"], "scroll"): tab["content"].scroll = scroll_mode if "content_control" in tab and hasattr(tab["content_control"], "scroll"): tab["content_control"].scroll = scroll_mode - + if self.content_container.content: self.content_container.content.update() self.page.update() @@ -127,7 +129,9 @@ class TabsManager: cumulative_width = 0 visible_tabs_count = 0 - tab_containers = [c for c in self.tab_bar.content.controls if isinstance(c, ft.Container)] + tab_containers = [ + c for c in self.tab_bar.content.controls if isinstance(c, ft.Container) + ] for i, tab in enumerate(self.manager.tabs): estimated_width = len(tab["title"]) * 10 + 32 + self.tab_bar.content.spacing @@ -183,7 +187,7 @@ class TabsManager: content_control = content horizontal_scroll = self.settings.get("horizontal_scroll", False) scroll_mode = ft.ScrollMode.ALWAYS if horizontal_scroll else ft.ScrollMode.AUTO - + tab_content = ft.Column( expand=True, scroll=scroll_mode, @@ -254,13 +258,17 @@ class TabsManager: return idx = self.manager.index - tab_containers = [c for c in self.tab_bar.content.controls if isinstance(c, ft.Container)] + tab_containers = [ + c for c in self.tab_bar.content.controls if isinstance(c, ft.Container) + ] control_to_remove = tab_containers[idx] self.manager.tabs.pop(idx) self.tab_bar.content.controls.remove(control_to_remove) - updated_tab_containers = [c for c in self.tab_bar.content.controls if isinstance(c, ft.Container)] + updated_tab_containers = [ + c for c in self.tab_bar.content.controls if isinstance(c, ft.Container) + ] for i, control in enumerate(updated_tab_containers): control.on_click = lambda e, i=i: self.select_tab(i) # type: ignore @@ -278,7 +286,9 @@ class TabsManager: """ self.manager.index = idx - tab_containers = [c for c in self.tab_bar.content.controls if isinstance(c, ft.Container)] + tab_containers = [ + c for c in self.tab_bar.content.controls if isinstance(c, ft.Container) + ] for i, control in enumerate(tab_containers): if i == idx: control.bgcolor = ft.Colors.BLUE_900 @@ -296,7 +306,7 @@ class TabsManager: url = tab["url_field"].value.strip() if not url: return - + placeholder_text = f"Loading content for {url}..." import ren_browser.app as app_module @@ -330,12 +340,12 @@ class TabsManager: def fetch_and_update(): parts = url.split(":", 1) if len(parts) != 2: - result = f"Error: Invalid URL format. Expected format: hash:/page/path" + result = "Error: Invalid URL format. Expected format: hash:/page/path" page_path = "" else: dest_hash = parts[0] page_path = parts[1] if parts[1].startswith("/") else f"/{parts[1]}" - + req = PageRequest(destination_hash=dest_hash, page_path=page_path) page_fetcher = PageFetcher() try: @@ -343,7 +353,7 @@ class TabsManager: except Exception as ex: app_module.log_error(str(ex)) result = f"Error: {ex}" - + try: tab = self.manager.tabs[idx] except IndexError: @@ -353,7 +363,7 @@ class TabsManager: new_control = render_micron(result, on_link_click=handle_link_click) else: new_control = render_plaintext(result) - + tab["content_control"] = new_control tab["content"].controls[0] = new_control if self.manager.index == idx: diff --git a/ren_browser/ui/settings.py b/ren_browser/ui/settings.py index ee2bd23..d70b283 100644 --- a/ren_browser/ui/settings.py +++ b/ren_browser/ui/settings.py @@ -77,8 +77,15 @@ def open_settings_tab(page: ft.Page, tab_manager): snack = ft.SnackBar( content=ft.Row( controls=[ - ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20), - ft.Text("Configuration saved! Restart app to apply changes.", color=ft.Colors.WHITE), + ft.Icon( + ft.Icons.CHECK_CIRCLE, + color=ft.Colors.GREEN_400, + size=20, + ), + ft.Text( + "Configuration saved! Restart app to apply changes.", + color=ft.Colors.WHITE, + ), ], tight=True, ), @@ -93,7 +100,9 @@ def open_settings_tab(page: ft.Page, tab_manager): content=ft.Row( controls=[ ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), - ft.Text("Failed to save configuration", color=ft.Colors.WHITE), + ft.Text( + "Failed to save configuration", color=ft.Colors.WHITE + ), ], tight=True, ), @@ -127,7 +136,9 @@ def open_settings_tab(page: ft.Page, tab_manager): content=ft.Row( controls=[ ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), - ft.Text("Failed to save configuration", color=ft.Colors.WHITE), + ft.Text( + "Failed to save configuration", color=ft.Colors.WHITE + ), ], tight=True, ), @@ -138,11 +149,16 @@ def open_settings_tab(page: ft.Page, tab_manager): snack.open = True page.update() return - + loading_snack = ft.SnackBar( content=ft.Row( controls=[ - ft.ProgressRing(width=16, height=16, stroke_width=2, color=ft.Colors.BLUE_400), + ft.ProgressRing( + width=16, + height=16, + stroke_width=2, + color=ft.Colors.BLUE_400, + ), ft.Text("Reloading Reticulum...", color=ft.Colors.WHITE), ], tight=True, @@ -153,17 +169,24 @@ def open_settings_tab(page: ft.Page, tab_manager): page.overlay.append(loading_snack) loading_snack.open = True page.update() - + def on_reload_complete(success, error): loading_snack.open = False page.update() - + if success: snack = ft.SnackBar( content=ft.Row( controls=[ - ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20), - ft.Text("Reticulum reloaded successfully!", color=ft.Colors.WHITE), + ft.Icon( + ft.Icons.CHECK_CIRCLE, + color=ft.Colors.GREEN_400, + size=20, + ), + ft.Text( + "Reticulum reloaded successfully!", + color=ft.Colors.WHITE, + ), ], tight=True, ), @@ -174,8 +197,12 @@ def open_settings_tab(page: ft.Page, tab_manager): snack = ft.SnackBar( content=ft.Row( controls=[ - ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), - ft.Text(f"Reload failed: {error}", color=ft.Colors.WHITE), + ft.Icon( + ft.Icons.ERROR, color=ft.Colors.RED_400, size=20 + ), + ft.Text( + f"Reload failed: {error}", color=ft.Colors.WHITE + ), ], tight=True, ), @@ -185,10 +212,11 @@ def open_settings_tab(page: ft.Page, tab_manager): page.overlay.append(snack) snack.open = True page.update() - + import ren_browser.app as app_module + app_module.reload_reticulum(page, on_reload_complete) - + except Exception as ex: snack = ft.SnackBar( content=ft.Row( @@ -217,8 +245,15 @@ def open_settings_tab(page: ft.Page, tab_manager): snack = ft.SnackBar( content=ft.Row( controls=[ - ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20), - ft.Text("Appearance settings saved and applied!", color=ft.Colors.WHITE), + ft.Icon( + ft.Icons.CHECK_CIRCLE, + color=ft.Colors.GREEN_400, + size=20, + ), + ft.Text( + "Appearance settings saved and applied!", + color=ft.Colors.WHITE, + ), ], tight=True, ), @@ -233,7 +268,10 @@ def open_settings_tab(page: ft.Page, tab_manager): content=ft.Row( controls=[ ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), - ft.Text("Failed to save appearance settings", color=ft.Colors.WHITE), + ft.Text( + "Failed to save appearance settings", + color=ft.Colors.WHITE, + ), ], tight=True, ), @@ -410,7 +448,14 @@ def open_settings_tab(page: ft.Page, tab_manager): nav_card = ft.Container( content=ft.Row( - controls=[btn_config, btn_appearance, btn_errors, btn_ret, btn_storage, btn_refresh], + controls=[ + btn_config, + btn_appearance, + btn_errors, + btn_ret, + btn_storage, + btn_refresh, + ], spacing=8, wrap=True, ), diff --git a/ren_browser/ui/ui.py b/ren_browser/ui/ui.py index 6b7861e..e2b6ef9 100644 --- a/ren_browser/ui/ui.py +++ b/ren_browser/ui/ui.py @@ -84,7 +84,9 @@ def build_ui(page: Page): tab_manager._on_tab_go(None, idx) if req.page_path.endswith(".mu"): - new_control = render_micron(result, on_link_click=handle_link_click) + new_control = render_micron( + result, on_link_click=handle_link_click + ) else: new_control = render_plaintext(result) tab["content_control"] = new_control diff --git a/tests/conftest.py b/tests/conftest.py index 045c791..f61bddc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -62,7 +62,9 @@ def sample_page_request(): from ren_browser.pages.page_request import PageRequest return PageRequest( - destination_hash="1234567890abcdef", page_path="/page/index.mu", field_data=None, + destination_hash="1234567890abcdef", + page_path="/page/index.mu", + field_data=None, ) diff --git a/tests/unit/test_announces.py b/tests/unit/test_announces.py index 94ea7af..7cd618d 100644 --- a/tests/unit/test_announces.py +++ b/tests/unit/test_announces.py @@ -19,7 +19,9 @@ class TestAnnounce: def test_announce_with_none_display_name(self): """Test Announce creation with None display name.""" announce = Announce( - destination_hash="1234567890abcdef", display_name=None, timestamp=1234567890, + destination_hash="1234567890abcdef", + display_name=None, + timestamp=1234567890, ) assert announce.destination_hash == "1234567890abcdef" diff --git a/tests/unit/test_logs.py b/tests/unit/test_logs.py index af39b37..2d2af8d 100644 --- a/tests/unit/test_logs.py +++ b/tests/unit/test_logs.py @@ -59,7 +59,9 @@ class TestLogsModule: assert len(logs.RET_LOGS) == 1 assert logs.RET_LOGS[0] == "[2023-01-01T12:00:00] Test RNS message" logs._original_rns_log.assert_called_once_with( - "Test RNS message", "arg1", kwarg1="value1", + "Test RNS message", + "arg1", + kwarg1="value1", ) assert result == "original_result" diff --git a/tests/unit/test_page_request.py b/tests/unit/test_page_request.py index 3dcf70f..dbbd0d4 100644 --- a/tests/unit/test_page_request.py +++ b/tests/unit/test_page_request.py @@ -7,7 +7,8 @@ class TestPageRequest: def test_page_request_creation(self): """Test basic PageRequest creation.""" request = PageRequest( - destination_hash="1234567890abcdef", page_path="/page/index.mu", + destination_hash="1234567890abcdef", + page_path="/page/index.mu", ) assert request.destination_hash == "1234567890abcdef" diff --git a/tests/unit/test_storage.py b/tests/unit/test_storage.py index 584f4f0..e91df59 100644 --- a/tests/unit/test_storage.py +++ b/tests/unit/test_storage.py @@ -51,11 +51,14 @@ class TestStorageManager: with ( patch("os.name", "posix"), patch.dict( - "os.environ", {"XDG_CONFIG_HOME": "/home/user/.config"}, clear=True, + "os.environ", + {"XDG_CONFIG_HOME": "/home/user/.config"}, + clear=True, + ), + patch("pathlib.Path.mkdir"), + patch( + "ren_browser.storage.storage.StorageManager._ensure_storage_directory", ), - patch("pathlib.Path.mkdir"),patch( - "ren_browser.storage.storage.StorageManager._ensure_storage_directory", - ), ): storage = StorageManager() storage._storage_dir = storage._get_storage_directory() @@ -71,7 +74,11 @@ class TestStorageManager: """Test storage directory detection for Android with ANDROID_DATA.""" with ( patch("os.name", "posix"), - patch.dict("os.environ", {"ANDROID_ROOT": "/system", "ANDROID_DATA": "/data"}, clear=True), + patch.dict( + "os.environ", + {"ANDROID_ROOT": "/system", "ANDROID_DATA": "/data"}, + clear=True, + ), patch("pathlib.Path.mkdir"), ): with patch( @@ -86,7 +93,11 @@ class TestStorageManager: """Test storage directory detection for Android with EXTERNAL_STORAGE.""" with ( patch("os.name", "posix"), - patch.dict("os.environ", {"ANDROID_ROOT": "/system", "EXTERNAL_STORAGE": "/storage/emulated/0"}, clear=True), + patch.dict( + "os.environ", + {"ANDROID_ROOT": "/system", "EXTERNAL_STORAGE": "/storage/emulated/0"}, + clear=True, + ), patch("pathlib.Path.mkdir"), ): with patch( @@ -102,9 +113,10 @@ class TestStorageManager: with ( patch("os.name", "posix"), patch.dict("os.environ", {"ANDROID_ROOT": "/system"}, clear=True), - patch("pathlib.Path.mkdir"),patch( - "ren_browser.storage.storage.StorageManager._ensure_storage_directory", - ), + patch("pathlib.Path.mkdir"), + patch( + "ren_browser.storage.storage.StorageManager._ensure_storage_directory", + ), ): storage = StorageManager() storage._storage_dir = storage._get_storage_directory() @@ -169,7 +181,8 @@ class TestStorageManager: assert result is True mock_page.client_storage.set.assert_called_with( - "ren_browser_config", config_content, + "ren_browser_config", + config_content, ) def test_save_config_fallback(self): @@ -182,13 +195,16 @@ class TestStorageManager: storage._storage_dir = Path(temp_dir) # Mock the reticulum config path to use temp dir and cause failure - with patch.object( - storage, - "get_reticulum_config_path", - return_value=Path(temp_dir) / "reticulum", - ), patch( - "pathlib.Path.write_text", - side_effect=PermissionError("Access denied"), + with ( + patch.object( + storage, + "get_reticulum_config_path", + return_value=Path(temp_dir) / "reticulum", + ), + patch( + "pathlib.Path.write_text", + side_effect=PermissionError("Access denied"), + ), ): config_content = "test config content" result = storage.save_config(config_content) @@ -196,7 +212,8 @@ class TestStorageManager: assert result is True # Check that the config was set to client storage mock_page.client_storage.set.assert_any_call( - "ren_browser_config", config_content, + "ren_browser_config", + config_content, ) # Verify that client storage was called at least once assert mock_page.client_storage.set.call_count >= 1 @@ -354,10 +371,13 @@ class TestStorageManager: with patch.object(StorageManager, "_get_storage_directory") as mock_get_dir: mock_get_dir.return_value = Path("/nonexistent/path") - with patch( - "pathlib.Path.mkdir", - side_effect=[PermissionError("Access denied"), None], - ), patch("tempfile.gettempdir", return_value="/tmp"): + with ( + patch( + "pathlib.Path.mkdir", + side_effect=[PermissionError("Access denied"), None], + ), + patch("tempfile.gettempdir", return_value="/tmp"), + ): storage = StorageManager() expected_fallback = Path("/tmp") / "ren_browser" @@ -444,7 +464,8 @@ class TestStorageManagerEdgeCases: storage = StorageManager() with patch( - "pathlib.Path.write_text", side_effect=PermissionError("Access denied"), + "pathlib.Path.write_text", + side_effect=PermissionError("Access denied"), ): test_path = Path("/mock/path") result = storage._is_writable(test_path) diff --git a/tests/unit/test_tabs.py b/tests/unit/test_tabs.py index f5ec11e..ecd49d6 100644 --- a/tests/unit/test_tabs.py +++ b/tests/unit/test_tabs.py @@ -105,7 +105,9 @@ class TestTabsManager: """Test that selecting a tab updates background colors correctly.""" tabs_manager._add_tab_internal("Tab 2", Mock()) - tab_controls = tabs_manager.tab_bar.content.controls[:-2] # Exclude add/close buttons + tab_controls = tabs_manager.tab_bar.content.controls[ + :-2 + ] # Exclude add/close buttons tabs_manager.select_tab(1) @@ -195,7 +197,9 @@ class TestTabsManager: """Test that tab click handlers are properly set.""" tabs_manager._add_tab_internal("Tab 2", Mock()) - tab_controls = tabs_manager.tab_bar.content.controls[:-2] # Exclude add/close buttons + tab_controls = tabs_manager.tab_bar.content.controls[ + :-2 + ] # Exclude add/close buttons for i, control in enumerate(tab_controls): assert control.on_click is not None @@ -240,7 +244,7 @@ class TestTabsManager: def test_adaptive_overflow_behavior(self, tabs_manager): """Test that the overflow menu adapts to tab changes.""" # With page width at 800, add enough tabs that some should overflow. - for i in range(10): # Total 11 tabs + for i in range(10): # Total 11 tabs tabs_manager._add_tab_internal(f"Tab {i + 2}", Mock()) # Check that an overflow menu exists @@ -249,13 +253,21 @@ class TestTabsManager: # Simulate a smaller screen, expecting more tabs to overflow tabs_manager.page.width = 400 tabs_manager._update_tab_visibility() - visible_tabs_small = sum(1 for c in tabs_manager.tab_bar.content.controls if isinstance(c, ft.Container) and c.visible) + visible_tabs_small = sum( + 1 + for c in tabs_manager.tab_bar.content.controls + if isinstance(c, ft.Container) and c.visible + ) assert visible_tabs_small < 11 # Simulate a larger screen, expecting all tabs to be visible tabs_manager.page.width = 1600 tabs_manager._update_tab_visibility() - visible_tabs_large = sum(1 for c in tabs_manager.tab_bar.content.controls if isinstance(c, ft.Container) and c.visible) + visible_tabs_large = sum( + 1 + for c in tabs_manager.tab_bar.content.controls + if isinstance(c, ft.Container) and c.visible + ) assert visible_tabs_large == 11 assert tabs_manager.overflow_menu is None diff --git a/tests/unit/test_ui.py b/tests/unit/test_ui.py index c7656d7..a6b60bb 100644 --- a/tests/unit/test_ui.py +++ b/tests/unit/test_ui.py @@ -29,7 +29,12 @@ class TestBuildUI: @patch("ren_browser.tabs.tabs.TabsManager") @patch("ren_browser.controls.shortcuts.Shortcuts") def test_build_ui_appbar_setup( - self, mock_shortcuts, mock_tabs, mock_fetcher, mock_announce_service, mock_page, + self, + mock_shortcuts, + mock_tabs, + mock_fetcher, + mock_announce_service, + mock_page, ): """Test that build_ui sets up the app bar correctly.""" mock_tab_manager = Mock() @@ -51,7 +56,12 @@ class TestBuildUI: @patch("ren_browser.tabs.tabs.TabsManager") @patch("ren_browser.controls.shortcuts.Shortcuts") def test_build_ui_drawer_setup( - self, mock_shortcuts, mock_tabs, mock_fetcher, mock_announce_service, mock_page, + self, + mock_shortcuts, + mock_tabs, + mock_fetcher, + mock_announce_service, + mock_page, ): """Test that build_ui sets up the drawer correctly.""" mock_tab_manager = Mock()