mews.page/validator/tests/test_lint_dtd.py
randogoth 060d4e00fd refactor: split the repo into self-contained validator, directory, quickstart, spec and site folders
validator/ holds mewslint (lint, css, fetch, DTD/CSS data); directory/ holds
the mews.page service (app, db, check, pages, cli) and depends on validator/;
quickstart/ holds mews-quickstart and md2mews.py for third parties who want
to create Mews content without cloning the repo; spec/ consolidates doc/ and
dtd/ with the canonical mews-0.1.css; site/ becomes a Pelican project with
the forward-facing pages as Markdown, the check form kept as a raw HTML
snippet. Root pyproject.toml is now a virtual uv workspace over validator,
directory and quickstart, with cross-cutting tests that span more than one
of them staying at the repo root. Makefile and flake.nix updated to match.
2026-10-13 12:29:23 +03:00

78 lines
2.7 KiB
Python

"""The DTD check, and the mapping from libxml2's messages to spec sections.
One test per message shape, so the mapping table in mews.lint stays pinned to
what libxml2 actually says.
"""
import pytest
from mews.lint import validate_bytes
# fixture name -> (section, level, code)
CASES = {
"script": ("4.1", "must", "dtd-element"),
"div": ("4.1", "must", "dtd-element"),
"sup": ("4.1", "must", "dtd-element"),
"style-element": ("5", "must", "dtd-styling"),
"style-attr": ("5", "must", "dtd-styling"),
"class-on-p": ("4.1", "must", "dtd-attribute"),
"img-width": ("4.1", "must", "dtd-attribute"),
"img-no-alt": ("4.1", "must", "dtd-required-attribute"),
"input-type-email": ("4.1", "must", "dtd-value"),
"nested-table": ("4.2", "must", "dtd-nested-table"),
"table-in-li": ("4.2", "must", "dtd-nested-table"),
"bad-body-class": ("5.3", "must", "dtd-variant"),
"empty-body": ("4.1", "must", "dtd-content"),
"head-link-before-meta": ("3.3", "must", "dtd-head"),
"link-bad-rel": ("3.3", "must", "link-rel"),
}
@pytest.mark.parametrize(("name", "expected"), CASES.items())
def test_dtd_failure_maps_to_its_section(fail_page, name, expected):
report = validate_bytes(fail_page(name))
section, level, code = expected
match = [f for f in report.findings if f.code == code]
assert match, f"{name}: no {code} finding in {[f.code for f in report.findings]}"
assert match[0].section == section
assert match[0].level == level
assert not report.conforms
def test_one_mistake_is_one_finding(fail_page):
"""A <script> makes libxml2 say three things; the author sees one."""
report = validate_bytes(fail_page("script"))
assert [f.code for f in report.findings] == ["dtd-element"]
def test_findings_carry_a_line_number(fail_page):
report = validate_bytes(fail_page("div"))
assert report.findings[0].location.startswith("line ")
def test_messages_name_the_element(fail_page):
report = validate_bytes(fail_page("div"))
assert "<div>" in report.findings[0].message
def test_enumerated_values_come_from_the_dtd(fail_page):
report = validate_bytes(fail_page("input-type-email"))
message = report.findings[0].message
assert "text, password, checkbox, radio, submit, reset, hidden" in message
def test_unmapped_message_still_gets_a_section():
from mews.lint import _map_error
code, message = _map_error("Something libxml2 made up", set())
assert code == "dtd-other"
assert "Mews DTD" in message
def test_entities_the_doctype_defines_are_resolved(conforming):
report = validate_bytes(conforming(body="<p>a&nbsp;b&mdash;c</p>"))
assert report.conforms
def test_a_conforming_page_passes(conforming):
assert validate_bytes(conforming()).conforms