Simple HTML Generation https://minihtml.trendels.name/
at main 190 lines 5.3 kB view raw
1from textwrap import dedent 2 3import pytest 4from pytest import raises as assert_raises 5 6from minihtml import CircularReferenceError, make_prototype, safe, text 7 8div = make_prototype("div") 9span = make_prototype("span", inline=True) 10img = make_prototype("img", inline=True, empty=True, omit_end_tag=True) 11iframe = make_prototype("iframe", empty=True, omit_end_tag=False) 12 13 14def test_prototype_repr(): 15 assert repr(div) == "<PrototypeNonEmpty div>" 16 assert repr(span) == "<PrototypeNonEmpty span (inline)>" 17 assert repr(img) == "<PrototypeEmpty img (inline, omit_end_tag)>" 18 assert repr(iframe) == "<PrototypeEmpty iframe>" 19 20 21def test_element_repr(): 22 assert repr(div()) == "<ElementNonEmpty div>" 23 assert repr(img()) == "<ElementEmpty img>" 24 25 26def test_render_bare_elements(): 27 assert str(div()) == "<div></div>" 28 assert str(img()) == "<img>" 29 assert str(iframe()) == "<iframe></iframe>" 30 31 32def test_positional_args_are_children(): 33 assert str(span(img())) == "<span><img></span>" 34 assert str(span("hello")) == "<span>hello</span>" 35 36 37def test_keyword_args_are_attributes(): 38 assert str(div(title="hello")) == '<div title="hello"></div>' 39 assert str(img(src="hello.png")) == '<img src="hello.png">' 40 41 42@pytest.mark.parametrize( 43 ("kwarg", "attribute"), 44 [ 45 ("foo", "foo"), 46 ("foo_bar", "foo-bar"), 47 ("foo__bar", "foo--bar"), 48 ("_foo", "-foo"), 49 ("__foo", "--foo"), 50 ("foo_", "foo"), 51 ("foo__", "foo"), 52 ("_", "_"), 53 ], 54) 55def test_attribute_name_mangling(kwarg: str, attribute: str): 56 assert str(div(**{kwarg: "test"})) == f'<div {attribute}="test"></div>' 57 assert str(img(**{kwarg: "test"})) == f'<img {attribute}="test">' 58 59 60def test_boolean_attributes(): 61 assert str(div(enabled=True)) == "<div enabled></div>" 62 assert str(div(enabled=False)) == "<div></div>" 63 assert str(div(_=True)) == "<div _></div>" 64 assert str(div(_=False)) == "<div></div>" 65 66 assert str(img(enabled=True)) == "<img enabled>" 67 assert str(img(enabled=False)) == "<img>" 68 assert str(img(_=True)) == "<img _>" 69 assert str(img(_=False)) == "<img>" 70 71 72def test_attribute_values_are_escaped(): 73 assert ( 74 str(div(title='hello"<world>&')) 75 == '<div title="hello&quot;&lt;world&gt;&amp;"></div>' 76 ) 77 78 79@pytest.mark.parametrize( 80 "name", 81 [ 82 "", 83 " ", 84 '"', 85 "'", 86 "/", 87 "=", 88 ">", 89 "a a", 90 'a"a', 91 "a'a", 92 "a/a", 93 "a>a", 94 ], 95) 96def test_attribute_names_are_validated(name: str): 97 with assert_raises(ValueError, match=f"Invalid attribute name: {name!r}"): 98 div(**{name: "test"}) 99 100 with assert_raises(ValueError, match=f"Invalid attribute name: {name!r}"): 101 img(**{name: "test"}) 102 103 104def test_text_contend_is_escaped(): 105 assert str(div('hello"<world>&')) == '<div>hello"&lt;world&gt;&amp;</div>' 106 107 108def test_indexing_adds_class_names(): 109 assert str(div["green"]) == '<div class="green"></div>' 110 assert str(div["green supergreen"]) == '<div class="green supergreen"></div>' 111 assert str(div["green"]["supergreen"]) == '<div class="green supergreen"></div>' 112 assert str(img["myclass"]) == '<img class="myclass">' 113 assert str(img["myclass"]["otherclass"]) == '<img class="myclass otherclass">' 114 115 116def test_indexing_sets_hashtag_id(): 117 assert str(div["#blue"]) == '<div id="blue"></div>' 118 assert str(div["green #blue"]) == '<div id="blue" class="green"></div>' 119 assert str(div["green #blue #red"]) == '<div id="red" class="green"></div>' 120 assert str(img["#my-id"]) == '<img id="my-id">' 121 122 123def test_calling_prototype_in_element_context_adds_child_element(): 124 with div as elem: 125 span("hello") 126 127 assert str(elem) == "<div><span>hello</span></div>" 128 129 with div["myclass"] as elem: 130 span("hello")(" again") 131 132 assert str(elem) == '<div class="myclass"><span>hello again</span></div>' 133 134 with div["myclass"] as elem: 135 img(src="hello.png") 136 137 assert str(elem) == '<div class="myclass"><img src="hello.png"></div>' 138 139 140def test_elements_used_as_positional_args_are_not_added_twice(): 141 with div as elem: 142 div["nested"](span("hello")) 143 144 assert str(elem) == dedent("""\ 145 <div> 146 <div class="nested"><span>hello</span></div> 147 </div>""") 148 149 with div["myclass"] as elem: 150 div["nested"](span("hello")(" again")) 151 152 assert str(elem) == dedent("""\ 153 <div class="myclass"> 154 <div class="nested"><span>hello again</span></div> 155 </div>""") 156 157 158def test_context_managers_can_be_nested(): 159 with div["outer"] as elem: 160 with div, div["inner"]: 161 span("hello") 162 163 assert str(elem) == dedent("""\ 164 <div class="outer"> 165 <div> 166 <div class="inner"><span>hello</span></div> 167 </div> 168 </div>""") 169 170 171def test_context_manager_with_text_content(): 172 with div as elem: 173 text("hello") 174 safe("<!-- this is a comment -->") 175 176 assert str(elem) == "<div>hello<!-- this is a comment --></div>" 177 178 179def test_circular_reference_raises_error_when_rendering(): 180 elem = div() 181 elem(elem) 182 183 with assert_raises(CircularReferenceError): 184 str(elem) 185 186 with div() as elem2: 187 div(elem2) 188 189 with assert_raises(CircularReferenceError): 190 str(elem2)