Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: STRF-10122 Fix forEach, withFirst helpers makeIterator utils #203

Merged
merged 1 commit into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion helpers/3p/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ helpers.first = function(array, n) {

helpers.forEach = function(array, options) {
var data = utils.createFrame(options, options.hash);
if (!array) {
return '';
}
var len = array.length;
var buffer = '';
var i = -1;
Expand Down Expand Up @@ -530,7 +533,9 @@ helpers.withBefore = function(array, idx, options) {
*/

helpers.withFirst = function(arr, idx, options) {
if (utils.isUndefined(arr)) {return '';}
if (utils.isEmpty(arr) || utils.isUndefined(arr)) {
return '';
}
arr = utils.result(arr);

if (!utils.isUndefined(idx)) {
Expand Down
77 changes: 42 additions & 35 deletions helpers/3p/utils/lib/makeIterator.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
/*!
* COPY of https://raw.githubusercontent.com/jonschlinkert/make-iterator/0.2.0/index.js
* COPY of https://raw.githubusercontent.com/jonschlinkert/make-iterator/0.3.0/index.js
*/
'use strict';
const typeOf = require('./kindOf');

const forOwn = require('./forOwn');

module.exports = function makeIterator(src, thisArg) {
if (src === null) {
return noop;
}

switch (typeof src) {
// function is the first to improve perf (most common case)
// also avoid using `Function#call` if not needed, which boosts
// perf a lot in some cases
module.exports = function makeIterator(target, thisArg) {
switch (typeOf(target)) {
case 'undefined':
case 'null':
return noop;
case 'function':
return (typeof thisArg !== 'undefined') ? function (val, i, arr) {
return src.call(thisArg, val, i, arr);
} : src;
// function is the first to improve perf (most common case)
// also avoid using `Function#call` if not needed, which boosts
// perf a lot in some cases
return (typeof thisArg !== 'undefined') ? function(val, i, arr) {
return target.call(thisArg, val, i, arr);
} : target;
case 'object':
return function (val) {
return deepMatches(val, src);
return function(val) {
return deepMatches(val, target);
};
case 'regexp':
return function(str) {
return target.test(str);
};
case 'string':
case 'number':
return prop(src);
default: {
return prop(target);
}
}
};

function containsMatch(array, value) {
Expand All @@ -40,45 +43,49 @@ function containsMatch(array, value) {
return false;
}

function matchArray(o, value) {
function matchArray(arr, value) {
var len = value.length;
var i = -1;

while (++i < len) {
if (!containsMatch(o, value[i])) {
if (!containsMatch(arr, value[i])) {
return false;
}
}
return true;
}

function matchObject(o, value) {
var res = true;
forOwn(value, function (val, key) {
if (!deepMatches(o[key], val)) {
// Return false to break out of forOwn early
return (res = false);
function matchObject(obj, value) {
for (var key in value) {
if (value.hasOwnProperty(key)) {
if (deepMatches(obj[key], value[key]) === false) {
return false;
}
}
});
return res;
}
return true;
}

/**
* Recursively compare objects
*/

function deepMatches(o, value) {
if (o && typeof o === 'object') {
if (Array.isArray(o) && Array.isArray(value)) {
return matchArray(o, value);
function deepMatches(val, value) {
if (typeOf(val) === 'object') {
if (Array.isArray(val) && Array.isArray(value)) {
return matchArray(val, value);
} else {
return matchObject(o, value);
return matchObject(val, value);
}
} else {
return o === value;
return isMatch(val, value);
}
}

function isMatch(target, val) {
return target === val;
}

function prop(name) {
return function(obj) {
return obj[name];
Expand Down
17 changes: 17 additions & 0 deletions spec/helpers/3p/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,17 @@ describe('array', function() {
expect(fn({arr: arr})).to.equal('falsefalsetrue');
done();
});

it('should return empty block', function(done) {
let arr = [];
var fn = hbs.compile('{{#forEach arr}}{{isLast}}{{/forEach}}');
expect(fn({arr})).to.equal('');

arr = null;
var fn = hbs.compile('{{#forEach arr}}{{isLast}}{{/forEach}}');
expect(fn({arr})).to.equal('');
done();
});
});

describe('inArray', function() {
Expand Down Expand Up @@ -405,6 +416,12 @@ describe('array', function() {
expect(hbs.compile('{{#withFirst}}{{/withFirst}}')()).to.equal('');
done();
});
it('should return an empty string when no array is passed:', function(done) {
const customContext = { array: null };
var fn = hbs.compile('{{#withFirst array}}{{/withFirst}}');
expect(fn(customContext)).to.equal('');
done();
});
it('should use the first two items in an array.', function(done) {
var fn = hbs.compile('{{#withFirst array 2}}{{this}} is smart.{{/withFirst}}');
expect(fn(context)).to.equal('a is smart.b is smart.');
Expand Down
66 changes: 66 additions & 0 deletions spec/helpers/3p/utils/lib/makeIterator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const Code = require("code");
const Lab = require("lab");
const lab = (exports.lab = Lab.script());
const expect = Code.expect;
const it = lab.it;
const describe = lab.describe;

var makeIterator = require("../../../../../helpers/3p/utils/lib/makeIterator");

describe('make iterator', function() {
it('should return source argument if it is already a function with no context', function(done) {
var fn = function() {};
expect(makeIterator(fn)).to.equal(fn);
done();
});

it('should return a function that calls object/deepMatches if argument is an object', function(done) {
var fn = makeIterator({ a: 1, b: { c: 2 } });
expect(fn({ a: 1, b: { c: 2, d: 3 } })).to.equal(true);
expect(fn({ a: 1, b: { c: 3 } })).to.equal(false);
done();
});

it('should return a function that calls object/deepMatches if argument is a regex', function(done) {
expect(makeIterator(/[a-c]/)(['a', 'b', 'c', 'd'])).to.equal(true);
expect(makeIterator(/[m-z]/)(['a', 'b', 'c', 'd'])).to.equal(false);
done();
});

it('should return a function that returns the property value if argument is a string', function(done) {
var fn = makeIterator('a');
expect(fn({a:1,b:2})).to.equal(1);
expect(fn({a:2,b:2})).to.equal(2);
done();
});

it('should return a function that returns the property value if argument is a number', function(done) {
var fn = makeIterator(1);
expect(fn([0,4,5])).to.equal(4);
expect(fn([6,7,8])).to.equal(7);
done();
});

it('should return an identify function if no args', function(done) {
var fn = makeIterator();
expect(fn(null)).to.equal(null);
expect(fn(void(0))).to.equal(void(0));
expect(fn(3)).to.equal(3);
done();
});

it('should return an identify function if first arg is `null`', function(done) {
var fn = makeIterator(null);
expect(fn(null)).to.equal(null);
expect(fn(void(0))).to.equal(void(0));
expect(fn(3)).to.equal(3);
done();
});

it('should return a function that is called with the specified context', function(done) {
var context = {};
var iterator = makeIterator(function() { return this; }, context);
expect(iterator()).to.equal(context);
done();
});
});