diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js index fb11903c8..ce549b5c7 100644 --- a/lib/handlebars/helpers/each.js +++ b/lib/handlebars/helpers/each.js @@ -49,6 +49,16 @@ export default function(instance) { execIteration(i, i, i === context.length - 1); } } + } else if (global.Symbol && context[global.Symbol.iterator]) { + const newContext = []; + const iterator = context[global.Symbol.iterator](); + for (let it = iterator.next(); !it.done; it = iterator.next()) { + newContext.push(it.value); + } + context = newContext; + for (let j = context.length; i < j; i++) { + execIteration(i, i, i === context.length - 1); + } } else { let priorKey; diff --git a/spec/builtins.js b/spec/builtins.js index ce6d8f4e8..b29927f06 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -254,6 +254,37 @@ describe('builtin helpers', function() { template({}); }, handlebarsEnv.Exception, 'Must pass iterator to #each'); }); + + if (global.Symbol && global.Symbol.iterator) { + it('each on iterable', function() { + function Iterator(arr) { + this.arr = arr; + this.index = 0; + } + Iterator.prototype.next = function() { + var value = this.arr[this.index]; + var done = this.index === this.arr.length; + if (!done) { + this.index++; + } + return { value: value, done: done }; + }; + function Iterable(arr) { + this.arr = arr; + } + Iterable.prototype[global.Symbol.iterator] = function() { + return new Iterator(this.arr); + }; + var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; + var goodbyes = new Iterable([{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]); + var goodbyesEmpty = new Iterable([]); + var hash = {goodbyes: goodbyes, world: 'world'}; + shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!', + 'each with array argument iterates over the contents when not empty'); + shouldCompileTo(string, {goodbyes: goodbyesEmpty, world: 'world'}, 'cruel world!', + 'each with array argument ignores the contents when empty'); + }); + } }); describe('#log', function() {