160 lines
6.7 KiB
Python
160 lines
6.7 KiB
Python
|
|
"""XHTML-MP emission: prolog, head, block mapping, and well-formedness."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from xml.etree import ElementTree
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from wapdown.renderers.xhtmlmp.renderer import XHTML_PROLOG
|
||
|
|
|
||
|
|
|
||
|
|
TWO_SECTIONS = "Intro.\n\n{.card One}\nFirst.\n\n{.card Two}\nSecond.\n"
|
||
|
|
|
||
|
|
|
||
|
|
def parse(markup: str) -> ElementTree.Element:
|
||
|
|
"""Parse the page, proving it is well-formed XML.
|
||
|
|
|
||
|
|
ElementTree honours the DOCTYPE declaration without trying to fetch the
|
||
|
|
external DTD, so this never depends on a network round trip to
|
||
|
|
wapforum.org.
|
||
|
|
"""
|
||
|
|
return ElementTree.fromstring(markup)
|
||
|
|
|
||
|
|
|
||
|
|
class TestDocumentShape:
|
||
|
|
def test_prolog_and_doctype(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("Body.\n")
|
||
|
|
assert markup.startswith(XHTML_PROLOG)
|
||
|
|
assert 'PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"' in markup
|
||
|
|
|
||
|
|
def test_root_element_is_html(self, render_xhtmlmp):
|
||
|
|
# ElementTree namespace-qualifies the tag because of the xhtml xmlns.
|
||
|
|
assert parse(render_xhtmlmp("Body.\n")).tag == "{http://www.w3.org/1999/xhtml}html"
|
||
|
|
|
||
|
|
def test_output_is_well_formed(self, render_xhtmlmp):
|
||
|
|
parse(render_xhtmlmp(TWO_SECTIONS))
|
||
|
|
|
||
|
|
def test_no_carriage_returns(self, render_xhtmlmp):
|
||
|
|
assert "\r" not in render_xhtmlmp(TWO_SECTIONS)
|
||
|
|
|
||
|
|
def test_card_breaks_do_not_split_the_page(self, render_xhtmlmp):
|
||
|
|
# Cards are a WAP 1.x screen-budget concern; XHTML-MP renders one
|
||
|
|
# continuous page and a {.card} marker draws nothing of its own.
|
||
|
|
markup = render_xhtmlmp(TWO_SECTIONS)
|
||
|
|
assert markup.count("<html") == 1
|
||
|
|
assert "First." in markup and "Second." in markup
|
||
|
|
|
||
|
|
def test_title_is_escaped_into_head(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("Body.\n", title="Fish & Chips")
|
||
|
|
assert "<title>Fish & Chips</title>" in markup
|
||
|
|
|
||
|
|
|
||
|
|
class TestBlockMapping:
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
("source", "expected"),
|
||
|
|
[
|
||
|
|
("Just text.\n", "<p>Just text.</p>"),
|
||
|
|
("> quoted\n", "<blockquote><p>quoted</p></blockquote>"),
|
||
|
|
("```\ncode\n```\n", "<pre>code</pre>"),
|
||
|
|
("---\n", "<hr/>"),
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_blocks(self, render_xhtmlmp, source, expected):
|
||
|
|
assert expected in render_xhtmlmp(source)
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("level", [1, 2, 3, 4, 5, 6])
|
||
|
|
def test_headings_use_real_heading_elements(self, render_xhtmlmp, level):
|
||
|
|
markup = render_xhtmlmp(f"{'#' * level} Title\n")
|
||
|
|
assert f"<h{level}>Title</h{level}>" in markup
|
||
|
|
assert "<big>" not in markup # no FIGlet/WML-style banner substitute
|
||
|
|
|
||
|
|
def test_heading_level_beyond_six_clamps(self, render_xhtmlmp):
|
||
|
|
# Markdown itself caps at h6, but a manually-crafted payload
|
||
|
|
# shouldn't be able to emit an invalid element name.
|
||
|
|
markup = render_xhtmlmp("###### Deep\n")
|
||
|
|
assert "<h6>Deep</h6>" in markup
|
||
|
|
|
||
|
|
def test_unordered_list(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("- a\n- b\n")
|
||
|
|
assert "<ul><li>a</li><li>b</li></ul>" in markup
|
||
|
|
|
||
|
|
def test_ordered_list(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("1. a\n2. b\n")
|
||
|
|
assert "<ol><li>a</li><li>b</li></ol>" in markup
|
||
|
|
|
||
|
|
def test_nested_list_is_spliced_into_parent_item(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("- a\n - nested\n- b\n")
|
||
|
|
# The sub-list must sit *inside* its parent <li>, not after it, or
|
||
|
|
# the result is not a valid nested list.
|
||
|
|
assert "<li>a<ul><li>nested</li></ul></li>" in markup
|
||
|
|
parsed = parse(f"<root>{markup[markup.index('<ul'):markup.index('</body>')]}</root>")
|
||
|
|
outer_items = parsed.find("ul").findall("li")
|
||
|
|
assert len(outer_items) == 2
|
||
|
|
assert outer_items[0].find("ul/li").text == "nested"
|
||
|
|
|
||
|
|
def test_blank_line_does_not_split_a_list(self, render_xhtmlmp):
|
||
|
|
# A loose Markdown list (blank line between items) is still one
|
||
|
|
# list, matching the WML renderer's own convention.
|
||
|
|
markup = render_xhtmlmp("- a\n\n- b\n")
|
||
|
|
assert markup.count("<ul>") == 1
|
||
|
|
assert "<li>a</li><li>b</li>" in markup
|
||
|
|
|
||
|
|
def test_paragraph_between_list_items_closes_the_list(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("- a\n\nText.\n\n- b\n")
|
||
|
|
assert markup.count("<ul>") == 2
|
||
|
|
|
||
|
|
def test_table(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("| A | B |\n| --- | --- |\n| 1 | 2 |\n")
|
||
|
|
assert "<table><tr><th>A</th><th>B</th></tr><tr><td>1</td><td>2</td></tr></table>" in markup
|
||
|
|
|
||
|
|
def test_table_is_not_wrapped_in_a_paragraph(self, render_xhtmlmp):
|
||
|
|
# Unlike WML, <table> is valid body-level content in XHTML-MP, so
|
||
|
|
# no <p> wrapper is needed (or wanted).
|
||
|
|
markup = render_xhtmlmp("| A |\n| --- |\n| 1 |\n")
|
||
|
|
assert "<p><table" not in markup
|
||
|
|
|
||
|
|
|
||
|
|
class TestInlineMapping:
|
||
|
|
def test_bold_and_italic(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("**bold** and *italic*\n")
|
||
|
|
assert "<strong>bold</strong>" in markup
|
||
|
|
assert "<em>italic</em>" in markup
|
||
|
|
|
||
|
|
def test_code_span_becomes_code_element(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("Some `code` here.\n")
|
||
|
|
assert "<code>code</code>" in markup
|
||
|
|
|
||
|
|
def test_link(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("[go](http://x.test/)\n")
|
||
|
|
assert '<a href="http://x.test/">go</a>' in markup
|
||
|
|
|
||
|
|
def test_link_label_may_carry_emphasis(self, render_xhtmlmp):
|
||
|
|
# Unlike WML, whose <a> content model is (#PCDATA | br | img)* and
|
||
|
|
# so strips emphasis from the label, XHTML-MP's <a> allows it.
|
||
|
|
markup = render_xhtmlmp("[**go** now](http://x.test/)\n")
|
||
|
|
assert '<a href="http://x.test/"><strong>go</strong> now</a>' in markup
|
||
|
|
|
||
|
|
def test_underscore_in_url_is_not_shredded_into_emphasis(self, render_xhtmlmp):
|
||
|
|
# Regression guard for the stash-before-emphasis ordering: without
|
||
|
|
# it, `/a_b_c` inside the URL would be read as `_b_` -> <em>b</em>.
|
||
|
|
markup = render_xhtmlmp("[link](/a_b_c)\n")
|
||
|
|
assert '<a href="/a_b_c">link</a>' in markup
|
||
|
|
|
||
|
|
def test_asterisk_in_url_is_not_shredded_into_emphasis(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("[link](/a*b*c)\n")
|
||
|
|
assert '<a href="/a*b*c">link</a>' in markup
|
||
|
|
|
||
|
|
def test_image(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp("\n")
|
||
|
|
assert '<img src="pic.png" alt="alt text"/>' in markup
|
||
|
|
|
||
|
|
def test_dollar_sign_is_not_doubled(self, render_xhtmlmp):
|
||
|
|
# XHTML-MP has none of WML's '$' -> '$$' variable-substitution
|
||
|
|
# quirk; a literal dollar sign should pass through as one.
|
||
|
|
markup = render_xhtmlmp("Permit fee: $5\n")
|
||
|
|
assert "Permit fee: $5" in markup
|
||
|
|
|
||
|
|
def test_entities_are_escaped(self, render_xhtmlmp):
|
||
|
|
markup = render_xhtmlmp('Fish & chips < > "\n')
|
||
|
|
assert "Fish & chips < > "" in markup
|