mirror of
https://github.com/Hopiu/bowser.git
synced 2026-03-16 19:10:24 +00:00
Add comprehensive test suite with pytest
- Add tests for URL parsing, cookies, HTML/CSS parsing - Add tests for browser/tab management and history - Add tests for layout and rendering components - Configure pytest with coverage reporting - Add test documentation and runner commands - All 54 tests passing
This commit is contained in:
parent
f1e4957e70
commit
ae6fcbfab4
14 changed files with 601 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__pycache__/
|
||||||
31
README.md
31
README.md
|
|
@ -18,6 +18,37 @@ uv sync
|
||||||
uv run bowser
|
uv run bowser
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
Run the test suite:
|
||||||
|
```bash
|
||||||
|
# Install dev dependencies
|
||||||
|
uv sync --extra dev
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# Run with verbose output
|
||||||
|
uv run pytest -v
|
||||||
|
|
||||||
|
# Run with coverage report
|
||||||
|
uv run pytest --cov=src --cov-report=html
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
uv run pytest tests/test_browser.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
# Format code
|
||||||
|
uv run black src tests
|
||||||
|
|
||||||
|
# Lint code
|
||||||
|
uv run ruff check src tests
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
uv run mypy src
|
||||||
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,29 @@ python_version = "3.11"
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unused_configs = true
|
warn_unused_configs = true
|
||||||
disallow_untyped_defs = false
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"-ra",
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"--showlocals",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["src"]
|
||||||
|
omit = ["tests/*", "*/conftest.py"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"def __repr__",
|
||||||
|
"raise AssertionError",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
]
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
64
tests/README.md
Normal file
64
tests/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Bowser Test Suite
|
||||||
|
|
||||||
|
This directory contains the test suite for the Bowser browser.
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
Run all tests:
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with verbose output:
|
||||||
|
```bash
|
||||||
|
uv run pytest -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Run specific test file:
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/test_browser.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with coverage:
|
||||||
|
```bash
|
||||||
|
uv run pytest --cov=src --cov-report=html
|
||||||
|
```
|
||||||
|
|
||||||
|
View coverage report:
|
||||||
|
```bash
|
||||||
|
open htmlcov/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Organization
|
||||||
|
|
||||||
|
- `test_url.py` - URL parsing and resolution
|
||||||
|
- `test_parser.py` - HTML/CSS parsing
|
||||||
|
- `test_browser.py` - Browser and tab management
|
||||||
|
- `test_cookies.py` - Cookie jar functionality
|
||||||
|
- `test_layout.py` - Layout engine components
|
||||||
|
- `test_render.py` - Rendering primitives
|
||||||
|
- `conftest.py` - Shared fixtures and configuration
|
||||||
|
|
||||||
|
## Writing Tests
|
||||||
|
|
||||||
|
Tests use pytest. Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_feature():
|
||||||
|
# Arrange
|
||||||
|
obj = MyClass()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = obj.method()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert result == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
Use mocks for GTK components:
|
||||||
|
```python
|
||||||
|
@patch('src.browser.browser.Gtk')
|
||||||
|
def test_with_gtk(mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
# test code
|
||||||
|
```
|
||||||
22
tests/conftest.py
Normal file
22
tests/conftest.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Pytest configuration and fixtures."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def configure_logging():
|
||||||
|
"""Configure logging for tests."""
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.WARNING, # Only show warnings/errors in tests
|
||||||
|
format="%(name)s %(levelname)s: %(message)s",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_browser():
|
||||||
|
"""Create a mock browser for testing."""
|
||||||
|
from unittest.mock import Mock
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
return browser
|
||||||
210
tests/test_browser.py
Normal file
210
tests/test_browser.py
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
"""Tests for browser tab management."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
from src.browser.browser import Browser
|
||||||
|
from src.browser.tab import Tab
|
||||||
|
from src.network.url import URL
|
||||||
|
|
||||||
|
|
||||||
|
class TestTab:
|
||||||
|
def test_tab_creation(self):
|
||||||
|
browser = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
assert tab.browser is browser
|
||||||
|
assert tab.current_url is None
|
||||||
|
assert tab.history == []
|
||||||
|
assert tab.history_index == -1
|
||||||
|
|
||||||
|
def test_tab_title_new(self):
|
||||||
|
browser = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
assert tab.title == "New Tab"
|
||||||
|
|
||||||
|
def test_tab_title_with_url(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
tab.load(URL("https://example.com"))
|
||||||
|
assert "example.com" in tab.title
|
||||||
|
|
||||||
|
def test_tab_load_adds_history(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
url1 = URL("https://example.com")
|
||||||
|
url2 = URL("https://other.com")
|
||||||
|
|
||||||
|
tab.load(url1)
|
||||||
|
assert len(tab.history) == 1
|
||||||
|
assert tab.history_index == 0
|
||||||
|
|
||||||
|
tab.load(url2)
|
||||||
|
assert len(tab.history) == 2
|
||||||
|
assert tab.history_index == 1
|
||||||
|
|
||||||
|
def test_tab_go_back(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
url1 = URL("https://example.com")
|
||||||
|
url2 = URL("https://other.com")
|
||||||
|
|
||||||
|
tab.load(url1)
|
||||||
|
tab.load(url2)
|
||||||
|
|
||||||
|
result = tab.go_back()
|
||||||
|
assert result is True
|
||||||
|
assert tab.history_index == 0
|
||||||
|
|
||||||
|
def test_tab_go_back_at_start(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
tab.load(URL("https://example.com"))
|
||||||
|
|
||||||
|
result = tab.go_back()
|
||||||
|
assert result is False
|
||||||
|
assert tab.history_index == 0
|
||||||
|
|
||||||
|
def test_tab_go_forward(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
|
||||||
|
tab.load(URL("https://example.com"))
|
||||||
|
tab.load(URL("https://other.com"))
|
||||||
|
tab.go_back()
|
||||||
|
|
||||||
|
result = tab.go_forward()
|
||||||
|
assert result is True
|
||||||
|
assert tab.history_index == 1
|
||||||
|
|
||||||
|
def test_tab_go_forward_at_end(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
tab.load(URL("https://example.com"))
|
||||||
|
|
||||||
|
result = tab.go_forward()
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
def test_tab_reload(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
tab.load(URL("https://example.com"))
|
||||||
|
|
||||||
|
result = tab.reload()
|
||||||
|
assert result is True
|
||||||
|
assert tab.history_index == 0
|
||||||
|
|
||||||
|
def test_tab_history_truncation(self):
|
||||||
|
browser = Mock()
|
||||||
|
browser._log = Mock()
|
||||||
|
tab = Tab(browser)
|
||||||
|
|
||||||
|
tab.load(URL("https://example.com"))
|
||||||
|
tab.load(URL("https://other.com"))
|
||||||
|
tab.load(URL("https://third.com"))
|
||||||
|
tab.go_back() # now at other.com
|
||||||
|
tab.load(URL("https://new.com")) # should truncate third.com
|
||||||
|
|
||||||
|
assert len(tab.history) == 3
|
||||||
|
assert tab.history_index == 2
|
||||||
|
|
||||||
|
|
||||||
|
@patch('src.browser.browser.Gtk')
|
||||||
|
class TestBrowser:
|
||||||
|
def test_browser_creation(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
assert browser.tabs == []
|
||||||
|
assert browser.active_tab is None
|
||||||
|
|
||||||
|
def test_new_tab(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
|
||||||
|
tab = browser.new_tab("https://example.com")
|
||||||
|
|
||||||
|
assert len(browser.tabs) == 1
|
||||||
|
assert browser.active_tab is tab
|
||||||
|
assert tab in browser.tabs
|
||||||
|
|
||||||
|
def test_set_active_tab(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
browser.chrome.paint = Mock()
|
||||||
|
browser.chrome.tabs_box = Mock()
|
||||||
|
|
||||||
|
tab1 = browser.new_tab("https://example.com")
|
||||||
|
tab2 = browser.new_tab("https://other.com")
|
||||||
|
|
||||||
|
browser.set_active_tab(tab1)
|
||||||
|
assert browser.active_tab is tab1
|
||||||
|
|
||||||
|
def test_close_tab(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
browser.chrome.paint = Mock()
|
||||||
|
browser.chrome.tabs_box = Mock()
|
||||||
|
|
||||||
|
tab1 = browser.new_tab("https://example.com")
|
||||||
|
tab2 = browser.new_tab("https://other.com")
|
||||||
|
|
||||||
|
browser.close_tab(tab1)
|
||||||
|
|
||||||
|
assert len(browser.tabs) == 1
|
||||||
|
assert tab1 not in browser.tabs
|
||||||
|
assert browser.active_tab is tab2
|
||||||
|
|
||||||
|
def test_close_active_tab_selects_previous(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
browser.chrome.paint = Mock()
|
||||||
|
browser.chrome.tabs_box = Mock()
|
||||||
|
|
||||||
|
tab1 = browser.new_tab("https://example.com")
|
||||||
|
tab2 = browser.new_tab("https://other.com")
|
||||||
|
tab3 = browser.new_tab("https://third.com")
|
||||||
|
|
||||||
|
browser.close_tab(tab3)
|
||||||
|
assert browser.active_tab is tab2
|
||||||
|
|
||||||
|
def test_close_last_tab(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
browser.chrome.paint = Mock()
|
||||||
|
browser.chrome.tabs_box = Mock()
|
||||||
|
|
||||||
|
tab = browser.new_tab("https://example.com")
|
||||||
|
browser.close_tab(tab)
|
||||||
|
|
||||||
|
assert len(browser.tabs) == 0
|
||||||
|
assert browser.active_tab is None
|
||||||
|
|
||||||
|
def test_navigate_to(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
browser.chrome.paint = Mock()
|
||||||
|
|
||||||
|
tab = browser.new_tab("https://example.com")
|
||||||
|
browser.navigate_to("https://other.com")
|
||||||
|
|
||||||
|
assert len(tab.history) == 2
|
||||||
|
|
||||||
|
def test_navigate_to_no_active_tab(self, mock_gtk):
|
||||||
|
browser = Browser()
|
||||||
|
browser.chrome.rebuild_tab_bar = Mock()
|
||||||
|
browser.chrome.update_address_bar = Mock()
|
||||||
|
|
||||||
|
browser.navigate_to("https://example.com")
|
||||||
|
|
||||||
|
assert len(browser.tabs) == 1
|
||||||
|
assert browser.active_tab is not None
|
||||||
41
tests/test_cookies.py
Normal file
41
tests/test_cookies.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
"""Tests for cookie management."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from src.network.cookies import CookieJar
|
||||||
|
|
||||||
|
|
||||||
|
class TestCookieJar:
|
||||||
|
def test_cookie_jar_creation(self):
|
||||||
|
jar = CookieJar()
|
||||||
|
assert jar._cookies == {}
|
||||||
|
|
||||||
|
def test_set_cookies(self):
|
||||||
|
jar = CookieJar()
|
||||||
|
jar.set_cookies("https://example.com", "session=abc123")
|
||||||
|
|
||||||
|
cookies = jar.get_cookie_header("https://example.com")
|
||||||
|
assert "session=abc123" in cookies
|
||||||
|
|
||||||
|
def test_get_cookie_header_empty(self):
|
||||||
|
jar = CookieJar()
|
||||||
|
cookies = jar.get_cookie_header("https://example.com")
|
||||||
|
assert cookies == ""
|
||||||
|
|
||||||
|
def test_multiple_cookies_same_origin(self):
|
||||||
|
jar = CookieJar()
|
||||||
|
jar.set_cookies("https://example.com", "session=abc123")
|
||||||
|
jar.set_cookies("https://example.com", "user=john")
|
||||||
|
|
||||||
|
cookies = jar.get_cookie_header("https://example.com")
|
||||||
|
assert "session=abc123" in cookies or "user=john" in cookies
|
||||||
|
|
||||||
|
def test_cookies_isolated_by_origin(self):
|
||||||
|
jar = CookieJar()
|
||||||
|
jar.set_cookies("https://example.com", "session=abc123")
|
||||||
|
jar.set_cookies("https://other.com", "session=xyz789")
|
||||||
|
|
||||||
|
cookies1 = jar.get_cookie_header("https://example.com")
|
||||||
|
cookies2 = jar.get_cookie_header("https://other.com")
|
||||||
|
|
||||||
|
assert "abc123" in cookies1
|
||||||
|
assert "xyz789" in cookies2
|
||||||
58
tests/test_layout.py
Normal file
58
tests/test_layout.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
"""Tests for layout components."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import Mock
|
||||||
|
from src.layout.document import DocumentLayout
|
||||||
|
from src.layout.block import BlockLayout, LineLayout
|
||||||
|
from src.layout.inline import TextLayout
|
||||||
|
from src.parser.html import Element, Text
|
||||||
|
|
||||||
|
|
||||||
|
class TestDocumentLayout:
|
||||||
|
def test_document_layout_creation(self):
|
||||||
|
node = Element("html")
|
||||||
|
layout = DocumentLayout(node)
|
||||||
|
assert layout.node is node
|
||||||
|
assert layout.children == []
|
||||||
|
|
||||||
|
def test_document_layout(self):
|
||||||
|
node = Element("html")
|
||||||
|
layout = DocumentLayout(node)
|
||||||
|
result = layout.layout(800, 1.0)
|
||||||
|
assert result == 800.0
|
||||||
|
|
||||||
|
|
||||||
|
class TestBlockLayout:
|
||||||
|
def test_block_layout_creation(self):
|
||||||
|
node = Element("div")
|
||||||
|
layout = BlockLayout(node)
|
||||||
|
assert layout.node is node
|
||||||
|
assert layout.children == []
|
||||||
|
|
||||||
|
def test_block_layout_with_parent(self):
|
||||||
|
parent_node = Element("body")
|
||||||
|
child_node = Element("div")
|
||||||
|
parent_layout = BlockLayout(parent_node)
|
||||||
|
child_layout = BlockLayout(child_node, parent=parent_layout)
|
||||||
|
assert child_layout.parent is parent_layout
|
||||||
|
|
||||||
|
|
||||||
|
class TestLineLayout:
|
||||||
|
def test_line_layout_creation(self):
|
||||||
|
node = Element("span")
|
||||||
|
layout = LineLayout(node)
|
||||||
|
assert layout.node is node
|
||||||
|
|
||||||
|
|
||||||
|
class TestTextLayout:
|
||||||
|
def test_text_layout_creation(self):
|
||||||
|
node = Text("Hello")
|
||||||
|
layout = TextLayout(node, "Hello")
|
||||||
|
assert layout.node is node
|
||||||
|
assert layout.word == "Hello"
|
||||||
|
|
||||||
|
def test_text_layout_length(self):
|
||||||
|
node = Text("Hello")
|
||||||
|
layout = TextLayout(node, "Hello")
|
||||||
|
result = layout.layout()
|
||||||
|
assert result == 5
|
||||||
52
tests/test_parser.py
Normal file
52
tests/test_parser.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
"""Tests for HTML parsing."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from src.parser.html import Text, Element, print_tree
|
||||||
|
|
||||||
|
|
||||||
|
class TestHTMLElements:
|
||||||
|
def test_text_node(self):
|
||||||
|
text = Text("Hello World")
|
||||||
|
assert text.text == "Hello World"
|
||||||
|
assert text.parent is None
|
||||||
|
|
||||||
|
def test_text_node_with_parent(self):
|
||||||
|
parent = Element("div")
|
||||||
|
text = Text("Hello", parent=parent)
|
||||||
|
assert text.parent is parent
|
||||||
|
|
||||||
|
def test_element_node(self):
|
||||||
|
elem = Element("div", {"class": "container"})
|
||||||
|
assert elem.tag == "div"
|
||||||
|
assert elem.attributes == {"class": "container"}
|
||||||
|
assert elem.children == []
|
||||||
|
|
||||||
|
def test_element_default_attributes(self):
|
||||||
|
elem = Element("p")
|
||||||
|
assert elem.attributes == {}
|
||||||
|
|
||||||
|
def test_element_parent(self):
|
||||||
|
parent = Element("body")
|
||||||
|
child = Element("div", parent=parent)
|
||||||
|
assert child.parent is parent
|
||||||
|
|
||||||
|
|
||||||
|
class TestPrintTree:
|
||||||
|
def test_print_single_element(self, capsys):
|
||||||
|
elem = Element("div")
|
||||||
|
print_tree(elem)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert "Element('div'" in captured.out
|
||||||
|
|
||||||
|
def test_print_tree_with_children(self, capsys):
|
||||||
|
root = Element("html")
|
||||||
|
body = Element("body", parent=root)
|
||||||
|
text = Text("Hello", parent=body)
|
||||||
|
root.children = [body]
|
||||||
|
body.children = [text]
|
||||||
|
|
||||||
|
print_tree(root)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert "Element('html'" in captured.out
|
||||||
|
assert "Element('body'" in captured.out
|
||||||
|
assert "Text('Hello')" in captured.out
|
||||||
55
tests/test_render.py
Normal file
55
tests/test_render.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""Tests for rendering primitives."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from src.render.paint import PaintCommand, DrawText
|
||||||
|
from src.render.composite import CompositedLayer
|
||||||
|
from src.render.fonts import get_font, linespace
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaintCommands:
|
||||||
|
def test_paint_command_creation(self):
|
||||||
|
cmd = PaintCommand((0, 0, 100, 100))
|
||||||
|
assert cmd.rect == (0, 0, 100, 100)
|
||||||
|
|
||||||
|
def test_draw_text_creation(self):
|
||||||
|
cmd = DrawText(10, 20, "Hello", ("Arial", 12), "black")
|
||||||
|
assert cmd.text == "Hello"
|
||||||
|
assert cmd.font == ("Arial", 12)
|
||||||
|
assert cmd.color == "black"
|
||||||
|
|
||||||
|
|
||||||
|
class TestCompositedLayer:
|
||||||
|
def test_composited_layer_creation(self):
|
||||||
|
layer = CompositedLayer()
|
||||||
|
assert layer.items == []
|
||||||
|
|
||||||
|
def test_composited_layer_with_item(self):
|
||||||
|
item = "mock_item"
|
||||||
|
layer = CompositedLayer(item)
|
||||||
|
assert len(layer.items) == 1
|
||||||
|
assert layer.items[0] == item
|
||||||
|
|
||||||
|
def test_add_item(self):
|
||||||
|
layer = CompositedLayer()
|
||||||
|
layer.add("item1")
|
||||||
|
layer.add("item2")
|
||||||
|
assert len(layer.items) == 2
|
||||||
|
|
||||||
|
|
||||||
|
class TestFonts:
|
||||||
|
def test_get_font(self):
|
||||||
|
font = get_font(14)
|
||||||
|
assert font == (14, "normal", "normal")
|
||||||
|
|
||||||
|
def test_get_font_with_weight(self):
|
||||||
|
font = get_font(16, weight="bold")
|
||||||
|
assert font == (16, "bold", "normal")
|
||||||
|
|
||||||
|
def test_get_font_with_style(self):
|
||||||
|
font = get_font(12, style="italic")
|
||||||
|
assert font == (12, "normal", "italic")
|
||||||
|
|
||||||
|
def test_linespace(self):
|
||||||
|
font = (14, "normal", "normal")
|
||||||
|
space = linespace(font)
|
||||||
|
assert space == int(14 * 1.2)
|
||||||
41
tests/test_url.py
Normal file
41
tests/test_url.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
"""Tests for URL parsing and resolution."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from src.network.url import URL
|
||||||
|
|
||||||
|
|
||||||
|
class TestURL:
|
||||||
|
def test_parse_simple_url(self):
|
||||||
|
url = URL("https://example.com")
|
||||||
|
assert str(url) == "https://example.com"
|
||||||
|
|
||||||
|
def test_parse_url_with_path(self):
|
||||||
|
url = URL("https://example.com/path/to/page")
|
||||||
|
assert str(url) == "https://example.com/path/to/page"
|
||||||
|
|
||||||
|
def test_parse_url_with_query(self):
|
||||||
|
url = URL("https://example.com/search?q=test")
|
||||||
|
assert str(url) == "https://example.com/search?q=test"
|
||||||
|
|
||||||
|
def test_origin(self):
|
||||||
|
url = URL("https://example.com:8080/path")
|
||||||
|
assert url.origin() == "https://example.com:8080"
|
||||||
|
|
||||||
|
def test_origin_default_port(self):
|
||||||
|
url = URL("https://example.com/path")
|
||||||
|
assert url.origin() == "https://example.com"
|
||||||
|
|
||||||
|
def test_resolve_relative_path(self):
|
||||||
|
base = URL("https://example.com/dir/page.html")
|
||||||
|
resolved = base.resolve("other.html")
|
||||||
|
assert str(resolved) == "https://example.com/dir/other.html"
|
||||||
|
|
||||||
|
def test_resolve_absolute_path(self):
|
||||||
|
base = URL("https://example.com/dir/page.html")
|
||||||
|
resolved = base.resolve("/root/page.html")
|
||||||
|
assert str(resolved) == "https://example.com/root/page.html"
|
||||||
|
|
||||||
|
def test_resolve_full_url(self):
|
||||||
|
base = URL("https://example.com/page.html")
|
||||||
|
resolved = base.resolve("https://other.com/page.html")
|
||||||
|
assert str(resolved) == "https://other.com/page.html"
|
||||||
Loading…
Reference in a new issue