From f954bc7e38b674c41163bb9d74dd35baa08f9255 Mon Sep 17 00:00:00 2001 From: Rex Morgan Date: Fri, 19 Jun 2026 20:56:17 -0400 Subject: [PATCH] fix: @partial-block usable in #if and as block partial (issue #519) Two sub-bugs fixed: - BindingContext.TryGetContextVariable now returns the PartialBlockTemplate delegate when the segment is 'partial-block', making {{#if @partial-block}} truthy whenever a block partial is in scope. - PartialBlockAccumulatorContext.GetAccumulatedBlock now handles an empty body ({{#> @partial-block }}{{/@partial-block}}) without crashing. Co-Authored-By: Claude Sonnet 4.6 --- source/Handlebars.Test/IssueTests.cs | 17 +++++++++++++++++ source/Handlebars/BindingContext.cs | 15 ++++++++++++++- .../PartialBlockAccumulatorContext.cs | 8 +++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/source/Handlebars.Test/IssueTests.cs b/source/Handlebars.Test/IssueTests.cs index 31136117..bef60863 100644 --- a/source/Handlebars.Test/IssueTests.cs +++ b/source/Handlebars.Test/IssueTests.cs @@ -733,5 +733,22 @@ public void UnrecognisedExpressionThrowsOutOfMemoryException() Assert.Throws(()=> Handlebars.Compile(source)); } + + // Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/519 + [Fact] + public void Issue519_PartialBlockUsableAsBlockAndInIf() + { + var handlebars = Handlebars.Create(); + handlebars.RegisterTemplate("myPartial", + @"Conditional:{{#if @partial-block}} {{> @partial-block}}{{/if}} +Plain: {{> @partial-block}} +Block:{{#> @partial-block }}{{/@partial-block}}"); + + var render = handlebars.Compile("{{#> myPartial}}Block content{{/myPartial}}"); + var actual = render(new { }); + Assert.Contains("Conditional: Block content", actual); + Assert.Contains("Plain: Block content", actual); + Assert.Contains("Block:Block content", actual); + } } } \ No newline at end of file diff --git a/source/Handlebars/BindingContext.cs b/source/Handlebars/BindingContext.cs index 2f1e6c24..19e36731 100644 --- a/source/Handlebars/BindingContext.cs +++ b/source/Handlebars/BindingContext.cs @@ -156,7 +156,20 @@ internal bool TryGetContextVariable(ChainSegment segment, out object value) return BlockParamsObject.TryGetValue(segment, out value) || ContextDataObject.TryGetValue(wellKnownVariable, out value); } - + + // Special handling for @partial-block: return the PartialBlockTemplate delegate + // so that {{#if @partial-block}} is truthy when a block partial is provided. + if (segment.LowerInvariant == "partial-block") + { + if (PartialBlockTemplate != null) + { + value = PartialBlockTemplate; + return true; + } + value = UndefinedBindingResult.Create(segment.TrimmedValue); + return false; + } + return BlockParamsObject.TryGetValue(segment, out value) || ContextDataObject.TryGetValue(segment, out value); } diff --git a/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs b/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs index d0f92a3c..e422d7f8 100644 --- a/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs +++ b/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs @@ -24,10 +24,16 @@ public override void HandleElement(Expression item) public override Expression GetAccumulatedBlock() { + Expression fallback = _body.Count == 0 + ? null + : _body.Count == 1 + ? _body.First() + : Expression.Block(_body); + return HandlebarsExpression.Partial( _startingNode.PartialName, _startingNode.Argument, - _body.Count > 1 ? Expression.Block(_body) : _body.First()); + fallback); } public override bool IsClosingElement(Expression item)