Repository: Textualize/textual

Issue:
Make moving a child before or after itself, a no-op
If we try to use `move_child` to move a widget to after/before itself, should we get an error or is that a no-op?
(Add tests to tests/test_widget_child_moving.py accordingly.)

Context: this arose from writing some functionality in a TODO app that sorts TODO items according to their due date and reorders them when the due date is edited. From this point of view, it makes sense for moving to after/before itself to be a no-op.
Otherwise, the sorting code needs to take extra steps to make sure we don't try to move things to where they already are.


Discussion:
The more I think about it, the more I think I'm inclined to agree with you that it makes sense to have it be a no-op. "Move child to where it is" seems like it shouldn't error even if it's a weird thing to ask, as it's not the same as calling `move_child` and not providing anywhere, or providing somewhere out of bounds. The request is a valid location and is within bounds.
No-op makes sense.
Note to self: fix the docstrings in `move_child` as well.
Note to self x2: Dave owes me a favour.
> Note to self x2: Dave owes me a favour.

Let's call it payback for retconning my first blog post. 😛
Does this require work? What happens now if you move a child before/after itself?

I agree that should be a no-op and not an error.
Just tested this and it appears to still be a problem. I'll turn these into proper `XFAIL` tests so we've got them noted.
And the problem is what, exactly? Moving a child before itself raises an error and we think it should be a no-op?
Yes and [yes](https://github.com/Textualize/textual/issues/1743#issuecomment-1422886317).

Resolving patch:
diff --git a/src/textual/widget.py b/src/textual/widget.py
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -795,19 +795,15 @@ def move_child(
 
         Args:
             child: The child widget to move.
-            before: Optional location to move before. An `int` is the index
-                of the child to move before, a `str` is a `query_one` query to
-                find the widget to move before.
-            after: Optional location to move after. An `int` is the index
-                of the child to move after, a `str` is a `query_one` query to
-                find the widget to move after.
+            before: Child widget or location index to move before.
+            after: Child widget or location index to move after.
 
         Raises:
             WidgetError: If there is a problem with the child or target.
 
         Note:
-            Only one of ``before`` or ``after`` can be provided. If neither
-            or both are provided a ``WidgetError`` will be raised.
+            Only one of `before` or `after` can be provided. If neither
+            or both are provided a `WidgetError` will be raised.
         """
 
         # One or the other of before or after are required. Can't do
@@ -817,6 +813,10 @@ def move_child(
         elif before is not None and after is not None:
             raise WidgetError("Only one of `before` or `after` can be handled.")
 
+        # We short-circuit the no-op, otherwise it will error later down the road.
+        if child is before or child is after:
+            return
+
         def _to_widget(child: int | Widget, called: str) -> Widget:
             """Ensure a given child reference is a Widget."""
             if isinstance(child, int):


Test patch:
diff --git a/tests/test_widget_child_moving.py b/tests/test_widget_child_moving.py
--- a/tests/test_widget_child_moving.py
+++ b/tests/test_widget_child_moving.py
@@ -42,22 +42,18 @@ async def test_move_child_to_outside() -> None:
             pilot.app.screen.move_child(child, before=Widget())
 
 
-@pytest.mark.xfail(
-    strict=True, reason="https://github.com/Textualize/textual/issues/1743"
-)
 async def test_move_child_before_itself() -> None:
     """Test moving a widget before itself."""
+
     async with App().run_test() as pilot:
         child = Widget(Widget())
         await pilot.app.mount(child)
         pilot.app.screen.move_child(child, before=child)
 
 
-@pytest.mark.xfail(
-    strict=True, reason="https://github.com/Textualize/textual/issues/1743"
-)
 async def test_move_child_after_itself() -> None:
     """Test moving a widget after itself."""
+    # Regression test for https://github.com/Textualize/textual/issues/1743
     async with App().run_test() as pilot:
         child = Widget(Widget())
         await pilot.app.mount(child)