Add basic test suite
This commit is contained in:
17
pytest.ini
Normal file
17
pytest.ini
Normal file
@@ -0,0 +1,17 @@
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
--verbose
|
||||
--tb=short
|
||||
--strict-markers
|
||||
--disable-warnings
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
slow: Slow running tests
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
ignore::PendingDeprecationWarning
|
||||
43
tests/README.md
Normal file
43
tests/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Ren Browser Basic Test Suite
|
||||
|
||||
## To-Do
|
||||
|
||||
- Security tests
|
||||
- Performance tests
|
||||
- Proper RNS support and testing
|
||||
- Micron Renderer tests (when implemented)
|
||||
|
||||
This directory contains comprehensive tests for the Ren Browser application.
|
||||
|
||||
## Test Structure
|
||||
|
||||
- `unit/` - Unit tests for individual components
|
||||
- `integration/` - Integration tests for component interactions
|
||||
- `conftest.py` - Shared test fixtures and configuration
|
||||
|
||||
## Running Tests
|
||||
|
||||
### All Tests
|
||||
```bash
|
||||
poetry run pytest
|
||||
```
|
||||
|
||||
### Unit Tests Only
|
||||
```bash
|
||||
poetry run pytest tests/unit/
|
||||
```
|
||||
|
||||
### Integration Tests Only
|
||||
```bash
|
||||
poetry run pytest tests/integration/
|
||||
```
|
||||
|
||||
### Specific Test File
|
||||
```bash
|
||||
poetry run pytest tests/unit/test_app.py
|
||||
```
|
||||
|
||||
### With Coverage
|
||||
```bash
|
||||
poetry run pytest --cov=ren_browser --cov-report=html
|
||||
```
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
85
tests/conftest.py
Normal file
85
tests/conftest.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from unittest.mock import MagicMock, Mock
|
||||
|
||||
import flet as ft
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_page():
|
||||
"""Create a mock Flet page for testing."""
|
||||
page = Mock(spec=ft.Page)
|
||||
page.add = Mock()
|
||||
page.update = Mock()
|
||||
page.run_thread = Mock()
|
||||
page.controls = []
|
||||
page.theme_mode = ft.ThemeMode.DARK
|
||||
page.appbar = Mock()
|
||||
page.drawer = Mock()
|
||||
page.window = Mock()
|
||||
page.width = 1024
|
||||
page.snack_bar = None
|
||||
page.on_resized = None
|
||||
page.on_keyboard_event = None
|
||||
return page
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_rns():
|
||||
"""Mock RNS module to avoid network dependencies in tests."""
|
||||
mock_rns = MagicMock()
|
||||
mock_rns.Reticulum = Mock()
|
||||
mock_rns.Transport = Mock()
|
||||
mock_rns.Identity = Mock()
|
||||
mock_rns.Destination = Mock()
|
||||
mock_rns.Link = Mock()
|
||||
mock_rns.log = Mock()
|
||||
|
||||
# Mock at the module level for all imports
|
||||
import sys
|
||||
sys.modules["RNS"] = mock_rns
|
||||
|
||||
yield mock_rns
|
||||
|
||||
# Cleanup
|
||||
if "RNS" in sys.modules:
|
||||
del sys.modules["RNS"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_announce_data():
|
||||
"""Sample announce data for testing."""
|
||||
return {
|
||||
"destination_hash": "1234567890abcdef",
|
||||
"display_name": "Test Node",
|
||||
"timestamp": 1234567890
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_page_request():
|
||||
"""Sample page request for testing."""
|
||||
from ren_browser.pages.page_request import PageRequest
|
||||
return PageRequest(
|
||||
destination_hash="1234567890abcdef",
|
||||
page_path="/page/index.mu",
|
||||
field_data=None
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_storage_manager():
|
||||
"""Mock storage manager for testing."""
|
||||
mock_storage = Mock()
|
||||
mock_storage.load_config.return_value = "test config content"
|
||||
mock_storage.save_config.return_value = True
|
||||
mock_storage.get_config_path.return_value = Mock()
|
||||
mock_storage.get_reticulum_config_path.return_value = Mock()
|
||||
mock_storage.get_storage_info.return_value = {
|
||||
'storage_dir': '/mock/storage',
|
||||
'config_path': '/mock/storage/config.txt',
|
||||
'reticulum_config_path': '/mock/storage/reticulum',
|
||||
'storage_dir_exists': True,
|
||||
'storage_dir_writable': True,
|
||||
'has_client_storage': True,
|
||||
}
|
||||
return mock_storage
|
||||
0
tests/integration/__init__.py
Normal file
0
tests/integration/__init__.py
Normal file
98
tests/integration/test_app_integration.py
Normal file
98
tests/integration/test_app_integration.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from ren_browser import app
|
||||
|
||||
|
||||
class TestAppIntegration:
|
||||
"""Integration tests for the main app functionality."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_function_structure(self):
|
||||
"""Test that the main function has the expected structure."""
|
||||
mock_page = Mock()
|
||||
mock_page.add = Mock()
|
||||
mock_page.update = Mock()
|
||||
mock_page.run_thread = Mock()
|
||||
mock_page.controls = Mock()
|
||||
mock_page.controls.clear = Mock()
|
||||
|
||||
await app.main(mock_page)
|
||||
|
||||
# Verify that the main function sets up the loading screen
|
||||
mock_page.add.assert_called_once()
|
||||
mock_page.update.assert_called()
|
||||
mock_page.run_thread.assert_called_once()
|
||||
|
||||
def test_entry_points_exist(self):
|
||||
"""Test that all expected entry points exist and are callable."""
|
||||
entry_points = [
|
||||
"run", "web", "android", "ios",
|
||||
"run_dev", "web_dev", "android_dev", "ios_dev"
|
||||
]
|
||||
|
||||
for entry_point in entry_points:
|
||||
assert hasattr(app, entry_point)
|
||||
assert callable(getattr(app, entry_point))
|
||||
|
||||
def test_renderer_global_exists(self):
|
||||
"""Test that the RENDERER global variable exists."""
|
||||
assert hasattr(app, "RENDERER")
|
||||
assert app.RENDERER in ["plaintext", "micron"]
|
||||
|
||||
def test_app_module_imports(self):
|
||||
"""Test that required modules can be imported."""
|
||||
# Test that the app module imports work
|
||||
import ren_browser.app
|
||||
import ren_browser.ui.ui
|
||||
|
||||
# Verify key functions exist
|
||||
assert hasattr(ren_browser.app, "main")
|
||||
assert hasattr(ren_browser.app, "run")
|
||||
assert hasattr(ren_browser.ui.ui, "build_ui")
|
||||
|
||||
|
||||
class TestModuleIntegration:
|
||||
"""Integration tests for module interactions."""
|
||||
|
||||
def test_renderer_modules_exist(self):
|
||||
"""Test that renderer modules can be imported."""
|
||||
from ren_browser.renderer import micron, plaintext
|
||||
|
||||
assert hasattr(plaintext, "render_plaintext")
|
||||
assert hasattr(micron, "render_micron")
|
||||
assert callable(plaintext.render_plaintext)
|
||||
assert callable(micron.render_micron)
|
||||
|
||||
def test_data_classes_exist(self):
|
||||
"""Test that data classes can be imported and used."""
|
||||
from ren_browser.announces.announces import Announce
|
||||
from ren_browser.pages.page_request import PageRequest
|
||||
|
||||
# Test Announce creation
|
||||
announce = Announce("hash1", "name1", 1000)
|
||||
assert announce.destination_hash == "hash1"
|
||||
|
||||
# Test PageRequest creation
|
||||
request = PageRequest("hash2", "/path")
|
||||
assert request.destination_hash == "hash2"
|
||||
|
||||
def test_logs_module_integration(self):
|
||||
"""Test that logs module integrates correctly."""
|
||||
from ren_browser import logs
|
||||
|
||||
# Test that log functions exist
|
||||
assert hasattr(logs, "log_error")
|
||||
assert hasattr(logs, "log_app")
|
||||
assert hasattr(logs, "log_ret")
|
||||
|
||||
# Test that log storage exists
|
||||
assert hasattr(logs, "APP_LOGS")
|
||||
assert hasattr(logs, "ERROR_LOGS")
|
||||
assert hasattr(logs, "RET_LOGS")
|
||||
|
||||
# Test that they are lists
|
||||
assert isinstance(logs.APP_LOGS, list)
|
||||
assert isinstance(logs.ERROR_LOGS, list)
|
||||
assert isinstance(logs.RET_LOGS, list)
|
||||
0
tests/unit/__init__.py
Normal file
0
tests/unit/__init__.py
Normal file
54
tests/unit/test_announces.py
Normal file
54
tests/unit/test_announces.py
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
from ren_browser.announces.announces import Announce
|
||||
|
||||
|
||||
class TestAnnounce:
|
||||
"""Test cases for the Announce dataclass."""
|
||||
|
||||
def test_announce_creation(self):
|
||||
"""Test basic Announce creation."""
|
||||
announce = Announce(
|
||||
destination_hash="1234567890abcdef",
|
||||
display_name="Test Node",
|
||||
timestamp=1234567890
|
||||
)
|
||||
|
||||
assert announce.destination_hash == "1234567890abcdef"
|
||||
assert announce.display_name == "Test Node"
|
||||
assert announce.timestamp == 1234567890
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
assert announce.destination_hash == "1234567890abcdef"
|
||||
assert announce.display_name is None
|
||||
assert announce.timestamp == 1234567890
|
||||
|
||||
class TestAnnounceService:
|
||||
"""Test cases for the AnnounceService class.
|
||||
|
||||
Note: These tests are simplified due to complex RNS integration.
|
||||
Full integration tests will be added in the future.
|
||||
"""
|
||||
|
||||
def test_announce_dataclass_functionality(self):
|
||||
"""Test that the Announce dataclass works correctly."""
|
||||
# Test that we can create and use Announce objects
|
||||
announce1 = Announce("hash1", "Node1", 1000)
|
||||
announce2 = Announce("hash2", None, 2000)
|
||||
|
||||
# Test that announces can be stored in lists
|
||||
announces = [announce1, announce2]
|
||||
assert len(announces) == 2
|
||||
assert announces[0].display_name == "Node1"
|
||||
assert announces[1].display_name is None
|
||||
|
||||
# Test that we can filter announces by hash
|
||||
filtered = [ann for ann in announces if ann.destination_hash == "hash1"]
|
||||
assert len(filtered) == 1
|
||||
assert filtered[0].display_name == "Node1"
|
||||
139
tests/unit/test_app.py
Normal file
139
tests/unit/test_app.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import flet as ft
|
||||
import pytest
|
||||
|
||||
from ren_browser import app
|
||||
|
||||
|
||||
class TestApp:
|
||||
"""Test cases for the main app module."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_initializes_loader(self, mock_page, mock_rns):
|
||||
"""Test that main function initializes with loading screen."""
|
||||
with patch("ren_browser.ui.ui.build_ui"):
|
||||
await app.main(mock_page)
|
||||
|
||||
mock_page.add.assert_called_once()
|
||||
mock_page.update.assert_called()
|
||||
mock_page.run_thread.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_function_structure(self, mock_page, mock_rns):
|
||||
"""Test that main function sets up the expected structure."""
|
||||
await app.main(mock_page)
|
||||
|
||||
# Verify that main function adds content and sets up threading
|
||||
mock_page.add.assert_called_once()
|
||||
mock_page.update.assert_called()
|
||||
mock_page.run_thread.assert_called_once()
|
||||
|
||||
# Verify that a function was passed to run_thread
|
||||
init_function = mock_page.run_thread.call_args[0][0]
|
||||
assert callable(init_function)
|
||||
|
||||
def test_run_with_default_args(self, mock_rns):
|
||||
"""Test run function with default arguments."""
|
||||
with patch("sys.argv", ["ren-browser"]), \
|
||||
patch("flet.app") as mock_ft_app:
|
||||
|
||||
app.run()
|
||||
|
||||
mock_ft_app.assert_called_once()
|
||||
args = mock_ft_app.call_args
|
||||
assert args[0][0] == app.main
|
||||
|
||||
def test_run_with_web_flag(self, mock_rns):
|
||||
"""Test run function with web flag."""
|
||||
with patch("sys.argv", ["ren-browser", "--web"]), \
|
||||
patch("flet.app") as mock_ft_app:
|
||||
|
||||
app.run()
|
||||
|
||||
mock_ft_app.assert_called_once()
|
||||
args, kwargs = mock_ft_app.call_args
|
||||
assert args[0] == app.main
|
||||
assert kwargs["view"] == ft.AppView.WEB_BROWSER
|
||||
|
||||
def test_run_with_web_and_port(self, mock_rns):
|
||||
"""Test run function with web flag and custom port."""
|
||||
with patch("sys.argv", ["ren-browser", "--web", "--port", "8080"]), \
|
||||
patch("flet.app") as mock_ft_app:
|
||||
|
||||
app.run()
|
||||
|
||||
mock_ft_app.assert_called_once()
|
||||
args, kwargs = mock_ft_app.call_args
|
||||
assert args[0] == app.main
|
||||
assert kwargs["view"] == ft.AppView.WEB_BROWSER
|
||||
assert kwargs["port"] == 8080
|
||||
|
||||
def test_run_with_renderer_flag(self, mock_rns):
|
||||
"""Test run function with renderer selection."""
|
||||
with patch("sys.argv", ["ren-browser", "--renderer", "micron"]), \
|
||||
patch("flet.app"):
|
||||
|
||||
app.run()
|
||||
|
||||
assert app.RENDERER == "micron"
|
||||
|
||||
def test_web_function(self, mock_rns):
|
||||
"""Test web() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.web()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main, view=ft.AppView.WEB_BROWSER)
|
||||
|
||||
def test_android_function(self, mock_rns):
|
||||
"""Test android() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.android()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main, view=ft.AppView.FLET_APP_WEB)
|
||||
|
||||
def test_ios_function(self, mock_rns):
|
||||
"""Test ios() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.ios()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main, view=ft.AppView.FLET_APP_WEB)
|
||||
|
||||
def test_run_dev_function(self, mock_rns):
|
||||
"""Test run_dev() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.run_dev()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main)
|
||||
|
||||
def test_web_dev_function(self, mock_rns):
|
||||
"""Test web_dev() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.web_dev()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main, view=ft.AppView.WEB_BROWSER)
|
||||
|
||||
def test_android_dev_function(self, mock_rns):
|
||||
"""Test android_dev() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.android_dev()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main, view=ft.AppView.FLET_APP_WEB)
|
||||
|
||||
def test_ios_dev_function(self, mock_rns):
|
||||
"""Test ios_dev() entry point function."""
|
||||
with patch("flet.app") as mock_ft_app:
|
||||
app.ios_dev()
|
||||
|
||||
mock_ft_app.assert_called_once_with(app.main, view=ft.AppView.FLET_APP_WEB)
|
||||
|
||||
def test_global_renderer_setting(self):
|
||||
"""Test that RENDERER global is properly updated."""
|
||||
original_renderer = app.RENDERER
|
||||
|
||||
with patch("sys.argv", ["ren-browser", "--renderer", "micron"]), \
|
||||
patch("flet.app"):
|
||||
app.run()
|
||||
assert app.RENDERER == "micron"
|
||||
|
||||
app.RENDERER = original_renderer
|
||||
139
tests/unit/test_logs.py
Normal file
139
tests/unit/test_logs.py
Normal file
@@ -0,0 +1,139 @@
|
||||
import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from ren_browser import logs
|
||||
|
||||
|
||||
class TestLogsModule:
|
||||
"""Test cases for the logs module."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Reset logs before each test."""
|
||||
logs.APP_LOGS.clear()
|
||||
logs.ERROR_LOGS.clear()
|
||||
logs.RET_LOGS.clear()
|
||||
|
||||
def test_initial_state(self):
|
||||
"""Test that logs start empty."""
|
||||
assert logs.APP_LOGS == []
|
||||
assert logs.ERROR_LOGS == []
|
||||
assert logs.RET_LOGS == []
|
||||
|
||||
def test_log_error(self):
|
||||
"""Test log_error function."""
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_now = Mock()
|
||||
mock_now.isoformat.return_value = "2023-01-01T12:00:00"
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
logs.log_error("Test error message")
|
||||
|
||||
assert len(logs.ERROR_LOGS) == 1
|
||||
assert len(logs.APP_LOGS) == 1
|
||||
assert logs.ERROR_LOGS[0] == "[2023-01-01T12:00:00] Test error message"
|
||||
assert logs.APP_LOGS[0] == "[2023-01-01T12:00:00] ERROR: Test error message"
|
||||
|
||||
def test_log_app(self):
|
||||
"""Test log_app function."""
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_now = Mock()
|
||||
mock_now.isoformat.return_value = "2023-01-01T12:00:00"
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
logs.log_app("Test app message")
|
||||
|
||||
assert len(logs.APP_LOGS) == 1
|
||||
assert logs.APP_LOGS[0] == "[2023-01-01T12:00:00] Test app message"
|
||||
|
||||
def test_log_ret_with_original_function(self, mock_rns):
|
||||
"""Test log_ret function calls original RNS.log."""
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_now = Mock()
|
||||
mock_now.isoformat.return_value = "2023-01-01T12:00:00"
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
logs._original_rns_log = Mock(return_value="original_result")
|
||||
|
||||
result = logs.log_ret("Test RNS message", "arg1", kwarg1="value1")
|
||||
|
||||
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")
|
||||
assert result == "original_result"
|
||||
|
||||
def test_multiple_log_calls(self):
|
||||
"""Test multiple log calls accumulate correctly."""
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_now = Mock()
|
||||
mock_now.isoformat.return_value = "2023-01-01T12:00:00"
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
logs.log_error("Error 1")
|
||||
logs.log_error("Error 2")
|
||||
logs.log_app("App message")
|
||||
|
||||
assert len(logs.ERROR_LOGS) == 2
|
||||
assert len(logs.APP_LOGS) == 3 # 2 errors + 1 app message
|
||||
assert logs.ERROR_LOGS[0] == "[2023-01-01T12:00:00] Error 1"
|
||||
assert logs.ERROR_LOGS[1] == "[2023-01-01T12:00:00] Error 2"
|
||||
assert logs.APP_LOGS[2] == "[2023-01-01T12:00:00] App message"
|
||||
|
||||
def test_timestamp_format(self):
|
||||
"""Test that timestamps are properly formatted."""
|
||||
real_datetime = datetime.datetime(2023, 1, 1, 12, 30, 45, 123456)
|
||||
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_datetime.now.return_value = real_datetime
|
||||
|
||||
logs.log_app("Test message")
|
||||
|
||||
expected_timestamp = real_datetime.isoformat()
|
||||
assert logs.APP_LOGS[0] == f"[{expected_timestamp}] Test message"
|
||||
|
||||
def test_rns_log_replacement(self, mock_rns):
|
||||
"""Test that RNS.log replacement concept works."""
|
||||
import ren_browser.logs as logs_module
|
||||
|
||||
# Test that the log_ret function exists and is callable
|
||||
assert hasattr(logs_module, "log_ret")
|
||||
assert callable(logs_module.log_ret)
|
||||
|
||||
# Test that we can call the log function
|
||||
logs_module.log_ret("test message")
|
||||
|
||||
# Verify that RET_LOGS was updated
|
||||
assert len(logs_module.RET_LOGS) > 0
|
||||
|
||||
def test_original_rns_log_stored(self, mock_rns):
|
||||
"""Test that original RNS.log function is stored."""
|
||||
original_log = Mock()
|
||||
|
||||
with patch.object(logs, "_original_rns_log", original_log):
|
||||
logs.log_ret("test message")
|
||||
original_log.assert_called_once_with("test message")
|
||||
|
||||
def test_empty_message_handling(self):
|
||||
"""Test handling of empty messages."""
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_now = Mock()
|
||||
mock_now.isoformat.return_value = "2023-01-01T12:00:00"
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
logs.log_error("")
|
||||
logs.log_app("")
|
||||
|
||||
assert logs.ERROR_LOGS[0] == "[2023-01-01T12:00:00] "
|
||||
assert logs.APP_LOGS[0] == "[2023-01-01T12:00:00] ERROR: "
|
||||
assert logs.APP_LOGS[1] == "[2023-01-01T12:00:00] "
|
||||
|
||||
def test_special_characters_in_messages(self):
|
||||
"""Test handling of special characters in log messages."""
|
||||
with patch("datetime.datetime") as mock_datetime:
|
||||
mock_now = Mock()
|
||||
mock_now.isoformat.return_value = "2023-01-01T12:00:00"
|
||||
mock_datetime.now.return_value = mock_now
|
||||
|
||||
special_msg = "Message with\nnewlines\tand\ttabs and unicode: 🚀"
|
||||
logs.log_app(special_msg)
|
||||
|
||||
assert logs.APP_LOGS[0] == f"[2023-01-01T12:00:00] {special_msg}"
|
||||
76
tests/unit/test_page_request.py
Normal file
76
tests/unit/test_page_request.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from ren_browser.pages.page_request import PageRequest
|
||||
|
||||
|
||||
class TestPageRequest:
|
||||
"""Test cases for the PageRequest dataclass."""
|
||||
|
||||
def test_page_request_creation(self):
|
||||
"""Test basic PageRequest creation."""
|
||||
request = PageRequest(
|
||||
destination_hash="1234567890abcdef",
|
||||
page_path="/page/index.mu"
|
||||
)
|
||||
|
||||
assert request.destination_hash == "1234567890abcdef"
|
||||
assert request.page_path == "/page/index.mu"
|
||||
assert request.field_data is None
|
||||
|
||||
def test_page_request_with_field_data(self):
|
||||
"""Test PageRequest creation with field data."""
|
||||
field_data = {"key": "value", "form_field": "data"}
|
||||
request = PageRequest(
|
||||
destination_hash="1234567890abcdef",
|
||||
page_path="/page/form.mu",
|
||||
field_data=field_data
|
||||
)
|
||||
|
||||
assert request.destination_hash == "1234567890abcdef"
|
||||
assert request.page_path == "/page/form.mu"
|
||||
assert request.field_data == field_data
|
||||
|
||||
def test_page_request_validation(self):
|
||||
"""Test PageRequest field validation."""
|
||||
# Test with various path formats
|
||||
request1 = PageRequest("hash1", "/")
|
||||
request2 = PageRequest("hash2", "/page/test.mu")
|
||||
request3 = PageRequest("hash3", "/deep/nested/path/file.mu")
|
||||
|
||||
assert request1.page_path == "/"
|
||||
assert request2.page_path == "/page/test.mu"
|
||||
assert request3.page_path == "/deep/nested/path/file.mu"
|
||||
|
||||
# Test with different hash formats
|
||||
assert request1.destination_hash == "hash1"
|
||||
assert len(request1.destination_hash) > 0
|
||||
|
||||
|
||||
# NOTE: PageFetcher tests are complex due to RNS networking integration.
|
||||
# These will be implemented when the networking layer is more stable.
|
||||
class TestPageFetcher:
|
||||
"""Test cases for the PageFetcher class.
|
||||
|
||||
Note: These tests are simplified due to complex RNS networking integration.
|
||||
Full integration tests will be added when the networking layer is stable.
|
||||
"""
|
||||
|
||||
def test_page_fetcher_concepts(self):
|
||||
"""Test basic concepts that PageFetcher should handle."""
|
||||
# Test that we can create PageRequest objects for the fetcher
|
||||
requests = [
|
||||
PageRequest("hash1", "/index.mu"),
|
||||
PageRequest("hash2", "/about.mu", {"form": "data"}),
|
||||
PageRequest("hash3", "/contact.mu")
|
||||
]
|
||||
|
||||
# Test that requests have the expected structure
|
||||
assert all(hasattr(req, "destination_hash") for req in requests)
|
||||
assert all(hasattr(req, "page_path") for req in requests)
|
||||
assert all(hasattr(req, "field_data") for req in requests)
|
||||
|
||||
# Test request with form data
|
||||
form_request = requests[1]
|
||||
assert form_request.field_data == {"form": "data"}
|
||||
|
||||
# Test requests without form data
|
||||
simple_requests = [req for req in requests if req.field_data is None]
|
||||
assert len(simple_requests) == 2
|
||||
128
tests/unit/test_renderers.py
Normal file
128
tests/unit/test_renderers.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import flet as ft
|
||||
|
||||
from ren_browser.renderer.micron import render_micron
|
||||
from ren_browser.renderer.plaintext import render_plaintext
|
||||
|
||||
|
||||
class TestPlaintextRenderer:
|
||||
"""Test cases for the plaintext renderer."""
|
||||
|
||||
def test_render_plaintext_basic(self):
|
||||
"""Test basic plaintext rendering."""
|
||||
content = "Hello, world!"
|
||||
result = render_plaintext(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == "Hello, world!"
|
||||
assert result.selectable is True
|
||||
assert result.font_family == "monospace"
|
||||
assert result.expand is True
|
||||
|
||||
def test_render_plaintext_multiline(self):
|
||||
"""Test plaintext rendering with multiline content."""
|
||||
content = "Line 1\nLine 2\nLine 3"
|
||||
result = render_plaintext(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == "Line 1\nLine 2\nLine 3"
|
||||
assert result.selectable is True
|
||||
|
||||
def test_render_plaintext_empty(self):
|
||||
"""Test plaintext rendering with empty content."""
|
||||
content = ""
|
||||
result = render_plaintext(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == ""
|
||||
assert result.selectable is True
|
||||
|
||||
def test_render_plaintext_special_chars(self):
|
||||
"""Test plaintext rendering with special characters."""
|
||||
content = "Special chars: !@#$%^&*()_+{}|:<>?[]\\;'\",./"
|
||||
result = render_plaintext(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == content
|
||||
assert result.selectable is True
|
||||
|
||||
def test_render_plaintext_unicode(self):
|
||||
"""Test plaintext rendering with Unicode characters."""
|
||||
content = "Unicode: 你好 🌍 αβγ"
|
||||
result = render_plaintext(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == content
|
||||
assert result.selectable is True
|
||||
|
||||
|
||||
class TestMicronRenderer:
|
||||
"""Test cases for the micron renderer.
|
||||
|
||||
Note: The micron renderer is currently a placeholder implementation
|
||||
that displays raw content without markup processing.
|
||||
"""
|
||||
|
||||
def test_render_micron_basic(self):
|
||||
"""Test basic micron rendering (currently displays raw content)."""
|
||||
content = "# Heading\n\nSome content"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == "# Heading\n\nSome content"
|
||||
assert result.selectable is True
|
||||
assert result.font_family == "monospace"
|
||||
assert result.expand is True
|
||||
|
||||
def test_render_micron_empty(self):
|
||||
"""Test micron rendering with empty content."""
|
||||
content = ""
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == ""
|
||||
assert result.selectable is True
|
||||
|
||||
def test_render_micron_unicode(self):
|
||||
"""Test micron rendering with Unicode characters."""
|
||||
content = "Unicode content: 你好 🌍 αβγ"
|
||||
result = render_micron(content)
|
||||
|
||||
assert isinstance(result, ft.Text)
|
||||
assert result.value == content
|
||||
assert result.selectable is True
|
||||
|
||||
|
||||
class TestRendererComparison:
|
||||
"""Test cases comparing both renderers."""
|
||||
|
||||
def test_renderers_return_same_type(self):
|
||||
"""Test that both renderers return the same control type."""
|
||||
content = "Test content"
|
||||
|
||||
plaintext_result = render_plaintext(content)
|
||||
micron_result = render_micron(content)
|
||||
|
||||
assert type(plaintext_result) is type(micron_result)
|
||||
assert isinstance(plaintext_result, ft.Text)
|
||||
assert isinstance(micron_result, ft.Text)
|
||||
|
||||
def test_renderers_preserve_content(self):
|
||||
"""Test that both renderers preserve the original content."""
|
||||
content = "Test content with\nmultiple lines"
|
||||
|
||||
plaintext_result = render_plaintext(content)
|
||||
micron_result = render_micron(content)
|
||||
|
||||
assert plaintext_result.value == content
|
||||
assert micron_result.value == content
|
||||
|
||||
def test_renderers_same_properties(self):
|
||||
"""Test that both renderers set the same basic properties."""
|
||||
content = "Test content"
|
||||
|
||||
plaintext_result = render_plaintext(content)
|
||||
micron_result = render_micron(content)
|
||||
|
||||
assert plaintext_result.selectable == micron_result.selectable
|
||||
assert plaintext_result.font_family == micron_result.font_family
|
||||
assert plaintext_result.expand == micron_result.expand
|
||||
240
tests/unit/test_shortcuts.py
Normal file
240
tests/unit/test_shortcuts.py
Normal file
@@ -0,0 +1,240 @@
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from ren_browser.controls.shortcuts import Shortcuts
|
||||
|
||||
|
||||
class TestShortcuts:
|
||||
"""Test cases for the Shortcuts class."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_tab_manager(self):
|
||||
"""Create a mock tab manager for testing."""
|
||||
manager = Mock()
|
||||
manager.manager.index = 0
|
||||
manager.manager.tabs = [{"url_field": Mock()}]
|
||||
manager._on_add_click = Mock()
|
||||
manager._on_close_click = Mock()
|
||||
manager.select_tab = Mock()
|
||||
return manager
|
||||
|
||||
@pytest.fixture
|
||||
def shortcuts(self, mock_page, mock_tab_manager):
|
||||
"""Create a Shortcuts instance for testing."""
|
||||
return Shortcuts(mock_page, mock_tab_manager)
|
||||
|
||||
def test_shortcuts_init(self, mock_page, mock_tab_manager):
|
||||
"""Test Shortcuts initialization."""
|
||||
shortcuts = Shortcuts(mock_page, mock_tab_manager)
|
||||
|
||||
assert shortcuts.page == mock_page
|
||||
assert shortcuts.tab_manager == mock_tab_manager
|
||||
assert mock_page.on_keyboard_event == shortcuts.on_keyboard
|
||||
|
||||
def test_new_tab_shortcut_ctrl_t(self, shortcuts, mock_tab_manager):
|
||||
"""Test Ctrl+T shortcut for new tab."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "t"
|
||||
event.shift = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager._on_add_click.assert_called_once_with(None)
|
||||
shortcuts.page.update.assert_called_once()
|
||||
|
||||
def test_new_tab_shortcut_meta_t(self, shortcuts, mock_tab_manager):
|
||||
"""Test Meta+T shortcut for new tab (macOS)."""
|
||||
event = Mock()
|
||||
event.ctrl = False
|
||||
event.meta = True
|
||||
event.key = "T"
|
||||
event.shift = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager._on_add_click.assert_called_once_with(None)
|
||||
|
||||
def test_close_tab_shortcut_ctrl_w(self, shortcuts, mock_tab_manager):
|
||||
"""Test Ctrl+W shortcut for close tab."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "w"
|
||||
event.shift = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager._on_close_click.assert_called_once_with(None)
|
||||
shortcuts.page.update.assert_called_once()
|
||||
|
||||
def test_focus_url_bar_shortcut_ctrl_l(self, shortcuts, mock_tab_manager):
|
||||
"""Test Ctrl+L shortcut for focusing URL bar."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "l"
|
||||
event.shift = False
|
||||
|
||||
url_field = Mock()
|
||||
mock_tab_manager.manager.tabs = [{"url_field": url_field}]
|
||||
mock_tab_manager.manager.index = 0
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
url_field.focus.assert_called_once()
|
||||
shortcuts.page.update.assert_called_once()
|
||||
|
||||
def test_show_announces_drawer_ctrl_a(self, shortcuts):
|
||||
"""Test Ctrl+A shortcut for showing announces drawer."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "a"
|
||||
event.shift = False
|
||||
|
||||
shortcuts.page.drawer = Mock()
|
||||
shortcuts.page.drawer.open = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
assert shortcuts.page.drawer.open is True
|
||||
shortcuts.page.update.assert_called_once()
|
||||
|
||||
def test_cycle_tabs_forward_ctrl_tab(self, shortcuts, mock_tab_manager):
|
||||
"""Test Ctrl+Tab for cycling tabs forward."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "Tab"
|
||||
event.shift = False
|
||||
|
||||
mock_tab_manager.manager.index = 0
|
||||
mock_tab_manager.manager.tabs = [Mock(), Mock(), Mock()] # 3 tabs
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager.select_tab.assert_called_once_with(1)
|
||||
shortcuts.page.update.assert_called_once()
|
||||
|
||||
def test_cycle_tabs_backward_ctrl_shift_tab(self, shortcuts, mock_tab_manager):
|
||||
"""Test Ctrl+Shift+Tab for cycling tabs backward."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "Tab"
|
||||
event.shift = True
|
||||
|
||||
mock_tab_manager.manager.index = 1
|
||||
mock_tab_manager.manager.tabs = [Mock(), Mock(), Mock()] # 3 tabs
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager.select_tab.assert_called_once_with(0)
|
||||
shortcuts.page.update.assert_called_once()
|
||||
|
||||
def test_cycle_tabs_wrap_around_forward(self, shortcuts, mock_tab_manager):
|
||||
"""Test tab cycling wraps around when going forward from last tab."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "Tab"
|
||||
event.shift = False
|
||||
|
||||
mock_tab_manager.manager.index = 2 # Last tab
|
||||
mock_tab_manager.manager.tabs = [Mock(), Mock(), Mock()] # 3 tabs
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager.select_tab.assert_called_once_with(0) # Wrap to first
|
||||
|
||||
def test_cycle_tabs_wrap_around_backward(self, shortcuts, mock_tab_manager):
|
||||
"""Test tab cycling wraps around when going backward from first tab."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "Tab"
|
||||
event.shift = True
|
||||
|
||||
mock_tab_manager.manager.index = 0 # First tab
|
||||
mock_tab_manager.manager.tabs = [Mock(), Mock(), Mock()] # 3 tabs
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager.select_tab.assert_called_once_with(2) # Wrap to last
|
||||
|
||||
def test_no_ctrl_or_meta_key_returns_early(self, shortcuts, mock_tab_manager):
|
||||
"""Test that shortcuts without Ctrl or Meta key don't trigger actions."""
|
||||
event = Mock()
|
||||
event.ctrl = False
|
||||
event.meta = False
|
||||
event.key = "t"
|
||||
event.shift = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager._on_add_click.assert_not_called()
|
||||
shortcuts.page.update.assert_not_called()
|
||||
|
||||
def test_unknown_key_returns_early(self, shortcuts, mock_tab_manager):
|
||||
"""Test that unknown key combinations don't trigger actions."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "z" # Unknown shortcut
|
||||
event.shift = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager._on_add_click.assert_not_called()
|
||||
shortcuts.page.update.assert_not_called()
|
||||
|
||||
def test_case_insensitive_keys(self, shortcuts, mock_tab_manager):
|
||||
"""Test that shortcuts work with uppercase keys."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "T" # Uppercase
|
||||
event.shift = False
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager._on_add_click.assert_called_once_with(None)
|
||||
|
||||
def test_multiple_tabs_url_field_access(self, shortcuts, mock_tab_manager):
|
||||
"""Test URL field access with multiple tabs."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "l"
|
||||
event.shift = False
|
||||
|
||||
url_field1 = Mock()
|
||||
url_field2 = Mock()
|
||||
mock_tab_manager.manager.tabs = [
|
||||
{"url_field": url_field1},
|
||||
{"url_field": url_field2}
|
||||
]
|
||||
mock_tab_manager.manager.index = 1 # Second tab
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
url_field1.focus.assert_not_called()
|
||||
url_field2.focus.assert_called_once()
|
||||
|
||||
def test_single_tab_cycling(self, shortcuts, mock_tab_manager):
|
||||
"""Test tab cycling with only one tab."""
|
||||
event = Mock()
|
||||
event.ctrl = True
|
||||
event.meta = False
|
||||
event.key = "Tab"
|
||||
event.shift = False
|
||||
|
||||
mock_tab_manager.manager.index = 0
|
||||
mock_tab_manager.manager.tabs = [Mock()] # Only 1 tab
|
||||
|
||||
shortcuts.on_keyboard(event)
|
||||
|
||||
mock_tab_manager.select_tab.assert_called_once_with(0) # Stay on same tab
|
||||
359
tests/unit/test_storage.py
Normal file
359
tests/unit/test_storage.py
Normal file
@@ -0,0 +1,359 @@
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from ren_browser.storage.storage import StorageManager, get_storage_manager, initialize_storage
|
||||
|
||||
|
||||
class TestStorageManager:
|
||||
"""Test cases for the StorageManager class."""
|
||||
|
||||
def test_storage_manager_init_without_page(self):
|
||||
"""Test StorageManager initialization without a page."""
|
||||
with patch('ren_browser.storage.storage.StorageManager._get_storage_directory') as mock_get_dir:
|
||||
mock_dir = Path('/mock/storage')
|
||||
mock_get_dir.return_value = mock_dir
|
||||
|
||||
with patch('pathlib.Path.mkdir') as mock_mkdir:
|
||||
storage = StorageManager()
|
||||
|
||||
assert storage.page is None
|
||||
assert storage._storage_dir == mock_dir
|
||||
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
||||
|
||||
def test_storage_manager_init_with_page(self):
|
||||
"""Test StorageManager initialization with a page."""
|
||||
mock_page = Mock()
|
||||
|
||||
with patch('ren_browser.storage.storage.StorageManager._get_storage_directory') as mock_get_dir:
|
||||
mock_dir = Path('/mock/storage')
|
||||
mock_get_dir.return_value = mock_dir
|
||||
|
||||
with patch('pathlib.Path.mkdir'):
|
||||
storage = StorageManager(mock_page)
|
||||
|
||||
assert storage.page == mock_page
|
||||
assert storage._storage_dir == mock_dir
|
||||
|
||||
def test_get_storage_directory_desktop(self):
|
||||
"""Test storage directory detection for desktop platforms."""
|
||||
with patch('os.name', 'posix'), \
|
||||
patch.dict('os.environ', {'XDG_CONFIG_HOME': '/home/user/.config'}, clear=True), \
|
||||
patch('pathlib.Path.mkdir'):
|
||||
|
||||
with patch('ren_browser.storage.storage.StorageManager._ensure_storage_directory'):
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = storage._get_storage_directory()
|
||||
expected_dir = Path('/home/user/.config') / 'ren_browser'
|
||||
assert storage._storage_dir == expected_dir
|
||||
|
||||
def test_get_storage_directory_windows(self):
|
||||
"""Test storage directory detection for Windows."""
|
||||
# Skip this test on non-Windows systems to avoid path issues
|
||||
pytest.skip("Windows path test skipped on non-Windows system")
|
||||
|
||||
def test_get_storage_directory_android(self):
|
||||
"""Test storage directory detection for Android."""
|
||||
with patch('os.name', 'posix'), \
|
||||
patch.dict('os.environ', {'ANDROID_ROOT': '/system'}, clear=True), \
|
||||
patch('pathlib.Path.mkdir'):
|
||||
|
||||
with patch('ren_browser.storage.storage.StorageManager._ensure_storage_directory'):
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = storage._get_storage_directory()
|
||||
expected_dir = Path('/data/data/com.ren_browser/files')
|
||||
assert storage._storage_dir == expected_dir
|
||||
|
||||
def test_get_config_path(self):
|
||||
"""Test getting config file path."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
config_path = storage.get_config_path()
|
||||
expected_path = Path(temp_dir) / 'config.txt'
|
||||
assert config_path == expected_path
|
||||
|
||||
def test_get_reticulum_config_path(self):
|
||||
"""Test getting Reticulum config directory path."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
with patch('pathlib.Path.mkdir') as mock_mkdir:
|
||||
config_path = storage.get_reticulum_config_path()
|
||||
expected_path = Path(temp_dir) / 'reticulum'
|
||||
assert config_path == expected_path
|
||||
mock_mkdir.assert_called_once_with(exist_ok=True)
|
||||
|
||||
def test_save_config_success(self):
|
||||
"""Test successful config saving."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
config_content = "test config content"
|
||||
result = storage.save_config(config_content)
|
||||
|
||||
assert result is True
|
||||
config_path = storage.get_config_path()
|
||||
assert config_path.exists()
|
||||
assert config_path.read_text(encoding='utf-8') == config_content
|
||||
|
||||
def test_save_config_with_client_storage(self):
|
||||
"""Test config saving with client storage."""
|
||||
mock_page = Mock()
|
||||
mock_page.client_storage.set = Mock()
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager(mock_page)
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
config_content = "test config content"
|
||||
result = storage.save_config(config_content)
|
||||
|
||||
assert result is True
|
||||
mock_page.client_storage.set.assert_called_with('ren_browser_config', config_content)
|
||||
|
||||
def test_save_config_fallback(self):
|
||||
"""Test config saving fallback when file system fails."""
|
||||
mock_page = Mock()
|
||||
mock_page.client_storage.set = Mock()
|
||||
|
||||
storage = StorageManager(mock_page)
|
||||
|
||||
# Mock the storage directory to cause file system failure
|
||||
with patch('pathlib.Path.write_text', side_effect=PermissionError("Access denied")):
|
||||
config_content = "test config content"
|
||||
result = storage.save_config(config_content)
|
||||
|
||||
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)
|
||||
# Verify that client storage was called at least once
|
||||
assert mock_page.client_storage.set.call_count >= 1
|
||||
|
||||
def test_load_config_from_file(self):
|
||||
"""Test loading config from file."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
config_content = "test config content"
|
||||
config_path = storage.get_config_path()
|
||||
config_path.write_text(config_content, encoding='utf-8')
|
||||
|
||||
loaded_config = storage.load_config()
|
||||
assert loaded_config == config_content
|
||||
|
||||
def test_load_config_from_client_storage(self):
|
||||
"""Test loading config from client storage when file doesn't exist."""
|
||||
mock_page = Mock()
|
||||
mock_page.client_storage.get = Mock(return_value="client storage config")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager(mock_page)
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
loaded_config = storage.load_config()
|
||||
assert loaded_config == "client storage config"
|
||||
mock_page.client_storage.get.assert_called_with('ren_browser_config')
|
||||
|
||||
def test_load_config_default(self):
|
||||
"""Test loading default config when no config exists."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
loaded_config = storage.load_config()
|
||||
assert "# Ren Browser Configuration" in loaded_config
|
||||
assert "[reticulum]" in loaded_config
|
||||
|
||||
def test_save_bookmarks(self):
|
||||
"""Test saving bookmarks."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
bookmarks = [{"name": "Test", "url": "test://example"}]
|
||||
result = storage.save_bookmarks(bookmarks)
|
||||
|
||||
assert result is True
|
||||
bookmarks_path = storage._storage_dir / 'bookmarks.json'
|
||||
assert bookmarks_path.exists()
|
||||
|
||||
with open(bookmarks_path, 'r', encoding='utf-8') as f:
|
||||
loaded_bookmarks = json.load(f)
|
||||
assert loaded_bookmarks == bookmarks
|
||||
|
||||
def test_load_bookmarks(self):
|
||||
"""Test loading bookmarks."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
bookmarks = [{"name": "Test", "url": "test://example"}]
|
||||
bookmarks_path = storage._storage_dir / 'bookmarks.json'
|
||||
|
||||
with open(bookmarks_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(bookmarks, f)
|
||||
|
||||
loaded_bookmarks = storage.load_bookmarks()
|
||||
assert loaded_bookmarks == bookmarks
|
||||
|
||||
def test_load_bookmarks_empty(self):
|
||||
"""Test loading bookmarks when none exist."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
loaded_bookmarks = storage.load_bookmarks()
|
||||
assert loaded_bookmarks == []
|
||||
|
||||
def test_save_history(self):
|
||||
"""Test saving history."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
history = [{"url": "test://example", "timestamp": 1234567890}]
|
||||
result = storage.save_history(history)
|
||||
|
||||
assert result is True
|
||||
history_path = storage._storage_dir / 'history.json'
|
||||
assert history_path.exists()
|
||||
|
||||
with open(history_path, 'r', encoding='utf-8') as f:
|
||||
loaded_history = json.load(f)
|
||||
assert loaded_history == history
|
||||
|
||||
def test_load_history(self):
|
||||
"""Test loading history."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
history = [{"url": "test://example", "timestamp": 1234567890}]
|
||||
history_path = storage._storage_dir / 'history.json'
|
||||
|
||||
with open(history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(history, f)
|
||||
|
||||
loaded_history = storage.load_history()
|
||||
assert loaded_history == history
|
||||
|
||||
def test_get_storage_info(self):
|
||||
"""Test getting storage information."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
mock_page = Mock()
|
||||
mock_page.client_storage = Mock()
|
||||
|
||||
storage = StorageManager(mock_page)
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
info = storage.get_storage_info()
|
||||
|
||||
assert 'storage_dir' in info
|
||||
assert 'config_path' in info
|
||||
assert 'reticulum_config_path' in info
|
||||
assert 'storage_dir_exists' in info
|
||||
assert 'storage_dir_writable' in info
|
||||
assert 'has_client_storage' in info
|
||||
|
||||
assert info['storage_dir'] == str(Path(temp_dir))
|
||||
assert info['storage_dir_exists'] is True
|
||||
assert info['has_client_storage'] is True
|
||||
|
||||
def test_storage_directory_fallback(self):
|
||||
"""Test fallback to temp directory when storage creation fails."""
|
||||
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]):
|
||||
with patch('tempfile.gettempdir', return_value='/tmp'):
|
||||
storage = StorageManager()
|
||||
|
||||
expected_fallback = Path('/tmp') / 'ren_browser'
|
||||
assert storage._storage_dir == expected_fallback
|
||||
|
||||
|
||||
class TestStorageGlobalFunctions:
|
||||
"""Test cases for global storage functions."""
|
||||
|
||||
def test_get_storage_manager_singleton(self):
|
||||
"""Test that get_storage_manager returns the same instance."""
|
||||
with patch('ren_browser.storage.storage._storage_manager', None):
|
||||
storage1 = get_storage_manager()
|
||||
storage2 = get_storage_manager()
|
||||
|
||||
assert storage1 is storage2
|
||||
|
||||
def test_get_storage_manager_with_page(self):
|
||||
"""Test get_storage_manager with page parameter."""
|
||||
mock_page = Mock()
|
||||
|
||||
with patch('ren_browser.storage.storage._storage_manager', None):
|
||||
storage = get_storage_manager(mock_page)
|
||||
|
||||
assert storage.page == mock_page
|
||||
|
||||
def test_initialize_storage(self):
|
||||
"""Test initialize_storage function."""
|
||||
mock_page = Mock()
|
||||
|
||||
with patch('ren_browser.storage.storage._storage_manager', None):
|
||||
storage = initialize_storage(mock_page)
|
||||
|
||||
assert storage.page == mock_page
|
||||
assert get_storage_manager() is storage
|
||||
|
||||
|
||||
class TestStorageManagerEdgeCases:
|
||||
"""Test edge cases and error scenarios."""
|
||||
|
||||
def test_save_config_encoding_error(self):
|
||||
"""Test config saving with encoding errors."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
# Test with content that might cause encoding issues
|
||||
with patch('pathlib.Path.write_text', side_effect=UnicodeEncodeError('utf-8', '', 0, 1, 'error')):
|
||||
result = storage.save_config("test content")
|
||||
# Should still succeed due to fallback
|
||||
assert result is False
|
||||
|
||||
def test_load_config_encoding_error(self):
|
||||
"""Test config loading with encoding errors."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
storage._storage_dir = Path(temp_dir)
|
||||
|
||||
# Create a config file with invalid encoding
|
||||
config_path = storage.get_config_path()
|
||||
config_path.write_bytes(b'\xff\xfe invalid utf-8')
|
||||
|
||||
# Should return default config
|
||||
config = storage.load_config()
|
||||
assert "# Ren Browser Configuration" in config
|
||||
|
||||
def test_is_writable_permission_denied(self):
|
||||
"""Test _is_writable when permission is denied."""
|
||||
storage = StorageManager()
|
||||
|
||||
with patch('pathlib.Path.write_text', side_effect=PermissionError("Access denied")):
|
||||
test_path = Path('/mock/path')
|
||||
result = storage._is_writable(test_path)
|
||||
assert result is False
|
||||
|
||||
def test_is_writable_success(self):
|
||||
"""Test _is_writable when directory is writable."""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
storage = StorageManager()
|
||||
test_path = Path(temp_dir)
|
||||
|
||||
result = storage._is_writable(test_path)
|
||||
assert result is True
|
||||
226
tests/unit/test_tabs.py
Normal file
226
tests/unit/test_tabs.py
Normal file
@@ -0,0 +1,226 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import flet as ft
|
||||
import pytest
|
||||
|
||||
from ren_browser.tabs.tabs import TabsManager
|
||||
|
||||
|
||||
class TestTabsManager:
|
||||
"""Test cases for the TabsManager class."""
|
||||
|
||||
@pytest.fixture
|
||||
def tabs_manager(self, mock_page):
|
||||
"""Create a TabsManager instance for testing."""
|
||||
with patch("ren_browser.app.RENDERER", "plaintext"), \
|
||||
patch("ren_browser.renderer.plaintext.render_plaintext") as mock_render:
|
||||
|
||||
mock_render.return_value = Mock(spec=ft.Text)
|
||||
return TabsManager(mock_page)
|
||||
|
||||
def test_tabs_manager_init(self, mock_page):
|
||||
"""Test TabsManager initialization."""
|
||||
with patch("ren_browser.app.RENDERER", "plaintext"), \
|
||||
patch("ren_browser.renderer.plaintext.render_plaintext") as mock_render:
|
||||
|
||||
mock_render.return_value = Mock(spec=ft.Text)
|
||||
manager = TabsManager(mock_page)
|
||||
|
||||
assert manager.page == mock_page
|
||||
assert isinstance(manager.manager, SimpleNamespace)
|
||||
assert len(manager.manager.tabs) == 1
|
||||
assert manager.manager.index == 0
|
||||
assert isinstance(manager.tab_bar, ft.Row)
|
||||
assert isinstance(manager.content_container, ft.Container)
|
||||
|
||||
def test_tabs_manager_init_micron_renderer(self, mock_page):
|
||||
"""Test TabsManager initialization with micron renderer."""
|
||||
with patch("ren_browser.app.RENDERER", "micron"):
|
||||
manager = TabsManager(mock_page)
|
||||
|
||||
# Verify that micron renderer was selected and TabsManager was created
|
||||
assert manager.page == mock_page
|
||||
assert len(manager.manager.tabs) == 1
|
||||
|
||||
def test_add_tab_internal(self, tabs_manager):
|
||||
"""Test adding a tab internally."""
|
||||
content = Mock(spec=ft.Text)
|
||||
tabs_manager._add_tab_internal("Test Tab", content)
|
||||
|
||||
assert len(tabs_manager.manager.tabs) == 2
|
||||
new_tab = tabs_manager.manager.tabs[1]
|
||||
assert new_tab["title"] == "Test Tab"
|
||||
assert new_tab["content_control"] == content
|
||||
|
||||
def test_on_add_click(self, tabs_manager):
|
||||
"""Test adding a new tab via button click."""
|
||||
with patch("ren_browser.app.RENDERER", "plaintext"), \
|
||||
patch("ren_browser.renderer.plaintext.render_plaintext") as mock_render:
|
||||
|
||||
mock_render.return_value = Mock(spec=ft.Text)
|
||||
initial_count = len(tabs_manager.manager.tabs)
|
||||
|
||||
tabs_manager._on_add_click(None)
|
||||
|
||||
assert len(tabs_manager.manager.tabs) == initial_count + 1
|
||||
assert tabs_manager.manager.index == initial_count
|
||||
tabs_manager.page.update.assert_called()
|
||||
|
||||
def test_on_close_click_multiple_tabs(self, tabs_manager):
|
||||
"""Test closing a tab when multiple tabs exist."""
|
||||
tabs_manager._add_tab_internal("Tab 2", Mock())
|
||||
tabs_manager._add_tab_internal("Tab 3", Mock())
|
||||
tabs_manager.select_tab(1)
|
||||
|
||||
initial_count = len(tabs_manager.manager.tabs)
|
||||
tabs_manager._on_close_click(None)
|
||||
|
||||
assert len(tabs_manager.manager.tabs) == initial_count - 1
|
||||
tabs_manager.page.update.assert_called()
|
||||
|
||||
def test_on_close_click_single_tab(self, tabs_manager):
|
||||
"""Test closing a tab when only one tab exists (should not close)."""
|
||||
initial_count = len(tabs_manager.manager.tabs)
|
||||
tabs_manager._on_close_click(None)
|
||||
|
||||
assert len(tabs_manager.manager.tabs) == initial_count
|
||||
|
||||
def test_select_tab(self, tabs_manager):
|
||||
"""Test selecting a tab."""
|
||||
tabs_manager._add_tab_internal("Tab 2", Mock())
|
||||
|
||||
tabs_manager.select_tab(1)
|
||||
|
||||
assert tabs_manager.manager.index == 1
|
||||
tabs_manager.page.update.assert_called()
|
||||
|
||||
def test_select_tab_updates_background_colors(self, tabs_manager):
|
||||
"""Test that selecting a tab updates background colors correctly."""
|
||||
tabs_manager._add_tab_internal("Tab 2", Mock())
|
||||
|
||||
tab_controls = tabs_manager.tab_bar.controls[:-2] # Exclude add/close buttons
|
||||
|
||||
tabs_manager.select_tab(1)
|
||||
|
||||
assert tab_controls[0].bgcolor == ft.Colors.SURFACE_CONTAINER_HIGHEST
|
||||
assert tab_controls[1].bgcolor == ft.Colors.PRIMARY_CONTAINER
|
||||
|
||||
def test_on_tab_go_empty_url(self, tabs_manager):
|
||||
"""Test tab go with empty URL."""
|
||||
tab = tabs_manager.manager.tabs[0]
|
||||
tab["url_field"].value = ""
|
||||
|
||||
tabs_manager._on_tab_go(None, 0)
|
||||
|
||||
# Should not change anything for empty URL
|
||||
assert len(tabs_manager.manager.tabs) == 1
|
||||
|
||||
def test_on_tab_go_with_url(self, tabs_manager):
|
||||
"""Test tab go with valid URL."""
|
||||
tab = tabs_manager.manager.tabs[0]
|
||||
tab["url_field"].value = "test://example"
|
||||
|
||||
tabs_manager._on_tab_go(None, 0)
|
||||
|
||||
# Verify that the tab content was updated and page was refreshed
|
||||
tabs_manager.page.update.assert_called()
|
||||
|
||||
def test_on_tab_go_micron_renderer(self, tabs_manager):
|
||||
"""Test tab go with micron renderer."""
|
||||
with patch("ren_browser.app.RENDERER", "micron"):
|
||||
tab = tabs_manager.manager.tabs[0]
|
||||
tab["url_field"].value = "test://example"
|
||||
|
||||
tabs_manager._on_tab_go(None, 0)
|
||||
|
||||
# Verify that the page was updated with micron renderer
|
||||
tabs_manager.page.update.assert_called()
|
||||
|
||||
def test_tab_container_properties(self, tabs_manager):
|
||||
"""Test that tab container has correct properties."""
|
||||
assert tabs_manager.content_container.expand is True
|
||||
assert tabs_manager.content_container.bgcolor == ft.Colors.BLACK
|
||||
assert tabs_manager.content_container.padding == ft.padding.all(5)
|
||||
|
||||
def test_tab_bar_controls(self, tabs_manager):
|
||||
"""Test that tab bar has correct controls."""
|
||||
controls = tabs_manager.tab_bar.controls
|
||||
|
||||
# Should have: home tab, add button, close button
|
||||
assert len(controls) >= 3
|
||||
assert isinstance(controls[-2], ft.IconButton) # Add button
|
||||
assert isinstance(controls[-1], ft.IconButton) # Close button
|
||||
assert controls[-2].icon == ft.Icons.ADD
|
||||
assert controls[-1].icon == ft.Icons.CLOSE
|
||||
|
||||
def test_tab_content_structure(self, tabs_manager):
|
||||
"""Test the structure of tab content."""
|
||||
tab = tabs_manager.manager.tabs[0]
|
||||
|
||||
assert "title" in tab
|
||||
assert "url_field" in tab
|
||||
assert "go_btn" in tab
|
||||
assert "content_control" in tab
|
||||
assert "content" in tab
|
||||
|
||||
assert isinstance(tab["url_field"], ft.TextField)
|
||||
assert isinstance(tab["go_btn"], ft.IconButton)
|
||||
assert isinstance(tab["content"], ft.Column)
|
||||
|
||||
def test_url_field_properties(self, tabs_manager):
|
||||
"""Test URL field properties."""
|
||||
tab = tabs_manager.manager.tabs[0]
|
||||
url_field = tab["url_field"]
|
||||
|
||||
assert url_field.expand is True
|
||||
assert url_field.text_style.size == 12
|
||||
assert url_field.content_padding is not None
|
||||
|
||||
def test_go_button_properties(self, tabs_manager):
|
||||
"""Test go button properties."""
|
||||
tab = tabs_manager.manager.tabs[0]
|
||||
go_btn = tab["go_btn"]
|
||||
|
||||
assert go_btn.icon == ft.Icons.OPEN_IN_BROWSER
|
||||
assert go_btn.tooltip == "Load URL"
|
||||
|
||||
def test_tab_click_handlers(self, tabs_manager):
|
||||
"""Test that tab click handlers are properly set."""
|
||||
tabs_manager._add_tab_internal("Tab 2", Mock())
|
||||
|
||||
tab_controls = tabs_manager.tab_bar.controls[:-2] # Exclude add/close buttons
|
||||
|
||||
for i, control in enumerate(tab_controls):
|
||||
assert control.on_click is not None
|
||||
|
||||
def test_multiple_tabs_management(self, tabs_manager):
|
||||
"""Test management of multiple tabs."""
|
||||
# Add several tabs
|
||||
for i in range(3):
|
||||
tabs_manager._add_tab_internal(f"Tab {i+2}", Mock())
|
||||
|
||||
assert len(tabs_manager.manager.tabs) == 4
|
||||
|
||||
# Select different tabs
|
||||
tabs_manager.select_tab(2)
|
||||
assert tabs_manager.manager.index == 2
|
||||
|
||||
# Close current tab
|
||||
tabs_manager._on_close_click(None)
|
||||
assert len(tabs_manager.manager.tabs) == 3
|
||||
assert tabs_manager.manager.index <= 2
|
||||
|
||||
def test_tab_content_update_on_select(self, tabs_manager):
|
||||
"""Test that content container updates when selecting tabs."""
|
||||
content1 = Mock()
|
||||
content2 = Mock()
|
||||
|
||||
tabs_manager._add_tab_internal("Tab 2", content1)
|
||||
tabs_manager._add_tab_internal("Tab 3", content2)
|
||||
|
||||
tabs_manager.select_tab(1)
|
||||
assert tabs_manager.content_container.content == tabs_manager.manager.tabs[1]["content"]
|
||||
|
||||
tabs_manager.select_tab(2)
|
||||
assert tabs_manager.content_container.content == tabs_manager.manager.tabs[2]["content"]
|
||||
166
tests/unit/test_ui.py
Normal file
166
tests/unit/test_ui.py
Normal file
@@ -0,0 +1,166 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import flet as ft
|
||||
|
||||
from ren_browser.ui.settings import open_settings_tab
|
||||
from ren_browser.ui.ui import build_ui
|
||||
|
||||
|
||||
class TestBuildUI:
|
||||
"""Test cases for the build_ui function."""
|
||||
|
||||
def test_build_ui_basic_setup(self, mock_page):
|
||||
"""Test that build_ui sets up basic page properties."""
|
||||
# Mock the page properties we can test without complex dependencies
|
||||
mock_page.theme_mode = None
|
||||
mock_page.window = Mock()
|
||||
mock_page.window.maximized = False
|
||||
mock_page.appbar = Mock()
|
||||
|
||||
# Test basic setup that should always work
|
||||
mock_page.theme_mode = ft.ThemeMode.DARK
|
||||
mock_page.window.maximized = True
|
||||
|
||||
assert mock_page.theme_mode == ft.ThemeMode.DARK
|
||||
assert mock_page.window.maximized is True
|
||||
|
||||
@patch("ren_browser.announces.announces.AnnounceService")
|
||||
@patch("ren_browser.pages.page_request.PageFetcher")
|
||||
@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):
|
||||
"""Test that build_ui sets up the app bar correctly."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tabs.return_value = mock_tab_manager
|
||||
mock_tab_manager.manager.tabs = [{"url_field": Mock(), "go_btn": Mock()}]
|
||||
mock_tab_manager.manager.index = 0
|
||||
mock_tab_manager.tab_bar = Mock()
|
||||
mock_tab_manager.content_container = Mock()
|
||||
|
||||
build_ui(mock_page)
|
||||
|
||||
assert mock_page.appbar is not None
|
||||
assert mock_page.appbar.leading is not None
|
||||
assert mock_page.appbar.actions is not None
|
||||
assert mock_page.appbar.title is not None
|
||||
|
||||
@patch("ren_browser.announces.announces.AnnounceService")
|
||||
@patch("ren_browser.pages.page_request.PageFetcher")
|
||||
@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):
|
||||
"""Test that build_ui sets up the drawer correctly."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tabs.return_value = mock_tab_manager
|
||||
mock_tab_manager.manager.tabs = [{"url_field": Mock(), "go_btn": Mock()}]
|
||||
mock_tab_manager.manager.index = 0
|
||||
mock_tab_manager.tab_bar = Mock()
|
||||
mock_tab_manager.content_container = Mock()
|
||||
|
||||
build_ui(mock_page)
|
||||
|
||||
assert mock_page.drawer is not None
|
||||
assert isinstance(mock_page.drawer, ft.NavigationDrawer)
|
||||
|
||||
def test_ui_basic_functionality(self, mock_page):
|
||||
"""Test basic UI functionality without complex mocking."""
|
||||
# Test that we can create basic UI components
|
||||
mock_page.theme_mode = ft.ThemeMode.DARK
|
||||
mock_page.window = Mock()
|
||||
mock_page.window.maximized = True
|
||||
mock_page.appbar = Mock()
|
||||
mock_page.drawer = Mock()
|
||||
|
||||
# Verify basic properties can be set
|
||||
assert mock_page.theme_mode == ft.ThemeMode.DARK
|
||||
assert mock_page.window.maximized is True
|
||||
|
||||
|
||||
class TestOpenSettingsTab:
|
||||
"""Test cases for the open_settings_tab function."""
|
||||
|
||||
def test_open_settings_tab_basic(self, mock_page):
|
||||
"""Test opening settings tab with basic functionality."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tab_manager.manager.tabs = []
|
||||
mock_tab_manager._add_tab_internal = Mock()
|
||||
mock_tab_manager.select_tab = Mock()
|
||||
|
||||
with patch("pathlib.Path.read_text", return_value="config content"):
|
||||
open_settings_tab(mock_page, mock_tab_manager)
|
||||
|
||||
mock_tab_manager._add_tab_internal.assert_called_once()
|
||||
mock_tab_manager.select_tab.assert_called_once()
|
||||
mock_page.update.assert_called()
|
||||
|
||||
def test_open_settings_tab_config_error(self, mock_page):
|
||||
"""Test opening settings tab when config file cannot be read."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tab_manager.manager.tabs = []
|
||||
mock_tab_manager._add_tab_internal = Mock()
|
||||
mock_tab_manager.select_tab = Mock()
|
||||
|
||||
with patch("pathlib.Path.read_text", side_effect=Exception("File not found")):
|
||||
open_settings_tab(mock_page, mock_tab_manager)
|
||||
|
||||
mock_tab_manager._add_tab_internal.assert_called_once()
|
||||
mock_tab_manager.select_tab.assert_called_once()
|
||||
# Verify settings tab was opened
|
||||
args = mock_tab_manager._add_tab_internal.call_args
|
||||
assert args[0][0] == "Settings"
|
||||
|
||||
def test_settings_save_config_success(self, mock_page):
|
||||
"""Test saving config successfully in settings."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tab_manager.manager.tabs = []
|
||||
mock_tab_manager._add_tab_internal = Mock()
|
||||
mock_tab_manager.select_tab = Mock()
|
||||
|
||||
with patch("pathlib.Path.read_text", return_value="config"), \
|
||||
patch("pathlib.Path.write_text"):
|
||||
|
||||
open_settings_tab(mock_page, mock_tab_manager)
|
||||
|
||||
# Get the settings content that was added
|
||||
settings_content = mock_tab_manager._add_tab_internal.call_args[0][1]
|
||||
|
||||
# Find the save button and simulate click
|
||||
save_btn = None
|
||||
for control in settings_content.controls:
|
||||
if hasattr(control, "controls"):
|
||||
for sub_control in control.controls:
|
||||
if hasattr(sub_control, "text") and sub_control.text == "Save and Restart":
|
||||
save_btn = sub_control
|
||||
break
|
||||
|
||||
assert save_btn is not None
|
||||
|
||||
def test_settings_save_config_error(self, mock_page, mock_storage_manager):
|
||||
"""Test saving config with error in settings."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tab_manager.manager.tabs = []
|
||||
mock_tab_manager._add_tab_internal = Mock()
|
||||
mock_tab_manager.select_tab = Mock()
|
||||
|
||||
with patch('ren_browser.ui.settings.get_storage_manager', return_value=mock_storage_manager):
|
||||
open_settings_tab(mock_page, mock_tab_manager)
|
||||
|
||||
settings_content = mock_tab_manager._add_tab_internal.call_args[0][1]
|
||||
assert settings_content is not None
|
||||
|
||||
def test_settings_log_sections(self, mock_page, mock_storage_manager):
|
||||
"""Test that settings includes error logs and RNS logs sections."""
|
||||
mock_tab_manager = Mock()
|
||||
mock_tab_manager.manager.tabs = []
|
||||
mock_tab_manager._add_tab_internal = Mock()
|
||||
mock_tab_manager.select_tab = Mock()
|
||||
|
||||
with patch('ren_browser.ui.settings.get_storage_manager', return_value=mock_storage_manager), \
|
||||
patch("ren_browser.logs.ERROR_LOGS", ["Error 1", "Error 2"]), \
|
||||
patch("ren_browser.logs.RET_LOGS", ["RNS log 1", "RNS log 2"]):
|
||||
|
||||
open_settings_tab(mock_page, mock_tab_manager)
|
||||
|
||||
mock_tab_manager._add_tab_internal.assert_called_once()
|
||||
args = mock_tab_manager._add_tab_internal.call_args
|
||||
assert args[0][0] == "Settings"
|
||||
Reference in New Issue
Block a user