Coverage for gws-app/gws/lib/svg/_test/element_test.py: 100%

87 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 22:59 +0200

1"""Tests for the SVG element module.""" 

2 

3import io 

4import pytest 

5 

6import gws 

7import gws.lib.xmlx as xmlx 

8import gws.lib.image 

9import gws.lib.mime 

10import gws.lib.svg.element as svg_element 

11 

12 

13def test_fragment_to_element_basic(): 

14 """Test basic fragment to element conversion.""" 

15 # Create a simple fragment  

16 circle = xmlx.tag('circle', {'cx': 50, 'cy': 50, 'r': 25, 'fill': 'red'}) 

17 rect = xmlx.tag('rect', {'x': 10, 'y': 10, 'width': 80, 'height': 80, 'fill': 'blue'}) 

18 

19 # Convert to SVG element  

20 svg = svg_element.fragment_to_element([circle, rect]) 

21 

22 # Verify the result  

23 assert svg.name == 'svg' 

24 assert svg.attr('xmlns') == 'http://www.w3.org/2000/svg' 

25 assert len(svg.children()) == 2 

26 assert svg.children()[0].name == 'circle' 

27 assert svg.children()[1].name == 'rect' 

28 

29 

30def test_fragment_to_element_with_attributes(): 

31 """Test fragment to element conversion with custom attributes.""" 

32 circle = xmlx.tag('circle', {'cx': 50, 'cy': 50, 'r': 25}) 

33 

34 # Add custom attributes  

35 custom_atts = {'width': '100', 'height': '100', 'viewBox': '0 0 100 100'} 

36 svg = svg_element.fragment_to_element([circle], custom_atts) 

37 

38 # Verify attributes were applied  

39 assert svg.attr('width') == '100' 

40 assert svg.attr('height') == '100' 

41 assert svg.attr('viewBox') == '0 0 100 100' 

42 

43 

44def test_fragment_to_element_z_index_sorting(): 

45 """Test that elements are sorted by z-index.""" 

46 # Create elements with different z-indices  

47 back = xmlx.tag('rect', {'x': 0, 'y': 0, 'width': 100, 'height': 100, 'fill': 'blue', 'z-index': 1}) 

48 middle = xmlx.tag('circle', {'cx': 50, 'cy': 50, 'r': 40, 'fill': 'green', 'z-index': 2}) 

49 front = xmlx.tag('circle', {'cx': 50, 'cy': 50, 'r': 20, 'fill': 'red', 'z-index': 3}) 

50 

51 # Add in reverse order  

52 svg = svg_element.fragment_to_element([front, back, middle]) 

53 

54 # Verify they're sorted by z-index  

55 children = svg.children() 

56 assert children[0].attrib.get( 

57 'fill') == 'blue' # z-index: 1 

58 assert children[1].attrib.get( 

59 'fill') == 'green' # z-index: 2 

60 assert children[2].attrib.get( 

61 'fill') == 'red' # z-index: 3 

62 

63 

64def test_fragment_to_element_empty_fragment(): 

65 """Test conversion with an empty fragment.""" 

66 svg = svg_element.fragment_to_element([]) 

67 

68 # Should still create an SVG element, just without children  

69 assert svg.name == 'svg' 

70 assert len(svg.children()) == 0 

71 

72 

73# def test_fragment_to_image(): 

74# Returnfunction is not implemented yet 

75# gws.lib.image.from_svg(el.to_string(), size, mime) 

76 

77 

78def test_sanitize_element_allowed_tags(): 

79 """Test sanitization of allowed tags.""" 

80 # Create an SVG with allowed tags  

81 svg = xmlx.tag('svg', 

82 {'width': '100', 'height': '100'}, 

83 xmlx.tag('circle', {'cx': '50', 'cy': '50', 'r': '40', 'fill': 'blue'}), 

84 xmlx.tag('rect', {'x': '10', 'y': '10', 'width': '80', 'height': '80', 'fill': 'red'}) 

85 ) 

86 

87 # Sanitize  

88 result = svg_element.sanitize_element(svg) 

89 

90 # Verify allowed tags are preserved  

91 assert result is not None 

92 assert len(result.children()) == 2 

93 assert result.children()[0].name == 'circle' 

94 assert result.children()[1].name == 'rect' 

95 

96 

97def test_sanitize_element_disallowed_tags(): 

98 """Test sanitization of disallowed tags.""" 

99 # Create an SVG with a disallowed tag (script)  

100 svg = xmlx.tag('svg', 

101 {'width': '100', 'height': '100'}, 

102 xmlx.tag('circle', {'cx': '50', 'cy': '50', 'r': '40'}), 

103 xmlx.tag('script', {}, "alert('XSS attack');") 

104 ) 

105 

106 # Sanitize  

107 result = svg_element.sanitize_element(svg) 

108 

109 # Verify disallowed tags are removed  

110 assert result is not None 

111 assert len(result.children()) == 1 

112 assert result.children()[0].name == 'circle' 

113 

114 

115def test_sanitize_element_allowed_attributes(): 

116 """Test sanitization of allowed attributes.""" 

117 # Create an element with allowed attributes  

118 svg = xmlx.tag('svg', {'width': '100', 'height': '100'}, 

119 xmlx.tag('circle', { 

120 'cx': '50', 

121 'cy': '50', 

122 'r': '40', 

123 'fill': 'blue', 

124 'stroke': 'black', 

125 'stroke-width': '2' 

126 }) 

127 ) 

128 

129 # Sanitize  

130 result = svg_element.sanitize_element(svg) 

131 

132 # Verify allowed attributes are preserved  

133 circle = result.children()[0] 

134 assert circle.attr('fill') == 'blue' 

135 assert circle.attr('stroke') == 'black' 

136 assert circle.attr('stroke-width') == '2' 

137 

138 

139def test_sanitize_element_disallowed_attributes(): 

140 """Test sanitization of disallowed attributes.""" 

141 # Create an element with disallowed attributes  

142 svg = xmlx.tag('svg', {'width': '100', 'height': '100'}, 

143 xmlx.tag('circle', { 

144 'cx': '50', 

145 'cy': '50', 

146 'r': '40', 

147 'fill': 'blue', 

148 'onmouseover': "alert('XSS attack');", 

149 # Disallowed 

150 'onclick': "maliciousFunction();" 

151 # Disallowed 

152 }) 

153 ) 

154 

155 # Sanitize  

156 result = svg_element.sanitize_element(svg) 

157 

158 # Verify disallowed attributes are removed  

159 circle = result.children()[0] 

160 assert circle.attr('fill') == 'blue' 

161 assert circle.attr('onmouseover') == '' 

162 assert circle.attr('onclick') == '' 

163 

164 

165def test_sanitize_element_url_attributes(): 

166 """Test sanitization of URL attributes.""" 

167 # Create an element with URL attributes  

168 svg = xmlx.tag('svg', {'width': '100', 'height': '100'}, 

169 xmlx.tag('circle', { 

170 'cx': '50', 

171 'cy': '50', 

172 'r': '40', 

173 'fill': 'url(http://evil.com/script.js)', 

174 # Should be removed 

175 'stroke': 'black' 

176 }) 

177 ) 

178 

179 # Sanitize  

180 result = svg_element.sanitize_element(svg) 

181 

182 # Verify URL attributes are removed  

183 circle = result.children()[0] 

184 assert circle.attr('fill') == '' 

185 assert circle.attr('stroke') == 'black' 

186 

187 

188def test_sanitize_element_nested_structure(): 

189 """Test sanitization of nested elements.""" 

190 # Create a nested structure with allowed and disallowed elements  

191 svg = xmlx.tag('svg', {'width': '100', 'height': '100'}, 

192 xmlx.tag('g', {'transform': 'translate(10,10)'}, 

193 xmlx.tag('circle', {'cx': '50', 'cy': '50', 'r': '40', 'fill': 'blue'}), 

194 xmlx.tag('script', {}, "alert('XSS attack');") 

195 # Disallowed 

196 ), 

197 xmlx.tag('foreignObject', {}, "Some foreign content") 

198 # Disallowed 

199 ) 

200 

201 # Sanitize  

202 result = svg_element.sanitize_element(svg) 

203 

204 # Verify structure is preserved but disallowed elements are removed  

205 assert result is not None 

206 assert len(result.children()) == 1 

207 

208 g = result.children()[0] 

209 assert g.name == 'g' 

210 assert len(g.children()) == 1 

211 assert g.children()[0].name == 'circle' 

212 

213 

214def test_sanitize_element_empty_result(): 

215 """Test sanitization that results in an empty SVG.""" 

216 # Create an SVG with only disallowed tags  

217 svg = xmlx.tag('svg', {'width': '100', 'height': '100'}, 

218 xmlx.tag('script', {}, "alert('XSS attack');"), 

219 xmlx.tag('foreignObject', {}, "Some foreign content") 

220 ) 

221 

222 # Sanitize  

223 result = svg_element.sanitize_element(svg) 

224 

225 # Verify we get an empty SVG  

226 assert not result 

227 

228 

229def test_sanitize_element_data_url(): 

230 """Test sanitization of data URLs.""" 

231 # Create an element with a data URL  

232 svg = xmlx.tag('svg', {'width': '100', 'height': '100'}, 

233 xmlx.tag('circle', { 

234 'cx': '50', 

235 'cy': '50', 

236 'r': '40', 

237 'fill': 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ+YWxlcnQoMSk8L3NjcmlwdD48L3N2Zz4=' 

238 }) 

239 ) 

240 

241 # Sanitize  

242 result = svg_element.sanitize_element(svg) 

243 

244 # Verify data URL is removed  

245 circle = result.children()[0] 

246 assert circle.attr('fill') == ''