You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2016/06/12 16:53:56 UTC
[15/50] incubator-freemarker git commit: Further tests for <#list xs
as k , v> and <#items as k , v>.
Further tests for <#list xs as k ,v> and <#items as k ,v>.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/d8d070a1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/d8d070a1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/d8d070a1
Branch: refs/heads/2.3
Commit: d8d070a1c24efc3c0573b7a51f3e45b8ec9112db
Parents: 1ecf10a
Author: ddekany <dd...@apache.org>
Authored: Mon May 30 23:53:32 2016 +0200
Committer: ddekany <dd...@apache.org>
Committed: Mon May 30 23:53:32 2016 +0200
----------------------------------------------------------------------
.../java/freemarker/core/ListErrorsTest.java | 137 ++++++++++++++
.../freemarker/core/ListValidationsTest.java | 125 -------------
.../test/templatesuite/TemplateTestCase.java | 5 +-
.../test/templatesuite/models/Listables.java | 104 +++++++++++
.../test/templatesuite/expected/listhash.txt | 183 +++++++++++++++++++
.../test/templatesuite/templates/listhash.ftl | 52 ++++++
.../freemarker/test/templatesuite/testcases.xml | 1 +
7 files changed, 480 insertions(+), 127 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/java/freemarker/core/ListErrorsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ListErrorsTest.java b/src/test/java/freemarker/core/ListErrorsTest.java
new file mode 100644
index 0000000..0c322c6
--- /dev/null
+++ b/src/test/java/freemarker/core/ListErrorsTest.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.core;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+import freemarker.test.templatesuite.models.Listables;
+
+public class ListErrorsTest extends TemplateTest {
+
+ @Test
+ public void testValid() throws IOException, TemplateException {
+ assertOutput("<#list 1..2 as x><#list 3..4>${x}:<#items as x>${x}</#items></#list>;</#list>", "1:34;2:34;");
+ assertOutput("<#list [] as x>${x}<#else><#list 1..2 as x>${x}<#sep>, </#list></#list>", "1, 2");
+ assertOutput("<#macro m>[<#nested 3>]</#macro>"
+ + "<#list 1..2 as x>"
+ + "${x}@${x?index}"
+ + "<@m ; x>"
+ + "${x},"
+ + "<#list 4..4 as x>${x}@${x?index}</#list>"
+ + "</@>"
+ + "${x}@${x?index}; "
+ + "</#list>",
+ "1@0[3,4@0]1@0; 2@1[3,4@0]2@1; ");
+ }
+
+ @Test
+ public void testInvalidItemsParseTime() throws IOException, TemplateException {
+ assertErrorContains("<#items as x>${x}</#items>",
+ "#items", "must be inside", "#list");
+ assertErrorContains("<#list xs><#macro m><#items as x></#items></#macro></#list>",
+ "#items", "must be inside", "#list");
+ assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>",
+ "#forEach", "doesn't support", "#items");
+ assertErrorContains("<#list xs as x><#items as x>${x}</#items></#list>",
+ "#list", "must not have", "#items", "as loopVar");
+ assertErrorContains("<#list xs><#list xs as x><#items as x>${x}</#items></#list></#list>",
+ "#list", "must not have", "#items", "as loopVar");
+ assertErrorContains("<#list xs></#list>",
+ "#list", "must have", "#items", "as loopVar");
+ assertErrorContains("<#forEach x in xs><#items as x></#items></#forEach>",
+ "#forEach", "doesn't support", "#items");
+ assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>",
+ "#forEach", "doesn't support", "#items");
+ }
+
+ @Test
+ public void testInvalidSepParseTime() throws IOException, TemplateException {
+ assertErrorContains("<#sep>, </#sep>",
+ "#sep", "must be inside", "#list", "#foreach");
+ assertErrorContains("<#sep>, ",
+ "#sep", "must be inside", "#list", "#foreach");
+ assertErrorContains("<#list xs as x><#else><#sep>, </#list>",
+ "#sep", "must be inside", "#list", "#foreach");
+ assertErrorContains("<#list xs as x><#macro m><#sep>, </#macro></#list>",
+ "#sep", "must be inside", "#list", "#foreach");
+ }
+
+ @Test
+ public void testInvalidItemsRuntime() throws IOException, TemplateException {
+ assertErrorContains("<#list 1..1><#items as x></#items><#items as x></#items></#list>",
+ "#items", "already entered earlier");
+ assertErrorContains("<#list 1..1><#items as x><#items as y>${x}/${y}</#items></#items></#list>",
+ "#items", "Can't nest #items into each other");
+ }
+
+ @Test
+ public void testInvalidLoopVarBuiltinLHO() {
+ assertErrorContains("<#list foos>${foo?index}</#list>",
+ "?index", "foo", "no loop variable");
+ assertErrorContains("<#list foos as foo></#list>${foo?index}",
+ "?index", "foo" , "no loop variable");
+ assertErrorContains("<#list foos as foo><#macro m>${foo?index}</#macro></#list>",
+ "?index", "foo" , "no loop variable");
+ assertErrorContains("<#list foos as foo><#function f>${foo?index}</#function></#list>",
+ "?index", "foo" , "no loop variable");
+ assertErrorContains("<#list xs as x>${foo?index}</#list>",
+ "?index", "foo" , "no loop variable");
+ assertErrorContains("<#list foos as foo><@m; foo>${foo?index}</@></#list>",
+ "?index", "foo" , "user defined directive");
+ assertErrorContains(
+ "<#list foos as foo><@m; foo><@m; foo>${foo?index}</@></@></#list>",
+ "?index", "foo" , "user defined directive");
+ assertErrorContains(
+ "<#list foos as foo><@m; foo>"
+ + "<#list foos as foo><@m; foo>${foo?index}</@></#list>"
+ + "</@></#list>",
+ "?index", "foo" , "user defined directive");
+ }
+
+ @Test
+ public void testKeyValueSameName() {
+ assertErrorContains("<#list {} as foo, foo></#list>",
+ "key", "value", "both" , "foo");
+ }
+
+ @Test
+ public void testCollectionVersusHash() {
+ assertErrorContains("<#list {} as i></#list>",
+ "as k, v");
+ assertErrorContains("<#list [] as k, v></#list>",
+ "only one loop variable");
+ }
+
+ @Test
+ public void testNonEx2NonStringKey() throws IOException, TemplateException {
+ addToDataModel("m", new Listables.NonEx2MapAdapter(ImmutableMap.of("k1", "v1", 2, "v2"), new DefaultObjectWrapper()));
+ assertOutput("<#list m?keys as k>${k};</#list>", "k1;2;");
+ assertErrorContains("<#list m as k, v></#list>",
+ "string", "number", ".TemplateHashModelEx2");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/java/freemarker/core/ListValidationsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ListValidationsTest.java b/src/test/java/freemarker/core/ListValidationsTest.java
deleted file mode 100644
index eed74ce..0000000
--- a/src/test/java/freemarker/core/ListValidationsTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package freemarker.core;
-
-import java.io.IOException;
-
-import org.junit.Test;
-
-import freemarker.template.TemplateException;
-import freemarker.test.TemplateTest;
-
-public class ListValidationsTest extends TemplateTest {
-
- @Test
- public void testValid() throws IOException, TemplateException {
- assertOutput("<#list 1..2 as x><#list 3..4>${x}:<#items as x>${x}</#items></#list>;</#list>", "1:34;2:34;");
- assertOutput("<#list [] as x>${x}<#else><#list 1..2 as x>${x}<#sep>, </#list></#list>", "1, 2");
- assertOutput("<#macro m>[<#nested 3>]</#macro>"
- + "<#list 1..2 as x>"
- + "${x}@${x?index}"
- + "<@m ; x>"
- + "${x},"
- + "<#list 4..4 as x>${x}@${x?index}</#list>"
- + "</@>"
- + "${x}@${x?index}; "
- + "</#list>",
- "1@0[3,4@0]1@0; 2@1[3,4@0]2@1; ");
- }
-
- @Test
- public void testInvalidItemsParseTime() throws IOException, TemplateException {
- assertErrorContains("<#items as x>${x}</#items>",
- "#items", "must be inside", "#list");
- assertErrorContains("<#list xs><#macro m><#items as x></#items></#macro></#list>",
- "#items", "must be inside", "#list");
- assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>",
- "#forEach", "doesn't support", "#items");
- assertErrorContains("<#list xs as x><#items as x>${x}</#items></#list>",
- "#list", "must not have", "#items", "as loopVar");
- assertErrorContains("<#list xs><#list xs as x><#items as x>${x}</#items></#list></#list>",
- "#list", "must not have", "#items", "as loopVar");
- assertErrorContains("<#list xs></#list>",
- "#list", "must have", "#items", "as loopVar");
- assertErrorContains("<#forEach x in xs><#items as x></#items></#forEach>",
- "#forEach", "doesn't support", "#items");
- assertErrorContains("<#list xs><#forEach x in xs><#items as x></#items></#forEach></#list>",
- "#forEach", "doesn't support", "#items");
- }
-
- @Test
- public void testInvalidSepParseTime() throws IOException, TemplateException {
- assertErrorContains("<#sep>, </#sep>",
- "#sep", "must be inside", "#list", "#foreach");
- assertErrorContains("<#sep>, ",
- "#sep", "must be inside", "#list", "#foreach");
- assertErrorContains("<#list xs as x><#else><#sep>, </#list>",
- "#sep", "must be inside", "#list", "#foreach");
- assertErrorContains("<#list xs as x><#macro m><#sep>, </#macro></#list>",
- "#sep", "must be inside", "#list", "#foreach");
- }
-
- @Test
- public void testInvalidItemsRuntime() throws IOException, TemplateException {
- assertErrorContains("<#list 1..1><#items as x></#items><#items as x></#items></#list>",
- "#items", "already entered earlier");
- assertErrorContains("<#list 1..1><#items as x><#items as y>${x}/${y}</#items></#items></#list>",
- "#items", "Can't nest #items into each other");
- }
-
- @Test
- public void testInvalidLoopVarBuiltinLHO() {
- assertErrorContains("<#list foos>${foo?index}</#list>",
- "?index", "foo", "no loop variable");
- assertErrorContains("<#list foos as foo></#list>${foo?index}",
- "?index", "foo" , "no loop variable");
- assertErrorContains("<#list foos as foo><#macro m>${foo?index}</#macro></#list>",
- "?index", "foo" , "no loop variable");
- assertErrorContains("<#list foos as foo><#function f>${foo?index}</#function></#list>",
- "?index", "foo" , "no loop variable");
- assertErrorContains("<#list xs as x>${foo?index}</#list>",
- "?index", "foo" , "no loop variable");
- assertErrorContains("<#list foos as foo><@m; foo>${foo?index}</@></#list>",
- "?index", "foo" , "user defined directive");
- assertErrorContains(
- "<#list foos as foo><@m; foo><@m; foo>${foo?index}</@></@></#list>",
- "?index", "foo" , "user defined directive");
- assertErrorContains(
- "<#list foos as foo><@m; foo>"
- + "<#list foos as foo><@m; foo>${foo?index}</@></#list>"
- + "</@></#list>",
- "?index", "foo" , "user defined directive");
- }
-
- @Test
- public void testKeyValueSameName() {
- assertErrorContains("<#list {} as foo, foo></#list>",
- "key", "value", "both" , "foo");
- }
-
- @Test
- public void testCollectionVersusHash() {
- assertErrorContains("<#list {} as i></#list>",
- "as k, v");
- assertErrorContains("<#list [] as k, v></#list>",
- "only one loop variable");
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
index 9ccf93f..9e9b4ff 100644
--- a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
@@ -277,8 +277,9 @@ public class TemplateTestCase extends FileTestCase {
});
dataModel.put("sqlDate", new java.sql.Date(1273955885023L));
dataModel.put("sqlTime", new java.sql.Time(74285023L));
- } else if (templateName.equals("list.ftl")
- || templateName.equals("list2.ftl") || templateName.equals("list3.ftl")) {
+ } else if (
+ templateName.equals("list.ftl") || templateName.equals("list2.ftl") || templateName.equals("list3.ftl")
+ || simpleTestName.equals("listhash")) {
dataModel.put("listables", new Listables());
} else if (simpleTestName.startsWith("number-format")) {
dataModel.put("int", new SimpleNumber(Integer.valueOf(1)));
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/java/freemarker/test/templatesuite/models/Listables.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/test/templatesuite/models/Listables.java b/src/test/java/freemarker/test/templatesuite/models/Listables.java
index 42d43dc..d607a7e 100644
--- a/src/test/java/freemarker/test/templatesuite/models/Listables.java
+++ b/src/test/java/freemarker/test/templatesuite/models/Listables.java
@@ -18,14 +18,44 @@
*/
package freemarker.test.templatesuite.models;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
import java.util.TreeSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import freemarker.core._DelayedJQuote;
+import freemarker.core._TemplateModelException;
+import freemarker.ext.beans.BeansWrapperBuilder;
+import freemarker.ext.util.WrapperTemplateModel;
+import freemarker.template.AdapterTemplateModel;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultMapAdapter;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.MapKeyValuePairIterator;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.SimpleCollection;
+import freemarker.template.SimpleHash;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateHashModelEx2;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelWithAPISupport;
+import freemarker.template.WrappingTemplateModel;
+import freemarker.template.TemplateHashModelEx2.KeyValuePairIterator;
+import freemarker.template.utility.ObjectWrapperWithAPISupport;
+
@SuppressWarnings("boxing")
public class Listables {
@@ -94,4 +124,78 @@ public class Listables {
return Collections.<Integer>emptySet().iterator();
}
+ public List<TemplateHashModelEx2> getHashEx2s() throws TemplateModelException {
+ Map<Object, Object> map;
+ map = new LinkedHashMap<Object, Object>();
+ map.put("k1", "v1");
+ map.put(2, "v2");
+ map.put("k3", "v3");
+ map.put(null, "v4");
+ map.put(true, "v5");
+ map.put(false, null);
+
+ return getMapsWrappedAsEx2(map);
+ }
+
+ public List<? extends TemplateHashModelEx> getEmptyHashes() throws TemplateModelException {
+ List<TemplateHashModelEx> emptyMaps = new ArrayList<TemplateHashModelEx>();
+ emptyMaps.addAll(getMapsWrappedAsEx2(Collections.emptyMap()));
+ emptyMaps.add((TemplateHashModelEx) new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_24).build()
+ .wrap(Collections.emptyMap()));
+ return emptyMaps;
+ }
+
+ /**
+ * Returns the map wrapped on various ways.
+ */
+ private List<TemplateHashModelEx2> getMapsWrappedAsEx2(Map<?, ?> map) throws TemplateModelException {
+ List<TemplateHashModelEx2> maps = new ArrayList<TemplateHashModelEx2>();
+
+ maps.add((SimpleHash) new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0).build().wrap(map));
+
+ maps.add((DefaultMapAdapter) new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_24).build().wrap(map));
+
+ BeansWrapperBuilder bwb = new BeansWrapperBuilder(Configuration.VERSION_2_3_24);
+ bwb.setSimpleMapWrapper(true);
+ maps.add((TemplateHashModelEx2) bwb.build().wrap(map));
+
+ return maps;
+ }
+
+ public TemplateHashModelEx getHashNonEx2() {
+ return new NonEx2MapAdapter(ImmutableMap.of("k1", 11, "k2", 22),
+ new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_24).build());
+ }
+
+ public static class NonEx2MapAdapter extends WrappingTemplateModel implements TemplateHashModelEx {
+
+ private final Map<?, ?> map;
+
+ public NonEx2MapAdapter(Map<?, ?> map, ObjectWrapper wrapper) {
+ super(wrapper);
+ this.map = map;
+ }
+
+ public TemplateModel get(String key) throws TemplateModelException {
+ return wrap(map.get(key));
+ }
+
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ public int size() {
+ return map.size();
+ }
+
+ public TemplateCollectionModel keys() {
+ return new SimpleCollection(map.keySet(), getObjectWrapper());
+ }
+
+ public TemplateCollectionModel values() {
+ return new SimpleCollection(map.values(), getObjectWrapper());
+ }
+
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/resources/freemarker/test/templatesuite/expected/listhash.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/test/templatesuite/expected/listhash.txt b/src/test/resources/freemarker/test/templatesuite/expected/listhash.txt
new file mode 100644
index 0000000..c603890
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/expected/listhash.txt
@@ -0,0 +1,183 @@
+
+Non-empty maps:
+
+ Map:
+
+ [
+ k1 = v1
+ 2 = v2
+ k3 = v3
+ null = v4
+ Y = v5
+ N = null
+ ]
+
+ [
+ k1 = v1; // @0=@0; odd=odd; Y=Y
+ 2 = v2; // @1=@1; even=even; Y=Y
+ k3 = v3; // @2=@2; odd=odd; Y=Y
+ null = v4; // @3=@3; even=even; Y=Y
+ Y = v5; // @4=@4; odd=odd; Y=Y
+ N = null // @5=@5; even=even; N=N
+ ]
+
+ {
+ [
+ k1 = v1; // @0=@0; odd=odd; Y=Y
+ 2 = v2; // @1=@1; even=even; Y=Y
+ k3 = v3; // @2=@2; odd=odd; Y=Y
+ null = v4; // @3=@3; even=even; Y=Y
+ Y = v5; // @4=@4; odd=odd; Y=Y
+ N = null // @5=@5; even=even; N=N
+ ]
+ }
+
+ Map:
+
+ [
+ k1 = v1
+ 2 = v2
+ k3 = v3
+ null = v4
+ Y = v5
+ N = null
+ ]
+
+ [
+ k1 = v1; // @0=@0; odd=odd; Y=Y
+ 2 = v2; // @1=@1; even=even; Y=Y
+ k3 = v3; // @2=@2; odd=odd; Y=Y
+ null = v4; // @3=@3; even=even; Y=Y
+ Y = v5; // @4=@4; odd=odd; Y=Y
+ N = null // @5=@5; even=even; N=N
+ ]
+
+ {
+ [
+ k1 = v1; // @0=@0; odd=odd; Y=Y
+ 2 = v2; // @1=@1; even=even; Y=Y
+ k3 = v3; // @2=@2; odd=odd; Y=Y
+ null = v4; // @3=@3; even=even; Y=Y
+ Y = v5; // @4=@4; odd=odd; Y=Y
+ N = null // @5=@5; even=even; N=N
+ ]
+ }
+
+ Map:
+
+ [
+ k1 = v1
+ 2 = v2
+ k3 = v3
+ null = v4
+ Y = v5
+ N = null
+ ]
+
+ [
+ k1 = v1; // @0=@0; odd=odd; Y=Y
+ 2 = v2; // @1=@1; even=even; Y=Y
+ k3 = v3; // @2=@2; odd=odd; Y=Y
+ null = v4; // @3=@3; even=even; Y=Y
+ Y = v5; // @4=@4; odd=odd; Y=Y
+ N = null // @5=@5; even=even; N=N
+ ]
+
+ {
+ [
+ k1 = v1; // @0=@0; odd=odd; Y=Y
+ 2 = v2; // @1=@1; even=even; Y=Y
+ k3 = v3; // @2=@2; odd=odd; Y=Y
+ null = v4; // @3=@3; even=even; Y=Y
+ Y = v5; // @4=@4; odd=odd; Y=Y
+ N = null // @5=@5; even=even; N=N
+ ]
+ }
+
+ Map:
+
+ [
+ k1 = 11
+ k2 = 22
+ ]
+
+ [
+ k1 = 11; // @0=@0; odd=odd; Y=Y
+ k2 = 22 // @1=@1; even=even; N=N
+ ]
+
+ {
+ [
+ k1 = 11; // @0=@0; odd=odd; Y=Y
+ k2 = 22 // @1=@1; even=even; N=N
+ ]
+ }
+
+
+Empty maps:
+
+ Map:
+
+ [
+ ]
+
+ [
+ Empty
+ ]
+
+ {
+ Empty
+ }
+
+ Map:
+
+ [
+ ]
+
+ [
+ Empty
+ ]
+
+ {
+ Empty
+ }
+
+ Map:
+
+ [
+ ]
+
+ [
+ Empty
+ ]
+
+ {
+ Empty
+ }
+
+ Map:
+
+ [
+ ]
+
+ [
+ Empty
+ ]
+
+ {
+ Empty
+ }
+
+
+ a @ 0, 1
+ aa = 11 @ 0 // inside a @ 0, 1
+ a @ 0, 1
+ --
+ b @ 1, 2
+ ba = 21 @ 0 // inside b @ 1, 2
+ bb = 22 @ 1 // inside b @ 1, 2
+ b @ 1, 2
+ --
+ c @ 2, 0
+ c @ 2, 0
+ --
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/resources/freemarker/test/templatesuite/templates/listhash.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/listhash.ftl b/src/test/resources/freemarker/test/templatesuite/templates/listhash.ftl
new file mode 100644
index 0000000..3668f89
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/templates/listhash.ftl
@@ -0,0 +1,52 @@
+<#setting booleanFormat='Y,N'>
+
+<#macro listings maps>
+ <#list maps as m>
+ Map:
+
+ [
+ <#list m as k, v>
+ ${k!'null'} = ${v!'null'}
+ </#list>
+ ]
+
+ [
+ <#list m as k, v>
+ ${k!'null'} = ${v!'null'}<#sep>;</#sep> // @${k?index}=@${v?index}; ${k?itemParity}=${v?itemParity}; ${k?hasNext}=${v?hasNext}
+ <#else>
+ Empty
+ </#list>
+ ]
+
+ {
+ <#list m>
+ [
+ <#items as k, v>
+ ${k!'null'} = ${v!'null'}<#sep>;</#sep> // @${k?index}=@${v?index}; ${k?itemParity}=${v?itemParity}; ${k?hasNext}=${v?hasNext}
+ </#items>
+ ]
+ <#else>
+ Empty
+ </#list>
+ }
+
+ </#list>
+</#macro>
+
+Non-empty maps:
+
+<@listings listables.hashEx2s />
+<@listings [ listables.hashNonEx2 ] />
+
+Empty maps:
+
+<@listings listables.emptyHashes />
+
+<#list { 'a': { 'aa': 11 }, 'b': { 'ba': 21, 'bb': 22 }, 'c': {} } as k1, v1>
+ ${k1} @ ${k1?index}, ${v1?size}
+ <#list v1 as k2, v2>
+ ${k2} = ${v2} @ ${k2?index} // inside ${k1} @ ${k1?index}, ${v1?size}
+ </#list>
+ ${k1} @ ${k1?index}, ${v1?size}
+ --
+</#list>
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d8d070a1/src/test/resources/freemarker/test/templatesuite/testcases.xml
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/test/templatesuite/testcases.xml b/src/test/resources/freemarker/test/templatesuite/testcases.xml
index cc42796..8df64bd 100644
--- a/src/test/resources/freemarker/test/templatesuite/testcases.xml
+++ b/src/test/resources/freemarker/test/templatesuite/testcases.xml
@@ -133,6 +133,7 @@
<testCase name="list-bis[#endTN]-collectionAdapter" expected="list-bis.txt">
<setting object_wrapper="DefaultObjectWrapper(2.3.22, forceLegacyNonListCollections=false)" />
</testCase>
+ <testCase name="listhash" />
<testCase name="listliteral" />
<testCase name="localization" >
<setting locale="en_AU"/>