Source code for tests.test_awesome_toc

# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Tests for the removal of the ToC entries awesome-lint forbids."""

from __future__ import annotations

import pytest

from repomatic.awesome_toc import (
    FORBIDDEN_HEADINGS,
    README_RE,
    fix_awesome_toc,
    forbidden_headings_for,
    headings,
    strip_toc_entries,
)

ENGLISH_SECTIONS = (
    "Contents",
    "Mango",
    "Papaya",
    "Lychee",
    "Contributing",
    "Footnotes",
)
"""Section titles of the reference readme, in document order."""

FRENCH_SECTIONS = ("Sommaire", "Mangue", "Papaye", "Litchi", "Contribuer", "Notes")
"""The same sections, translated. Only positions tie one list to the other.

`Contribuer` is why `[tool.typos] default.extend-words` carries an entry for it:
this is the token's only occurrence, and without the entry the unattended
`fix-typos` job proposes "correcting" the French to English. Do not prune the
allowlist entry while this fixture stands.
"""


[docs] def render_readme(sections: tuple[str, ...]) -> str: """Render a readme whose ToC lists every section, as mdformat-toc leaves it.""" entries = "\n".join(f"- [{title}](#{title.lower()})" for title in sections) body = "\n\n".join(f"## {title}\n\nSome prose." for title in sections[1:]) return ( f"## {sections[0]}\n\n" "<!-- mdformat-toc start --slug=github --no-anchors --maxlevel=6 --minlevel=2 -->" f"\n\n{entries}\n\n" "<!-- mdformat-toc end -->\n\n" f"{body}\n" )
[docs] def toc_entries(content: str) -> list[str]: """List the link texts of a readme's ToC block.""" block = content.split("mdformat-toc start")[1].split( "mdformat-toc end", maxsplit=1 )[0] return [ line.strip().removeprefix("- [").split("](")[0] for line in block.splitlines() if line.strip().startswith("- [") ]
[docs] @pytest.mark.parametrize( ("filename", "matches"), ( ("readme.md", True), ("readme.zh.md", True), ("readme.pt-br.md", True), ("readme.txt", False), ("readmemd", False), ("readme.zh.rst", False), ("contributing.md", False), ("my-readme.md", False), ), ) def test_readme_re(filename, matches): """Only a root readme and its translations are rewritten.""" assert bool(README_RE.match(filename)) is matches
[docs] def test_headings_skips_fenced_code(): """A `#` line inside a code fence is a comment, not a heading.""" content = ( "# Recipe\n\n" "```bash\n" "# Peel the mango first.\n" "peel --fruit mango\n" "```\n\n" "## Papaya ##\n" ) assert headings(content) == ["Recipe", "Papaya"]
[docs] def test_strip_toc_entries_leaves_the_rest_of_the_document_alone(): """A list item outside the ToC block is not a ToC entry.""" content = render_readme(ENGLISH_SECTIONS) + "\n- [Contributing](#contributing)\n" updated, removed = strip_toc_entries(content, {"Contributing"}) assert removed == ["Contributing"] assert updated.endswith("- [Contributing](#contributing)\n")
[docs] def test_strip_toc_entries_without_a_toc_block(): """A readme carrying no ToC marker is returned untouched.""" content = "## Mango\n\n- [Contents](#contents)\n" assert strip_toc_entries(content, set(FORBIDDEN_HEADINGS)) == (content, [])
[docs] def test_forbidden_headings_for_maps_positions_onto_a_translation(): """A translated section is forbidden when its English peer is.""" forbidden = forbidden_headings_for( render_readme(FRENCH_SECTIONS), list(ENGLISH_SECTIONS) ) assert forbidden == {*FORBIDDEN_HEADINGS, "Sommaire", "Contribuer", "Notes"}
[docs] def test_forbidden_headings_for_falls_back_on_a_count_mismatch(caplog): """A translation that dropped a section is matched on English names only.""" forbidden = forbidden_headings_for( render_readme(FRENCH_SECTIONS[:-1]), list(ENGLISH_SECTIONS) ) assert forbidden == set(FORBIDDEN_HEADINGS) assert "Heading count differs" in caplog.text
[docs] def test_fix_awesome_toc_strips_reference_and_translation(tmp_path): """The reported bug: a translated ToC keeps the entries the English one loses.""" (tmp_path / "readme.md").write_text( render_readme(ENGLISH_SECTIONS), encoding="UTF-8" ) (tmp_path / "readme.fr.md").write_text( render_readme(FRENCH_SECTIONS), encoding="UTF-8" ) report = fix_awesome_toc(tmp_path) assert report[tmp_path / "readme.md"] == ["Contents", "Contributing", "Footnotes"] assert report[tmp_path / "readme.fr.md"] == ["Sommaire", "Contribuer", "Notes"] assert toc_entries((tmp_path / "readme.md").read_text(encoding="UTF-8")) == [ "Mango", "Papaya", "Lychee", ] assert toc_entries((tmp_path / "readme.fr.md").read_text(encoding="UTF-8")) == [ "Mangue", "Papaye", "Litchi", ]
[docs] def test_fix_awesome_toc_matches_an_untranslated_section_by_name(tmp_path): """A section left in English is still caught, positions or not.""" mixed = ("Contents", "Mangue", "Papaye", "Litchi", "Contribuer", "Footnotes") (tmp_path / "readme.md").write_text( render_readme(ENGLISH_SECTIONS), encoding="UTF-8" ) (tmp_path / "readme.fr.md").write_text(render_readme(mixed), encoding="UTF-8") report = fix_awesome_toc(tmp_path) assert report[tmp_path / "readme.fr.md"] == [ "Contents", "Contribuer", "Footnotes", ]
[docs] def test_fix_awesome_toc_is_idempotent(tmp_path): """Re-running finds nothing left to remove.""" (tmp_path / "readme.md").write_text( render_readme(ENGLISH_SECTIONS), encoding="UTF-8" ) (tmp_path / "readme.fr.md").write_text( render_readme(FRENCH_SECTIONS), encoding="UTF-8" ) assert fix_awesome_toc(tmp_path) before = (tmp_path / "readme.fr.md").read_text(encoding="UTF-8") assert fix_awesome_toc(tmp_path) == {} assert (tmp_path / "readme.fr.md").read_text(encoding="UTF-8") == before
[docs] def test_fix_awesome_toc_without_a_reference_readme(tmp_path, caplog): """A translation alone still loses the entries awesome-lint names.""" mixed = ("Contents", "Mangue", "Papaye", "Litchi", "Contribuer", "Footnotes") (tmp_path / "readme.fr.md").write_text(render_readme(mixed), encoding="UTF-8") report = fix_awesome_toc(tmp_path) assert report[tmp_path / "readme.fr.md"] == ["Contents", "Footnotes"] assert "No readme.md" in caplog.text
[docs] def test_fix_awesome_toc_rewrites_the_reference_first(tmp_path): """The reference is processed before any translation is compared to it.""" for name in ("readme.md", "readme.fr.md", "readme.zh.md"): (tmp_path / name).write_text(render_readme(ENGLISH_SECTIONS), encoding="UTF-8") assert [path.name for path in fix_awesome_toc(tmp_path)] == [ "readme.md", "readme.fr.md", "readme.zh.md", ]