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
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Tests for the SVG element module."""
3import io
4import pytest
6import gws
7import gws.lib.xmlx as xmlx
8import gws.lib.image
9import gws.lib.mime
10import gws.lib.svg.element as svg_element
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'})
19 # Convert to SVG element
20 svg = svg_element.fragment_to_element([circle, rect])
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'
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})
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)
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'
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})
51 # Add in reverse order
52 svg = svg_element.fragment_to_element([front, back, middle])
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
64def test_fragment_to_element_empty_fragment():
65 """Test conversion with an empty fragment."""
66 svg = svg_element.fragment_to_element([])
68 # Should still create an SVG element, just without children
69 assert svg.name == 'svg'
70 assert len(svg.children()) == 0
73# def test_fragment_to_image():
74# Returnfunction is not implemented yet
75# gws.lib.image.from_svg(el.to_string(), size, mime)
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 )
87 # Sanitize
88 result = svg_element.sanitize_element(svg)
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'
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 )
106 # Sanitize
107 result = svg_element.sanitize_element(svg)
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'
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 )
129 # Sanitize
130 result = svg_element.sanitize_element(svg)
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'
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 )
155 # Sanitize
156 result = svg_element.sanitize_element(svg)
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') == ''
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 )
179 # Sanitize
180 result = svg_element.sanitize_element(svg)
182 # Verify URL attributes are removed
183 circle = result.children()[0]
184 assert circle.attr('fill') == ''
185 assert circle.attr('stroke') == 'black'
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 )
201 # Sanitize
202 result = svg_element.sanitize_element(svg)
204 # Verify structure is preserved but disallowed elements are removed
205 assert result is not None
206 assert len(result.children()) == 1
208 g = result.children()[0]
209 assert g.name == 'g'
210 assert len(g.children()) == 1
211 assert g.children()[0].name == 'circle'
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 )
222 # Sanitize
223 result = svg_element.sanitize_element(svg)
225 # Verify we get an empty SVG
226 assert not result
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 )
241 # Sanitize
242 result = svg_element.sanitize_element(svg)
244 # Verify data URL is removed
245 circle = result.children()[0]
246 assert circle.attr('fill') == ''