Repository: Textualize/textual
File: tests/test_reactive.py (Python)

Code before:
            1 / 0

    class MyApp(App):
        def compose(self) -> ComposeResult:
            yield MyWidget("foobar")

    app = MyApp()
    async with app.run_test():
        assert app.query_one(MyWidget).foo == "foobar"


async def test_no_duplicate_external_watchers() -> None:
    """Make sure we skip duplicated watchers."""

    counter = 0

    class Holder(Widget):
        attr = var(None)

    class MyApp(App[None]):
        def __init__(self) -> None:
            super().__init__()
            self.holder = Holder()

        def on_mount(self) -> None:
            self.watch(self.holder, "attr", self.callback)

        def callback(self) -> None:
            nonlocal counter
            counter += 1

    app = MyApp()
    async with app.run_test():
        assert counter == 1
        app.holder.attr = 73
        assert counter == 2


async def test_external_watch_init_does_not_propagate() -> None:
    """Regression test for https://github.com/Textualize/textual/issues/3878.

    Make sure that when setting an extra watcher programmatically and `init` is set,
    we init only the new watcher and not the other ones, but at the same
    time make sure both watchers work in regular circumstances.
    """

    logs: list[str] = []

    class SomeWidget(Widget):
        test_1: var[int] = var(0)
        test_2: var[int] = var(0, init=False)

Review comment (refactor):
The test name/docstring says it's checking that we skip duplicate watchers, but the test no longer involves a duplicate watcher. It seems to just be checking the basic usage of `watch`, which is probably covered by another test.

I reckon this should be updated such that are **two** watchers defined with `self.watch(self.holder, "attr", self.callback)`, and checking that the `self.callback` fires only once on init, and once again on setting the value of `attr`.

Perhaps more importantly, another test should probably cover this case:  If I define one watcher as `self.watch(self.holder, "attr", self.first_callback)` and then another with `self.watch(self.holder, "attr", self.second_callback)`. Do I see one run of `self.first_callback` followed by one run of `self.second_callback` on init? Then when I assign `attr`, do I see the another one run of each callback in the correct order?

Code after:
            1 / 0

    class MyApp(App):
        def compose(self) -> ComposeResult:
            yield MyWidget("foobar")

    app = MyApp()
    async with app.run_test():
        assert app.query_one(MyWidget).foo == "foobar"


async def test_no_duplicate_external_watchers() -> None:
    """Make sure we skip duplicated watchers."""

    counter = 0

    class Holder(Widget):
        attr = var(None)

    class MyApp(App[None]):
        def __init__(self) -> None:
            super().__init__()
            self.holder = Holder()

        def on_mount(self) -> None:
            self.watch(self.holder, "attr", self.callback)
            self.watch(self.holder, "attr", self.callback)

        def callback(self) -> None:
            nonlocal counter
            counter += 1

    app = MyApp()
    async with app.run_test():
        assert counter == 1
        app.holder.attr = 73
        assert counter == 2


async def test_external_watch_init_does_not_propagate() -> None:
    """Regression test for https://github.com/Textualize/textual/issues/3878.

    Make sure that when setting an extra watcher programmatically and `init` is set,
    we init only the new watcher and not the other ones, but at the same
    time make sure both watchers work in regular circumstances.
    """

    logs: list[str] = []

    class SomeWidget(Widget):
        test_1: var[int] = var(0)