From 9e0b047121bafc2d3b4333988cadeafeac62d705 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 19 Oct 2024 11:16:00 +0100 Subject: [PATCH] snapshot test --- src/textual/app.py | 3 +- src/textual/css/constants.py | 4 + src/textual/css/stylesheet.py | 4 +- src/textual/dom.py | 2 + src/textual/widget.py | 10 +- .../test_snapshots/test_pseudo_classes.svg | 153 ++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 31 ++++ 7 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 tests/snapshot_tests/__snapshots__/test_snapshots/test_pseudo_classes.svg diff --git a/src/textual/app.py b/src/textual/app.py index e829110b60..bbb33b4e41 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -3153,7 +3153,8 @@ def _register( self._register_child(parent, widget, before, after) if widget._nodes: self._register(widget, *widget._nodes, cache=cache) - apply_stylesheet(widget, cache=cache) + for widget in widget_list: + apply_stylesheet(widget, cache=cache) if not self._running: # If the app is not running, prevent awaiting of the widget tasks diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py index 026e78be45..82af4e4912 100644 --- a/src/textual/css/constants.py +++ b/src/textual/css/constants.py @@ -72,6 +72,10 @@ "inline", "light", "nocolor", + "first-of-type", + "last-of-type", + "odd", + "even", } VALID_OVERLAY: Final = {"none", "screen"} VALID_CONSTRAIN: Final = {"inflect", "inside", "none"} diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index bf7bd35776..27f11508f9 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -439,7 +439,6 @@ def _check_rule( "last-of_type", "odd", "even", - "focus-within", } def apply( @@ -484,6 +483,9 @@ def apply( node._has_hover_style = "hover" in all_pseudo_classes node._has_focus_within = "focus-within" in all_pseudo_classes + node._has_order_style = not all_pseudo_classes.isdisjoint( + {"first-of-type", "last-of-type", "odd", "even"} + ) cache_key: tuple | None = None diff --git a/src/textual/dom.py b/src/textual/dom.py index 79261bb0f3..99c0c31fd6 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -220,6 +220,8 @@ def __init__( ) self._has_hover_style: bool = False self._has_focus_within: bool = False + self._has_order_style: bool = False + """The node has an ordered dependent pseudo-style (`:odd`, `:even`, `:first-of-type`, `:last-of-type`)""" self._reactive_connect: ( dict[str, tuple[MessagePump, Reactive[object] | object]] | None ) = None diff --git a/src/textual/widget.py b/src/textual/widget.py index f6f05f84ba..cf072cba6e 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -777,7 +777,7 @@ def last_of_type(self) -> bool: if parent._nodes._updates == self._last_of_type[0]: return self._last_of_type[1] widget_type = type(self) - for node in reversed(parent.children): + for node in reversed(parent._nodes): if isinstance(node, widget_type): self._last_of_type = (parent._nodes._updates, node is self) return self._last_of_type[1] @@ -1166,8 +1166,16 @@ def mount( parent, *widgets, before=insert_before, after=insert_after ) + def update_styles(children: Iterable[DOMNode]) -> None: + """Update order related CSS""" + for child in children: + if child._has_order_style: + child._update_styles() + + self.call_later(update_styles, list(self.children)) await_mount = AwaitMount(self, mounted) self.call_next(await_mount) + return await_mount def mount_all( diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_pseudo_classes.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_pseudo_classes.svg new file mode 100644 index 0000000000..013fc91c3f --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_pseudo_classes.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PSApp + + + + + + + + + + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +Item 1 + +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Item 2 + + + +Item 3 + + + +Item 4 + + + +Item 5 + + + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +HELLO + +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 8ec68c0414..a9d3a9ec0b 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -2374,3 +2374,34 @@ def compose(self) -> ComposeResult: yield Label("A margin of 4, should be 4 cells around the text") assert snap_compare(FRApp()) + + +def test_pseudo_classes(snap_compare): + """Test pseudo classes added in https://github.com/Textualize/textual/pull/5139 + + You should see 6 bars, with alternating green and red backgrounds. + + The first bar should have a red border. + + The last bar should have a green border. + + """ + + class PSApp(App): + CSS = """ + Label { width: 1fr; height: 1fr; } + Label:first-of-type { border:heavy red; } + Label:last-of-type { border:heavy green; } + Label:odd {background: $success 20%; } + Label:even {background: $error 20%; } + """ + + def compose(self) -> ComposeResult: + for item_number in range(5): + yield Label(f"Item {item_number+1}") + + def on_mount(self) -> None: + # Mounting a new widget should updated previous widgets, as the last of type has changed + self.mount(Label("HELLO")) + + assert snap_compare(PSApp())