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

1from string.templatelib import Template 

2 

3from tdom import html, svg 

4 

5# svg() — tag case-fixing 

6 

7 

8def test_svg_clippath_case_fixed(): 

9 result = svg(t"<clipPath id='mask'></clipPath>") 

10 assert result == '<clipPath id="mask"></clipPath>' 

11 

12 

13def test_svg_lineargradient_case_fixed(): 

14 result = svg(t"<linearGradient id='grad'></linearGradient>") 

15 assert result == '<linearGradient id="grad"></linearGradient>' 

16 

17 

18def test_svg_femergenode_self_closing_case_fixed(): 

19 result = svg(t"<feMergeNode />") 

20 assert result == "<feMergeNode></feMergeNode>" 

21 

22 

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>' 

26 

27 

28# ------------------------------ 

29# svg() — attribute case-fixing 

30# ------------------------------ 

31 

32 

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>' 

36 

37 

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 

43 

44 

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 

49 

50 

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 

55 

56 

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 

61 

62 

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>' 

67 

68 

69def test_svg_interpolated_child(): 

70 label = "hello" 

71 result = svg(t"<text>{label}</text>") 

72 assert result == "<text>hello</text>" 

73 

74 

75def test_svg_fragment_multiple_roots(): 

76 result = svg(t"<circle /><rect />") 

77 assert result == "<circle></circle><rect></rect>" 

78 

79 

80# --------------------------------------------------------- 

81# svg() vs html() — same strings, distinct parse results 

82# --------------------------------------------------------- 

83 

84 

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>" 

91 

92 

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>' 

97 

98 

99# ------------------------------- 

100# svg() composable inside html() 

101# ------------------------------- 

102 

103 

104def test_svg_fragment_embedded_in_html(): 

105 def icon() -> Template: 

106 return t'<rect viewBox="0 0 10 10" />' 

107 

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 ) 

112 

113 

114def test_svg_fragment_with_spread_attr(): 

115 def icon(attrs: dict[str, str]) -> Template: 

116 return t"<rect {attrs} />" 

117 

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 ) 

123 

124 

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>&pi;</mi></math> 

148 </div> 

149 </foreignObject> 

150 </svg> 

151 </body> 

152</html>""" 

153 res = html(svg_doc) 

154 assert res.count("<foreignObject") == 2