Refactor test files to remove unnecessary imports and improve readability

- Removed unused imports from various test files to streamline code.
- Cleaned up whitespace in test cases for better consistency.
- Updated dependency management in `uv.lock` to reflect changes in optional dependencies.
- Ensured all tests maintain functionality while enhancing clarity and organization.
This commit is contained in:
Benedikt Willi 2026-01-12 10:22:34 +01:00
parent 4b3ba9144d
commit c9ef5e5c44
29 changed files with 989 additions and 625 deletions

345
.github/skills/skills.md vendored Normal file
View file

@ -0,0 +1,345 @@
# Skills Index
This file defines **canonical project skills** the LLM must follow. Each skill specifies the *one correct command* for a common project action.
1. **Run the Project**`uv run bowser`
2. **Test the Project**`uv run pytest`
3. **Lint the Project**`uv run ruff`
Deviating from these commands is considered incorrect behavior unless explicitly instructed.
---
# Skill: Run the Project with `uv run bowser`
## Purpose
Teach the LLM **how and when to run this project** using the canonical command:
```bash
uv run bowser
```
This skill ensures the LLM consistently uses the correct entry point, avoids adhoc commands, and follows project conventions.
---
## Canonical Command
**Always run the project using:**
```bash
uv run bowser
```
Do **not**:
- Call `python` directly
- Run scripts via file paths
- Use alternative task runners (e.g. `make`, `poetry run`, `pipenv run`)
`uv` is the authoritative environment and dependency manager for this project, and `bowser` is the defined runtime entry point.
---
## What `uv run bowser` Means
- `uv run`:
- Ensures dependencies are resolved and installed according to the project configuration
- Executes commands inside the correct, isolated environment
- `bowser`:
- The projects primary executable / CLI
- Encapsulates startup logic, configuration loading, and runtime behavior
Together, they guarantee a reproducible and correct execution environment.
---
## When to Use This Command
Use `uv run bowser` whenever you need to:
- Start the application
- Run the main service or agent
- Execute project logic endtoend
- Validate runtime behavior
- Demonstrate how the project is launched
If the task says **“run the project”**, **“start the app”**, or **“execute Bowser”**, this is the command.
---
## When *Not* to Use This Command
Do **not** use `uv run bowser` when:
- Running tests (use the projects test command instead)
- Running oneoff scripts unless explicitly routed through `bowser`
- Installing dependencies
- Linting or formatting code
If unsure, default to **not running anything** and explain what would be executed.
---
## How to Explain This to Humans
When documenting or instructing users, say:
> “Run the project with `uv run bowser`.”
Optionally add:
> “This ensures the correct environment and entry point are used.”
Do **not** overexplain unless the user asks.
---
## Error Handling Guidance
If `uv run bowser` fails:
1. Assume a dependency or configuration issue
2. Report the error output verbatim
3. Do **not** substitute another execution method
4. Suggest fixing the root cause, not changing the command
---
## Mental Model for the LLM
- There is **one** correct way to run the project
- That way is **stable and intentional**
- Deviating from it is a bug
Think of `uv run bowser` as:
> “The projects onswitch.”
---
## Summary (Checklist)
Before suggesting how to run the project, verify:
- [ ] You are using `uv run`
- [ ] You are invoking `bowser`
- [ ] You are not calling Python directly
- [ ] You are not inventing alternate commands
If all are true, you are doing it right.
---
# Skill: Test the Project with `uv run pytest`
## Purpose
Teach the LLM **how and when to run tests** for this project using the canonical command:
```bash
uv run pytest
```
This skill ensures tests are executed in the correct environment, using the projects standard tooling, without inventing alternate commands.
---
## Canonical Command
**Always run tests using:**
```bash
uv run pytest
```
Do **not**:
- Call `pytest` directly
- Use `python -m pytest`
- Run tests via ad-hoc scripts or task runners
`uv` is the authoritative environment manager, and `pytest` is the test runner for this project.
---
## What `uv run pytest` Means
- `uv run`:
- Ensures dependencies (including test dependencies) are resolved correctly
- Runs inside the same environment model as the application
- `pytest`:
- Discovers and runs the projects test suite
- Applies project-level configuration (e.g. `pytest.ini`, `pyproject.toml`)
Together, they guarantee consistent and reproducible test execution.
---
## When to Use This Command
Use `uv run pytest` whenever you need to:
- Run the full test suite
- Verify a change before or after modifying code
- Reproduce a failing test
- Validate behavior without starting the application
If the task says **“run tests”**, **“test the project”**, or **“verify with pytest”**, this is the command.
---
## When *Not* to Use This Command
Do **not** use `uv run pytest` when:
- Running the application (use `uv run bowser`)
- Installing dependencies
- Linting or formatting code
- Executing non-test scripts
If unsure, default to explaining what tests would be run rather than executing them.
---
## Error Handling Guidance
If `uv run pytest` fails:
1. Capture and report the full pytest output
2. Identify whether the failure is:
- A test assertion failure
- A missing dependency or import error
- A configuration issue
3. Do **not** change the command to work around the failure
4. Fix the underlying cause, then re-run the same command
---
## Mental Model for the LLM
- There is **one** correct way to run tests
- Test execution should mirror the real runtime environment
- Consistency matters more than convenience
Think of `uv run pytest` as:
> “The projects truth-check.”
---
## Summary (Checklist)
Before suggesting how to test the project, verify:
- [ ] You are using `uv run`
- [ ] You are invoking `pytest`
- [ ] You are not calling Python directly
- [ ] You are not inventing alternate test commands
If all are true, you are doing it right.
---
# Skill: Lint the Project with `uv run ruff`
## Purpose
Teach the LLM **how and when to lint the project** using the canonical command:
```bash
uv run ruff
```
This skill ensures linting is performed consistently, using the projects configured rules and environment.
---
## Canonical Command
**Always lint the project using:**
```bash
uv run ruff
```
Do **not**:
- Call `ruff` directly
- Use alternative linters unless explicitly instructed
- Invoke formatting or linting via ad-hoc scripts
`uv` guarantees the correct environment, and `ruff` enforces the projects linting standards.
---
## What `uv run ruff` Means
- `uv run`:
- Executes linting inside the managed project environment
- Ensures the correct version of `ruff` and dependencies are used
- `ruff`:
- Performs fast, opinionated linting
- Applies rules configured in project files (e.g. `pyproject.toml`)
Together, they provide deterministic and repeatable lint results.
---
## When to Use This Command
Use `uv run ruff` whenever you need to:
- Check code quality
- Identify linting or style issues
- Validate changes before committing
- Respond to a request to “lint the project” or “run ruff”
---
## When *Not* to Use This Command
Do **not** use `uv run ruff` when:
- Running the application (`uv run bowser`)
- Running tests (`uv run pytest`)
- Formatting code unless `ruff` is explicitly configured to do so
- Installing dependencies
If unsure, explain what linting would check instead of executing it.
---
## Error Handling Guidance
If `uv run ruff` reports issues:
1. Treat findings as authoritative
2. Report errors or warnings clearly
3. Do **not** suppress or bypass lint rules
4. Fix the code, then re-run the same command
---
## Mental Model for the LLM
- Linting enforces shared standards
- Speed and consistency matter more than flexibility
- There is **one** correct linting command
Think of `uv run ruff` as:
> “The projects code-quality gate.”
---
## Summary (Checklist)
Before suggesting how to lint the project, verify:
- [ ] You are using `uv run`
- [ ] You are invoking `ruff`
- [ ] You are not inventing alternate linting tools
If all are true, you are doing it right.

View file

@ -16,7 +16,7 @@ dependencies = [
"Jinja2>=3.0", # Template engine for pages
]
[project.optional-dependencies]
[dependency-groups]
dev = [
"pytest>=9.0.0",
"pytest-cov>=7.0.0",
@ -35,12 +35,14 @@ managed = true
packages = ["src"]
[tool.black]
line-length = 100
line-length = 120
target-version = ["py311"]
[tool.ruff]
line-length = 100
line-length = 120
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "W"]
[tool.mypy]

View file

@ -1,3 +1,4 @@
# ruff: noqa: E402
"""Browser entry and orchestration."""
import gi

View file

@ -1,3 +1,4 @@
# ruff: noqa: E402
"""Browser chrome (Adwaita UI)."""
import gi
@ -433,8 +434,10 @@ class Chrome:
# Store sub-timings for display
if self.debug_mode:
self._render_sub_timings = sub_timings
self._visible_line_count = len([l for l in self.text_layout
if self.scroll_y - 50 <= l["y"] + l["font_size"] <= self.scroll_y + height + 50])
self._visible_line_count = len([
line for line in self.text_layout
if self.scroll_y - 50 <= line["y"] + line["font_size"] <= self.scroll_y + height + 50
])
def _draw_selection_highlight(self, canvas, width: int):
"""Draw selection highlight rectangle."""
@ -531,7 +534,13 @@ class Chrome:
if self._render_sub_timings:
y += 16
text_paint.setColor(skia.Color(150, 200, 255, 255))
canvas.drawString(f"render_dom breakdown ({self._visible_line_count} lines):", panel_x + 5, y, small_font, text_paint)
canvas.drawString(
f"render_dom breakdown ({self._visible_line_count} lines):",
panel_x + 5,
y,
small_font,
text_paint,
)
sub_sorted = sorted(self._render_sub_timings.items(), key=lambda x: x[1], reverse=True)
for name, duration in sub_sorted:
@ -750,7 +759,7 @@ class Chrome:
line_bottom = line_info["y"] + line_info["height"]
line_left = line_info["x"]
char_positions = line_info.get("char_positions", [])
text = line_info["text"]
# text not needed for highlight geometry
# Skip lines completely outside selection
if line_bottom < sel_start[1] or line_top > sel_end[1]:
@ -764,13 +773,21 @@ class Chrome:
if line_top <= sel_start[1] < line_bottom:
# Find character index at sel_start x
start_char_idx = self._x_to_char_index(sel_start[0], line_left, char_positions)
hl_left = line_left + char_positions[start_char_idx] if start_char_idx < len(char_positions) else line_left
hl_left = (
line_left + char_positions[start_char_idx]
if start_char_idx < len(char_positions)
else line_left
)
# If this line contains the end of selection
if line_top <= sel_end[1] < line_bottom:
# Find character index at sel_end x
end_char_idx = self._x_to_char_index(sel_end[0], line_left, char_positions)
hl_right = line_left + char_positions[end_char_idx] if end_char_idx < len(char_positions) else hl_right
hl_right = (
line_left + char_positions[end_char_idx]
if end_char_idx < len(char_positions)
else hl_right
)
# Draw highlight
if hl_right > hl_left:

View file

@ -1,6 +1,6 @@
"""Tab and frame orchestration stubs."""
from typing import Optional
from typing import Optional, TYPE_CHECKING
import logging
from ..network.url import URL
@ -8,6 +8,9 @@ from ..network import http
from ..parser.html import parse_html, Element
from ..templates import render_startpage, render_error_page
if TYPE_CHECKING:
from .browser import Browser
class Frame:
def __init__(self, tab: "Tab", parent_frame=None, frame_element=None):

View file

@ -2,7 +2,6 @@
from ..parser.html import Element, Text
from ..render.fonts import get_font, linespace
from .block import BlockLayout, LineLayout
class LayoutLine:

View file

@ -1,6 +1,6 @@
"""Inline and text layout."""
from ..render.fonts import get_font, measure_text, linespace
from ..render.fonts import get_font, linespace
class TextLayout:

View file

@ -7,7 +7,12 @@ import logging
from .url import URL
def request(url: URL, payload: Optional[bytes] = None, method: str = "GET", max_redirects: int = 10) -> Tuple[int, str, bytes]:
def request(
url: URL,
payload: Optional[bytes] = None,
method: str = "GET",
max_redirects: int = 10,
) -> Tuple[int, str, bytes]:
"""
Fetch a URL and follow redirects, returning (status_code, content_type, body).

View file

@ -5,7 +5,7 @@ from typing import Optional
from ..parser.html import Element
from ..layout.document import DocumentLayout
from .fonts import get_font
from .paint import DisplayList, DrawText, DrawRect
from .paint import DisplayList
class RenderPipeline:

View file

@ -1,6 +1,5 @@
"""Template rendering utilities."""
import os
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, select_autoescape
import logging
@ -66,13 +65,7 @@ def render_error_page(status_code: int, url: str = "", error_message: str = "")
"""
logger = logging.getLogger("bowser.templates")
# Map common status codes to templates
template_map = {
404: "error_404.html",
500: "error_500.html",
# Network errors
"network": "error_network.html",
}
# Determine template per status
if status_code == 404:
template = "error_404.html"

View file

@ -1,6 +1,5 @@
"""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
@ -140,7 +139,7 @@ class TestBrowser:
browser.chrome.tabs_box = Mock()
tab1 = browser.new_tab("https://example.com")
tab2 = browser.new_tab("https://other.com")
_ = browser.new_tab("https://other.com")
browser.set_active_tab(tab1)
assert browser.active_tab is tab1
@ -168,7 +167,7 @@ class TestBrowser:
browser.chrome.paint = Mock()
browser.chrome.tabs_box = Mock()
tab1 = browser.new_tab("https://example.com")
_ = browser.new_tab("https://example.com")
tab2 = browser.new_tab("https://other.com")
tab3 = browser.new_tab("https://third.com")

View file

@ -1,6 +1,5 @@
"""Tests for cookie management."""
import pytest
from src.network.cookies import CookieJar

View file

@ -1,7 +1,6 @@
"""Tests for DOM graph visualization."""
import pytest
from src.parser.html import parse_html, Element, Text
from src.parser.html import parse_html
from src.debug.dom_graph import generate_dot_graph, print_dom_tree
@ -93,8 +92,8 @@ class TestDOMGraph:
# Should have increasing indentation
lines = tree.split('\n')
# Find the nested <p> line - should be more indented than <div>
p_line = [l for l in lines if '<p>' in l][0]
div_line = [l for l in lines if '<div>' in l][0]
p_line = [line for line in lines if '<p>' in line][0]
div_line = [line for line in lines if '<div>' in line][0]
# Count leading spaces
p_indent = len(p_line) - len(p_line.lstrip())

View file

@ -1,8 +1,6 @@
"""Tests for DOM graph page rendering."""
import pytest
from src.templates import render_dom_graph_page
from pathlib import Path
import tempfile
import os

View file

@ -1,8 +1,7 @@
"""Tests for Frame and content loading."""
import pytest
from unittest.mock import Mock, patch
from src.browser.tab import Frame, Tab
from src.browser.tab import Tab
from src.network.url import URL

View file

@ -1,6 +1,5 @@
"""Tests for HTML parsing functionality."""
import pytest
from src.parser.html import parse_html, Text, Element

View file

@ -1,7 +1,7 @@
"""Tests for HTTP functionality."""
import pytest
from unittest.mock import Mock, patch, MagicMock
from unittest.mock import Mock, patch
from src.network.url import URL
from src.network import http

View file

@ -1,8 +1,9 @@
# ruff: noqa: E402
"""Tests for layout components."""
import pytest
import sys
from unittest.mock import Mock, patch, MagicMock
from unittest.mock import MagicMock
# Mock skia before importing layout modules
mock_skia = MagicMock()
@ -131,7 +132,12 @@ class TestDocumentLayout:
body = Element("body")
p = Element("p")
# Long text that should wrap
p.children = [Text("This is a very long paragraph that should wrap to multiple lines when the width is narrow enough")]
p.children = [
Text(
"This is a very long paragraph that should wrap to multiple lines "
"when the width is narrow enough"
)
]
body.children = [p]
layout = DocumentLayout(body)

View file

@ -1,6 +1,5 @@
"""Tests for HTML parsing."""
import pytest
from src.parser.html import Text, Element, print_tree

View file

@ -1,8 +1,9 @@
# ruff: noqa: E402
"""Tests for rendering primitives."""
import pytest
import sys
from unittest.mock import Mock, patch, MagicMock
from unittest.mock import MagicMock
# Mock skia before importing render modules
mock_skia = MagicMock()

View file

@ -1,6 +1,5 @@
"""Tests for template rendering."""
import pytest
from src.templates import render_template, render_error_page, render_startpage

View file

@ -1,6 +1,5 @@
"""Tests for URL parsing and resolution."""
import pytest
from src.network.url import URL

View file

@ -1,6 +1,5 @@
"""Tests for URL normalization."""
import pytest
from src.browser.browser import Browser

17
uv.lock
View file

@ -49,7 +49,7 @@ dependencies = [
{ name = "skia-python" },
]
[package.optional-dependencies]
[package.dev-dependencies]
dev = [
{ name = "black" },
{ name = "mypy" },
@ -60,16 +60,19 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "black", marker = "extra == 'dev'", specifier = ">=25.0" },
{ name = "jinja2", specifier = ">=3.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.9.0" },
{ name = "pygobject", specifier = ">=3.54.0" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.0" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.0.0" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" },
{ name = "skia-python", specifier = ">=87.9" },
]
provides-extras = ["dev"]
[package.metadata.requires-dev]
dev = [
{ name = "black", specifier = ">=25.0" },
{ name = "mypy", specifier = ">=1.9.0" },
{ name = "pytest", specifier = ">=9.0.0" },
{ name = "pytest-cov", specifier = ">=7.0.0" },
{ name = "ruff", specifier = ">=0.9.0" },
]
[[package]]
name = "click"