From c035817ba879459aa6901bbe8289c5168dbddacc Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 6 Jun 2019 14:09:03 +0000 Subject: [PATCH] Unit test markup and fix all issues found --- cheuph/markup.py | 27 ++++++++--- test/__init__.py | 5 ++ test/test_markup.py | 113 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 test/__init__.py create mode 100644 test/test_markup.py diff --git a/cheuph/markup.py b/cheuph/markup.py index 0fdb499..a3834ea 100644 --- a/cheuph/markup.py +++ b/cheuph/markup.py @@ -1,10 +1,8 @@ -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Iterable, List, Mapping, Optional, Tuple, Union __all__ = ["Attributes", "Chunk", "AttributedText", "AT"] - -Attributes = Dict[str, Any] - +Attributes = Mapping[str, Any] # TODO remove empty Chunks in join_chunks class Chunk: @@ -46,6 +44,12 @@ class Chunk: # Uncommon special methods + def __eq__(self, other: object) -> bool: + if not isinstance(other, Chunk): + return NotImplemented + + return self._text == other._text and self._attributes == other._attributes + def __getitem__(self, key: Union[int, slice]) -> "Chunk": return Chunk(self.text[key], self._attributes) @@ -90,7 +94,6 @@ class Chunk: return Chunk(self.text, new_attributes) - class AttributedText: """ Objects of this class are immutable and behave str-like. Supported @@ -139,6 +142,12 @@ class AttributedText: def __add__(self, other: "AttributedText") -> "AttributedText": return AttributedText.from_chunks(self._chunks + other._chunks) + def __eq__(self, other: object) -> bool: + if not isinstance(other, AttributedText): + return NotImplemented + + return self._chunks == other._chunks + def __getitem__(self, key: Union[int, slice]) -> "AttributedText": chunks: List[Chunk] @@ -152,6 +161,12 @@ class AttributedText: def __len__(self) -> int: return sum(map(len, self._chunks)) + def __mul__(self, other: int) -> "AttributedText": + if not isinstance(other, int): + return NotImplemented + + return self.from_chunks(self.chunks * other) + # Properties @property @@ -325,7 +340,7 @@ class AttributedText: return self[:start] + middle + self[stop:] def set_at(self, name: str, value: Any, pos: int) -> "AttributedText": - return self.set(name, value, pos, pos) + return self.set(name, value, pos, pos + 1) def remove(self, name: str, diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..3f7343a --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,5 @@ +from .test_markup import * + +__all__ = [] + +__all__+= test_element_rendering.__all__ diff --git a/test/test_markup.py b/test/test_markup.py new file mode 100644 index 0000000..1085236 --- /dev/null +++ b/test/test_markup.py @@ -0,0 +1,113 @@ +import unittest + +from cheuph import AT + +__all__ = ["TestAttributedText"] + +class TestAttributedText(unittest.TestCase): + + SAMPLE_STRINGS = [ + "Hello world", + "\n", + " ", + "multiline\nstring\nwith\nmultiple\nlines", + ] + + def setUp(self): + self.text = AT("This is a sample string.") + self.text = self.text.set("attribute", "value", 5, 21) + self.text = self.text.set_at("attribute2", "value2", 13) + + def test_equality_of_empty_text(self): + self.assertEqual(AT(), AT()) + + def test_string_to_AT_to_string(self): + for string in self.SAMPLE_STRINGS: + with self.subTest(string=repr(string)): + self.assertEqual(string, str(AT(string))) + + def test_homomorphism_on_string_concatenation(self): + self.assertEqual("hello world", str(AT("hello") + AT(" world"))) + + result_1 = "".join(self.SAMPLE_STRINGS) + result_2 = AT().join(AT(string) for string in self.SAMPLE_STRINGS) + self.assertEqual(result_1, str(result_2)) + + def test_setting_and_getting_an_attribute(self): + # Lower and upper bounds + + self.assertIsNone(self.text.get(4, "attribute")) + self.assertEqual("value", self.text.get(5, "attribute")) + self.assertEqual("value", self.text.get(20, "attribute")) + self.assertIsNone(self.text.get(21, "attribute")) + + self.assertIsNone(self.text.get(12, "attribute2")) + self.assertEqual("value2", self.text.get(13, "attribute2")) + self.assertIsNone(self.text.get(14, "attribute2")) + + # Variants on get() + + self.assertEqual({"attribute": "value"}, self.text.at(10)) + self.assertEqual("value", self.text.at(10).get("attribute")) + self.assertEqual("value", self.text.get(10, "attribute")) + + self.assertEqual({}, self.text.at(2)) + self.assertIsNone(self.text.get(2, "attribute")) + + + self.assertEqual({"attribute": "value", "attribute2": "value2"}, + self.text.at(13)) + + def test_slicing_and_putting_back_together_with_attributes(self): + text2 = self.text[:4] + self.text[4:11] + self.text[11:22] + self.text[22:] + text3 = self.text[:-20] + self.text[-20:-4] + self.text[-4:] + + self.assertEqual(self.text, text2) + self.assertEqual(self.text, text3) + + def test_removing_attributes(self): + text = self.text.remove("attribute", 9, 15) + + # Lower and upper bounds + + self.assertEqual("value", text.get(8, "attribute")) + self.assertIsNone(text.get(9, "attribute")) + self.assertIsNone(text.get(14, "attribute")) + self.assertEqual("value", text.get(15, "attribute")) + + # Other attribute wasn't touched + + self.assertIsNone(text.get(12, "attribute2")) + self.assertEqual("value2", text.get(13, "attribute2")) + self.assertIsNone(text.get(14, "attribute2")) + + def test_immutability(self): + text = self.text + text2 = text.remove("attribute", 10, 12) + text3 = text2.set("attribute", "bar", 10, 14) + text4 = text3.set_at("attribute", "xyz", 11) + + for i in range(5, 21): + self.assertEqual("value", text.get(i, "attribute")) + + for i in range(10, 12): + self.assertIsNone(text2.get(i, "attribute")) + + for i in range(10, 14): + self.assertEqual("bar", text3.get(i, "attribute")) + + self.assertEqual("xyz", text4.get(11, "attribute")) + + def test_joining_with_attributes(self): + snippet1 = AT("hello", foo="bar") + snippet2 = AT("world", foo="qux") + space = AT(" ", space=True, foo="herbert") + + text1 = snippet1 + space + snippet2 + text2 = space.join([snippet1, snippet2]) + + self.assertEqual(text1, text2) + + # TODO test find_all() + # TODO test split_by() + # TODO test __mul__()