diff --git a/changes/224.feature.rst b/changes/224.feature.rst deleted file mode 100644 index ec5c45f..0000000 --- a/changes/224.feature.rst +++ /dev/null @@ -1 +0,0 @@ -It is now possible to create a node without supplying an initial style. diff --git a/src/travertino/node.py b/src/travertino/node.py index b852843..06306c4 100644 --- a/src/travertino/node.py +++ b/src/travertino/node.py @@ -1,5 +1,9 @@ class Node: - def __init__(self, style=None, applicator=None, children=None): + def __init__(self, style, applicator=None, children=None): + # Explicitly set the internal attribute first, since the setter for style will + # access the applicator property. + self._applicator = None + self.style = style self.applicator = applicator @@ -23,10 +27,6 @@ def style(self): @style.setter def style(self, style): - if style is None: - self._style = None - return - self._style = style.copy() self.intrinsic = self.style.IntrinsicSize() self.layout = self.style.Box(self) @@ -41,19 +41,16 @@ def applicator(self): Assigning an applicator triggers an application of the node's style if a style has already been assigned. """ - # Has to have a fallback, because it's accessed before its assignment in the - # setter for style - return getattr(self, "_applicator", None) + return self._applicator @applicator.setter def applicator(self, applicator): self._applicator = applicator + self.style._applicator = applicator + if applicator: applicator.node = self - if self.style is not None: - self.style._applicator = applicator - @property def root(self): """The root of the tree containing this node. diff --git a/tests/test_node.py b/tests/test_node.py index 17d9228..1df2ea3 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -245,7 +245,7 @@ def test_clear(): assert node.children == [] -def test_create_with_only_style(): +def test_create_with_no_applicator(): style = Style(int_prop=5) node = Node(style=style) @@ -258,131 +258,113 @@ def test_create_with_only_style(): node.style.reapply.assert_not_called() -def test_assign_only_style(): +def test_create_with_applicator(): style = Style(int_prop=5) - node = Node() - node.style = style + applicator = Mock() + node = Node(style=style, applicator=applicator) # Style copies on assignment. assert isinstance(node.style, Style) assert node.style == style assert node.style is not style - # Since no applicator has been assigned, style wasn't applied. - node.style.reapply.assert_not_called() - - -def test_create_with_only_applicator(): - applicator = Mock() - - # No reapply should be attempted. If it were, it would raise an AttributeError as - # None has no reapply() method. - node = Node(applicator=applicator) - # Applicator assignment does *not* copy. assert node.applicator is applicator - + # Applicator gets a reference back to its node and to the style. assert applicator.node is node + assert node.style._applicator is applicator - -def test_assign_only_applicator(): - applicator = Mock() - node = Node() - - # No reapply should be attempted. If it were, it would raise an AttributeError as - # None has no reapply() method. - node.applicator = applicator - - # Applicator assignment does *not* copy. - assert node.applicator is applicator - - -def test_create_with_style_and_applicator(): - style = Style(int_prop=5) - applicator = Mock() - node = Node(style=style, applicator=applicator) - - # With both style and applicator present, style should be applied. + # Assigning a non-None applicator should always apply style. node.style.reapply.assert_called_once() - # Should have also assigned the applicator the style. - assert node.style._applicator is applicator -def test_style_then_applicator(): - style = Style(int_prop=5) +@pytest.mark.parametrize( + "node", + [ + Node(style=Style()), + Node(style=Style(), applicator=Mock()), + ], +) +def test_assign_applicator(node): + node.style.reapply.reset_mock() + applicator = Mock() - node = Node(style=style) node.applicator = applicator - # With both style and applicator present, style should be applied. - node.style.reapply.assert_called_once() - # Should have also assigned the applicator the style. + # Applicator assignment does *not* copy. + assert node.applicator is applicator + # Applicator gets a reference back to its node and to the style. + assert applicator.node is node assert node.style._applicator is applicator - -def test_applicator_then_style(): - style = Style(int_prop=5) - applicator = Mock() - node = Node(applicator=applicator) - node.style = style - - # With both style and applicator present, style should be applied. + # Assigning a non-None applicator should always apply style. node.style.reapply.assert_called_once() - # Should have also assigned the applicator the style. - assert node.style._applicator is applicator @pytest.mark.parametrize( "node", [ - Node(), Node(style=Style()), - Node(applicator=Mock()), Node(style=Style(), applicator=Mock()), ], ) def test_assign_applicator_none(node): - if node.style is not None: - node.style.reapply.reset_mock() + node.style.reapply.reset_mock() node.applicator = None assert node.applicator is None - if node.style is not None: - # Should be updated on style as well - assert node.style._applicator is None - # Assigning None to applicator does not trigger reapply. - node.style.reapply.assert_not_called() + # Should be updated on style as well + assert node.style._applicator is None + # Assigning None to applicator does not trigger reapply. + node.style.reapply.assert_not_called() -@pytest.mark.parametrize( - "node", - [ - Node(), - Node(style=Style()), - Node(applicator=Mock()), - Node(style=Style(), applicator=Mock()), - ], -) -def test_assign_style_none(node): - node.style = None - assert node.style is None +def test_assign_style_with_applicator(): + style_1 = Style(int_prop=5) + node = Node(style=style_1, applicator=Mock()) - # No reapply should be attempted. If it were, it would raise an AttributeError as - # None has no reapply() method. + node.style.reapply.reset_mock() + style_2 = Style(int_prop=10) + node.style = style_2 + + # Style copies on assignment. + assert isinstance(node.style, Style) + assert node.style == style_2 + assert node.style is not style_2 + + assert node.style != style_1 + + # Since an applicator has already been assigned, assigning style applies the style. + node.style.reapply.assert_called_once() + + +def test_assign_style_with_no_applicator(): + style_1 = Style(int_prop=5) + node = Node(style=style_1) + + node.style.reapply.reset_mock() + style_2 = Style(int_prop=10) + node.style = style_2 + + # Style copies on assignment. + assert isinstance(node.style, Style) + assert node.style == style_2 + assert node.style is not style_2 + + assert node.style != style_1 + + # Since no applicator was present, style should not be applied. + node.style.reapply.assert_not_called() def test_apply_before_node_is_ready(): style = BrokenStyle() applicator = Mock() - with pytest.warns(RuntimeWarning): - Node(style=style, applicator=applicator) - with pytest.warns(RuntimeWarning): node = Node(style=style) node.applicator = applicator with pytest.warns(RuntimeWarning): - node = Node(applicator=applicator) - node.style = style + Node(style=style, applicator=applicator)