import pytest from fastcore.basics import NotStr from fasthtml.components import * from myfasthtml.test.matcher import matches, StartsWith, Contains, DoesNotContain, Empty, DoNotCheck, ErrorOutput, \ ErrorComparisonOutput, AttributeForbidden, AnyValue, NoChildren, TestObject from myfasthtml.test.testclient import MyFT class Dummy: def __init__(self, attr1, attr2=None): self.attr1 = attr1 self.attr2 = attr2 class Dummy2: def __init__(self, attr1, attr2): self.attr1 = attr1 self.attr2 = attr2 class TestMatches: @pytest.mark.parametrize('actual, expected', [ (None, None), (123, 123), (Div(), Div()), ([Div(), Span()], [Div(), Span()]), ({"key": Div(attr="value")}, {"key": Div(attr="value")}), ({"key": Dummy(attr1="value")}, {"key": TestObject(Dummy, attr1="value")}), (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"))), (Div(attr1="value"), Div(attr1=AnyValue())), (None, DoNotCheck()), (123, DoNotCheck()), (Div(), DoNotCheck()), ([Div(), Span()], DoNotCheck()), (NotStr("123456"), NotStr("123")), # for NotStr, only the beginning is checked (Div(), Div(Empty())), (Div(), Div(NoChildren())), (Div(attr1="value"), Div(NoChildren())), (Div(attr1="value1"), Div(AttributeForbidden("attr2"))), (Div(123), Div(123)), (Div(Span(123)), Div(Span(123))), (Div(Span(123)), Div(DoNotCheck())), (Dummy(123, "value"), TestObject(Dummy, attr1=123, attr2="value")), (Dummy(123, "value"), TestObject(Dummy, attr2="value")), (Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123))), (Dummy(123, "value"), TestObject("Dummy", attr1=123, attr2="value")), ]) def test_i_can_match(self, 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 types are different"), ([Div(), Span()], [Div(), Div()], "The types 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)'"), (Div(attr1=None), Div(attr1=AnyValue()), "'attr1' is not found in Actual"), (Div(), Div(attr1=AnyValue()), "'attr1' is not found in Actual"), (NotStr("456"), NotStr("123"), "Notstr values are different"), (Div(attr="value"), Div(Empty()), "The condition 'Empty()' is not satisfied"), (Div(Span()), Div(NoChildren()), "The condition 'NoChildren()' is not satisfied"), (Div(120), Div(Empty()), "The condition 'Empty()' is not satisfied"), (Div(Span()), Div(Empty()), "The condition 'Empty()' is not satisfied"), (Div(), Div(Span()), "Actual is lesser than expected"), (Div(), Div(123), "Actual is lesser than expected"), (Div(Span()), Div(Div()), "The types 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 types are different"), (Div(Span(Div())), Div(Span(Span())), "The types are different"), (Div(attr1="value1"), Div(AttributeForbidden("attr1")), "condition 'AttributeForbidden(attr1)' is not satisfied"), (Div(123, "value"), TestObject(Dummy, attr1=123, attr2="value2"), "The types are different"), (Dummy(123, "value"), TestObject(Dummy, attr1=123, attr3="value3"), "'attr3' is not found in Actual"), (Dummy(123, "value"), TestObject(Dummy, attr1=123, attr2="value2"), "The values are different for 'attr2'"), (Div(Div(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "The types are different"), (Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr3="value3")), "'attr3' is not found in Actual"), (Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "are different for 'attr2'"), (Div(123, "value"), TestObject("Dummy", attr1=123, attr2="value2"), "The types are different"), (Dummy(123, "value"), TestObject("Dummy", attr1=123, attr2=Contains("value2")), "The condition 'Contains(value2)' is not satisfied"), ]) def test_i_can_detect_errors(self, 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(self, 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) class TestErrorOutput: def test_i_can_output_error_path(self): """The output follows the representation of the given 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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(self): """I can display error when the condition predicate is not satisfied.""" 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(self): """I can display error when the element has children""" 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_text(self): """I can display error when the children is not a FT""" elt = Div("Hello world", 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"', ' "Hello world"', ' (div "id"="child_1")', ' (div "id"="child_2")', ')', ] def test_i_can_output_error_child_element_indicating_sub_children(self): 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(self): 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(self): 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_error_test_object(self): elt = TestObject(Dummy, attr1=123, attr2="value2") expected = elt path = "" error_output = ErrorOutput(path, elt, expected) error_output.compute() assert error_output.output == ['(Dummy "attr1"="123" "attr2"="value2")'] def test_i_can_output_error_test_object_wrong_type(self): elt = Div(attr1=123, attr2="value2") expected = TestObject(Dummy, attr1=123, attr2="value2") path = "" error_output = ErrorOutput(path, elt, expected) error_output.compute() assert error_output.output == [ '(div "attr1"="123" "attr2"="value2")', ' ^^^ ' ] def test_i_can_output_error_test_object_wrong_type_2(self): elt = Dummy2(attr1=123, attr2="value2") expected = TestObject(Dummy, attr1=123, attr2="value2") path = "" error_output = ErrorOutput(path, elt, expected) error_output.compute() assert error_output.output == [ '(Dummy2 "attr1"="123" "attr2"="value2")', ' ^^^^^^ ' ] def test_i_can_output_error_test_object_wrong_type_3(self): elt = Div(attr1=123, attr2="value2") expected = TestObject("Dummy", attr1=123, attr2="value2") path = "" error_output = ErrorOutput(path, elt, expected) error_output.compute() assert error_output.output == [ '(div "attr1"="123" "attr2"="value2")', ' ^^^ ' ] def test_i_can_output_error_test_object_wrong_value(self): elt = Dummy(attr1="456", attr2="value2") expected = TestObject(Dummy, attr1="123", attr2="value2") path = "" error_output = ErrorOutput(path, elt, expected) error_output.compute() assert error_output.output == [ '(Dummy "attr1"="456" "attr2"="value2")', ' ^^^^^^^^^^^^^ ' ] class TestErrorComparisonOutput: def test_i_can_output_comparison(self): 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(self): 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(self): 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(self): 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(self): 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(self): 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)") ^^^^^^^^^^^^^^^^ |""" def test_i_can_see_the_diff_with_test_object_when_wrong_type(self): actual = Div(attr1=123, attr2="value2") expected = TestObject(Dummy, attr1=123, attr2="value2") actual_out = ErrorOutput("dummy", actual, expected) expected_out = ErrorOutput("div", expected, expected) comparison_out = ErrorComparisonOutput(actual_out, expected_out) res = comparison_out.render() assert "\n" + res == ''' (div "attr1"="123" "attr2"="value2") | (Dummy "attr1"="123" "attr2"="value2") ^^^ |'''