diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /share/extensions/tests/test_inkex_elements_base.py | |
parent | Initial commit. (diff) | |
download | inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip |
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/tests/test_inkex_elements_base.py')
-rw-r--r-- | share/extensions/tests/test_inkex_elements_base.py | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/share/extensions/tests/test_inkex_elements_base.py b/share/extensions/tests/test_inkex_elements_base.py new file mode 100644 index 0000000..6efd01d --- /dev/null +++ b/share/extensions/tests/test_inkex_elements_base.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python3 +# coding=utf-8 +""" +Test the element API base classes and basic functionality +""" +import random +from lxml import etree + +from inkex.elements import ( + load_svg, + ShapeElement, + Group, + Rectangle, + Tspan, + TextElement, + Line, +) +from inkex.elements._base import NodeBasedLookup, BaseElement +from inkex.transforms import Transform +from inkex.styles import Style +from inkex.utils import FragmentError +from inkex.tester import TestCase +from inkex.tester.svg import svg_file + + +class FakeShape(ShapeElement): # pylint: disable=abstract-method + """A protend shape""" + + tag_name = "fake" + + +class SvgTestCase(TestCase): + """Test SVG""" + + source_file = "complextransform.test.svg" + + def setUp(self): + super().setUp() + self.svg = svg_file(self.data_file("svg", self.source_file)) + + +class OverridenElementTestCase(SvgTestCase): + """Test element overriding functionality""" + + def test_tag_names(self): + """ + Test tag names for custom and unknown tags + """ + doc = load_svg( + """ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:x="http://x.com/x"> + <g id="good"></g> + <badsvg id="bad">Unknown SVG tag</badsvg> + <x:othertag id="ugly"></x:othertag> +</svg>""" + ) + svg = doc.getroot() + + good = svg.getElementById("good") + self.assertEqual(good.TAG, "g") + bad = svg.getElementById("bad") + self.assertEqual(bad.TAG, "badsvg") + ugly = svg.getElementById("ugly") + self.assertEqual(ugly.TAG, "othertag") + + def test_reference_count(self): + """ + Test inkex.element.BaseElement-derived object type is preserved on adding to group + + See https://gitlab.com/inkscape/extensions/-/issues/81 for details + """ + grp = Group() + for _ in range(10): + rect = Rectangle() + grp.add(rect) + + for elem in grp: + self.assertEqual(type(elem), Rectangle) + + def test_class_lookup(self): + """Can we find element classes with simple xpaths""" + self.assertEqual(NodeBasedLookup.find_class("svg:rect"), Rectangle) + self.assertEqual(NodeBasedLookup.find_class("//svg:svg/svg:g"), Group) + self.assertRaises(KeyError, NodeBasedLookup.find_class, "svg:g[layer]") + + def test_abstract_raises(self): + """Abstract classes cannot be instantiated""" + self.assertRaises(NotImplementedError, FakeShape().get_path) + self.assertRaises(AttributeError, FakeShape().set_path, None) + + def test_add(self): + """Can add single or multiple elements with passthrough""" + elem = self.svg.getElementById("D") + group = elem.add(Group(id="foo")) + self.assertEqual(group.get("id"), "foo") + groups = elem.add(Group(id="f1"), Group(id="f2")) + self.assertEqual(len(groups), 2) + self.assertEqual(groups[0].get("id"), "f1") + self.assertEqual(groups[1].get("id"), "f2") + + def test_creation(self): + """Create elements with attributes""" + group = Group().update(inkscape__label="Foo") + self.assertEqual(group.get("inkscape:label"), "Foo") + group = Group().update(inkscape__label="Bar") + self.assertEqual(group.label, "Bar") + + def test_tostring(self): + """Elements can be printed as strings""" + self.assertEqual(Group().tostring(), b"<g/>") + elem = Group(id="bar") + path = elem.add(Tspan(id="foo")) + elem.transform.add_translate(50, 50) + path.style["fill"] = "red" + self.assertEqual( + elem.tostring(), + b'<g transform="translate(50, 50)"><tspan id="foo" style="fill:red"/></g>', + ) + + +class AttributeHandelingTestCase(SvgTestCase): + """Test how attributes are handled""" + + def test_chained_multiple_attrs(self): + """Set multiple attributes at a time""" + group = Group().update(attr1="A", attr2="B").update(attr3="C", attr4="D") + self.assertEqual(group.get("attr1"), "A") + self.assertEqual(group.get("attr2"), "B") + self.assertEqual(group.get("attr3"), "C") + self.assertEqual(group.get("attr4"), "D") + + # remove attributes, setting them to None + group.update(attr1=None, attr4=None) + + self.assertEqual(group.get("attr1"), None) + self.assertEqual(group.get("attr2"), "B") + self.assertEqual(group.get("attr3"), "C") + self.assertEqual(group.get("attr4"), None) + + self.assertEqual(group.pop("attr2"), "B") + self.assertEqual(group.pop("attr3"), "C") + + def test_set_wrapped_attribute(self): + """Remove wrapped attribute using .set()""" + group = Group().update(transform=Transform(scale=2)) + self.assertEqual(group.transform.matrix[0][0], 2) + self.assertEqual(group.transform.matrix[1][1], 2) + + group.update(transform=None) + self.assertEqual(group.transform, Transform()) + + def test_pop_wrapped_attribute(self): + """Remove wrapped attribute using .pop()""" + group = Group() + + self.assertEqual(group.pop("transform"), Transform()) + + group.update(transform=Transform(scale=2)) + self.assertEqual(group.pop("transform"), Transform(scale=2)) + self.assertEqual(group.pop("transform"), Transform()) + self.assertRaises(AttributeError, getattr, group, "foo") + + def test_pop_regular_attribute(self): + """Remove wrapped attribute using .pop()""" + group = Group() + + self.assertEqual(group.get("attr1"), None) + + group.update(attr1="42") + self.assertEqual(group.pop("attr1"), "42") + self.assertEqual(group.pop("attr1"), None) + + def test_update_consistant(self): + """Update doesn't keep callbacks around""" + elem = self.svg.getElementById("D") + tr_a = Transform(translate=(10, 10)) + tr_b = Transform(translate=(-20, 15)) + elem.transform = tr_a + elem.transform = tr_b + self.assertEqual(str(elem.transform), "translate(-20, 15)") + tr_a.add_translate(10, 10) + self.assertEqual(str(elem.transform), "translate(-20, 15)") + elem.set("transform", None) + self.assertEqual(elem.get("transform"), None) + + def test_in_place_style(self): + """Do styles update when we set them""" + elem = self.svg.getElementById("D") + elem.style["fill"] = "purple" + self.assertEqual(elem.get("style"), "fill:purple") + elem.style = {"stroke-dashoffset": "1"} + self.assertEqual(elem.get("style"), "stroke-dashoffset:1") + elem.style = Style(stroke="red") + self.assertEqual(elem.get("style"), "stroke:red") + elem.style.update("grape:2;strawberry:nice;") + self.assertEqual(elem.get("style"), "stroke:red;grape:2;strawberry:nice") + + def test_random_id(self): + """Test setting a random id""" + elem = self.svg.getElementById("D") + elem.set_random_id("Thing") + self.assertEqual(elem.get("id"), "Thing5815") + elem.set_random_id("Thing", size=2) + self.assertEqual(elem.get("id"), "Thing85") + elem.set_random_id() + self.assertEqual(elem.get("id"), "path5392") + # No document root, no random id allowed + self.assertRaises(FragmentError, elem.copy().set_random_id) + + def test_random_ids(self): + """Test setting a tree of ids""" + elem = self.svg.getElementById("D") + self.svg.set_random_ids(prefix="TreeItem") + self.assertEqual(self.svg.get("id"), "TreeItem5815") + self.assertEqual(self.svg[0].get("id"), "TreeItem8555") + self.assertEqual(elem.get("id"), "TreeItem2036") + + def test_set_id_backlinks(self): + """Changing an id can update backlinks""" + elem = self.svg.getElementById("path1") + rect1 = Rectangle() + rect1.set("clip-path", "url(#path1)") + rect2 = Rectangle() + rect2.set("mask", "url(#path1)") + elem.addnext(rect1) + self.svg.getElementById("G").add(rect2) + elem.set_id("plant54", True) + self.assertEqual(self.svg.getElementById("G").get("xlink:href"), "#plant54") + self.assertEqual(self.svg.getElementById("G").href, elem) + self.assertEqual( + str(self.svg.getElementById("B").style), "fill:#eee;joker:url(#plant54)" + ) + self.assertEqual(rect1.get("clip-path"), "url(#plant54)") + self.assertEqual(rect2.get("mask"), "url(#plant54)") + + def test_set_id_blacklist(self): + """Test that the set_random_id function respects a blacklist""" + elem: BaseElement = self.svg.getElementById("D") + + state = random.getstate() + elem.set_random_id("Thing") + self.assertEqual(elem.get("id"), "Thing5815") + random.setstate(state) + elem.set_random_id("Other", blacklist=["Other5815"]) + self.assertEqual(elem.get("id"), "Other8555") + + def test_get_element_by_name(self): + """Get elements by name""" + self.assertEqual(self.svg.getElementByName("Key").get("id"), "K") + self.assertEqual(self.svg.getElementByName("Elm", "svg:g").get("id"), "L") + self.assertEqual(self.svg.getElementByName("Mine").get("id"), "M") + self.assertEqual(self.svg.getElementByName("doesntexist"), None) + self.assertEqual(self.svg.getElementByName("Key", "rect"), None) + + def test_insensitive(self): + """Element inkscape sensitivity""" + elem = self.svg.getElementByName("Key") + self.assertTrue(elem.is_sensitive()) + elem.set_sensitive(False) + self.assertFalse(elem.is_sensitive()) + self.assertEqual(elem.get("sodipodi:insensitive"), "true") + elem.set_sensitive(True) + self.assertTrue(elem.is_sensitive()) + self.assertEqual(elem.get("sodipodi:insensitive"), None) + + def test_title_description(self): + """Adding a description to an element""" + + def _content(elem): + return tuple(f"{child.TAG}:{child.text}" for child in elem) + + self.svg.getElementById("K").title = "Before" + elem = self.svg.getElementById("L") + elem.desc = "Dancing" + elem.title = "Start" + self.assertEqual(elem.desc, "Dancing") + self.assertEqual(elem.title, "Start") + self.svg.getElementById("G").title = "After" + # Title and Desc elements have been added + self.assertEqual(_content(elem), ("title:Start", "desc:Dancing", "line:None")) + # No extra elements created, no conflicts. + elem.title = "Stop" + self.assertEqual(_content(elem), ("title:Stop", "desc:Dancing", "line:None")) + # Deletion works too, try twice so we know it doesn't error on not-found + del elem.desc + del elem.desc + self.assertEqual(_content(elem), ("title:Stop", "line:None")) + + +class TransformationTestCase(SvgTestCase): + """Test transformative functions""" + + def test_bounding_box(self): + """Elements can have bounding boxes""" + elem = self.svg.getElementById("D") + self.assertEqual(tuple(elem.bounding_box()), ((60.0, 100.0), (130.0, 170.00))) + self.assertTrue(elem.bounding_box().center.is_close((80.0, 150.0))) + self.assertEqual( + tuple(TextElement(x="10", y="5").bounding_box()), ((10, 10), (5, 5)) + ) + group = Group(elem) + self.assertEqual(elem.bounding_box(), group.bounding_box()) + + def test_transform(self): + """In-place modified transforms are retained""" + elem = self.svg.getElementById("D") + self.assertEqual(str(elem.transform), "translate(30, 10)") + elem.transform.add_translate(-10, 10) + self.assertEqual(str(elem.transform), "translate(20, 20)") + + def test_scale(self): + """In-place scaling from blank transform""" + elem = self.svg.getElementById("F") + self.assertEqual(elem.transform, Transform()) + self.assertEqual(elem.get("transform"), None) + elem.transform.add_scale(1.0666666666666667, 1.0666666666666667) + self.assertEqual(elem.get("transform"), Transform(scale=1.06667)) + self.assertIn(b"transform", etree.tostring(elem)) + + def test_in_place_transforms(self): + """Do style and transforms update correctly""" + elem = self.svg.getElementById("D") + self.assertEqual(type(elem.transform), Transform) + self.assertEqual(type(elem.style), Style) + self.assertTrue(elem.transform) + elem.transform = Transform() + self.assertEqual(elem.transform, Transform()) + self.assertEqual(elem.get("transform"), None) + self.assertNotIn(b"transform", etree.tostring(elem)) + elem.transform.add_translate(10, 10) + self.assertIn(b"transform", etree.tostring(elem)) + elem.transform.add_translate(-10, -10) + self.assertNotIn(b"transform", etree.tostring(elem)) + + def test_composed_transform(self): + """Calculate every transform down to the other element""" + elem = self.svg.getElementById("F") + grandparent = elem.getparent().getparent() + transform3 = elem.composed_transform(grandparent) + transform4 = elem.composed_transform() + self.assertNotEqual(transform3, transform4) + + +class RelationshipTestCase(SvgTestCase): + """Test relationships between elements""" + + def test_findall(self): + """Findall elements in svg""" + groups = self.svg.findall("svg:g") + self.assertEqual(len(groups), 1) + + def test_copy(self): + """Test copying elements""" + elem = self.svg.getElementById("D") + cpy = elem.copy() + self.assertFalse(cpy.getparent()) + self.assertFalse(cpy.get("id")) + + def test_duplicate(self): + """Test duplicating elements""" + elem = self.svg.getElementById("D") + dup = elem.duplicate() + self.assertTrue(dup.get("id")) + self.assertNotEqual(elem.get("id"), dup.get("id")) + self.assertEqual(elem.getparent(), dup.getparent()) + + def test_duplicate_group(self): + """Check that when duplicating a group, all ids are replaced + (Issue https://gitlab.com/inkscape/extensions/-/issues/379)""" + elem: BaseElement = self.svg.getElementById("A") + dup = elem.duplicate() + elem_ids = [i.get_id() for i in elem.iter() if hasattr(i, "get_id")] + dup_ids = [i.get_id() for i in dup.iter() if hasattr(i, "get_id")] + + in_both = [i for i in elem_ids if i in dup_ids] + + self.assertListEqual(in_both, []) + + def test_replace_with(self): + """Replacing nodes in a tree""" + rect = self.svg.getElementById("E") + path = rect.to_path_element() + rect.replace_with(path) + self.assertEqual(rect.getparent(), None) + self.assertEqual(path.getparent(), self.svg.getElementById("C")) + + def test_descendants(self): + """Elements can walk their descendants""" + self.assertEqual( + tuple(self.svg.descendants().ids), + ( + "mydoc", + "path1", + "base", + "metadata7", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + ), + ) + get = self.svg.getElementById + self.assertEqual(tuple(get("L").descendants().ids), ("L", "M")) + self.assertEqual(tuple(get("M").descendants().ids), ("M",)) + + def test_deep_descendants(self): + """Create a very deep svg and test getting decendants""" + svg = '<svg xmlns="http://www.w3.org/2000/svg">' + for i in range(1000): + svg += f'<g id="{i}">' + svg = load_svg(svg + ("</g>" * 1000) + "</svg>").getroot() + + self.assertEqual( + tuple(svg.descendants().ids), tuple(str(i) for i in range(1000)) + ) + self.assertEqual( + tuple(svg.getElementById("998").descendants().ids), ("998", "999") + ) + self.assertEqual(tuple(svg.getElementById("999").descendants().ids), ("999",)) + + def test_ancestors(self): + """Element descendants of elements""" + get = self.svg.getElementById + self.assertEqual(tuple(get("M").ancestors().ids), ("L", "K", "A", "mydoc")) + self.assertEqual( + tuple(get("M").ancestors(stop_at=[None]).ids), ("L", "K", "A", "mydoc") + ) + self.assertEqual(tuple(get("M").ancestors(stop_at=[get("K")]).ids), ("L", "K")) + self.assertEqual(tuple(get("M").ancestors(stop_at=[get("L")]).ids), ("L",)) + + def test_deep_ancestors(self): + """Create a very deep svg and test getting ancestors""" + svg = '<svg xmlns="http://www.w3.org/2000/svg">' + for i in range(1000): + svg += f'<g id="{i}">' + svg = load_svg(svg + ("</g>" * 1000) + "</svg>").getroot() + self.assertEqual( + tuple(svg.getElementById("999").ancestors().ids), + tuple(str(i) for i in range(998, -1, -1)), + ) + + def test_luca(self): + """Test last common ancestor""" + get = self.svg.getElementById + self.assertEqual(tuple(get("M").ancestors(get("M")).ids), ("L",)) + self.assertEqual(tuple(get("G").ancestors(get("H")).ids), ("C",)) + self.assertEqual(tuple(get("M").ancestors(get("H")).ids), ("L", "K", "A")) |