Simple HTML Generation https://minihtml.trendels.name/
at main 189 lines 4.9 kB view raw
1from contextvars import ContextVar 2from textwrap import dedent 3 4from minihtml import ( 5 Component, 6 Element, 7 Slots, 8 component, 9 component_scripts, 10 component_styles, 11 template, 12 text, 13) 14from minihtml.tags import body, div, head, html, main, script, style, title 15 16 17def test_template_renders_as_html_with_doctype_and_trailing_newline(): 18 @template() 19 def my_template(message: str) -> Element: 20 return div(message) 21 22 assert my_template("hello").render() == dedent("""\ 23 <!doctype html> 24 <div>hello</div> 25 """) 26 27 28def test_template_can_disable_doctype(): 29 @template() 30 def my_template(message: str) -> Element: 31 return div(message) 32 33 assert my_template("hello").render(doctype=False) == "<div>hello</div>\n" 34 35 36def test_template_with_layout_component(): 37 @component(slots=["title", "content"], default="content") 38 def my_layout(slots: Slots) -> Element: 39 with html as elem: 40 with head, title: 41 slots.slot("title") 42 with body: 43 with div["#content"]: 44 slots.slot() 45 46 return elem 47 48 @template(layout=my_layout) 49 def my_template(layout: Component, message: str) -> None: 50 with layout.slot("title"): 51 text("my title") 52 div(message) 53 54 assert my_template("hello").render() == dedent("""\ 55 <!doctype html> 56 <html> 57 <head> 58 <title>my title</title> 59 </head> 60 <body> 61 <div id="content"> 62 <div>hello</div> 63 </div> 64 </body> 65 </html> 66 """) 67 68 # Test that layout is not cached 69 assert my_template("goodbye").render() == dedent("""\ 70 <!doctype html> 71 <html> 72 <head> 73 <title>my title</title> 74 </head> 75 <body> 76 <div id="content"> 77 <div>goodbye</div> 78 </div> 79 </body> 80 </html> 81 """) 82 83 84def test_template_collects_and_deduplicates_component_styles_and_scripts(): 85 @component( 86 style=style(".my-component { background: #ccc }"), 87 script=[ 88 script("// 1st script goes here"), 89 script("// 2nd script goes here"), 90 ], 91 ) 92 def my_component(slots: Slots) -> Element: 93 return div["my-component"] 94 95 @component( 96 style=style("main { background: #eee }"), 97 script=script("// layout script goes here"), 98 ) 99 def my_layout(slots: Slots) -> Element: 100 with html as elem: 101 with head: 102 component_styles() 103 with body: 104 with main: 105 slots.slot() 106 component_scripts() 107 108 return elem 109 110 @template(layout=my_layout) 111 def my_template(layout: Component) -> None: 112 my_component() 113 my_component() 114 115 assert my_template().render() == dedent("""\ 116 <!doctype html> 117 <html> 118 <head> 119 <style>main { background: #eee }</style> 120 <style>.my-component { background: #ccc }</style> 121 </head> 122 <body> 123 <main> 124 <div class="my-component"></div> 125 <div class="my-component"></div> 126 </main> 127 <script>// layout script goes here</script> 128 <script>// 1st script goes here</script> 129 <script>// 2nd script goes here</script> 130 </body> 131 </html> 132 """) 133 134 135def test_passing_component_styles_and_scripts_as_arguments(): 136 @component( 137 style=style(".my-component { background: #ccc }"), 138 script=[ 139 script("// 1st script goes here"), 140 script("// 2nd script goes here"), 141 ], 142 ) 143 def my_component(slots: Slots) -> Element: 144 return div["my-component"] 145 146 @template() 147 def my_template() -> Element: 148 return html( 149 head( 150 component_styles(), 151 ), 152 body( 153 my_component(), 154 my_component(), 155 component_scripts(), 156 ), 157 ) 158 159 assert my_template().render() == dedent("""\ 160 <!doctype html> 161 <html> 162 <head> 163 <style>.my-component { background: #ccc }</style> 164 </head> 165 <body> 166 <div class="my-component"></div> 167 <div class="my-component"></div> 168 <script>// 1st script goes here</script> 169 <script>// 2nd script goes here</script> 170 </body> 171 </html> 172 """) 173 174 175name_context = ContextVar[str]("name") 176 177 178def test_template_is_rendered_lazily(): 179 @template() 180 def my_template() -> Element: 181 return div(name_context.get()) 182 183 t = my_template() 184 185 name_context.set("fred") 186 assert t.render(doctype=False) == "<div>fred</div>\n" 187 188 name_context.set("barney") 189 assert t.render(doctype=False) == "<div>barney</div>\n"