from wagtail.search.backends import get_search_backend from tests.conftest import EventPageFactory, GenericPageFactory SEARCH_QUERY = """ query Search($query: String) { results: search(query: $query) { __typename ... on PageInterface { title } } } """ def _index(page): # Wagtail's post_save signal enqueues indexing via django-tasks, which isn't # drained synchronously in tests. Call the backend directly so the page is # findable through the live search code path. get_search_backend().add(page) def _titles_for(body, typename): return [r["title"] for r in body["data"]["results"] if r["__typename"] == typename] def test_search_returns_live_generic_page(home_page, graphql_post): page = GenericPageFactory( parent=home_page, title="PublishedGenericSearchToken", slug="published-generic-search", ) _index(page) response, body = graphql_post(SEARCH_QUERY, {"query": "PublishedGenericSearchToken"}) assert response.status_code == 200 assert "errors" not in body, body assert "PublishedGenericSearchToken" in _titles_for(body, "GenericPage") def test_search_excludes_draft_generic_page(home_page, graphql_post): page = GenericPageFactory( parent=home_page, title="DraftGenericSearchToken", slug="draft-generic-search", live=False, ) _index(page) response, body = graphql_post(SEARCH_QUERY, {"query": "DraftGenericSearchToken"}) assert response.status_code == 200 assert "errors" not in body, body assert "DraftGenericSearchToken" not in _titles_for(body, "GenericPage") def test_search_returns_live_event_page(home_page, event_index, graphql_post): page = EventPageFactory( parent=event_index, title="PublishedEventSearchToken", slug="published-event-search", ) _index(page) response, body = graphql_post(SEARCH_QUERY, {"query": "PublishedEventSearchToken"}) assert response.status_code == 200 assert "errors" not in body, body assert "PublishedEventSearchToken" in _titles_for(body, "EventPage") def test_search_excludes_draft_event_page(home_page, event_index, graphql_post): page = EventPageFactory( parent=event_index, title="DraftEventSearchToken", slug="draft-event-search", live=False, ) _index(page) response, body = graphql_post(SEARCH_QUERY, {"query": "DraftEventSearchToken"}) assert response.status_code == 200 assert "errors" not in body, body assert "DraftEventSearchToken" not in _titles_for(body, "EventPage") def test_search_results_not_grouped_by_type(home_page, event_index, graphql_post): # Two pages of different types matching the query equally, plus a third # page of one of those types that should rank highest. Under the # per-model-iteration resolver, all Generic results come before all Event # results (or vice versa) — type-grouped — so the highest-relevance Event # ends up after a less-relevant Generic. Cross-type relevance ordering # should put the strongest match first regardless of type. weak_generic = GenericPageFactory( parent=home_page, title="Klatremus klatremus klatremus", slug="weak-generic", ) weak_event = EventPageFactory( parent=event_index, title="Klatremus klatremus klatremus", slug="weak-event", ) strong_event = EventPageFactory( parent=event_index, title="Klatremus klatremus klatremus klatremus klatremus klatremus", slug="strong-event", ) _index(weak_generic) _index(weak_event) _index(strong_event) response, body = graphql_post(SEARCH_QUERY, {"query": "klatremus"}) assert response.status_code == 200 assert "errors" not in body, body order = [ (r["__typename"], r["title"]) for r in body["data"]["results"] if r["__typename"] in ("GenericPage", "EventPage") ] assert len(order) == 3, order # Per-type grouping would put all results of one type consecutively # before the other type. Cross-type relevance ordering should interleave. types_seen = [t for t, _ in order] assert types_seen != ["GenericPage", "EventPage", "EventPage"], order assert types_seen != ["EventPage", "EventPage", "GenericPage"], order