ruff fixes and formatting

This commit is contained in:
2025-11-16 00:46:42 -06:00
parent 3cddaeb2b9
commit 5ec677437e
13 changed files with 244 additions and 122 deletions

View File

@@ -93,40 +93,41 @@ async def main(page: Page):
def reload_reticulum(page: Page, on_complete=None): def reload_reticulum(page: Page, on_complete=None):
"""Hot reload Reticulum with updated configuration. """Hot reload Reticulum with updated configuration.
Args: Args:
page: Flet page instance page: Flet page instance
on_complete: Optional callback to run when reload is complete on_complete: Optional callback to run when reload is complete
""" """
def reload_thread(): def reload_thread():
import time import time
try: try:
global RNS_INSTANCE global RNS_INSTANCE
if RNS_INSTANCE: if RNS_INSTANCE:
try: try:
RNS_INSTANCE.exit_handler() RNS_INSTANCE.exit_handler()
print("RNS exit handler completed") print("RNS exit handler completed")
except Exception as e: except Exception as e:
print(f"Warning during RNS shutdown: {e}") print(f"Warning during RNS shutdown: {e}")
RNS.Reticulum._Reticulum__instance = None RNS.Reticulum._Reticulum__instance = None
RNS.Transport.destinations = [] RNS.Transport.destinations = []
RNS_INSTANCE = None RNS_INSTANCE = None
print("RNS instance cleared") print("RNS instance cleared")
time.sleep(0.5) time.sleep(0.5)
# Initialize storage system # Initialize storage system
storage = initialize_storage(page) storage = initialize_storage(page)
# Get Reticulum config directory from storage manager # Get Reticulum config directory from storage manager
config_dir = storage.get_reticulum_config_path() config_dir = storage.get_reticulum_config_path()
# Ensure any saved config is written to filesystem before RNS init # Ensure any saved config is written to filesystem before RNS init
try: try:
saved_config = storage.load_config() 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") config_file_path.write_text(saved_config, encoding="utf-8")
except Exception as e: except Exception as e:
print(f"Warning: Failed to write config file: {e}") print(f"Warning: Failed to write config file: {e}")
try: try:
# Re-initialize Reticulum # Re-initialize Reticulum
import ren_browser.logs import ren_browser.logs
ren_browser.logs.setup_rns_logging() ren_browser.logs.setup_rns_logging()
RNS_INSTANCE = RNS.Reticulum(str(config_dir)) RNS_INSTANCE = RNS.Reticulum(str(config_dir))
# Success # Success
if on_complete: if on_complete:
on_complete(True, None) on_complete(True, None)
except Exception as e: except Exception as e:
print(f"Error reinitializing Reticulum: {e}") print(f"Error reinitializing Reticulum: {e}")
if on_complete: if on_complete:
on_complete(False, str(e)) on_complete(False, str(e))
except Exception as e: except Exception as e:
print(f"Error during reload: {e}") print(f"Error during reload: {e}")
if on_complete: if on_complete:
on_complete(False, str(e)) on_complete(False, str(e))
page.run_thread(reload_thread) page.run_thread(reload_thread)
@@ -172,10 +174,17 @@ def run():
help="Select renderer (plaintext or micron)", help="Select renderer (plaintext or micron)",
) )
parser.add_argument( 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( 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( parser.add_argument(
"-c", "-c",

View File

@@ -22,7 +22,7 @@ def hex_to_rgb(hex_color: str) -> str:
def parse_micron_line(line: str) -> list: def parse_micron_line(line: str) -> list:
"""Parse a single line of micron markup into styled text spans. """Parse a single line of micron markup into styled text spans.
Returns list of dicts with 'text', 'bold', 'italic', 'underline', 'color', 'bgcolor'. Returns list of dicts with 'text', 'bold', 'italic', 'underline', 'color', 'bgcolor'.
""" """
spans = [] spans = []
@@ -37,14 +37,16 @@ def parse_micron_line(line: str) -> list:
while i < len(line): while i < len(line):
if line[i] == "`" and i + 1 < len(line): if line[i] == "`" and i + 1 < len(line):
if current_text: if current_text:
spans.append({ spans.append(
"text": current_text, {
"bold": bold, "text": current_text,
"italic": italic, "bold": bold,
"underline": underline, "italic": italic,
"color": color, "underline": underline,
"bgcolor": bgcolor, "color": color,
}) "bgcolor": bgcolor,
}
)
current_text = "" current_text = ""
tag = line[i + 1] tag = line[i + 1]
@@ -59,13 +61,13 @@ def parse_micron_line(line: str) -> list:
underline = not underline underline = not underline
i += 2 i += 2
elif tag == "F" and i + 5 <= len(line): 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 i += 5
elif tag == "f": elif tag == "f":
color = None color = None
i += 2 i += 2
elif tag == "B" and i + 5 <= len(line): 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 i += 5
elif tag == "b": elif tag == "b":
bgcolor = None bgcolor = None
@@ -85,21 +87,23 @@ def parse_micron_line(line: str) -> list:
i += 1 i += 1
if current_text: if current_text:
spans.append({ spans.append(
"text": current_text, {
"bold": bold, "text": current_text,
"italic": italic, "bold": bold,
"underline": underline, "italic": italic,
"color": color, "underline": underline,
"bgcolor": bgcolor, "color": color,
}) "bgcolor": bgcolor,
}
)
return spans return spans
def render_micron(content: str, on_link_click=None) -> ft.Control: def render_micron(content: str, on_link_click=None) -> ft.Control:
"""Render micron markup content to a Flet control. """Render micron markup content to a Flet control.
Falls back to plaintext renderer if parsing fails. Falls back to plaintext renderer if parsing fails.
Args: 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: def _render_micron_internal(content: str, on_link_click=None) -> ft.Control:
"""Internal micron rendering implementation. """Internal micron rendering implementation.
Args: Args:
content: Micron markup content to render. content: Micron markup content to render.
on_link_click: Optional callback function(url) called when a link is clicked. 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: if "`[" in line:
row_controls = [] row_controls = []
remaining = line
last_end = 0 last_end = 0
for link_match in re.finditer(r"`\[([^`]*)`([^\]]*)\]", line): for link_match in re.finditer(r"`\[([^`]*)`([^\]]*)\]", line):
before = line[last_end:link_match.start()] before = line[last_end : link_match.start()]
if before: if before:
before_spans = parse_micron_line(before) before_spans = parse_micron_line(before)
for span in before_spans: for span in before_spans:
row_controls.append(create_text_span(span)) row_controls.append(create_text_span(span))
label = link_match.group(1) label = link_match.group(1)
url = link_match.group(2) url = link_match.group(2)
def make_link_handler(link_url): def make_link_handler(link_url):
def handler(e): def handler(e):
if on_link_click: if on_link_click:
on_link_click(link_url) on_link_click(link_url)
return handler return handler
row_controls.append( 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), on_click=make_link_handler(url),
), ),
) )
last_end = link_match.end() last_end = link_match.end()
after = line[last_end:] after = line[last_end:]
if after: if after:
after_spans = parse_micron_line(after) after_spans = parse_micron_line(after)

View File

@@ -51,9 +51,7 @@ class StorageManager:
elif "APPDATA" in os.environ: # Windows elif "APPDATA" in os.environ: # Windows
storage_dir = pathlib.Path(os.environ["APPDATA"]) / "ren_browser" storage_dir = pathlib.Path(os.environ["APPDATA"]) / "ren_browser"
elif "XDG_CONFIG_HOME" in os.environ: # Linux XDG standard elif "XDG_CONFIG_HOME" in os.environ: # Linux XDG standard
storage_dir = ( storage_dir = pathlib.Path(os.environ["XDG_CONFIG_HOME"]) / "ren_browser"
pathlib.Path(os.environ["XDG_CONFIG_HOME"]) / "ren_browser"
)
else: else:
storage_dir = pathlib.Path.home() / ".ren_browser" storage_dir = pathlib.Path.home() / ".ren_browser"
@@ -126,7 +124,8 @@ class StorageManager:
if self.page and hasattr(self.page, "client_storage"): 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", config_content)
self.page.client_storage.set( 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 return True
@@ -193,7 +192,8 @@ class StorageManager:
if self.page and hasattr(self.page, "client_storage"): if self.page and hasattr(self.page, "client_storage"):
self.page.client_storage.set( self.page.client_storage.set(
"ren_browser_bookmarks", json.dumps(bookmarks), "ren_browser_bookmarks",
json.dumps(bookmarks),
) )
return True return True
@@ -258,7 +258,9 @@ class StorageManager:
json.dump(settings, f, indent=2) json.dump(settings, f, indent=2)
if self.page and hasattr(self.page, "client_storage"): 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 return True
except Exception: except Exception:
@@ -270,7 +272,7 @@ class StorageManager:
"horizontal_scroll": False, "horizontal_scroll": False,
"page_bgcolor": "#000000", "page_bgcolor": "#000000",
} }
try: try:
settings_path = self._storage_dir / "settings.json" settings_path = self._storage_dir / "settings.json"
if settings_path.exists(): if settings_path.exists():

View File

@@ -32,10 +32,10 @@ class TabsManager:
self.page = page self.page = page
self.page.on_resize = self._on_resize self.page.on_resize = self._on_resize
self.manager = SimpleNamespace(tabs=[], index=0) self.manager = SimpleNamespace(tabs=[], index=0)
storage = get_storage_manager(page) storage = get_storage_manager(page)
self.settings = storage.load_app_settings() self.settings = storage.load_app_settings()
self.tab_bar = ft.Container( self.tab_bar = ft.Container(
content=ft.Row( content=ft.Row(
spacing=6, spacing=6,
@@ -60,7 +60,9 @@ class TabsManager:
self._on_tab_go(None, 0) self._on_tab_go(None, 0)
default_content = ( 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" if app_module.RENDERER == "micron"
else render_plaintext("Welcome to Ren Browser") else render_plaintext("Welcome to Ren Browser")
) )
@@ -96,16 +98,16 @@ class TabsManager:
self.settings = settings self.settings = settings
bgcolor = settings.get("page_bgcolor", "#000000") bgcolor = settings.get("page_bgcolor", "#000000")
self.content_container.bgcolor = bgcolor self.content_container.bgcolor = bgcolor
horizontal_scroll = settings.get("horizontal_scroll", False) horizontal_scroll = settings.get("horizontal_scroll", False)
scroll_mode = ft.ScrollMode.ALWAYS if horizontal_scroll else ft.ScrollMode.AUTO scroll_mode = ft.ScrollMode.ALWAYS if horizontal_scroll else ft.ScrollMode.AUTO
for tab in self.manager.tabs: for tab in self.manager.tabs:
if "content" in tab and hasattr(tab["content"], "scroll"): if "content" in tab and hasattr(tab["content"], "scroll"):
tab["content"].scroll = scroll_mode tab["content"].scroll = scroll_mode
if "content_control" in tab and hasattr(tab["content_control"], "scroll"): if "content_control" in tab and hasattr(tab["content_control"], "scroll"):
tab["content_control"].scroll = scroll_mode tab["content_control"].scroll = scroll_mode
if self.content_container.content: if self.content_container.content:
self.content_container.content.update() self.content_container.content.update()
self.page.update() self.page.update()
@@ -127,7 +129,9 @@ class TabsManager:
cumulative_width = 0 cumulative_width = 0
visible_tabs_count = 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): for i, tab in enumerate(self.manager.tabs):
estimated_width = len(tab["title"]) * 10 + 32 + self.tab_bar.content.spacing estimated_width = len(tab["title"]) * 10 + 32 + self.tab_bar.content.spacing
@@ -183,7 +187,7 @@ class TabsManager:
content_control = content content_control = content
horizontal_scroll = self.settings.get("horizontal_scroll", False) horizontal_scroll = self.settings.get("horizontal_scroll", False)
scroll_mode = ft.ScrollMode.ALWAYS if horizontal_scroll else ft.ScrollMode.AUTO scroll_mode = ft.ScrollMode.ALWAYS if horizontal_scroll else ft.ScrollMode.AUTO
tab_content = ft.Column( tab_content = ft.Column(
expand=True, expand=True,
scroll=scroll_mode, scroll=scroll_mode,
@@ -254,13 +258,17 @@ class TabsManager:
return return
idx = self.manager.index 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] control_to_remove = tab_containers[idx]
self.manager.tabs.pop(idx) self.manager.tabs.pop(idx)
self.tab_bar.content.controls.remove(control_to_remove) 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): for i, control in enumerate(updated_tab_containers):
control.on_click = lambda e, i=i: self.select_tab(i) # type: ignore control.on_click = lambda e, i=i: self.select_tab(i) # type: ignore
@@ -278,7 +286,9 @@ class TabsManager:
""" """
self.manager.index = idx 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): for i, control in enumerate(tab_containers):
if i == idx: if i == idx:
control.bgcolor = ft.Colors.BLUE_900 control.bgcolor = ft.Colors.BLUE_900
@@ -296,7 +306,7 @@ class TabsManager:
url = tab["url_field"].value.strip() url = tab["url_field"].value.strip()
if not url: if not url:
return return
placeholder_text = f"Loading content for {url}..." placeholder_text = f"Loading content for {url}..."
import ren_browser.app as app_module import ren_browser.app as app_module
@@ -330,12 +340,12 @@ class TabsManager:
def fetch_and_update(): def fetch_and_update():
parts = url.split(":", 1) parts = url.split(":", 1)
if len(parts) != 2: 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 = "" page_path = ""
else: else:
dest_hash = parts[0] dest_hash = parts[0]
page_path = parts[1] if parts[1].startswith("/") else f"/{parts[1]}" page_path = parts[1] if parts[1].startswith("/") else f"/{parts[1]}"
req = PageRequest(destination_hash=dest_hash, page_path=page_path) req = PageRequest(destination_hash=dest_hash, page_path=page_path)
page_fetcher = PageFetcher() page_fetcher = PageFetcher()
try: try:
@@ -343,7 +353,7 @@ class TabsManager:
except Exception as ex: except Exception as ex:
app_module.log_error(str(ex)) app_module.log_error(str(ex))
result = f"Error: {ex}" result = f"Error: {ex}"
try: try:
tab = self.manager.tabs[idx] tab = self.manager.tabs[idx]
except IndexError: except IndexError:
@@ -353,7 +363,7 @@ class TabsManager:
new_control = render_micron(result, on_link_click=handle_link_click) new_control = render_micron(result, on_link_click=handle_link_click)
else: else:
new_control = render_plaintext(result) new_control = render_plaintext(result)
tab["content_control"] = new_control tab["content_control"] = new_control
tab["content"].controls[0] = new_control tab["content"].controls[0] = new_control
if self.manager.index == idx: if self.manager.index == idx:

View File

@@ -77,8 +77,15 @@ def open_settings_tab(page: ft.Page, tab_manager):
snack = ft.SnackBar( snack = ft.SnackBar(
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20), ft.Icon(
ft.Text("Configuration saved! Restart app to apply changes.", color=ft.Colors.WHITE), 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, tight=True,
), ),
@@ -93,7 +100,9 @@ def open_settings_tab(page: ft.Page, tab_manager):
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), 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, tight=True,
), ),
@@ -127,7 +136,9 @@ def open_settings_tab(page: ft.Page, tab_manager):
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), 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, tight=True,
), ),
@@ -138,11 +149,16 @@ def open_settings_tab(page: ft.Page, tab_manager):
snack.open = True snack.open = True
page.update() page.update()
return return
loading_snack = ft.SnackBar( loading_snack = ft.SnackBar(
content=ft.Row( content=ft.Row(
controls=[ 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), ft.Text("Reloading Reticulum...", color=ft.Colors.WHITE),
], ],
tight=True, tight=True,
@@ -153,17 +169,24 @@ def open_settings_tab(page: ft.Page, tab_manager):
page.overlay.append(loading_snack) page.overlay.append(loading_snack)
loading_snack.open = True loading_snack.open = True
page.update() page.update()
def on_reload_complete(success, error): def on_reload_complete(success, error):
loading_snack.open = False loading_snack.open = False
page.update() page.update()
if success: if success:
snack = ft.SnackBar( snack = ft.SnackBar(
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20), ft.Icon(
ft.Text("Reticulum reloaded successfully!", color=ft.Colors.WHITE), ft.Icons.CHECK_CIRCLE,
color=ft.Colors.GREEN_400,
size=20,
),
ft.Text(
"Reticulum reloaded successfully!",
color=ft.Colors.WHITE,
),
], ],
tight=True, tight=True,
), ),
@@ -174,8 +197,12 @@ def open_settings_tab(page: ft.Page, tab_manager):
snack = ft.SnackBar( snack = ft.SnackBar(
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), ft.Icon(
ft.Text(f"Reload failed: {error}", color=ft.Colors.WHITE), ft.Icons.ERROR, color=ft.Colors.RED_400, size=20
),
ft.Text(
f"Reload failed: {error}", color=ft.Colors.WHITE
),
], ],
tight=True, tight=True,
), ),
@@ -185,10 +212,11 @@ def open_settings_tab(page: ft.Page, tab_manager):
page.overlay.append(snack) page.overlay.append(snack)
snack.open = True snack.open = True
page.update() page.update()
import ren_browser.app as app_module import ren_browser.app as app_module
app_module.reload_reticulum(page, on_reload_complete) app_module.reload_reticulum(page, on_reload_complete)
except Exception as ex: except Exception as ex:
snack = ft.SnackBar( snack = ft.SnackBar(
content=ft.Row( content=ft.Row(
@@ -217,8 +245,15 @@ def open_settings_tab(page: ft.Page, tab_manager):
snack = ft.SnackBar( snack = ft.SnackBar(
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20), ft.Icon(
ft.Text("Appearance settings saved and applied!", color=ft.Colors.WHITE), ft.Icons.CHECK_CIRCLE,
color=ft.Colors.GREEN_400,
size=20,
),
ft.Text(
"Appearance settings saved and applied!",
color=ft.Colors.WHITE,
),
], ],
tight=True, tight=True,
), ),
@@ -233,7 +268,10 @@ def open_settings_tab(page: ft.Page, tab_manager):
content=ft.Row( content=ft.Row(
controls=[ controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20), 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, tight=True,
), ),
@@ -410,7 +448,14 @@ def open_settings_tab(page: ft.Page, tab_manager):
nav_card = ft.Container( nav_card = ft.Container(
content=ft.Row( 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, spacing=8,
wrap=True, wrap=True,
), ),

View File

@@ -84,7 +84,9 @@ def build_ui(page: Page):
tab_manager._on_tab_go(None, idx) tab_manager._on_tab_go(None, idx)
if req.page_path.endswith(".mu"): 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: else:
new_control = render_plaintext(result) new_control = render_plaintext(result)
tab["content_control"] = new_control tab["content_control"] = new_control

View File

@@ -62,7 +62,9 @@ def sample_page_request():
from ren_browser.pages.page_request import PageRequest from ren_browser.pages.page_request import PageRequest
return 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,
) )

View File

@@ -19,7 +19,9 @@ class TestAnnounce:
def test_announce_with_none_display_name(self): def test_announce_with_none_display_name(self):
"""Test Announce creation with None display name.""" """Test Announce creation with None display name."""
announce = Announce( announce = Announce(
destination_hash="1234567890abcdef", display_name=None, timestamp=1234567890, destination_hash="1234567890abcdef",
display_name=None,
timestamp=1234567890,
) )
assert announce.destination_hash == "1234567890abcdef" assert announce.destination_hash == "1234567890abcdef"

View File

@@ -59,7 +59,9 @@ class TestLogsModule:
assert len(logs.RET_LOGS) == 1 assert len(logs.RET_LOGS) == 1
assert logs.RET_LOGS[0] == "[2023-01-01T12:00:00] Test RNS message" assert logs.RET_LOGS[0] == "[2023-01-01T12:00:00] Test RNS message"
logs._original_rns_log.assert_called_once_with( logs._original_rns_log.assert_called_once_with(
"Test RNS message", "arg1", kwarg1="value1", "Test RNS message",
"arg1",
kwarg1="value1",
) )
assert result == "original_result" assert result == "original_result"

View File

@@ -7,7 +7,8 @@ class TestPageRequest:
def test_page_request_creation(self): def test_page_request_creation(self):
"""Test basic PageRequest creation.""" """Test basic PageRequest creation."""
request = PageRequest( request = PageRequest(
destination_hash="1234567890abcdef", page_path="/page/index.mu", destination_hash="1234567890abcdef",
page_path="/page/index.mu",
) )
assert request.destination_hash == "1234567890abcdef" assert request.destination_hash == "1234567890abcdef"

View File

@@ -51,11 +51,14 @@ class TestStorageManager:
with ( with (
patch("os.name", "posix"), patch("os.name", "posix"),
patch.dict( 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 = StorageManager()
storage._storage_dir = storage._get_storage_directory() storage._storage_dir = storage._get_storage_directory()
@@ -71,7 +74,11 @@ class TestStorageManager:
"""Test storage directory detection for Android with ANDROID_DATA.""" """Test storage directory detection for Android with ANDROID_DATA."""
with ( with (
patch("os.name", "posix"), 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"), patch("pathlib.Path.mkdir"),
): ):
with patch( with patch(
@@ -86,7 +93,11 @@ class TestStorageManager:
"""Test storage directory detection for Android with EXTERNAL_STORAGE.""" """Test storage directory detection for Android with EXTERNAL_STORAGE."""
with ( with (
patch("os.name", "posix"), 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"), patch("pathlib.Path.mkdir"),
): ):
with patch( with patch(
@@ -102,9 +113,10 @@ class TestStorageManager:
with ( with (
patch("os.name", "posix"), patch("os.name", "posix"),
patch.dict("os.environ", {"ANDROID_ROOT": "/system"}, clear=True), patch.dict("os.environ", {"ANDROID_ROOT": "/system"}, clear=True),
patch("pathlib.Path.mkdir"),patch( patch("pathlib.Path.mkdir"),
"ren_browser.storage.storage.StorageManager._ensure_storage_directory", patch(
), "ren_browser.storage.storage.StorageManager._ensure_storage_directory",
),
): ):
storage = StorageManager() storage = StorageManager()
storage._storage_dir = storage._get_storage_directory() storage._storage_dir = storage._get_storage_directory()
@@ -169,7 +181,8 @@ class TestStorageManager:
assert result is True assert result is True
mock_page.client_storage.set.assert_called_with( mock_page.client_storage.set.assert_called_with(
"ren_browser_config", config_content, "ren_browser_config",
config_content,
) )
def test_save_config_fallback(self): def test_save_config_fallback(self):
@@ -182,13 +195,16 @@ class TestStorageManager:
storage._storage_dir = Path(temp_dir) storage._storage_dir = Path(temp_dir)
# Mock the reticulum config path to use temp dir and cause failure # Mock the reticulum config path to use temp dir and cause failure
with patch.object( with (
storage, patch.object(
"get_reticulum_config_path", storage,
return_value=Path(temp_dir) / "reticulum", "get_reticulum_config_path",
), patch( return_value=Path(temp_dir) / "reticulum",
"pathlib.Path.write_text", ),
side_effect=PermissionError("Access denied"), patch(
"pathlib.Path.write_text",
side_effect=PermissionError("Access denied"),
),
): ):
config_content = "test config content" config_content = "test config content"
result = storage.save_config(config_content) result = storage.save_config(config_content)
@@ -196,7 +212,8 @@ class TestStorageManager:
assert result is True assert result is True
# Check that the config was set to client storage # Check that the config was set to client storage
mock_page.client_storage.set.assert_any_call( 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 # Verify that client storage was called at least once
assert mock_page.client_storage.set.call_count >= 1 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: with patch.object(StorageManager, "_get_storage_directory") as mock_get_dir:
mock_get_dir.return_value = Path("/nonexistent/path") mock_get_dir.return_value = Path("/nonexistent/path")
with patch( with (
"pathlib.Path.mkdir", patch(
side_effect=[PermissionError("Access denied"), None], "pathlib.Path.mkdir",
), patch("tempfile.gettempdir", return_value="/tmp"): side_effect=[PermissionError("Access denied"), None],
),
patch("tempfile.gettempdir", return_value="/tmp"),
):
storage = StorageManager() storage = StorageManager()
expected_fallback = Path("/tmp") / "ren_browser" expected_fallback = Path("/tmp") / "ren_browser"
@@ -444,7 +464,8 @@ class TestStorageManagerEdgeCases:
storage = StorageManager() storage = StorageManager()
with patch( 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") test_path = Path("/mock/path")
result = storage._is_writable(test_path) result = storage._is_writable(test_path)

View File

@@ -105,7 +105,9 @@ class TestTabsManager:
"""Test that selecting a tab updates background colors correctly.""" """Test that selecting a tab updates background colors correctly."""
tabs_manager._add_tab_internal("Tab 2", Mock()) 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) tabs_manager.select_tab(1)
@@ -195,7 +197,9 @@ class TestTabsManager:
"""Test that tab click handlers are properly set.""" """Test that tab click handlers are properly set."""
tabs_manager._add_tab_internal("Tab 2", Mock()) 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): for i, control in enumerate(tab_controls):
assert control.on_click is not None assert control.on_click is not None
@@ -240,7 +244,7 @@ class TestTabsManager:
def test_adaptive_overflow_behavior(self, tabs_manager): def test_adaptive_overflow_behavior(self, tabs_manager):
"""Test that the overflow menu adapts to tab changes.""" """Test that the overflow menu adapts to tab changes."""
# With page width at 800, add enough tabs that some should overflow. # 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()) tabs_manager._add_tab_internal(f"Tab {i + 2}", Mock())
# Check that an overflow menu exists # Check that an overflow menu exists
@@ -249,13 +253,21 @@ class TestTabsManager:
# Simulate a smaller screen, expecting more tabs to overflow # Simulate a smaller screen, expecting more tabs to overflow
tabs_manager.page.width = 400 tabs_manager.page.width = 400
tabs_manager._update_tab_visibility() 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 assert visible_tabs_small < 11
# Simulate a larger screen, expecting all tabs to be visible # Simulate a larger screen, expecting all tabs to be visible
tabs_manager.page.width = 1600 tabs_manager.page.width = 1600
tabs_manager._update_tab_visibility() 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 visible_tabs_large == 11
assert tabs_manager.overflow_menu is None assert tabs_manager.overflow_menu is None

View File

@@ -29,7 +29,12 @@ class TestBuildUI:
@patch("ren_browser.tabs.tabs.TabsManager") @patch("ren_browser.tabs.tabs.TabsManager")
@patch("ren_browser.controls.shortcuts.Shortcuts") @patch("ren_browser.controls.shortcuts.Shortcuts")
def test_build_ui_appbar_setup( 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.""" """Test that build_ui sets up the app bar correctly."""
mock_tab_manager = Mock() mock_tab_manager = Mock()
@@ -51,7 +56,12 @@ class TestBuildUI:
@patch("ren_browser.tabs.tabs.TabsManager") @patch("ren_browser.tabs.tabs.TabsManager")
@patch("ren_browser.controls.shortcuts.Shortcuts") @patch("ren_browser.controls.shortcuts.Shortcuts")
def test_build_ui_drawer_setup( 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.""" """Test that build_ui sets up the drawer correctly."""
mock_tab_manager = Mock() mock_tab_manager = Mock()