Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
- uses: astral-sh/setup-uv@v7
- run: uv python install ${{ matrix.python-version }}
- run: uv sync --all-extras --dev
- run: uv build --wheel -q # generates _templates.py via build hook
- run: uv run coverage run -m pytest -q && uv run coverage report --fail-under=90

lint:
Expand All @@ -25,5 +26,6 @@ jobs:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v7
- run: uv sync --all-extras --dev
- run: uv build --wheel -q # generates _templates.py via build hook
- run: uv run ruff check .
- run: uv run ruff format --check .
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ dist/
wheels/
*.egg-info

# Cache
.cache

# Virtual environments
.venv

Expand All @@ -18,4 +21,7 @@ uv.lock
# Coverage
.coverage
htmlcov/
*,cover
*,cover

# Generated files
jinjatest/coverage/_templates.py
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
lint:
uv run ruff check .
uv run ruff format --check .
uv run ty check .

lint-fix:
uv run ruff check --fix .
Expand Down
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Stop writing brittle substring assertions. Test your templates with structure, v
## Installation

```bash
pip install jinjatest
uv add jinjatest
```

With YAML support:
```bash
pip install jinjatest[yaml]
uv add jinjatest[yaml]
```

## Why jinjatest?
Expand Down Expand Up @@ -403,6 +403,48 @@ spec = TemplateSpec.from_file("template.j2")
spec.assert_variables_subset_of({"user_name", "plan", "items"})
```

## Template Coverage

Track branch coverage for your Jinja templates to ensure all conditional paths are tested.

### Quick Start

```bash
pytest --jt-cov
```

### CLI Options

| Option | Description |
|--------|-------------|
| `--jt-cov` | Enable template coverage |
| `--jt-cov-fail-under=N` | Fail if coverage below N% |
| `--jt-cov-report=TYPE` | Report type: `term`, `term-missing`, `term-verbose`, `html`, `json`, `xml` |
| `--jt-cov-html=DIR` | HTML report directory |
| `--jt-cov-json=FILE` | JSON report file |
| `--jt-cov-xml=FILE` | JUnit XML report file |
| `--jt-cov-exclude=PATTERN` | Glob pattern to exclude templates |

### Configuration (pyproject.toml)

```toml
[tool.jinjatest.coverage]
enabled = true
fail_under = 80
report = ["term", "html"]
html_dir = "jt-htmlcov"
exclude_patterns = ["**/vendor/**", "*.partial.j2"]
```

### What's Tracked

- `{% if %}` / `{% elif %}` / `{% else %}` branches
- `{% for %}` loops (body and else)
- `{% macro %}` definitions
- `{% block %}` definitions (template inheritance)
- `{% include %}` statements
- Ternary expressions (`{{ x if cond else y }}`)

## License

MIT
1 change: 1 addition & 0 deletions jinjatest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def test_welcome_pro_user():
"TemplateMarkers",
# Utilities
"normalize_text",
# Coverage (lazy import via jinjatest.coverage)
]

# Optional YAML exports
Expand Down
75 changes: 75 additions & 0 deletions jinjatest/coverage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Jinja template branch coverage tracking.

This module provides automatic branch coverage tracking for Jinja templates.

Example:
from jinjatest.coverage import (
get_coverage_collector,
CoverageReporter,
ReportConfig,
)

# Enable coverage collection
collector = get_coverage_collector()
collector.enable()

# ... run tests with TemplateSpec ...

# Generate reports
summary = collector.get_summary()
reporter = CoverageReporter(ReportConfig(fail_under=80))
reporter.terminal_report(summary)
"""

from jinjatest.coverage.collector import (
CoverageCollector,
CoverageSummary,
get_coverage_collector,
reset_coverage_collector,
set_coverage_collector,
)
from jinjatest.coverage.discovery import (
BranchDiscovery,
BranchInfo,
DiscoveryResult,
)
from jinjatest.coverage.instrumenter import (
AutoInstrumenter,
InstrumentationResult,
)
from jinjatest.coverage.reporter import (
CoverageReporter,
HTMLReporter,
JSONReporter,
JUnitReporter,
ReportConfig,
TerminalReporter,
)
from jinjatest.coverage.tracker import (
BranchCoverage,
TemplateCoverage,
TemplateCoverageStats,
)

__all__ = [
"CoverageCollector",
"CoverageSummary",
"get_coverage_collector",
"set_coverage_collector",
"reset_coverage_collector",
"BranchDiscovery",
"BranchInfo",
"DiscoveryResult",
"AutoInstrumenter",
"InstrumentationResult",
"TemplateCoverage",
"TemplateCoverageStats",
"BranchCoverage",
"CoverageReporter",
"TerminalReporter",
"JSONReporter",
"HTMLReporter",
"JUnitReporter",
"ReportConfig",
]
Loading