"""Deck-level WML emission: prolog, head, block mapping, and validity.""" from __future__ import annotations import re from xml.etree import ElementTree import pytest from wapdown.renderers.wml.renderer import WML_PROLOG TWO_SECTIONS = "Intro.\n\n{.card One}\nFirst.\n\n{.card Two}\nSecond.\n" def parse(markup: str) -> ElementTree.Element: """Parse the deck, proving it is well-formed XML. ElementTree honours the DOCTYPE declaration without trying to fetch the external DTD, which matters because wapforum.org has been gone for years. """ return ElementTree.fromstring(markup) class TestDocumentShape: def test_prolog_and_doctype(self, render): markup = render("Body.\n") assert markup.startswith(WML_PROLOG) assert 'PUBLIC "-//WAPFORUM//DTD WML 1.3//EN"' in markup def test_root_element_is_wml(self, render): assert parse(render("Body.\n")).tag == "wml" def test_output_is_well_formed(self, render): parse(render(TWO_SECTIONS)) def test_no_carriage_returns(self, render): # WML is XML served over HTTP, not a DOS text file. assert "\r" not in render(TWO_SECTIONS) def test_ends_with_a_single_newline(self, render): markup = render("Body.\n") assert markup.endswith("\n") class TestCardContentModel: """WML 1.3 declares as (onevent*, timer?, (do | p)*).""" def test_card_children_are_only_do_and_p(self, render): for card in parse(render(TWO_SECTIONS)).findall("card"): assert {child.tag for child in card} <= {"do", "p"} def test_table_is_wrapped_in_a_paragraph(self, render): # Regression: was emitted as a direct child of , which # the DTD does not permit -- it is flow content and belongs in a

. markup = render("| A | B |\n| --- | --- |\n| 1 | 2 |\n") assert "

Just text.

"), ("- item\n", "

- item

"), ("> quoted\n", "

quoted

"), ("---\n", "

------------

"), ], ) def test_blocks(self, render, source, expected): assert expected in render(source) def test_first_heading_is_emphasised_larger(self, render): markup = render("# Title\n\n## Sub\n") assert "

Title

" in markup assert "

Sub

" in markup def test_ordered_list_is_numbered_manually(self, render): # WML has no list element, so the numbers become literal text. markup = render("1. first\n2. second\n") assert "

1. first

" in markup assert "

2. second

" in markup def test_ordered_list_numbering_restarts_after_a_paragraph(self, render): markup = render("1. a\n\nBreak.\n\n1. b\n") assert markup.count("

1. ") == 2 def test_code_block_lines_join_with_breaks(self, render): markup = render("```\none\ntwo\n```\n") assert "

one
two

" in markup def test_code_block_content_is_escaped(self, render): assert "<wml>" in render("```\n\n```\n") def test_table_header_row_is_bold(self, render): markup = render("| A | B |\n| --- | --- |\n| 1 | 2 |\n") assert "
" in markup assert "" in markup def test_ragged_table_rows_are_padded(self, render): markup = render("| A | B | C |\n| --- | --- | --- |\n| 1 |\n") assert '
A1
' in markup assert markup.count("
") % 3 == 0 class TestHead: def test_no_head_by_default(self, render): assert "" not in render("Body.\n") def test_cache_control(self, render): markup = render("Body.\n", cache_control=0) assert '' in markup parse(markup) def test_access_control(self, render): markup = render("Body.\n", access_domain="example.com", access_path="/wap") assert '' in markup parse(markup) def test_head_precedes_template_and_cards(self, render): markup = render(TWO_SECTIONS, cache_control=60) assert [child.tag for child in parse(markup)][:2] == ["head", "template"] class TestTitles: def test_explicit_title_reaches_the_menu_card(self, render): assert '' in render(TWO_SECTIONS, title="Deck") def test_single_card_is_untitled_without_an_explicit_title(self, render): # The document's own heading already renders into the card body. assert '' in render("# Heading\n\nBody.\n") def test_title_is_escaped(self, render): markup = render(TWO_SECTIONS, title='Fish & "Chips"') assert 'title="Fish & "Chips""' in markup parse(markup) def test_dollar_in_a_title_is_doubled(self, render): assert 'title="$$5"' in render(TWO_SECTIONS, title="$5") class TestMultiDeck: def test_one_file_per_section(self, render_output): output = render_output(TWO_SECTIONS, deck_per_card=True) assert sorted(doc.name for doc in output.documents) == [ "index.wml", "one.wml", "two.wml", ] def test_menu_links_across_files(self, render_output): docs = {d.name: d.text for d in render_output(TWO_SECTIONS, deck_per_card=True).documents} assert 'One' in docs["index.wml"] def test_links_within_the_same_file_stay_local(self, render_output): docs = {d.name: d.text for d in render_output(TWO_SECTIONS, deck_per_card=True).documents} assert "index.wml#" not in docs["one.wml"].split("")[-1] def test_every_file_is_well_formed(self, render_output): for doc in render_output(TWO_SECTIONS, deck_per_card=True).documents: parse(doc.text) def test_single_section_document_stays_one_file(self, render_output): output = render_output("# Title\n\nBody.\n", deck_per_card=True) assert len(output.documents) == 1 assert output.documents[0].name is None def test_duplicate_section_titles_get_distinct_filenames(self, render_output): source = "Intro.\n\n{.card Same}\nA.\n\n{.card Same}\nB.\n" names = [d.name for d in render_output(source, deck_per_card=True).documents] assert len(set(names)) == len(names)