diff --git a/source/Handlebars.Test/Issues/Issue285Tests.cs b/source/Handlebars.Test/Issues/Issue285Tests.cs new file mode 100644 index 00000000..11c28e02 --- /dev/null +++ b/source/Handlebars.Test/Issues/Issue285Tests.cs @@ -0,0 +1,104 @@ +using Xunit; + +namespace HandlebarsDotNet.Test.Issues +{ + /// + /// Regression tests for GitHub issue #285: + /// Support the includeZero=true hash argument on the built-in #if helper, + /// matching Handlebars.js behaviour (https://handlebarsjs.com/guide/builtin-helpers.html#if). + /// + public class Issue285Tests + { + [Fact] + public void IfWithIncludeZeroTrue_ZeroInt_RendersBlock() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = 0 }); + Assert.Equal("yes", result); + } + + [Fact] + public void IfWithIncludeZeroTrue_ZeroDouble_RendersBlock() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = 0.0 }); + Assert.Equal("yes", result); + } + + [Fact] + public void IfWithIncludeZeroTrue_NonZeroInt_RendersBlock() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = 1 }); + Assert.Equal("yes", result); + } + + [Fact] + public void IfWithIncludeZeroFalse_ZeroInt_DoesNotRenderBlock() + { + var source = "{{#if value includeZero=false}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = 0 }); + Assert.Equal("no", result); + } + + [Fact] + public void IfWithoutIncludeZero_ZeroInt_StillTreatedAsFalsy() + { + var source = "{{#if value}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = 0 }); + Assert.Equal("no", result); + } + + [Fact] + public void IfWithIncludeZeroTrue_NullValue_StillTreatedAsFalsy() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = (object)null }); + Assert.Equal("no", result); + } + + [Fact] + public void IfWithIncludeZeroTrue_EmptyString_StillTreatedAsFalsy() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = string.Empty }); + Assert.Equal("no", result); + } + + [Fact] + public void IfWithIncludeZeroTrue_FalseBool_StillTreatedAsFalsy() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = false }); + Assert.Equal("no", result); + } + + [Fact] + public void IfWithIncludeZeroTrue_TrueBool_RendersBlock() + { + var source = "{{#if value includeZero=true}}yes{{else}}no{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = true }); + Assert.Equal("yes", result); + } + + [Fact] + public void IfWithHashArgument_DoesNotCrash() + { + // Regression test: passing any hash arg to #if previously threw + // InvalidOperationException: "Sequence contains more than one element". + var source = "{{#if value includeZero=true}}yes{{/if}}"; + var template = Handlebars.Compile(source); + var result = template(new { value = 42 }); + Assert.Equal("yes", result); + } + } +} diff --git a/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/ConditionalBlockAccumulatorContext.cs b/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/ConditionalBlockAccumulatorContext.cs index 297bff2e..d0b0a973 100644 --- a/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/ConditionalBlockAccumulatorContext.cs +++ b/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/ConditionalBlockAccumulatorContext.cs @@ -8,13 +8,13 @@ namespace HandlebarsDotNet.Compiler internal class ConditionalBlockAccumulatorContext : BlockAccumulatorContext { private enum TestType { Direct, Reverse } - + private static readonly HashSet ValidHelperNames = new HashSet { "if", "unless" }; - + private readonly List _conditionalBlock = new List(); private Expression _currentCondition; private List _bodyBuffer = new List(); - + public sealed override string BlockName { get; protected set; } public ConditionalBlockAccumulatorContext(Expression startingNode) diff --git a/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs b/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs index 0040dcd9..06f7bc28 100644 --- a/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs +++ b/source/Handlebars/Compiler/Translation/Expression/BoolishConverter.cs @@ -30,7 +30,6 @@ protected override Expression VisitBoolishExpression(BoolishExpression bex) var @object = Arg(condition); var includeZero = Arg(hashParameters.Parameters.Count == 1 ? hashParameters.Parameters[IncludeZero] : Expression.Constant(false)); return Call(() => HandlebarsUtils.IsTruthyOrNonEmpty(@object, includeZero)); - } } }