From a35aa5ecd4990556f7651401288b1e25dbd91a5b Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 11 Sep 2023 11:37:20 +0200 Subject: [PATCH] Qute: introduce InjectableFragment - type-safe fragments should honor the selected variant - fixes https://github.com/quarkiverse/quarkus-renarde/issues/165 --- .../fragment/CheckedTemplateFragmentTest.java | 16 +++- .../CheckedTemplateFragmentVariantTest.java | 54 +++++++++++ .../qute/runtime/TemplateProducer.java | 93 +++++++++++++++++-- .../main/java/io/quarkus/qute/Template.java | 4 + 4 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentVariantTest.java diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java index c5a7cb3502be3..4b143f2c73ff7 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentTest.java @@ -11,6 +11,7 @@ import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.TemplateGlobal; import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Variant; import io.quarkus.test.QuarkusUnitTest; public class CheckedTemplateFragmentTest { @@ -26,10 +27,21 @@ public class CheckedTemplateFragmentTest { "{#fragment id=foo}{#for i in bar}{i_count}. <{i}>{#if i_hasNext}, {/if}{/for}{/fragment}"), "templates/CheckedTemplateFragmentTest/foos.html")); + @SuppressWarnings("unchecked") @Test public void testFragment() { - assertEquals("Foo", Templates.items(null).getFragment("item").data("it", new Item("Foo")).render()); - assertEquals("Foo", Templates.items$item(new Item("Foo")).render()); + TemplateInstance items = Templates.items(null); + List variants = (List) items.getAttribute(TemplateInstance.VARIANTS); + assertEquals(1, variants.size()); + assertEquals("text/html", variants.get(0).getContentType()); + assertEquals("Foo", items.getFragment("item").data("it", new Item("Foo")).render()); + + TemplateInstance fragment = Templates.items$item(new Item("Foo")); + variants = (List) fragment.getAttribute(TemplateInstance.VARIANTS); + assertEquals(1, variants.size()); + assertEquals("text/html", variants.get(0).getContentType()); + assertEquals("Foo", fragment.render()); + assertEquals("FooAndBar is a long name", Templates.items$item(new Item("FooAndBar")).render()); assertEquals("1. <1>, 2. <2>, 3. <3>, 4. <4>, 5. <5>", Templates.foos$foo().render()); } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentVariantTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentVariantTest.java new file mode 100644 index 0000000000000..62461b07bbd1d --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/fragment/CheckedTemplateFragmentVariantTest.java @@ -0,0 +1,54 @@ +package io.quarkus.qute.deployment.typesafe.fragment; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.CheckedTemplate; +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Variant; +import io.quarkus.test.QuarkusUnitTest; + +public class CheckedTemplateFragmentVariantTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(Templates.class, Item.class) + .addAsResource(new StringAsset( + "{#each items}{#fragment id='item'}

{it.name}

{/fragment}{/each}"), + "templates/CheckedTemplateFragmentVariantTest/items.html") + .addAsResource(new StringAsset( + "{#each items}{#fragment id='item'}{it.name}{/fragment}{/each}"), + "templates/CheckedTemplateFragmentVariantTest/items.txt")); + + @SuppressWarnings("unchecked") + @Test + public void testFragment() { + TemplateInstance fragment = Templates.items$item(new Item("Foo")); + List variants = (List) fragment.getAttribute(TemplateInstance.VARIANTS); + assertEquals(2, variants.size()); + + assertEquals("

Foo

", + fragment.setAttribute(TemplateInstance.SELECTED_VARIANT, Variant.forContentType("text/html")).render()); + assertEquals("Foo", + fragment.setAttribute(TemplateInstance.SELECTED_VARIANT, Variant.forContentType("text/plain")).render()); + // A variant for application/json does not exist, use the default - html wins + assertEquals("

Foo

", + fragment.setAttribute(TemplateInstance.SELECTED_VARIANT, Variant.forContentType("application/json")).render()); + } + + @CheckedTemplate + public static class Templates { + + static native TemplateInstance items(List items); + + static native TemplateInstance items$item(Item it); + + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java index 10b9900f0272f..b35a7e5efc999 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/TemplateProducer.java @@ -87,9 +87,6 @@ Template getTemplate(InjectionPoint injectionPoint) { if (path == null || path.isEmpty()) { throw new IllegalStateException("No template location specified"); } - // We inject a delegating template in order to: - // 1. Be able to select an appropriate variant if needed - // 2. Be able to reload the template when needed, i.e. when the cache is cleared return new InjectableTemplate(path, templateVariants, engine); } @@ -100,11 +97,18 @@ public Template getInjectableTemplate(String path) { return new InjectableTemplate(path, templateVariants, engine); } + /** + * We inject a delegating template in order to: + * + * 1. Be able to select an appropriate variant if needed + * 2. Be able to reload the template when needed, i.e. when the cache is cleared + */ static class InjectableTemplate implements Template { private final String path; private final TemplateVariants variants; private final Engine engine; + // Some methods may only work if a single template variant is found private final LazyValue