diff --git a/CHANGELOG.md b/CHANGELOG.md index de6d5f8e..3fc55826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +### Changed + +- Make value fields nullable on `PageChooserBlock`, `SnippetChooserBlock`, `DocumentChooserBlock` and `ImageChooserBlock` ([#396](https://github.com/torchbox/wagtail-grapple/pull/396)) + ## [0.25.1] - 2024-04-21 ### Changed diff --git a/grapple/types/streamfield.py b/grapple/types/streamfield.py index f62b9b74..71b79745 100644 --- a/grapple/types/streamfield.py +++ b/grapple/types/streamfield.py @@ -436,7 +436,7 @@ def register_streamfield_blocks(): from .snippets import SnippetTypes class PageChooserBlock(graphene.ObjectType): - page = graphene.Field(get_page_interface(), required=True) + page = graphene.Field(get_page_interface(), required=False) class Meta: interfaces = (StreamFieldInterface,) @@ -445,7 +445,7 @@ def resolve_page(self, info, **kwargs): return self.value.specific class DocumentChooserBlock(graphene.ObjectType): - document = graphene.Field(get_document_type(), required=True) + document = graphene.Field(get_document_type(), required=False) class Meta: interfaces = (StreamFieldInterface,) @@ -454,7 +454,7 @@ def resolve_document(self, info, **kwargs): return self.value class ImageChooserBlock(graphene.ObjectType): - image = graphene.Field(get_image_type(), required=True) + image = graphene.Field(get_image_type(), required=False) class Meta: interfaces = (StreamFieldInterface,) @@ -474,7 +474,7 @@ def resolve_image(self, info, **kwargs): if SnippetObjectType is not None: class SnippetChooserBlock(graphene.ObjectType): - snippet = graphene.Field(SnippetObjectType, required=True) + snippet = graphene.Field(SnippetObjectType, required=False) class Meta: interfaces = (StreamFieldInterface,) diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py index 374120ce..1ab7d826 100644 --- a/tests/test_interfaces.py +++ b/tests/test_interfaces.py @@ -80,7 +80,7 @@ def test_schema_for_page_with_graphql_interface(self): [{"name": "CustomInterface"}, {"name": "PageInterface"}], ) - def test_schem_for_snippet_with_graphql_interface(self): + def test_schema_for_snippet_with_graphql_interface(self): results = self.introspect_schema_by_type("Advert") self.assertListEqual( sorted(results["data"]["__type"]["interfaces"], key=lambda x: x["name"]), diff --git a/tests/test_models_types.py b/tests/test_models_types.py index 305bf77a..987df8d2 100644 --- a/tests/test_models_types.py +++ b/tests/test_models_types.py @@ -1,7 +1,12 @@ import graphene from django.test import TestCase +from wagtail.blocks.field_block import PageChooserBlock +from wagtail.documents.blocks import DocumentChooserBlock +from wagtail.images.blocks import ImageChooserBlock +from wagtail.snippets.blocks import SnippetChooserBlock +from grapple import registry from grapple.actions import get_field_type from grapple.exceptions import IllegalDeprecation from grapple.models import ( @@ -183,3 +188,58 @@ def test_collection_field_required_deprecated(self): required=True, deprecation_reason=self.deprecation_reason, )() + + +class ChooserBlocksTest(TestCase): + """ + Test that "Chooser" blocks take null values to ensure correct handling + of deleted objects referenced in these blocks. + """ + + def test_snippet_chooser_block_value_field_not_required(self): + """ + Test that the SnippetChooserBlock snippet field is nullable in the + GraphQL schema. + """ + block = registry.registry.streamfield_blocks[SnippetChooserBlock] + field = block.snippet + + # Check that field is not required by asserting type isn't `NonNull` + self.assertIsInstance(field, graphene.types.field.Field) + self.assertNotIsInstance(field.type, graphene.NonNull) + + def test_document_chooser_block_value_field_not_required(self): + """ + Test that the DocumentChooserBlock document field is nullable in the + GraphQL schema. + """ + block = registry.registry.streamfield_blocks[DocumentChooserBlock] + field = block.document + + # Check that field is not required by asserting type isn't `NonNull` + self.assertIsInstance(field, graphene.types.field.Field) + self.assertNotIsInstance(field.type, graphene.NonNull) + + def test_image_chooser_block_value_field_not_required(self): + """ + Test that the ImageChooserBlock image field is nullable in the GraphQL + schema. + """ + block = registry.registry.streamfield_blocks[ImageChooserBlock] + field = block.image + + # Check that field is not required by asserting type isn't `NonNull` + self.assertIsInstance(field, graphene.types.field.Field) + self.assertNotIsInstance(field.type, graphene.NonNull) + + def test_page_chooser_block_value_field_not_required(self): + """ + Test that the PageChooserBlock page field is nullable in the GraphQL + schema. + """ + block = registry.registry.streamfield_blocks[PageChooserBlock] + field = block.page + + # Check that field is not required by asserting type isn't `NonNull` + self.assertIsInstance(field, graphene.types.field.Field) + self.assertNotIsInstance(field.type, graphene.NonNull)