[PATCH b4 2/2] review-tui: test null Patchwork series name in bulk set-state

Christian Brauner <[email protected]> Tue, 30 Jun 2026 15:41:33 +0200
Newsgroups org.kernel.linux.tools
Message-ID <20260630-review-tui-null-series-name-v1-2-67d7c9334454@kernel.org>
Add a regression test that drives the set-state action on a series whose
name is None. Before the fix the apply-state modal raised VisualError
("unable to display 'NoneType'") while laying out its heading Label; the
test asserts the modal now renders the '(no subject)' placeholder instead.

The apply worker -- the only thing the modal's on_mount starts -- is stubbed
out so the modal stays mounted for inspection and never touches the network,
while the compose + layout pass that used to crash still runs.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 src/tests/test_tui_pw.py | 44 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/src/tests/test_tui_pw.py b/src/tests/test_tui_pw.py
index 2360166..755064b 100644
--- a/src/tests/test_tui_pw.py
+++ b/src/tests/test_tui_pw.py
@@ -21,7 +21,7 @@ import b4.review
 import b4.review.tracking as tracking
 import liblore
 from b4.review._review import PwFetchResult
-from b4.review_tui._modals import SetStateScreen
+from b4.review_tui._modals import ApplyStateModal, SetStateScreen
 from b4.review_tui._pw_app import PwApp, PwFetchProgress
 
 # ---------------------------------------------------------------------------
@@ -685,3 +685,45 @@ class TestPwBulkSetState:
             app.action_set_state()
             await pilot.pause()
             assert not isinstance(app.screen, SetStateScreen)
+
+
+class TestPwSetStateNullName:
+    """A Patchwork series with a null ``name`` must not crash set-state.
+
+    Patchwork returns ``name: null`` for a series with no cover letter. The
+    bulk set-state path built the apply modal's heading with
+    ``series.get('name', '(no subject)')``, whose default only applies when the
+    key is *absent* -- a present-but-None name sailed through as ``None`` into
+    ``ApplyStateModal``'s Label, which Textual refuses to render ("unable to
+    display 'NoneType'"). It must fall back to '(no subject)'.
+    """
+
+    @pytest.mark.asyncio
+    async def test_null_name_falls_back_to_no_subject(
+        self, monkeypatch: pytest.MonkeyPatch
+    ) -> None:
+        # Skip the apply worker (the only thing on_mount starts) so the modal
+        # stays up and off the network; we just need it to compose and lay out
+        # its Labels -- the render pass that raised VisualError before the fix.
+        monkeypatch.setattr(ApplyStateModal, 'on_mount', lambda self: None)
+
+        series = _mk_series(1)
+        series['name'] = None
+        _install_series(monkeypatch, [series])
+        app = PwApp('k', 'https://pw.example.org', 'proj')
+        async with app.run_test(size=(120, 30)) as pilot:
+            await app.workers.wait_for_complete()
+            await pilot.pause()
+            app.action_set_state()
+            await pilot.pause()
+            assert isinstance(app.screen, SetStateScreen)
+            await app.screen.dismiss(('reviewing', False))
+            # Mounting + laying out the modal is what crashed before the fix;
+            # reaching the assertions at all proves it now renders.
+            await pilot.pause()
+
+            modal = app.screen
+            assert isinstance(modal, ApplyStateModal)
+            assert modal._series_name == '(no subject)'
+            label = modal.query_one('#apply-series', Label)
+            assert _static_text(label) == '(no subject)'

-- 
2.53.0