Repository: wagtail/wagtail

Issue:
Search on listing views doesn't work unless the `?q=` param exists in the URL
<!--
Found a bug? Please fill out the sections below. 👍
-->

### Issue Summary

Possible regression in https://github.com/wagtail/wagtail/pull/9768

The `URLSearchParams.get()` returns `null` if the param doesn't exist, so the following code:

https://github.com/wagtail/wagtail/blob/a3f10acae17c892d843c419495e4204adb3ed991/client/src/entrypoints/admin/core.js#L270-L276

will crash during `currentQuery.trim()` when searching on the listing views (snippets, images, etc.) if the `?q=` param doesn't exist in the URL.

Might be a good time to add `required=False` in here as well:

https://github.com/wagtail/wagtail/blob/a3f10acae17c892d843c419495e4204adb3ed991/wagtail/admin/forms/search.py#L12

to remove this silly error when `q` is an empty string:

<img width="473" alt="image" src="https://user-images.githubusercontent.com/6379424/213499685-ce37c064-2635-434f-952f-e85fae4ab9af.png">

<!--
A summary of the issue.
-->

### Steps to Reproduce

1. Spin up bakerydemo
2. Open the images listing
3. Try to search



Discussion:

Resolving patch:
diff --git a/wagtail/admin/forms/search.py b/wagtail/admin/forms/search.py
--- a/wagtail/admin/forms/search.py
+++ b/wagtail/admin/forms/search.py
@@ -9,4 +9,8 @@ def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.fields["q"].widget.attrs = {"placeholder": placeholder}
 
-    q = forms.CharField(label=gettext_lazy("Search term"), widget=forms.TextInput())
+    q = forms.CharField(
+        label=gettext_lazy("Search term"),
+        widget=forms.TextInput(),
+        required=False,
+    )


Test patch:
diff --git a/wagtail/images/tests/test_admin_views.py b/wagtail/images/tests/test_admin_views.py
--- a/wagtail/images/tests/test_admin_views.py
+++ b/wagtail/images/tests/test_admin_views.py
@@ -44,6 +44,16 @@ def test_simple(self):
         self.assertEqual(response.status_code, 200)
         self.assertTemplateUsed(response, "wagtailimages/images/index.html")
         self.assertContains(response, "Add an image")
+        # The search box should not raise an error
+        self.assertNotContains(response, "This field is required.")
+
+    def test_empty_q(self):
+        response = self.get({"q": ""})
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context["query_string"], "")
+        self.assertContains(response, "Add an image")
+        # The search box should not raise an error
+        self.assertNotContains(response, "This field is required.")
 
     def test_search(self):
         response = self.get({"q": "Hello"})
diff --git a/wagtail/snippets/tests/test_snippets.py b/wagtail/snippets/tests/test_snippets.py
--- a/wagtail/snippets/tests/test_snippets.py
+++ b/wagtail/snippets/tests/test_snippets.py
@@ -479,6 +479,23 @@ def test_simple(self):
         self.assertIn(self.snippet_b, items)
         self.assertIn(self.snippet_c, items)
 
+        # The search box should not raise an error
+        self.assertNotContains(response, "This field is required.")
+
+    def test_empty_q(self):
+        response = self.get({"q": ""})
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, "wagtailsnippets/snippets/type_index.html")
+
+        # All snippets should be in items
+        items = list(response.context["page_obj"].object_list)
+        self.assertIn(self.snippet_a, items)
+        self.assertIn(self.snippet_b, items)
+        self.assertIn(self.snippet_c, items)
+
+        # The search box should not raise an error
+        self.assertNotContains(response, "This field is required.")
+
     def test_is_searchable(self):
         self.assertTrue(self.get().context["is_searchable"])