Skip to content

Commit

Permalink
Merge pull request #14 from blackflux/dev
Browse files Browse the repository at this point in the history
[Gally]: master <- dev
  • Loading branch information
simlu authored Jan 25, 2021
2 parents ad3f2ef + c38df72 commit e908b98
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 40 deletions.
58 changes: 36 additions & 22 deletions src/core/contains.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
const contains = (haystack, needle) => {
const needleType = typeof needle;
const haystackType = typeof haystack;
if (needleType !== haystackType) {
return false;
}
if (needleType === 'object') {
const needleIsArray = Array.isArray(needle);
const haystackIsArray = Array.isArray(haystack);
if (needleIsArray !== haystackIsArray) {
return false;
const objectScan = require('object-scan');

const scanner = objectScan(['**'], {
rtn: 'context',
abort: true,
breakFn: ({
isLeaf, isMatch, property, value, context
}) => {
const { stack } = context;
const last = stack[stack.length - 1];
if (isMatch && !(property in last)) {
context.result = false;
return true;
}
// exact match for arrays
if (needleIsArray) {
if (needle.length !== haystack.length) {
return false;
const current = isMatch ? last[property] : last;
if (isLeaf) {
if (value !== current) {
context.result = false;
return true;
}
return needle.every((e, idx) => contains(haystack[idx], e));
} else if (
value instanceof Object !== current instanceof Object
|| Array.isArray(value) !== Array.isArray(current)
|| (Array.isArray(value) && value.length !== current.length)
) {
context.result = false;
return true;
}
// subset match for object
return Object.keys(needle).every((key) => contains(haystack[key], needle[key]));
stack.push(current);
return false;
},
filterFn: ({ context }) => {
context.stack.pop();
return context.result !== true;
}
// default comparison
return haystack === needle;
};
});

module.exports = contains;
module.exports = (tree, subtree) => {
const { result } = scanner(subtree, { stack: [tree], result: true });
return result;
};
100 changes: 82 additions & 18 deletions test/core/contains.spec.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,93 @@
const expect = require('chai').expect;
const { describe } = require('node-tdd');
const contains = require('../../src/core/contains');

describe('Testing contains', () => {
it('Testing String', () => {
expect(contains('value1', 'value1')).to.equal(true);
expect(contains('value1', 'value2')).to.equal(false);
describe('Testing contains', { timeout: 100000 }, () => {
it('Batch test', ({ fixture }) => {
const gen = fixture('gen');
const containsRec = fixture('contains-rec');
for (let x = 0; x < 10000; x += 1) {
const tree = gen();
const subtree = gen();
expect(contains(tree, subtree)).to.equal(containsRec(tree, subtree));
}
});

it('Testing List', () => {
expect(contains([1, 2, 3], [1, 2, 3])).to.equal(true);
expect(contains([{ key: 'value1' }], [{ key: 'value1' }])).to.equal(true);
expect(contains([{ key: 'value1' }], [{ key: 'value2' }])).to.equal(false);
expect(contains([1, 2, 3], [3, 2, 1])).to.equal(false);
expect(contains([1, 2, 3], [1, 2])).to.equal(false);
expect(contains([], [])).to.equal(true);
describe('Testing String', () => {
it('Testing equal', () => {
expect(contains('value1', 'value1')).to.equal(true);
});
it('Testing not equal', () => {
expect(contains('value1', 'value2')).to.equal(false);
});
});

it('Testing Object', () => {
expect(contains({}, {})).to.equal(true);
expect(contains({ key: 'value1' }, { key: 'value1' })).to.equal(true);
expect(contains({ key: 'value1' }, { key: 'value2' })).to.equal(false);
describe('Testing Array', () => {
it('Testing equal arrays (containing three equal numbers)', () => {
expect(contains([1, 2, 3], [1, 2, 3])).to.equal(true);
});
it('Testing not equal arrays (containing three equal numbers in different order)', () => {
expect(contains([1, 2, 3], [3, 2, 1])).to.equal(false);
});
it('Testing equal arrays (containing single object)', () => {
expect(contains([{ key: 'value1' }], [{ key: 'value1' }])).to.equal(true);
});
it('Testing not equal arrays (containing different single object)', () => {
expect(contains([{ key: 'value1' }], [{ key: 'value2' }])).to.equal(false);
});
it('Testing not equal arrays (array contains number that are a subset)', () => {
expect(contains([1, 2, 3], [1, 2])).to.equal(false);
});
it('Testing empty arrays equal', () => {
expect(contains([], [])).to.equal(true);
});
it('Testing nested empty arrays equal', () => {
expect(contains([[], []], [[], []])).to.equal(true);
});
it('Testing nested not equal (different arrays, removed)', () => {
expect(contains([['x'], []], [[], []])).to.equal(false);
});
it('Testing nested not equal (different arrays, added)', () => {
expect(contains([[], []], [['x'], []])).to.equal(false);
});
});

it('Testing Type Mismatch', () => {
expect(contains({}, '')).to.equal(false);
expect(contains({}, [])).to.equal(false);
describe('Testing Object', () => {
it('Testing empty objects equal', () => {
expect(contains({}, {})).to.equal(true);
});
it('Testing objects equal with single key', () => {
expect(contains({ key: 'value1' }, { key: 'value1' })).to.equal(true);
});
it('Testing objects different with same keys, but different values', () => {
expect(contains({ key: 'value1' }, { key: 'value2' })).to.equal(false);
});
it('Testing different keys (added)', () => {
expect(contains({ key: 'value1' }, { key: 'value1', foo: 'bar' })).to.equal(false);
});
it('Testing different keys (removed)', () => {
expect(contains({ key: 'value1', foo: 'bar' }, { key: 'value1' })).to.equal(true);
});
});

describe('Testing Type Mismatch', () => {
it('Testing empty object vs empty string', () => {
expect(contains({}, '')).to.equal(false);
});
it('Testing empty object vs empty array', () => {
expect(contains({}, [])).to.equal(false);
});
it('Testing empty array vs empty string', () => {
expect(contains([], '')).to.equal(false);
});
it('Testing empty array vs empty object', () => {
expect(contains([], {})).to.equal(false);
});
it('Testing empty string vs empty object', () => {
expect(contains('', {})).to.equal(false);
});
it('Testing empty string vs empty array', () => {
expect(contains('', [])).to.equal(false);
});
});
});
27 changes: 27 additions & 0 deletions test/core/contains.spec.js__fixtures/contains-rec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const containsRec = (haystack, needle) => {
const needleType = typeof needle;
const haystackType = typeof haystack;
if (needleType !== haystackType) {
return false;
}
if (needleType === 'object') {
const needleIsArray = Array.isArray(needle);
const haystackIsArray = Array.isArray(haystack);
if (needleIsArray !== haystackIsArray) {
return false;
}
// exact match for arrays
if (needleIsArray) {
if (needle.length !== haystack.length) {
return false;
}
return needle.every((e, idx) => containsRec(haystack[idx], e));
}
// subset match for object
return Object.keys(needle).every((key) => containsRec(haystack[key], needle[key]));
}
// default comparison
return haystack === needle;
};

module.exports = containsRec;
18 changes: 18 additions & 0 deletions test/core/contains.spec.js__fixtures/gen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const rand = (values) => Math.floor(Math.random() * values);

const gen = (depth) => {
const v = rand(3);
if (v === 0 || depth >= 3) {
return rand(3);
}
if (v === 1) {
return Array.from({ length: rand(3) }, () => gen(depth + 1));
}
const r = {};
for (let idx = 0, len = gen(3) + 1; idx < len; idx += 1) {
r[['A', 'B', 'C'][idx]] = gen(depth + 1);
}
return r;
};

module.exports = () => gen(0);

0 comments on commit e908b98

Please sign in to comment.