Coverage for tdom / svg_test.py: 100%
63 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-03 21:23 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-03 21:23 +0000
1from string.templatelib import Template
3from tdom import html, svg
5# svg() — tag case-fixing
8def test_svg_clippath_case_fixed():
9 result = svg(t"<clipPath id='mask'></clipPath>")
10 assert result == '<clipPath id="mask"></clipPath>'
13def test_svg_lineargradient_case_fixed():
14 result = svg(t"<linearGradient id='grad'></linearGradient>")
15 assert result == '<linearGradient id="grad"></linearGradient>'
18def test_svg_femergenode_self_closing_case_fixed():
19 result = svg(t"<feMergeNode />")
20 assert result == "<feMergeNode></feMergeNode>"
23def test_svg_nested_tags_case_fixed():
24 result = svg(t"<g><clipPath id='c'><rect /></clipPath></g>")
25 assert result == '<g><clipPath id="c"><rect></rect></clipPath></g>'
28# ------------------------------
29# svg() — attribute case-fixing
30# ------------------------------
33def test_svg_viewbox_attr_case_fixed():
34 result = svg(t'<rect viewBox="0 0 10 10" />')
35 assert result == '<rect viewBox="0 0 10 10"></rect>'
38def test_svg_case_sensitivity():
39 # SVG attributes like viewBox are case-sensitive
40 result = html(t'<svg viewBox="0 0 100 100"></svg>')
41 # We expect viewBox, not viewbox
42 assert "viewBox" in result
45def test_svg_tag_case_sensitivity():
46 # SVG tags like linearGradient are case-sensitive
47 result = html(t"<svg><linearGradient></linearGradient></svg>")
48 assert "linearGradient" in result
51def test_svg_tag_case_sensitivity_outside_svg():
52 # Outside SVG, tags should be lowercased
53 result = html(t"<linearGradient></linearGradient>")
54 assert "lineargradient" in result
57def test_svg_attr_case_sensitivity_outside_svg():
58 # Outside SVG, attributes should be lowercased
59 result = html(t'<div viewBox="0 0 100 100"></div>')
60 assert "viewbox" in result
63def test_svg_interpolated_attr():
64 cx, cy, r = 50, 50, 40
65 result = svg(t'<circle cx="{cx}" cy="{cy}" r="{r}" />')
66 assert result == '<circle cx="50" cy="50" r="40"></circle>'
69def test_svg_interpolated_child():
70 label = "hello"
71 result = svg(t"<text>{label}</text>")
72 assert result == "<text>hello</text>"
75def test_svg_fragment_multiple_roots():
76 result = svg(t"<circle /><rect />")
77 assert result == "<circle></circle><rect></rect>"
80# ---------------------------------------------------------
81# svg() vs html() — same strings, distinct parse results
82# ---------------------------------------------------------
85def test_svg_and_html_produce_different_results_for_same_strings():
86 # html() lowercases clipPath (no SVG context); svg() preserves it.
87 html_result = html(t"<clipPath></clipPath>")
88 svg_result = svg(t"<clipPath></clipPath>")
89 assert html_result == "<clippath></clippath>"
90 assert svg_result == "<clipPath></clipPath>"
93def test_html_full_svg_document_still_works():
94 # html() auto-detects SVG context when <svg> is present — no regression.
95 result = html(t"<svg><clipPath id='c'></clipPath></svg>")
96 assert result == '<svg><clipPath id="c"></clipPath></svg>'
99# -------------------------------
100# svg() composable inside html()
101# -------------------------------
104def test_svg_fragment_embedded_in_html():
105 def icon() -> Template:
106 return t'<rect viewBox="0 0 10 10" />'
108 result = html(t'<div class="icon"><svg>{icon()}</svg></div>')
109 assert (
110 result == '<div class="icon"><svg><rect viewBox="0 0 10 10"></rect></svg></div>'
111 )
114def test_svg_fragment_with_spread_attr():
115 def icon(attrs: dict[str, str]) -> Template:
116 return t"<rect {attrs} />"
118 rect_attrs = {"viewbox": "0 0 10 10"}
119 result = html(t'<div class="icon"><svg>{icon(attrs=rect_attrs)}</svg></div>')
120 assert (
121 result == '<div class="icon"><svg><rect viewBox="0 0 10 10"></rect></svg></div>'
122 )
125def test_svg_nesting():
126 svg_doc = t"""<!doctype html>
127<html lang="en">
128 <head>
129 <meta charset="utf8">
130 </head>
131 <body>
132 <svg width=1000 height=1000>
133 <rect width="100%" height="100%" fill="red"></rect>
134 <foreignObject width=500 height=500>
135 text,fo,svg,html
136 <div>
137 <!-- This should be lowercased because it is actually in HTML. -->
138 <foreignObject></foreignObject>
139 text,div,fo,svg,html
140 <svg width=300 height=300>
141 <rect width="100%" height="100%" fill="blue"></rect>
142 <circle cx=50 cy=50 r="15" fill="green"></circle>
143 <foreignObject width=100 height=100>
144 <span style="font-size: 10px">text,span,fo,svg,div,fo,svg,html</span>
145 </foreignObject>
146 </svg>
147 <math><mi>π</mi></math>
148 </div>
149 </foreignObject>
150 </svg>
151 </body>
152</html>"""
153 res = html(svg_doc)
154 assert res.count("<foreignObject") == 2