diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/FragmentSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/FragmentSectionHelper.java index 0fb1a948b9fe1..dcb28af105470 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/FragmentSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/FragmentSectionHelper.java @@ -19,12 +19,15 @@ public class FragmentSectionHelper implements SectionHelper { private static final String ID = "id"; + // the generated id of the template that declares this fragment section + private final String generatedTemplateId; private final String identifier; private final Expression rendered; - FragmentSectionHelper(String identifier, Expression isVisible) { + FragmentSectionHelper(String identifier, Expression rendered, String generatedTemplateId) { this.identifier = identifier; - this.rendered = isVisible; + this.rendered = rendered; + this.generatedTemplateId = generatedTemplateId; } public String getIdentifier() { @@ -33,11 +36,7 @@ public String getIdentifier() { @Override public CompletionStage resolve(SectionResolutionContext context) { - if (rendered == null - // executed from an include section - || context.getParameters().containsKey(Template.Fragment.ATTRIBUTE) - // the attribute is set if executed separately via Template.Fragment - || context.resolutionContext().getAttribute(Fragment.ATTRIBUTE) != null) { + if (isAlwaysExecuted(context)) { return context.execute(); } return context.resolutionContext().evaluate(rendered).thenCompose(r -> { @@ -45,6 +44,17 @@ public CompletionStage resolve(SectionResolutionContext context) { }); } + private boolean isAlwaysExecuted(SectionResolutionContext context) { + if (rendered == null + // executed from an include section + || context.getParameters().containsKey(Fragment.ATTRIBUTE)) { + return true; + } + Object attribute = context.resolutionContext().getAttribute(Fragment.ATTRIBUTE); + // the attribute is set if executed separately via Template.Fragment + return attribute != null && attribute.equals(generatedTemplateId + identifier); + } + public static class Factory implements SectionHelperFactory { static final Pattern FRAGMENT_PATTERN = Pattern.compile("[a-zA-Z0-9_]+"); @@ -99,7 +109,7 @@ public FragmentSectionHelper initialize(SectionInitContext context) { .build(); } } - return new FragmentSectionHelper(id, context.getExpression(RENDERED)); + return new FragmentSectionHelper(id, context.getExpression(RENDERED), generatedId); } @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java index 7d787d0efde55..0396b248d2393 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java @@ -391,7 +391,9 @@ public Template getOriginalTemplate() { @Override public TemplateInstance instance() { TemplateInstance instance = super.instance(); - instance.setAttribute(Fragment.ATTRIBUTE, true); + // when a fragment is executed separately we need a way to instruct FragmentSectionHelper to ignore the "renreded" parameter + // Fragment.ATTRIBUTE contains the generated id of the template that declares the fragment section and the fragment identifier + instance.setAttribute(Fragment.ATTRIBUTE, TemplateImpl.this.getGeneratedId() + FragmentImpl.this.getId()); return instance; } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java index 020877d2ac5d5..2bf58e9605318 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/FragmentTest.java @@ -77,4 +77,41 @@ public void testInvalidId() { expected.getMessage()); } + @Test + public void testNestedFragmentRendered() { + Engine engine = Engine.builder().addDefaults().build(); + Template alpha = engine.parse(""" + OK + {#fragment id=\"nested\" rendered=false} + NOK + {/} + {#fragment id=\"visible\"} + 01 + {/fragment} + """); + engine.putTemplate("alpha", alpha); + assertEquals("OK01", alpha.render().replaceAll("\\s", "")); + assertEquals("NOK", alpha.getFragment("nested").render().trim()); + + Template bravo = engine.parse(""" + {#include $nested} + {#fragment id=\"nested\" rendered=false} + OK + {/} + """); + assertEquals("OK", bravo.render().trim()); + assertEquals("OK", bravo.getFragment("nested").render().trim()); + + assertEquals("NOK", engine.parse("{#include alpha$nested /}").render().trim()); + Template charlie = engine.parse("{#include alpha /}"); + assertEquals("OK01", charlie.render().replaceAll("\\s", "")); + + Template delta = engine.parse(""" + {#fragment id=\"nested\" rendered=false} + {#include alpha /} + {/} + """); + assertEquals("OK01", delta.getFragment("nested").render().replaceAll("\\s", "")); + } + }