Compare commits
3 Commits
WorkingOnB
...
AddingTest
| Author | SHA1 | Date | |
|---|---|---|---|
| bad2cab28e | |||
| 80215913b6 | |||
| 847684a15b |
345
src/myfasthtml/core/matcher.py
Normal file
345
src/myfasthtml/core/matcher.py
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from fastcore.basics import NotStr
|
||||||
|
|
||||||
|
from myfasthtml.core.testclient import MyFT
|
||||||
|
|
||||||
|
|
||||||
|
class Predicate:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.value})"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Predicate):
|
||||||
|
return False
|
||||||
|
return self.value == other.value
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class StartsWith(Predicate):
|
||||||
|
def __init__(self, value):
|
||||||
|
super().__init__(value)
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
return actual.startswith(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class Contains(Predicate):
|
||||||
|
def __init__(self, value):
|
||||||
|
super().__init__(value)
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
return self.value in actual
|
||||||
|
|
||||||
|
|
||||||
|
class DoesNotContain(Predicate):
|
||||||
|
def __init__(self, value):
|
||||||
|
super().__init__(value)
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
return self.value not in actual
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DoNotCheck:
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Empty:
|
||||||
|
desc: str = None
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorOutput:
|
||||||
|
def __init__(self, path, element, expected):
|
||||||
|
self.path = path
|
||||||
|
self.element = element
|
||||||
|
self.expected = expected
|
||||||
|
self.output = []
|
||||||
|
self.indent = ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _unconstruct_path_item(item):
|
||||||
|
if "#" in item:
|
||||||
|
elt_name, elt_id = item.split("#")
|
||||||
|
return elt_name, "id", elt_id
|
||||||
|
elif match := re.match(r'(\w+)\[(class|name)=([^]]+)]', item):
|
||||||
|
return match.groups()
|
||||||
|
return item, None, None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
self.compute()
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
# first render the path hierarchy
|
||||||
|
for p in self.path.split(".")[:-1]:
|
||||||
|
elt_name, attr_name, attr_value = self._unconstruct_path_item(p)
|
||||||
|
path_str = self._str_element(MyFT(elt_name, {attr_name: attr_value}), keep_open=True)
|
||||||
|
self._add_to_output(f"{path_str}")
|
||||||
|
self.indent += " "
|
||||||
|
|
||||||
|
# then render the element
|
||||||
|
if hasattr(self.expected, "tag") and hasattr(self.element, "tag"):
|
||||||
|
# display the tag and its attributes
|
||||||
|
tag_str = self._str_element(self.element, self.expected)
|
||||||
|
self._add_to_output(tag_str)
|
||||||
|
|
||||||
|
# Try to show where the differences are
|
||||||
|
error_str = self._detect_error(self.element, self.expected)
|
||||||
|
if error_str:
|
||||||
|
self._add_to_output(error_str)
|
||||||
|
|
||||||
|
# render the children
|
||||||
|
if len(self.expected.children) > 0:
|
||||||
|
self.indent += " "
|
||||||
|
element_index = 0
|
||||||
|
for expected_child in self.expected.children:
|
||||||
|
if hasattr(expected_child, "tag"):
|
||||||
|
if element_index < len(self.element.children):
|
||||||
|
# display the child
|
||||||
|
element_child = self.element.children[element_index]
|
||||||
|
child_str = self._str_element(element_child, expected_child, keep_open=False)
|
||||||
|
self._add_to_output(child_str)
|
||||||
|
|
||||||
|
# manage errors in children
|
||||||
|
child_error_str = self._detect_error(element_child, expected_child)
|
||||||
|
if child_error_str:
|
||||||
|
self._add_to_output(child_error_str)
|
||||||
|
element_index += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# When there are fewer children than expected, we display a placeholder
|
||||||
|
child_str = "! ** MISSING ** !"
|
||||||
|
self._add_to_output(child_str)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._add_to_output(expected_child)
|
||||||
|
|
||||||
|
self.indent = self.indent[:-2]
|
||||||
|
self._add_to_output(")")
|
||||||
|
else:
|
||||||
|
self._add_to_output(str(self.element))
|
||||||
|
# Try to show where the differences are
|
||||||
|
error_str = self._detect_error(self.element, self.expected)
|
||||||
|
if error_str:
|
||||||
|
self._add_to_output(error_str)
|
||||||
|
|
||||||
|
def _add_to_output(self, msg):
|
||||||
|
self.output.append(f"{self.indent}{msg}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _str_element(element, expected=None, keep_open=None):
|
||||||
|
# compare to itself if no expected element is provided
|
||||||
|
if expected is None:
|
||||||
|
expected = element
|
||||||
|
|
||||||
|
# the attributes are compared to the expected element
|
||||||
|
elt_attrs = {attr_name: element.attrs.get(attr_name, "** MISSING **") for attr_name in
|
||||||
|
[attr_name for attr_name in expected.attrs if attr_name is not None]}
|
||||||
|
elt_attrs_str = " ".join(f'"{attr_name}"="{attr_value}"' for attr_name, attr_value in elt_attrs.items())
|
||||||
|
|
||||||
|
#
|
||||||
|
tag_str = f"({element.tag} {elt_attrs_str}"
|
||||||
|
|
||||||
|
# manage the closing tag
|
||||||
|
if keep_open is False:
|
||||||
|
tag_str += " ...)" if len(element.children) > 0 else ")"
|
||||||
|
elif keep_open is True:
|
||||||
|
tag_str += "..." if elt_attrs_str == "" else " ..."
|
||||||
|
else:
|
||||||
|
# close the tag if there are no children
|
||||||
|
if len(element.children) == 0: tag_str += ")"
|
||||||
|
|
||||||
|
return tag_str
|
||||||
|
|
||||||
|
def _detect_error(self, element, expected):
|
||||||
|
if hasattr(expected, "tag") and hasattr(element, "tag"):
|
||||||
|
tag_str = len(element.tag) * (" " if element.tag == expected.tag else "^")
|
||||||
|
elt_attrs = {attr_name: element.attrs.get(attr_name, "** MISSING **") for attr_name in expected.attrs}
|
||||||
|
attrs_in_error = [attr_name for attr_name, attr_value in elt_attrs.items() if
|
||||||
|
not self._matches(attr_value, expected.attrs[attr_name])]
|
||||||
|
if attrs_in_error:
|
||||||
|
elt_attrs_error = " ".join(len(f'"{name}"="{value}"') * ("^" if name in attrs_in_error else " ")
|
||||||
|
for name, value in elt_attrs.items())
|
||||||
|
error_str = f" {tag_str} {elt_attrs_error}"
|
||||||
|
return error_str
|
||||||
|
else:
|
||||||
|
if not self._matches(element, expected):
|
||||||
|
return len(str(element)) * "^"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _matches(element, expected):
|
||||||
|
if element == expected:
|
||||||
|
return True
|
||||||
|
elif isinstance(expected, Predicate):
|
||||||
|
return expected.validate(element)
|
||||||
|
else:
|
||||||
|
return element == expected
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorComparisonOutput:
|
||||||
|
def __init__(self, actual_error_output, expected_error_output):
|
||||||
|
self.actual_error_output = actual_error_output
|
||||||
|
self.expected_error_output = expected_error_output
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def adjust(to_adjust, reference):
|
||||||
|
for index, ref_line in enumerate(reference):
|
||||||
|
if "^^" in ref_line:
|
||||||
|
# insert an empty line in to_adjust
|
||||||
|
to_adjust.insert(index, "")
|
||||||
|
|
||||||
|
return to_adjust
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
# init if needed
|
||||||
|
if not self.actual_error_output.output:
|
||||||
|
self.actual_error_output.compute()
|
||||||
|
if not self.expected_error_output.output:
|
||||||
|
self.expected_error_output.compute()
|
||||||
|
|
||||||
|
actual = self.actual_error_output.output
|
||||||
|
expected = self.expected_error_output.output
|
||||||
|
|
||||||
|
# actual = self.adjust(actual, expected) # does not seem to be needed
|
||||||
|
expected = self.adjust(expected, actual)
|
||||||
|
|
||||||
|
actual_max_length = len(max(actual, key=len))
|
||||||
|
# expected_max_length = len(max(expected, key=len))
|
||||||
|
|
||||||
|
output = []
|
||||||
|
for a, e in zip(actual, expected):
|
||||||
|
line = f"{a:<{actual_max_length}} | {e}".rstrip()
|
||||||
|
output.append(line)
|
||||||
|
|
||||||
|
return "\n".join(output)
|
||||||
|
|
||||||
|
|
||||||
|
def matches(actual, expected, path=""):
|
||||||
|
def print_path(p):
|
||||||
|
return f"Path : '{p}'\n" if p else ""
|
||||||
|
|
||||||
|
def _type(x):
|
||||||
|
return type(x)
|
||||||
|
|
||||||
|
def _debug(elt):
|
||||||
|
return str(elt) if elt else "None"
|
||||||
|
|
||||||
|
def _debug_compare(a, b):
|
||||||
|
actual_out = ErrorOutput(path, a, b)
|
||||||
|
expected_out = ErrorOutput(path, b, b)
|
||||||
|
|
||||||
|
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
|
||||||
|
return comparison_out.render()
|
||||||
|
|
||||||
|
def _error_msg(msg, _actual=None, _expected=None):
|
||||||
|
if _actual is None and _expected is None:
|
||||||
|
debug_info = ""
|
||||||
|
elif _actual is None:
|
||||||
|
debug_info = _debug(_expected)
|
||||||
|
elif _expected is None:
|
||||||
|
debug_info = _debug(_actual)
|
||||||
|
else:
|
||||||
|
debug_info = _debug_compare(_actual, _expected)
|
||||||
|
|
||||||
|
return f"{print_path(path)}Error : {msg}\n{debug_info}"
|
||||||
|
|
||||||
|
def _assert_error(msg, _actual=None, _expected=None):
|
||||||
|
assert False, _error_msg(msg, _actual=_actual, _expected=_expected)
|
||||||
|
|
||||||
|
def _get_current_path(elt):
|
||||||
|
if hasattr(elt, "tag"):
|
||||||
|
res = f"{elt.tag}"
|
||||||
|
if "id" in elt.attrs:
|
||||||
|
res += f"#{elt.attrs['id']}"
|
||||||
|
elif "name" in elt.attrs:
|
||||||
|
res += f"[name={elt.attrs['name']}]"
|
||||||
|
elif "class" in elt.attrs:
|
||||||
|
res += f"[class={elt.attrs['class']}]"
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
return elt.__class__.__name__
|
||||||
|
|
||||||
|
if actual is not None and expected is None:
|
||||||
|
_assert_error("Actual is not None while expected is None", _actual=actual)
|
||||||
|
|
||||||
|
if isinstance(expected, DoNotCheck):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if actual is None and expected is not None:
|
||||||
|
_assert_error("Actual is None while expected is ", _expected=expected)
|
||||||
|
|
||||||
|
# set the path
|
||||||
|
path += "." + _get_current_path(actual) if path else _get_current_path(actual)
|
||||||
|
|
||||||
|
assert _type(actual) == _type(expected) or (hasattr(actual, "tag") and hasattr(expected, "tag")), \
|
||||||
|
_error_msg("The types are different: ", _actual=actual, _expected=expected)
|
||||||
|
|
||||||
|
if isinstance(expected, (list, tuple)):
|
||||||
|
if len(actual) < len(expected):
|
||||||
|
_assert_error("Actual is smaller than expected: ", _actual=actual, _expected=expected)
|
||||||
|
if len(actual) > len(expected):
|
||||||
|
_assert_error("Actual is bigger than expected: ", _actual=actual, _expected=expected)
|
||||||
|
|
||||||
|
for actual_child, expected_child in zip(actual, expected):
|
||||||
|
assert matches(actual_child, expected_child, path=path)
|
||||||
|
|
||||||
|
elif isinstance(expected, NotStr):
|
||||||
|
to_compare = actual.s.lstrip('\n').lstrip()
|
||||||
|
assert to_compare.startswith(expected.s), _error_msg("Notstr values are different: ",
|
||||||
|
_actual=to_compare,
|
||||||
|
_expected=expected.s)
|
||||||
|
|
||||||
|
elif hasattr(expected, "tag"):
|
||||||
|
# validate the tags names
|
||||||
|
assert actual.tag == expected.tag, _error_msg("The elements are different.",
|
||||||
|
_actual=actual.tag,
|
||||||
|
_expected=expected.tag)
|
||||||
|
|
||||||
|
# special case when the expected element is empty
|
||||||
|
if len(expected.children) > 0 and isinstance(expected.children[0], Empty):
|
||||||
|
assert len(actual.children) == 0, _error_msg("Actual is not empty:", _actual=actual)
|
||||||
|
assert len(actual.attrs) == 0, _error_msg("Actual is not empty:", _actual=actual)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# compare the attributes
|
||||||
|
for expected_attr, expected_value in expected.attrs.items():
|
||||||
|
assert expected_attr in actual.attrs, _error_msg(f"'{expected_attr}' is not found in Actual.",
|
||||||
|
_actual=actual.attrs)
|
||||||
|
|
||||||
|
if isinstance(expected_value, Predicate):
|
||||||
|
assert expected_value.validate(actual.attrs[expected_attr]), \
|
||||||
|
_error_msg(f"The condition '{expected_value}' is not satisfied.",
|
||||||
|
_actual=actual,
|
||||||
|
_expected=expected)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert actual.attrs[expected_attr] == expected.attrs[expected_attr], \
|
||||||
|
_error_msg(f"The values are different for '{expected_attr}': ",
|
||||||
|
_actual=actual.attrs[expected_attr],
|
||||||
|
_expected=expected.attrs[expected_attr])
|
||||||
|
|
||||||
|
# compare the children
|
||||||
|
if len(actual.children) < len(expected.children):
|
||||||
|
_assert_error("Actual is lesser than expected: ", _actual=actual, _expected=expected)
|
||||||
|
|
||||||
|
for actual_child, expected_child in zip(actual.children, expected.children):
|
||||||
|
assert matches(actual_child, expected_child, path=path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert actual == expected, _error_msg("The values are different: ",
|
||||||
|
_actual=actual,
|
||||||
|
_expected=expected)
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -15,6 +16,8 @@ from myfasthtml.core.commands import mount_commands
|
|||||||
class MyFT:
|
class MyFT:
|
||||||
tag: str
|
tag: str
|
||||||
attrs: dict
|
attrs: dict
|
||||||
|
children: list['MyFT'] = dataclasses.field(default_factory=list)
|
||||||
|
text: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class TestableElement:
|
class TestableElement:
|
||||||
|
|||||||
338
tests/test_matches.py
Normal file
338
tests/test_matches.py
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
import pytest
|
||||||
|
from fastcore.basics import NotStr
|
||||||
|
from fasthtml.components import *
|
||||||
|
|
||||||
|
from myfasthtml.core.matcher import matches, StartsWith, Contains, DoesNotContain, Empty, DoNotCheck, ErrorOutput, \
|
||||||
|
ErrorComparisonOutput
|
||||||
|
from myfasthtml.core.testclient import MyFT
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('actual, expected', [
|
||||||
|
(None, None),
|
||||||
|
(123, 123),
|
||||||
|
(Div(), Div()),
|
||||||
|
([Div(), Span()], [Div(), Span()]),
|
||||||
|
(Div(attr1="value"), Div(attr1="value")),
|
||||||
|
(Div(attr1="value", attr2="value"), Div(attr1="value")),
|
||||||
|
(Div(attr1="valueXXX", attr2="value"), Div(attr1=StartsWith("value"))),
|
||||||
|
(Div(attr1="before value after", attr2="value"), Div(attr1=Contains("value"))),
|
||||||
|
(Div(attr1="before after", attr2="value"), Div(attr1=DoesNotContain("value"))),
|
||||||
|
(None, DoNotCheck()),
|
||||||
|
(123, DoNotCheck()),
|
||||||
|
(Div(), DoNotCheck()),
|
||||||
|
([Div(), Span()], DoNotCheck()),
|
||||||
|
(NotStr("123456"), NotStr("123")), # for NotStr, only the beginning is checked
|
||||||
|
(Div(), Div(Empty())),
|
||||||
|
(Div(123), Div(123)),
|
||||||
|
(Div(Span(123)), Div(Span(123))),
|
||||||
|
(Div(Span(123)), Div(DoNotCheck())),
|
||||||
|
])
|
||||||
|
def test_i_can_match(actual, expected):
|
||||||
|
assert matches(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('actual, expected, error_message', [
|
||||||
|
(None, Div(), "Actual is None"),
|
||||||
|
(Div(), None, "Actual is not None"),
|
||||||
|
(123, Div(), "The types are different"),
|
||||||
|
(123, 124, "The values are different"),
|
||||||
|
([Div(), Span()], [], "Actual is bigger than expected"),
|
||||||
|
([], [Div(), Span()], "Actual is smaller than expected"),
|
||||||
|
("not a list", [Div(), Span()], "The types are different"),
|
||||||
|
([Div(), Span()], [Div(), 123], "The types are different"),
|
||||||
|
(Div(), Span(), "The elements are different"),
|
||||||
|
([Div(), Span()], [Div(), Div()], "The elements are different"),
|
||||||
|
(Div(), Div(attr1="value"), "'attr1' is not found in Actual"),
|
||||||
|
(Div(attr2="value"), Div(attr1="value"), "'attr1' is not found in Actual"),
|
||||||
|
(Div(attr1="value1"), Div(attr1="value2"), "The values are different for 'attr1'"),
|
||||||
|
(Div(attr1="value1"), Div(attr1=StartsWith("value2")), "The condition 'StartsWith(value2)' is not satisfied"),
|
||||||
|
(Div(attr1="value1"), Div(attr1=Contains("value2")), "The condition 'Contains(value2)' is not satisfied"),
|
||||||
|
(Div(attr1="value1 value2"), Div(attr1=DoesNotContain("value2")), "The condition 'DoesNotContain(value2)'"),
|
||||||
|
(NotStr("456"), NotStr("123"), "Notstr values are different"),
|
||||||
|
(Div(attr="value"), Div(Empty()), "Actual is not empty"),
|
||||||
|
(Div(120), Div(Empty()), "Actual is not empty"),
|
||||||
|
(Div(Span()), Div(Empty()), "Actual is not empty"),
|
||||||
|
(Div(), Div(Span()), "Actual is lesser than expected"),
|
||||||
|
(Div(), Div(123), "Actual is lesser than expected"),
|
||||||
|
(Div(Span()), Div(Div()), "The elements are different"),
|
||||||
|
(Div(123), Div(Div()), "The types are different"),
|
||||||
|
(Div(123), Div(456), "The values are different"),
|
||||||
|
(Div(Span(), Span()), Div(Span(), Div()), "The elements are different"),
|
||||||
|
(Div(Span(Div())), Div(Span(Span())), "The elements are different"),
|
||||||
|
])
|
||||||
|
def test_i_can_detect_errors(actual, expected, error_message):
|
||||||
|
with pytest.raises(AssertionError) as exc_info:
|
||||||
|
matches(actual, expected)
|
||||||
|
assert error_message in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('element, expected_path', [
|
||||||
|
(Div(), "Path : 'div"),
|
||||||
|
(Div(Span()), "Path : 'div.span"),
|
||||||
|
(Div(Span(Div())), "Path : 'div.span.div"),
|
||||||
|
(Div(id="div_id"), "Path : 'div#div_id"),
|
||||||
|
(Div(cls="div_class"), "Path : 'div[class=div_class]"),
|
||||||
|
(Div(name="div_class"), "Path : 'div[name=div_class]"),
|
||||||
|
(Div(attr="value"), "Path : 'div"),
|
||||||
|
(Div(Span(Div(), cls="span_class"), id="div_id"), "Path : 'div#div_id.span[class=span_class].div"),
|
||||||
|
])
|
||||||
|
def test_i_can_properly_show_path(element, expected_path):
|
||||||
|
def _construct_test_element(source, tail):
|
||||||
|
res = MyFT(source.tag, source.attrs)
|
||||||
|
if source.children:
|
||||||
|
res.children = [_construct_test_element(child, tail) for child in source.children]
|
||||||
|
else:
|
||||||
|
res.children = [tail]
|
||||||
|
return res
|
||||||
|
|
||||||
|
with pytest.raises(AssertionError) as exc_info:
|
||||||
|
actual = _construct_test_element(element, "Actual")
|
||||||
|
expected = _construct_test_element(element, "Expected")
|
||||||
|
matches(actual, expected)
|
||||||
|
|
||||||
|
assert expected_path in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_path():
|
||||||
|
elt = Div()
|
||||||
|
expected = Div()
|
||||||
|
path = "div#div_id.div.span[class=span_class].p[name=p_name].div"
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "id"="div_id" ...',
|
||||||
|
' (div ...',
|
||||||
|
' (span "class"="span_class" ...',
|
||||||
|
' (p "name"="p_name" ...',
|
||||||
|
' (div )']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_attribute():
|
||||||
|
elt = Div(attr1="value1", attr2="value2")
|
||||||
|
expected = elt
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value1" "attr2"="value2")']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_attribute_missing_1():
|
||||||
|
elt = Div(attr2="value2")
|
||||||
|
expected = Div(attr1="value1", attr2="value2")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="** MISSING **" "attr2"="value2")',
|
||||||
|
' ^^^^^^^^^^^^^^^^^^^^^^^ ']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_attribute_missing_2():
|
||||||
|
elt = Div(attr1="value1")
|
||||||
|
expected = Div(attr1="value1", attr2="value2")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value1" "attr2"="** MISSING **")',
|
||||||
|
' ^^^^^^^^^^^^^^^^^^^^^^^']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_attribute_wrong_value():
|
||||||
|
elt = Div(attr1="value3", attr2="value2")
|
||||||
|
expected = Div(attr1="value1", attr2="value2")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value3" "attr2"="value2")',
|
||||||
|
' ^^^^^^^^^^^^^^^^ ']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_constant():
|
||||||
|
elt = 123
|
||||||
|
expected = elt
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['123']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_constant_wrong_value():
|
||||||
|
elt = 123
|
||||||
|
expected = 456
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['123',
|
||||||
|
'^^^']
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_when_predicate():
|
||||||
|
elt = "before value after"
|
||||||
|
expected = Contains("value")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ["before value after"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_when_predicate_wrong_value():
|
||||||
|
elt = "before after"
|
||||||
|
expected = Contains("value")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ["before after",
|
||||||
|
"^^^^^^^^^^^^"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_child_element():
|
||||||
|
elt = Div(P(id="p_id"), Div(id="child_1"), Div(id="child_2"), attr1="value1")
|
||||||
|
expected = elt
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value1"',
|
||||||
|
' (p "id"="p_id")',
|
||||||
|
' (div "id"="child_1")',
|
||||||
|
' (div "id"="child_2")',
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_child_element_indicating_sub_children():
|
||||||
|
elt = Div(P(id="p_id"), Div(Div(id="child_2"), id="child_1"), attr1="value1")
|
||||||
|
expected = elt
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value1"',
|
||||||
|
' (p "id"="p_id")',
|
||||||
|
' (div "id"="child_1" ...)',
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_child_element_wrong_value():
|
||||||
|
elt = Div(P(id="p_id"), Div(id="child_2"), attr1="value1")
|
||||||
|
expected = Div(P(id="p_id"), Div(id="child_1"), attr1="value1")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value1"',
|
||||||
|
' (p "id"="p_id")',
|
||||||
|
' (div "id"="child_2")',
|
||||||
|
' ^^^^^^^^^^^^^^',
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_error_fewer_elements():
|
||||||
|
elt = Div(P(id="p_id"), attr1="value1")
|
||||||
|
expected = Div(P(id="p_id"), Div(id="child_1"), attr1="value1")
|
||||||
|
path = ""
|
||||||
|
error_output = ErrorOutput(path, elt, expected)
|
||||||
|
error_output.compute()
|
||||||
|
assert error_output.output == ['(div "attr1"="value1"',
|
||||||
|
' (p "id"="p_id")',
|
||||||
|
' ! ** MISSING ** !',
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_comparison():
|
||||||
|
actual = Div(P(id="p_id"), attr1="value1")
|
||||||
|
expected = actual
|
||||||
|
actual_out = ErrorOutput("", actual, expected)
|
||||||
|
expected_out = ErrorOutput("", expected, expected)
|
||||||
|
|
||||||
|
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
|
||||||
|
|
||||||
|
res = comparison_out.render()
|
||||||
|
|
||||||
|
assert "\n" + res == '''
|
||||||
|
(div "attr1"="value1" | (div "attr1"="value1"
|
||||||
|
(p "id"="p_id") | (p "id"="p_id")
|
||||||
|
) | )'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_comparison_with_path():
|
||||||
|
actual = Div(P(id="p_id"), attr1="value1")
|
||||||
|
expected = actual
|
||||||
|
actual_out = ErrorOutput("div#div_id.span[class=cls].div", actual, expected)
|
||||||
|
expected_out = ErrorOutput("div#div_id.span[class=cls].div", expected, expected)
|
||||||
|
|
||||||
|
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
|
||||||
|
|
||||||
|
res = comparison_out.render()
|
||||||
|
|
||||||
|
assert "\n" + res == '''
|
||||||
|
(div "id"="div_id" ... | (div "id"="div_id" ...
|
||||||
|
(span "class"="cls" ... | (span "class"="cls" ...
|
||||||
|
(div "attr1"="value1" | (div "attr1"="value1"
|
||||||
|
(p "id"="p_id") | (p "id"="p_id")
|
||||||
|
) | )'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_comparison_when_missing_attributes():
|
||||||
|
actual = Div(P(id="p_id"), attr1="value1")
|
||||||
|
expected = Div(P(id="p_id"), attr2="value1")
|
||||||
|
actual_out = ErrorOutput("", actual, expected)
|
||||||
|
expected_out = ErrorOutput("", expected, expected)
|
||||||
|
|
||||||
|
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
|
||||||
|
|
||||||
|
res = comparison_out.render()
|
||||||
|
|
||||||
|
assert "\n" + res == '''
|
||||||
|
(div "attr2"="** MISSING **" | (div "attr2"="value1"
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^ |
|
||||||
|
(p "id"="p_id") | (p "id"="p_id")
|
||||||
|
) | )'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_comparison_when_wrong_attributes():
|
||||||
|
actual = Div(P(id="p_id"), attr1="value2")
|
||||||
|
expected = Div(P(id="p_id"), attr1="value1")
|
||||||
|
actual_out = ErrorOutput("", actual, expected)
|
||||||
|
expected_out = ErrorOutput("", expected, expected)
|
||||||
|
|
||||||
|
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
|
||||||
|
|
||||||
|
res = comparison_out.render()
|
||||||
|
|
||||||
|
assert "\n" + res == '''
|
||||||
|
(div "attr1"="value2" | (div "attr1"="value1"
|
||||||
|
^^^^^^^^^^^^^^^^ |
|
||||||
|
(p "id"="p_id") | (p "id"="p_id")
|
||||||
|
) | )'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_output_comparison_when_fewer_elements():
|
||||||
|
actual = Div(P(id="p_id"), attr1="value1")
|
||||||
|
expected = Div(Span(id="s_id"), P(id="p_id"), attr1="value1")
|
||||||
|
actual_out = ErrorOutput("", actual, expected)
|
||||||
|
expected_out = ErrorOutput("", expected, expected)
|
||||||
|
|
||||||
|
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
|
||||||
|
|
||||||
|
res = comparison_out.render()
|
||||||
|
|
||||||
|
assert "\n" + res == '''
|
||||||
|
(div "attr1"="value1" | (div "attr1"="value1"
|
||||||
|
(p "id"="p_id") | (span "id"="s_id")
|
||||||
|
^ ^^^^^^^^^^^ |
|
||||||
|
! ** MISSING ** ! | (p "id"="p_id")
|
||||||
|
) | )'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_see_the_diff_when_matching():
|
||||||
|
actual = Div(attr1="value1")
|
||||||
|
expected = Div(attr1=Contains("value2"))
|
||||||
|
|
||||||
|
with pytest.raises(AssertionError) as exc_info:
|
||||||
|
matches(actual, expected)
|
||||||
|
|
||||||
|
debug_output = str(exc_info.value)
|
||||||
|
assert "\n" + debug_output == """
|
||||||
|
Path : 'div'
|
||||||
|
Error : The condition 'Contains(value2)' is not satisfied.
|
||||||
|
(div "attr1"="value1") | (div "attr1"="Contains(value2)")
|
||||||
|
^^^^^^^^^^^^^^^^ |"""
|
||||||
Reference in New Issue
Block a user