diff --git a/python/tests/integration/test_config.py b/python/tests/integration/test_config.py index 1c5d1f3..011a18f 100644 --- a/python/tests/integration/test_config.py +++ b/python/tests/integration/test_config.py @@ -129,6 +129,33 @@ def test_symlinked_config_anchors_relative_paths_to_its_target( assert not (alias_dir / "site").exists() +@pytest.mark.parametrize( + ("config_name", "watch_path"), + [ + ("mkdocs.yml", "mkdocs.yml"), + ("zensical.toml", "zensical.toml"), + ("mkdocs.yml", "."), + ], +) +def test_build_with_config_in_watch( + tmp_path: Path, config_name: str, watch_path: str +) -> None: + # Include the config directly or through its directory, alongside the docs. + if config_name == "zensical.toml": + config_path = _make_toml_project( + tmp_path, toml_extra=f'watch = ["docs", "{watch_path}"]' + ) + else: + config_path = _make_yml_project( + tmp_path, yml_extra=f'watch: ["docs", "{watch_path}"]' + ) + + zensical.build(str(config_path), {"clean": False, "strict": True}) + + # A successful exit must also produce the documentation page. + assert (tmp_path / "site" / "index.html").is_file() + + def test_navigation_title_precedes_metadata_and_heading(tmp_path: Path) -> None: """Configured titles have the same highest precedence as in MkDocs.""" config = _make_yml_project( diff --git a/python/tests/unit/test_config.py b/python/tests/unit/test_config.py index 0e479ee..8da3b33 100644 --- a/python/tests/unit/test_config.py +++ b/python/tests/unit/test_config.py @@ -146,6 +146,48 @@ def test_site_dir_docs_dir_cant_be_equal(tmp_path: Path) -> None: parse_config(str(config_file)) +@pytest.mark.parametrize( + "watch_path", + [ + "mkdocs.yml", + "./mkdocs.yml", + "{root}/mkdocs.yml", + ".", + "aliases/config.yml", + "aliases", + ], +) +def test_watch_excludes_config_file(tmp_path: Path, watch_path: str) -> None: + # Keep another file with the same name in the extra watched files. + extras = tmp_path / "extras" + extras.mkdir() + other_config = extras / "mkdocs.yml" + other_config.write_text("site_name: Other\n", encoding="utf-8") + + # Cover symlinks as direct watch entries and inside watched directories. + if watch_path.startswith("aliases"): + aliases = tmp_path / "aliases" + aliases.mkdir() + try: + (aliases / "config.yml").symlink_to(tmp_path / "mkdocs.yml") + except OSError as error: + pytest.skip(f"symbolic links unavailable: {error}") + + config_file = _write_mkdocs_config( + tmp_path, + _minimal_yaml( + watch=["extras", watch_path.format(root=tmp_path.as_posix())] + ), + ) + + parsed = parse_config(str(config_file)) + + # The config is already watched by Rust; unrelated files must stay watched. + assert {path for path, _ in parsed["watched_files"]} == { + str(other_config.resolve()) + } + + # --------------------------------------------------------------------------- # Markdown extensions # --------------------------------------------------------------------------- diff --git a/python/zensical/config.py b/python/zensical/config.py index 0fee9ec..2a34244 100644 --- a/python/zensical/config.py +++ b/python/zensical/config.py @@ -696,12 +696,15 @@ def _apply_defaults(config: dict, path: str) -> dict: | _list_watch_files(config, path) # watch ) - # We watch theme directories by default on the Rust side, - # so we need to prevent duplicates from here, in case - # users add theme directories to the watch option + # Rust already watches the theme directories and the config file. theme_files = _list_templates(config) - watched_files -= set(theme_files) - config["watched_files"] = sorted(watched_files) + excluded_files = {Path(path).resolve() for path, _ in theme_files} + excluded_files.add(Path(path).resolve()) + config["watched_files"] = sorted( + (file_path, mtime) + for file_path, mtime in watched_files + if Path(file_path).resolve() not in excluded_files + ) # Hash all templates, so we rebuild if something changes config["template_hash"] = _hash(theme_files)