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
17 changes: 7 additions & 10 deletions tests/test_check_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
)
from great_docs._layout import Layout
from great_docs.cli import cli
from tests.support import mock_config

requires_quarto = pytest.mark.skipif(
_check_quarto_available() is not None,
Expand Down Expand Up @@ -1736,18 +1737,15 @@ def test_no_config_file(self, tmp_path):
def test_no_reference_enabled(self, tmp_path):
(tmp_path / "great-docs.yml").write_text("display_name: Test\n")
(tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n')
mock_config = MagicMock()
mock_config.reference_enabled = False
with patch("great_docs.config.Config", return_value=mock_config):
config = mock_config(reference_enabled=False)
with patch("great_docs.config.Config", return_value=config):
result = extract_docstring_examples(tmp_path)
assert result == []

def test_no_package_detected(self, tmp_path):
(tmp_path / "great-docs.yml").write_text("display_name: Test\n")
mock_config = MagicMock()
mock_config.reference_enabled = True
mock_config.reference = [{"contents": ["Foo"]}]
with patch("great_docs.config.Config", return_value=mock_config):
config = mock_config(reference_enabled=True, reference=[{"contents": ["Foo"]}])
with patch("great_docs.config.Config", return_value=config):
result = extract_docstring_examples(tmp_path)
assert result == []

Expand All @@ -1756,9 +1754,8 @@ def test_reads_config_from_docs_layout(self, tmp_path):
(tmp_path / "docs").mkdir()
(tmp_path / "docs" / "great-docs.yml").write_text("display_name: Test\n")
layout = Layout.make(tmp_path)
mock_config = MagicMock()
mock_config.reference_enabled = False
with patch("great_docs.config.Config", return_value=mock_config) as config_cls:
config = mock_config(reference_enabled=False)
with patch("great_docs.config.Config", return_value=config) as config_cls:
result = extract_docstring_examples(tmp_path, layout=layout)
assert result == []
assert config_cls.call_args.kwargs["config_path"] == layout.config_path
Expand Down
448 changes: 214 additions & 234 deletions tests/test_config.py

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions tests/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
run_lint,
)
from great_docs._utils import QUARTO_YML_HEADER
from tests.support import mock_config


@pytest.mark.parametrize("directory", [".", "docs", "website"])
Expand Down Expand Up @@ -239,8 +240,7 @@ def _mock_docs() -> MagicMock:
prevent page checks from treating mocks as source paths.
"""
docs = MagicMock()
docs._config.seo_enabled = False
docs._config.sections = []
docs._config = mock_config(seo_enabled=False, sections=[])
docs._find_user_guide_dir.return_value = None
docs._get_custom_page_sources.return_value = []
docs._find_index_source_file.return_value = (None, None)
Expand Down Expand Up @@ -925,7 +925,6 @@ def test_successful_lint_run(self, mock_gd_cls, mock_griffe_load, tmp_path):
mock_gd._detect_package_name.return_value = "mypkg"
mock_gd._resolve_importable_name.return_value = "mypkg"
mock_gd._get_package_exports.return_value = ["func_a", "func_b"]
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
mock_gd_cls.return_value = mock_gd

Expand Down Expand Up @@ -955,7 +954,6 @@ def test_ambiguity_check_respects_the_renderer_member_selection(
mock_gd._detect_package_name.return_value = "mypkg"
mock_gd._resolve_importable_name.return_value = "mypkg"
mock_gd._get_package_exports.return_value = ["StoreCache", "NetCache"]
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
# The renderer documents NetCache itself but, per its `members: false`
# config, none of its members — only StoreCache.flush gets a page.
Expand Down Expand Up @@ -999,7 +997,6 @@ def test_an_unresolvable_reference_reads_no_sources(
mock_gd._detect_package_name.return_value = "mypkg"
mock_gd._resolve_importable_name.return_value = "mypkg"
mock_gd._get_package_exports.return_value = ["func_a"]
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
mock_gd.documented_objects.return_value = []
mock_gd_cls.return_value = mock_gd
Expand All @@ -1024,7 +1021,6 @@ def test_resolves_module_name_when_project_name_differs(
mock_gd._detect_package_name.return_value = "my-dist"
mock_gd._resolve_importable_name.return_value = "actual_module"
mock_gd._get_package_exports.return_value = ["func_a"]
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
mock_gd_cls.return_value = mock_gd

Expand All @@ -1049,7 +1045,6 @@ def test_selective_checks(self, mock_gd_cls, mock_griffe_load, tmp_path):
mock_gd._detect_module_name.return_value = None
mock_gd._normalize_package_name.return_value = "mypkg"
mock_gd._get_package_exports.return_value = ["func_a"]
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
mock_gd_cls.return_value = mock_gd

Expand Down Expand Up @@ -1106,7 +1101,6 @@ def test_no_exports(self, mock_gd_cls, mock_griffe_load, tmp_path):
mock_gd._detect_package_name.return_value = "mypkg"
mock_gd._normalize_package_name.return_value = "mypkg"
mock_gd._get_package_exports.return_value = None
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
mock_gd_cls.return_value = mock_gd

Expand Down Expand Up @@ -1165,7 +1159,6 @@ def test_quiet_suppresses_output(self, mock_gd_cls, mock_griffe_load, tmp_path,
mock_gd._detect_package_name.return_value = "mypkg"
mock_gd._normalize_package_name.return_value = "mypkg"
mock_gd._get_package_exports.return_value = ["func_a"]
mock_gd._config.get.return_value = "numpy"
mock_gd._config.__getitem__.return_value = "numpy"
mock_gd_cls.return_value = mock_gd

Expand Down
12 changes: 4 additions & 8 deletions tests/test_markdown_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,22 @@

from great_docs._website import markdown_pages
from great_docs.config import Config


def _config(tmp_path: Path, yaml: str = "") -> Config:
(tmp_path / "great-docs.yml").write_text(yaml, encoding="utf-8")
return Config(tmp_path)
from tests.support import write_config


def test_adds_the_filter_after_existing_filters(tmp_path):
quarto: dict = {"filters": ["interlinks"]}
markdown_pages.register(quarto, _config(tmp_path))
markdown_pages.register(quarto, write_config(tmp_path))
assert quarto["filters"] == ["interlinks", "markdown-pages"]


def test_adds_nothing_when_disabled(tmp_path):
quarto: dict = {}
markdown_pages.register(quarto, _config(tmp_path, "markdown_pages: false\n"))
markdown_pages.register(quarto, write_config(tmp_path, "markdown_pages: false\n"))
assert quarto == {}


def test_never_adds_the_filter_twice(tmp_path):
quarto: dict = {"filters": ["markdown-pages"]}
markdown_pages.register(quarto, _config(tmp_path))
markdown_pages.register(quarto, write_config(tmp_path))
assert quarto["filters"] == ["markdown-pages"]
19 changes: 10 additions & 9 deletions tests/test_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
list_tools,
read_resource,
)
from tests.support import mock_config


def test_tool_schemas_accept_config_selection() -> None:
Expand Down Expand Up @@ -356,12 +357,10 @@ def test_with_config_shows_configuration_checkmark(
monkeypatch.chdir(tmp_path)
mock_docs = MagicMock()
mock_docs._detect_package_name.return_value = "mypackage"
mock_docs._config.cli_enabled = False
mock_docs._config.mcp_enabled = False
mock_docs._config = mock_config(cli_enabled=False, mcp_enabled=False, sections=[])
mock_docs._config.__getitem__ = lambda self, key: (
[] if key in ("user_guide", "versions") else None
)
mock_docs._config.sections = []
with patch("great_docs.mcp._get_great_docs", return_value=mock_docs):
result = asyncio.run(_handle_status({}))
assert "✓" in result[0].text
Expand Down Expand Up @@ -569,7 +568,7 @@ def test_with_function_exports(self, tmp_path: Path, monkeypatch: pytest.MonkeyP
"functions": ["do_thing", "helper"],
"classes": [],
}
mock_docs._config.reference = []
mock_docs._config = mock_config(reference=[])
with patch("great_docs.mcp._get_great_docs", return_value=mock_docs):
result = asyncio.run(_handle_scan({}))
assert "do_thing" in result[0].text
Expand All @@ -586,7 +585,7 @@ def test_with_class_exports_and_verbose(self, tmp_path: Path, monkeypatch: pytes
"classes": ["MyClass"],
"class_method_names": {"MyClass": ["method_a"]},
}
mock_docs._config.reference = []
mock_docs._config = mock_config(reference=[])
with patch("great_docs.mcp._get_great_docs", return_value=mock_docs):
result = asyncio.run(_handle_scan({"verbose": True}))
assert "MyClass" in result[0].text
Expand All @@ -602,7 +601,7 @@ def test_reference_config_marks_configured_items(
mock_docs._normalize_package_name.return_value = "mypkg"
mock_docs._get_package_exports.return_value = ["do_thing"]
mock_docs._categorize_api_objects.return_value = {"functions": ["do_thing"]}
mock_docs._config.reference = [{"contents": ["do_thing"]}]
mock_docs._config = mock_config(reference=[{"contents": ["do_thing"]}])
with patch("great_docs.mcp._get_great_docs", return_value=mock_docs):
result = asyncio.run(_handle_scan({}))
assert "[x]" in result[0].text
Expand Down Expand Up @@ -671,10 +670,12 @@ def _mock_docs(self, pkg="mypkg", features=None):
mock = MagicMock()
mock._detect_package_name.return_value = pkg
cfg = features or {}
mock._config.cli_enabled = cfg.get("cli", False)
mock._config.mcp_enabled = cfg.get("mcp", False)
mock._config = mock_config(
cli_enabled=cfg.get("cli", False),
mcp_enabled=cfg.get("mcp", False),
sections=cfg.get("sections", []),
)
mock._config.__getitem__ = lambda self, key: cfg.get(key, [])
mock._config.sections = cfg.get("sections", [])
return mock

def test_shows_package_name(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
Expand Down
22 changes: 9 additions & 13 deletions tests/test_nav_icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@

from great_docs._icons import LUCIDE_ICONS, get_icon_svg, list_icons
from great_docs.config import Config


def _make_config(tmp_path: Path, yaml_text: str) -> Config:
(tmp_path / "great-docs.yml").write_text(yaml_text, encoding="utf-8")
return Config(tmp_path)
from tests.support import write_config


class TestLucideIcons:
Expand Down Expand Up @@ -93,11 +89,11 @@ def test_default_is_none(self, tmp_path: Path):
assert cfg.nav_icons is None

def test_false_is_none(self, tmp_path: Path):
cfg = _make_config(tmp_path, "nav_icons: false\n")
cfg = write_config(tmp_path, "nav_icons: false\n")
assert cfg.nav_icons is None

def test_navbar_only(self, tmp_path: Path):
cfg = _make_config(
cfg = write_config(
tmp_path,
"nav_icons:\n navbar:\n User Guide: book-open\n Reference: code-2\n",
)
Expand All @@ -108,7 +104,7 @@ def test_navbar_only(self, tmp_path: Path):
assert icons["navbar"]["Reference"] == "code-2"

def test_sidebar_only(self, tmp_path: Path):
cfg = _make_config(
cfg = write_config(
tmp_path,
"nav_icons:\n sidebar:\n Getting Started: rocket\n",
)
Expand All @@ -118,7 +114,7 @@ def test_sidebar_only(self, tmp_path: Path):
assert icons["sidebar"]["Getting Started"] == "rocket"

def test_both_navbar_and_sidebar(self, tmp_path: Path):
cfg = _make_config(
cfg = write_config(
tmp_path,
"nav_icons:\n navbar:\n Recipes: chef-hat\n sidebar:\n Quick Start: rocket\n",
)
Expand All @@ -128,19 +124,19 @@ def test_both_navbar_and_sidebar(self, tmp_path: Path):
assert icons["sidebar"]["Quick Start"] == "rocket"

def test_empty_dict_returns_none(self, tmp_path: Path):
cfg = _make_config(tmp_path, "nav_icons:\n foo: bar\n")
cfg = write_config(tmp_path, "nav_icons:\n foo: bar\n")
# No 'navbar' or 'sidebar' keys -> None
assert cfg.nav_icons is None

def test_nav_icons_navbar_property(self, tmp_path: Path):
cfg = _make_config(
cfg = write_config(
tmp_path,
"nav_icons:\n navbar:\n Changelog: history\n",
)
assert cfg.nav_icons_navbar == {"Changelog": "history"}

def test_nav_icons_sidebar_property(self, tmp_path: Path):
cfg = _make_config(
cfg = write_config(
tmp_path,
"nav_icons:\n sidebar:\n Config: settings\n",
)
Expand All @@ -156,7 +152,7 @@ def test_nav_icons_sidebar_empty_when_not_configured(self, tmp_path: Path):

def test_values_coerced_to_strings(self, tmp_path: Path):
# Ensure non-string YAML values get coerced
cfg = _make_config(
cfg = write_config(
tmp_path,
"nav_icons:\n navbar:\n 123: home\n",
)
Expand Down
37 changes: 9 additions & 28 deletions tests/test_page_dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,7 @@

from great_docs._website.page_dates import copy_portraits, head_tags, site_authors
from great_docs.config import Config


def _config(project: Path, yaml: str = "show_dates: true\n") -> Config:
"""
Write and load a project configuration

Parameters
----------
project
Project directory.
yaml
Contents of `great-docs.yml`.

Returns
-------
:
The project configuration.
"""
(project / "great-docs.yml").write_text(yaml, encoding="utf-8")
return Config(project)
from tests.support import write_config


def _tags(source: Path, config: Config, authors: list[dict] | None = None) -> dict[str, str]:
Expand Down Expand Up @@ -87,7 +68,7 @@ def test_front_matter_dates_win_over_git(tmp_path: Path):
encoding="utf-8",
)

tags = _tags(page, _config(tmp_path))
tags = _tags(page, write_config(tmp_path, "show_dates: true\n"))

assert 'content="2026-03-15T10:30:00Z"' in tags["gd-page-modified"]
assert 'content="2025-06-01T00:00:00"' in tags["gd-page-created"]
Expand All @@ -104,7 +85,7 @@ def test_committed_page_takes_dates_from_git(tmp_path: Path):
_git(repo, "add", ".")
_git(repo, "commit", "-q", "-m", "Add page")

tags = _tags(page, _config(tmp_path))
tags = _tags(page, write_config(tmp_path, "show_dates: true\n"))

# Older Git writes UTC as `+00:00`, newer Git as `Z`
committed = datetime(2024, 5, 6, 7, 8, 9, tzinfo=timezone.utc)
Expand All @@ -119,7 +100,7 @@ def test_untracked_page_takes_modified_date_from_mtime(tmp_path: Path):
page = tmp_path / "page.qmd"
page.write_text("Text\n", encoding="utf-8")

tags = _tags(page, _config(tmp_path))
tags = _tags(page, write_config(tmp_path, "show_dates: true\n"))

assert set(tags) == {"gd-page-modified"}

Expand Down Expand Up @@ -147,7 +128,7 @@ def test_author_details_come_from_configured_authors(
page = tmp_path / "page.qmd"
page.write_text("---\nauthor: Ada\n---\n", encoding="utf-8")

tags = _tags(page, _config(tmp_path), [entry])
tags = _tags(page, write_config(tmp_path, "show_dates: true\n"), [entry])

assert 'content="Ada"' in tags["gd-page-author"]
assert f'content="{image}"' in tags["gd-page-author-image"]
Expand All @@ -160,7 +141,7 @@ def test_author_details_come_from_configured_authors(
def test_show_author_false_drops_author_tags(tmp_path: Path):
page = tmp_path / "page.qmd"
page.write_text("---\nauthor: Ada\n---\n", encoding="utf-8")
config = _config(tmp_path, "show_dates: true\nshow_author: false\n")
config = write_config(tmp_path, "show_dates: true\nshow_author: false\n")

tags = _tags(page, config, [{"name": "Ada", "github": "ada"}])

Expand All @@ -171,7 +152,7 @@ def test_author_name_is_escaped(tmp_path: Path):
page = tmp_path / "page.qmd"
page.write_text("---\nauthor: 'O''Brien & \"Co\"'\n---\n", encoding="utf-8")

tags = _tags(page, _config(tmp_path))
tags = _tags(page, write_config(tmp_path, "show_dates: true\n"))

assert tags["gd-page-author"] == (
'<meta name="gd-page-author" content="O&#x27;Brien &amp; &quot;Co&quot;">'
Expand All @@ -182,14 +163,14 @@ def test_show_dates_off_builds_no_tags(tmp_path: Path):
page = tmp_path / "page.qmd"
page.write_text("Text\n", encoding="utf-8")

assert head_tags(page, _config(tmp_path, "show_dates: false\n"), []) == []
assert head_tags(page, write_config(tmp_path, "show_dates: false\n"), []) == []


def test_local_portrait_is_copied_and_addressed_on_the_site(tmp_path: Path):
"""Preserve query strings, fragments and remote portrait URLs"""
(tmp_path / "img").mkdir()
(tmp_path / "img" / "ada.png").write_bytes(b"png")
config = _config(
config = write_config(
tmp_path,
"show_dates: true\nauthors:\n"
" - {name: Ada, image: 'img/ada.png?v=2#face'}\n"
Expand Down
Loading
Loading