From 9b91afa3f7df95600eb416a972a739a5c3452c42 Mon Sep 17 00:00:00 2001 From: curforever <77817413+curforever@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:07:39 +0000 Subject: [PATCH] Handle null dynamic keys in lookup helper Fixes #1060. --- .../handlebars/helper/LookupHelper.java | 2 +- .../jknack/handlebars/LookupHelperTest.java | 132 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 handlebars/src/test/java/com/github/jknack/handlebars/LookupHelperTest.java diff --git a/handlebars/src/main/java/com/github/jknack/handlebars/helper/LookupHelper.java b/handlebars/src/main/java/com/github/jknack/handlebars/helper/LookupHelper.java index 29b69a7a..09f322ff 100644 --- a/handlebars/src/main/java/com/github/jknack/handlebars/helper/LookupHelper.java +++ b/handlebars/src/main/java/com/github/jknack/handlebars/helper/LookupHelper.java @@ -36,7 +36,7 @@ public Object apply(final Object context, final Options options) throws IOExcept if (context == null) { return null; } - if (options.params.length <= 0) { + if (options.params.length <= 0 || options.param(0) == null) { return context; } Context ctx = Context.newBuilder(options.context, context).build(); diff --git a/handlebars/src/test/java/com/github/jknack/handlebars/LookupHelperTest.java b/handlebars/src/test/java/com/github/jknack/handlebars/LookupHelperTest.java new file mode 100644 index 00000000..6598b444 --- /dev/null +++ b/handlebars/src/test/java/com/github/jknack/handlebars/LookupHelperTest.java @@ -0,0 +1,132 @@ +/* + * Handlebars.java: https://github.com/jknack/handlebars.java + * Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 + * Copyright (c) 2012 Edgar Espina + */ +package com.github.jknack.handlebars; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import com.github.jknack.handlebars.helper.LookupHelper; + +class LookupHelperTest extends AbstractTest { + + @Test + void nullKeyFallsBackToContext() throws IOException { + Hash context = new Hash().$("name", "fallback"); + assertSame(context, lookup(context, (Object) null)); + } + + @Test + void nullKeyFallsBackToNonMapContext() throws IOException { + Object context = new Object(); + assertSame(context, lookup(context, (Object) null)); + } + + @Test + void missingNestedDynamicKeyRendersFallback() throws IOException { + shouldCompileTo( + "{{#with (lookup object person.id)}}{{name}}{{/with}}", + new Hash().$("object", new Hash().$("name", "fallback")).$("person", new Hash()), + "fallback"); + } + + @Test + void explicitNullDynamicKeyRendersFallback() throws IOException { + shouldCompileTo( + "{{#with (lookup object key)}}{{name}}{{/with}}", + new Hash().$("object", new Hash().$("name", "fallback")).$("key", null), + "fallback"); + } + + @Test + void nullParentKeyFallsBackInsideEach() throws IOException { + shouldCompileTo( + "{{#each objects}}{{#with (lookup this ../key)}}{{name}};{{/with}}{{/each}}", + new Hash() + .$("objects", Arrays.asList(new Hash().$("name", "A"), new Hash().$("name", "B"))) + .$("key", null), + "A;B;"); + } + + @Test + void nullKeyDoesNotLookUpTheStringNull() throws IOException { + Hash context = new Hash().$("name", "fallback").$("null", "literal"); + assertSame(context, lookup(context, (Object) null)); + } + + @Test + void stringNullRemainsAPropertyName() throws IOException { + assertEquals("literal", lookup(new Hash().$("null", "literal"), "null")); + } + + @Test + void nullContextRemainsNull() throws IOException { + assertNull(lookup(null, "name")); + } + + @Test + void nullContextAndKeyRemainNull() throws IOException { + assertNull(lookup(null, (Object) null)); + } + + @Test + void missingParameterFallsBackToContext() throws IOException { + Hash context = new Hash().$("name", "fallback"); + assertSame(context, lookup(context)); + } + + @Test + void missingPropertyFallsBackToContext() throws IOException { + Hash context = new Hash().$("name", "fallback"); + assertSame(context, lookup(context, "absent")); + } + + @Test + void nullPropertyFallsBackToContext() throws IOException { + Hash context = new Hash().$("value", null); + assertSame(context, lookup(context, "value")); + } + + @Test + void falsePropertyIsReturned() throws IOException { + assertEquals(false, lookup(new Hash().$("value", false), "value")); + } + + @Test + void zeroPropertyIsReturned() throws IOException { + assertEquals(0, lookup(new Hash().$("value", 0), "value")); + } + + @Test + void emptyPropertyIsReturned() throws IOException { + assertEquals("", lookup(new Hash().$("value", ""), "value")); + } + + @Test + void nestedPropertyIsReturned() throws IOException { + assertEquals( + "found", lookup(new Hash().$("person", new Hash().$("name", "found")), "person.name")); + } + + private Object lookup(final Object context, final Object... params) throws IOException { + Handlebars handlebars = new Handlebars(); + Options options = + new Options.Builder( + handlebars, + LookupHelper.NAME, + TagType.VAR, + Context.newContext(context), + handlebars.compileInline("")) + .setParams(params) + .build(); + return LookupHelper.INSTANCE.apply(context, options); + } +}