diff --git a/tests/test_build_explainers.py b/tests/test_build_explainers.py index 557319a..a41309c 100644 --- a/tests/test_build_explainers.py +++ b/tests/test_build_explainers.py @@ -149,6 +149,53 @@ def test_render_markdown_offsets_heading_levels_by_one(): assert '
Deep
' in html # h6 + 1 stays h6, not h7 +def test_render_markdown_flushes_lists_quotes_rules_and_code(): + script = importlib.import_module("scripts.build_explainers") + source = ( + "Paragraph before a list.\n\n" + "- first item\n- second item\n\n" + "1. numbered item\n\n" + "> quoted line\n\n" + "---\n\n" + "```python\nprint('ok')\n```\n" + ) + + html = script.render_markdown(source, set()) + + assert "

Paragraph before a list.

" in html + assert "" in html + assert "
  1. numbered item
" in html + assert "

quoted line

" in html + assert "
" in html + assert '
print('ok')
' in html + + +def test_resolve_link_target_handles_unknown_markdown_and_project_paths(): + script = importlib.import_module("scripts.build_explainers") + + assert script.resolve_link_target("unknown-topic.md?view=full#intro", {"known-topic"}) == "unknown-topic.html?view=full#intro" + assert script.resolve_link_target("../Benefits%20Denial/", set()) == "../index.html#project-benefits" + assert script.resolve_link_target("../notebooks/example.ipynb", set()) == "../notebooks/example.ipynb" + + +def test_faq_skips_nonparagraph_content_and_truncates_long_why_answer(): + script = importlib.import_module("scripts.build_explainers") + why = "First sentence. " + ("additional context " * 40) + markdown = ( + "## Definition\n\n| Term | Meaning |\n| --- | --- |\n\n" + "A definition paragraph.\n\n" + "## Why It Matters\n\n- a list item\n\n" + f"{why}\n\n## Example\n\nLater text.\n" + ) + + pairs = script._faq(_entry("sample", "Sample Topic"), markdown) + + assert pairs[0] == ("What is Sample Topic?", "A definition paragraph.") + assert pairs[1][0] == "Why does this matter for fairness?" + assert len(pairs[1][1]) <= 501 + assert pairs[1][1].startswith("First sentence.") + + def test_seo_title_uses_the_short_title_and_rejects_a_long_one(): # #1105: a over 65 characters is cut in search results. script = importlib.import_module("scripts.build_explainers") @@ -349,6 +396,15 @@ def test_the_build_refuses_a_missing_or_empty_og_image(site): script.main() +def test_the_build_rejects_a_data_entry_without_a_slug(site): + script, root, entries = site + del entries[0]["slug"] + (root / "assets" / "explainers-data.json").write_text(json.dumps(entries), encoding="utf-8") + + with pytest.raises(KeyError, match="slug"): + script.main() + + def test_the_mirror_drops_a_stale_explainer(site): script, root, _ = site stale = root / "faircode" / "_explainers" / "removed-topic.md" diff --git a/tests/test_check_issue_refs.py b/tests/test_check_issue_refs.py index 05fc53e..3a11dce 100644 --- a/tests/test_check_issue_refs.py +++ b/tests/test_check_issue_refs.py @@ -116,6 +116,51 @@ def test_the_closes_of_a_pull_request_come_from_the_event_payload(tmp_path): assert script.pull_request_closes({"GITHUB_EVENT_NAME": "pull_request", "GITHUB_EVENT_PATH": str(tmp_path / "missing.json")}) == set() +def test_a_malformed_pull_request_event_is_ignored(tmp_path): + payload = tmp_path / "event.json" + payload.write_text("not json", encoding="utf-8") + assert script.pull_request_closes({"GITHUB_EVENT_NAME": "pull_request", "GITHUB_EVENT_PATH": str(payload)}) == set() + + +def test_load_states_skips_when_gh_is_missing_or_the_issue_query_fails(monkeypatch): + monkeypatch.setattr(script.shutil, "which", lambda _name: None) + assert script.load_states(None) is None + + class Failed: + returncode = 1 + + monkeypatch.setattr(script.shutil, "which", lambda _name: "/usr/bin/gh") + monkeypatch.setattr(script.subprocess, "run", lambda *args, **kwargs: Failed()) + assert script.load_states(None) is None + + +def test_load_states_merges_pull_requests_when_available(monkeypatch): + class Result: + def __init__(self, returncode, stdout): + self.returncode = returncode + self.stdout = stdout + + results = iter(( + Result(0, '[{"number": 1, "state": "OPEN"}]'), + Result(0, '[{"number": 2, "state": "MERGED"}]'), + )) + monkeypatch.setattr(script.shutil, "which", lambda _name: "/usr/bin/gh") + monkeypatch.setattr(script.subprocess, "run", lambda *args, **kwargs: next(results)) + assert script.load_states(None) == {1: "open", 2: "merged"} + + +def test_load_states_keeps_issue_states_when_the_pull_request_query_fails(monkeypatch): + class Result: + def __init__(self, returncode, stdout): + self.returncode = returncode + self.stdout = stdout + + results = iter((Result(0, '[{"number": 1, "state": "CLOSED"}]'), Result(1, ""))) + monkeypatch.setattr(script.shutil, "which", lambda _name: "/usr/bin/gh") + monkeypatch.setattr(script.subprocess, "run", lambda *args, **kwargs: next(results)) + assert script.load_states(None) == {1: "closed"} + + def _docs(tmp_path, monkeypatch): issues = tmp_path / "issues.json" issues.write_text(json.dumps([{"number": n, "state": s.upper()} for n, s in STATES.items()]))