Skip to content
Closed
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
47 changes: 47 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Run tests

on:
push:
branches: [main]
paths:
- 'action.yml'
- 'docs/*.py'
- 'docs/action.yml'
- 'tests/**'
- 'pyproject.toml'
- 'uv.lock'
- '.github/workflows/run-tests.yml'
pull_request:
branches: [main]
paths:
- 'action.yml'
- 'docs/*.py'
- 'docs/action.yml'
- 'tests/**'
- 'pyproject.toml'
- 'uv.lock'
- '.github/workflows/run-tests.yml'

permissions: {}

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0

- name: Run tests
run: uv run --no-dev --group test coverage run -m pytest -v

- name: Report coverage
run: |
uv run --no-dev --group test coverage report
uv run --no-dev --group test coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY"
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,23 @@ docs = [
"mkdocs-material>=9.7.7",
"pyyaml>=6.0.3",
]
test = [
# the tests exercise the scripts in docs/, so they need the docs dependencies
{ include-group = "docs" },
"coverage>=7.16.2",
"pytest>=9.1.1",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
# the docs scripts are not a package; let the tests import them by module name
pythonpath = ["docs"]
addopts = ["-ra", "--strict-config", "--strict-markers"]
filterwarnings = ["error"]

[tool.coverage.run]
branch = true
source = ["docs"]

[tool.coverage.report]
show_missing = true
115 changes: 115 additions & 0 deletions tests/test_badge_hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Tests for docs/badge_hook.py, the mkdocs hook that renders ``<!-- md:... -->`` badges."""

from unittest.mock import MagicMock

import badge_hook
import pytest

RELEASES = "https://github.com/cpp-linter/cpp-linter-action/releases/"


def render(markdown: str) -> str:
return badge_hook.on_page_markdown(
markdown, page=MagicMock(), config=MagicMock(), files=MagicMock()
)


def test_text_without_badges_is_unchanged():
markdown = "# Title\n\n<!-- a regular comment -->\nSome `code`.\n"
assert render(markdown) == markdown


def test_version_badge_links_to_the_release_tag():
href = RELEASES + "v1.2.0"
assert render("<!-- md:version 1.2.0 -->") == (
'<span class="mdx-badge">'
f'<span class="mdx-badge__icon">[:material-tag-outline:]({href} "minimum version")</span>'
f'<span class="mdx-badge__text">[1.2.0]({href} "minimum version")</span>'
"</span>"
)


def test_version_badge_keeps_a_non_numeric_ref():
assert f"({RELEASES}latest " in render("<!-- md:version latest -->")
assert "/vlatest" not in render("<!-- md:version latest -->")


def test_default_badge_shows_the_value_as_yaml():
assert render("<!-- md:default 'llvm' -->") == (
'<span class="mdx-badge">'
'<span class="mdx-badge__icon">Default</span>'
"<span class=\"mdx-badge__text\">`#!yaml 'llvm'`</span>"
"</span>"
)


def test_permission_badge_links_to_the_permission_section():
assert render("<!-- md:permission contents: read #file-changes -->") == (
'<span class="mdx-badge">'
'<span class="mdx-badge__icon">'
'[:material-lock:](permissions.md#file-changes "required permissions")</span>'
'<span class="mdx-badge__text">'
'[`#!yaml contents: read`](permissions.md#file-changes "required permission")</span>'
"</span>"
)


def test_permission_badge_without_anchor_links_to_the_page():
rendered = render("<!-- md:permission pull-requests: write -->")
assert '(permissions.md "required permissions")' in rendered
assert "[`#!yaml pull-requests: write`](permissions.md " in rendered


def test_experimental_flag_badge():
assert render("<!-- md:flag experimental -->") == (
'<span class="mdx-badge">'
'<span class="mdx-badge__icon">:material-flask-outline:{ .mdx-badge--heart }</span>'
'<span class="mdx-badge__text">experimental</span>'
"</span>"
)


def test_badges_are_replaced_on_every_line():
rendered = render(
"### `style`\n\n"
"<!-- md:version 1.2.0 -->\n"
"<!-- MD:default 'llvm' -->\n" # the marker is matched case-insensitively
"\nThe style rules to use.\n"
)
assert "<!--" not in rendered
assert rendered.startswith("### `style`\n\n<span")
assert rendered.endswith("</span>\n\nThe style rules to use.\n")
assert rendered.count('<span class="mdx-badge">') == 2


def test_unknown_badge_type_is_an_error():
with pytest.raises(RuntimeError, match="Unknown badge type: deprecated"):
render("<!-- md:deprecated 2.0.0 -->")


def test_unsupported_flag_is_an_error():
with pytest.raises(ValueError, match="Unsupported badge flag: beta"):
render("<!-- md:flag beta -->")


def test_permission_without_a_name_is_an_error():
with pytest.raises(ValueError, match="failed to parse permissions from #anchor"):
render("<!-- md:permission #anchor -->")


@pytest.mark.parametrize(
("icon", "text", "expected"),
[
(
"i",
"t",
'<span class="mdx-badge__icon">i</span><span class="mdx-badge__text">t</span>',
),
("i", "", '<span class="mdx-badge__icon">i</span>'),
("", "t", '<span class="mdx-badge__text">t</span>'),
("", "", ""),
],
ids=["icon-and-text", "icon-only", "text-only", "empty"],
)
def test_badge_omits_empty_parts(icon: str, text: str, expected: str):
assert badge_hook._badge(icon, text) == f'<span class="mdx-badge">{expected}</span>'
Loading
Loading