From 969300768665b2f48f42616bfe36b5aa66bebfc2 Mon Sep 17 00:00:00 2001 From: Alex Morega Date: Wed, 27 Sep 2023 11:35:51 +0300 Subject: [PATCH] Implement `preserveSvg` flag for `srcSet()` (#370) Co-authored-by: Alex Morega --- docs/general-usage/graphql-types.rst | 1 + grapple/types/images.py | 29 +++++++++++++++--- tests/test_image_types.py | 44 ++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/docs/general-usage/graphql-types.rst b/docs/general-usage/graphql-types.rst index 9fdeae36..ff441b6e 100644 --- a/docs/general-usage/graphql-types.rst +++ b/docs/general-usage/graphql-types.rst @@ -61,6 +61,7 @@ the root query type like so: srcSet( sizes: [Int] format: String + preserveSvg: Boolean ): String isSvg: Boolean! diff --git a/grapple/types/images.py b/grapple/types/images.py index 68cb42e3..3777abb1 100644 --- a/grapple/types/images.py +++ b/grapple/types/images.py @@ -59,6 +59,24 @@ def get_rendition_field_kwargs() -> dict[str, graphene.Scalar]: return kwargs +def get_src_set_field_kwargs() -> dict[str, graphene.Scalar]: + """ + Returns a list of kwargs for the srcSet field. + Extracted for convenience, to accommodate for the conditional logic needed for various Wagtail versions. + """ + kwargs = { + "sizes": graphene.List(graphene.Int), + "format": graphene.String(), + } + if WAGTAIL_VERSION > (5, 0): + kwargs["preserve_svg"] = graphene.Boolean( + description="Prevents raster image operations (e.g. `format-webp`, `bgcolor`, etc.) being applied to SVGs. " + "More info: https://docs.wagtail.org/en/stable/topics/images.html#svg-images" + ) + + return kwargs + + def rendition_allowed(filter_specs: str) -> bool: """Checks a given rendition filter is allowed""" allowed_filters = grapple_settings.ALLOWED_IMAGE_FILTERS @@ -110,9 +128,7 @@ class ImageObjectType(DjangoObjectType): collection = graphene.Field(lambda: CollectionObjectType, required=True) tags = graphene.List(graphene.NonNull(lambda: TagObjectType), required=True) rendition = graphene.Field(get_rendition_type, **get_rendition_field_kwargs()) - src_set = graphene.String( - sizes=graphene.List(graphene.Int), format=graphene.String() - ) + src_set = graphene.String(**get_src_set_field_kwargs()) if WAGTAIL_VERSION > (5, 0): is_svg = graphene.Boolean(required=True) @@ -181,6 +197,7 @@ def resolve_src_set( info: GraphQLResolveInfo, sizes: list[int], format: str | None = None, + preserve_svg: bool = False, **kwargs, ) -> str: """ @@ -191,7 +208,11 @@ def resolve_src_set( if instance.file.name is not None: rendition_list = [ ImageObjectType.resolve_rendition( - instance, info, width=width, **format_kwarg + instance, + info, + width=width, + preserve_svg=preserve_svg, + **format_kwarg, ) for width in sizes if rendition_allowed(f"width-{width}{filter_suffix}") diff --git a/tests/test_image_types.py b/tests/test_image_types.py index 12297213..236977a9 100644 --- a/tests/test_image_types.py +++ b/tests/test_image_types.py @@ -230,9 +230,11 @@ def test_schema_for_svg_related_fields_and_arguments(self): field["name"]: field for field in results["data"]["__type"]["fields"] } rendition_args = {arg["name"]: arg for arg in mapping["rendition"]["args"]} + src_set_args = {arg["name"]: arg for arg in mapping["srcSet"]["args"]} self.assertNotIn("isSvg", mapping) self.assertNotIn("preserveSvg", rendition_args) + self.assertNotIn("preserveSvg", src_set_args) if WAGTAIL_VERSION >= (5, 0): @@ -262,6 +264,7 @@ def test_schema_for_svg_related_fields_and_arguments(self): field["name"]: field for field in results["data"]["__type"]["fields"] } rendition_args = {arg["name"]: arg for arg in mapping["rendition"]["args"]} + src_set_args = {arg["name"]: arg for arg in mapping["srcSet"]["args"]} self.assertIn("isSvg", mapping) self.assertEqual(mapping["isSvg"]["type"]["kind"], "NON_NULL") @@ -271,6 +274,12 @@ def test_schema_for_svg_related_fields_and_arguments(self): "Prevents raster image operations (e.g. `format-webp`, `bgcolor`, etc.) being applied to SVGs. " "More info: https://docs.wagtail.org/en/stable/topics/images.html#svg-images", ) + self.assertEqual(src_set_args["preserveSvg"]["type"]["name"], "Boolean") + self.assertEqual( + src_set_args["preserveSvg"]["description"], + "Prevents raster image operations (e.g. `format-webp`, `bgcolor`, etc.) being applied to SVGs. " + "More info: https://docs.wagtail.org/en/stable/topics/images.html#svg-images", + ) def test_svg_rendition(self): query = """ @@ -310,6 +319,24 @@ def test_svg_rendition_with_raster_format_with_preserve_svg(self): ) ) + def test_svg_src_set_with_raster_format_with_preserve_svg(self): + query = """ + query ($id: ID!) { + image(id: $id) { + srcSet(sizes: [100], format: "webp", preserveSvg: true) + } + } + """ + + results = self.client.execute( + query, variables={"id": self.example_svg_image.id} + ) + self.assertTrue( + results["data"]["image"]["srcSet"] + .split()[0] + .endswith("test.width-100.svg") + ) + def test_svg_rendition_with_raster_format_without_preserve_svg(self): query = """ query ($id: ID!) { @@ -329,6 +356,23 @@ def test_svg_rendition_with_raster_format_without_preserve_svg(self): "'SvgImage' object has no attribute 'save_as_webp'", ) + def test_svg_src_set_with_raster_format_without_preserve_svg(self): + query = """ + query ($id: ID!) { + image(id: $id) { + srcSet(sizes: [100], format: "webp") + } + } + """ + + results = self.client.execute( + query, variables={"id": self.example_svg_image.id} + ) + self.assertEqual( + results["errors"][0]["message"], + "'SvgImage' object has no attribute 'save_as_webp'", + ) + def test_svg_rendition_with_filters_passed_through_to_svg_safe_spec(self): # bgcolor is not one of the allowed filters, so we should end with an empty filter spec query = """