pax_global_header 0000666 0000000 0000000 00000000064 14267037150 0014517 g ustar 00root root 0000000 0000000 52 comment=941a932b8be873bd119c6680c368116c52845e4a
hoek-10.0.1/ 0000775 0000000 0000000 00000000000 14267037150 0012524 5 ustar 00root root 0000000 0000000 hoek-10.0.1/.github/ 0000775 0000000 0000000 00000000000 14267037150 0014064 5 ustar 00root root 0000000 0000000 hoek-10.0.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14267037150 0016121 5 ustar 00root root 0000000 0000000 hoek-10.0.1/.github/workflows/ci-module.yml 0000664 0000000 0000000 00000000314 14267037150 0020520 0 ustar 00root root 0000000 0000000 name: ci
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
jobs:
test:
uses: hapijs/.github/.github/workflows/ci-module.yml@master
with:
min-node-version: 14
hoek-10.0.1/.gitignore 0000664 0000000 0000000 00000000154 14267037150 0014514 0 ustar 00root root 0000000 0000000 **/node_modules
**/package-lock.json
coverage.*
**/.DS_Store
**/._*
**/*.pem
**/.vs
**/.vscode
**/.idea
hoek-10.0.1/API.md 0000775 0000000 0000000 00000031544 14267037150 0013471 0 ustar 00root root 0000000 0000000
### Object
**hoek** provides several helpful methods for objects and arrays.
#### clone(obj, [options])
Clones an object or an array. A *deep copy* is made (duplicates everything, including values that are
objects, as well as non-enumerable properties) where:
- `obj` - the object to be cloned.
- `options` - optional settings:
- `symbols` - clone symbol properties. Defaults to `true`.
- `shallow` - one of:
- an array of object key strings (dot-separated or array-based key paths) to shallow copy from `obj` instead of deep.
- `true` to shallow copy all object properties. Used to shallow copy an object with non-enumerable properties and prototype;
```javascript
const nestedObj = {
w: /^something$/ig,
x: {
a: [1, 2, 3],
b: 123456,
c: new Date()
},
y: 'y',
z: new Date()
};
const copy = Hoek.clone(nestedObj);
copy.x.b = 100;
console.log(copy.y); // results in 'y'
console.log(nestedObj.x.b); // results in 123456
console.log(copy.x.b); // results in 100
```
Clones an object or array excluding some keys which are shallow copied:
```javascript
const nestedObj = {
w: /^something$/ig,
x: {
a: [1, 2, 3],
b: 123456,
c: new Date()
},
y: 'y',
z: new Date()
};
const copy = Hoek.clone(nestedObj, { shallow: ['x'] });
copy.x.b = 100;
console.log(copy.y); // results in 'y'
console.log(nestedObj.x.b); // results in 100
console.log(copy.x.b); // results in 100
```
#### merge(target, source, [options])
Merge all the properties of source into target where:
- `target` - the object onto which the properties of `source` are copied to.
- `source` - the object copied onto `target`.
- `options` - optional settings:
- `nullOverride` - if `true`, a `null` value in the source overrides any existing value in the `defaults`.
If `false`, `null` values in the `source` are ignored. Defaults to `true`.
- `mergeArrays` - if `true`, array values from `source` are appended to existing array values in `target`.
Defaults to `true`.
- `symbols` - clone symbol properties. Defaults to `true`.
Note that source wins in conflict, and by default null and undefined from source are applied.
Merge is destructive where the target is modified. For non destructive merge, use `applyToDefaults`.
```javascript
const target = {a: 1, b : 2};
const source = {a: 0, c: 5};
const source2 = {a: null, c: 5};
Hoek.merge(target, source); // results in {a: 0, b: 2, c: 5}
Hoek.merge(target, source2); // results in {a: null, b: 2, c: 5}
Hoek.merge(target, source2, { nullOverride: false} ); // results in {a: 1, b: 2, c: 5}
const targetArray = [1, 2, 3];
const sourceArray = [4, 5];
Hoek.merge(targetArray, sourceArray); // results in [1, 2, 3, 4, 5]
Hoek.merge(targetArray, sourceArray, { mergeArrays: false }); // results in [4, 5]
```
#### applyToDefaults(defaults, source, [options])
Apply source to a copy of the defaults where:
- `defaults` - the default object to clone and then apply `source` onto.
- `source` - the object applied to the `defaults`.
- `options` - optional settings:
- `nullOverride` - if `true`, a `null` value in the source overrides any existing value in the `defaults`.
If `false`, `null` values in the `source` are ignored. Defaults to `false`.
- `shallow` - an array of dot-separated or array-based key paths to shallow copy values in `source`.
```javascript
const defaults = { host: "localhost", port: 8000 };
const source = { port: 8080 };
const config = Hoek.applyToDefaults(defaults, source); // results in { host: "localhost", port: 8080 }
```
Apply source with a null value to a copy of the defaults
```javascript
const defaults = { host: "localhost", port: 8000 };
const source = { host: null, port: 8080 };
const config = Hoek.applyToDefaults(defaults, source, { nullOverride: true }); // results in { host: null, port: 8080 }
```
Apply source to a copy of the defaults where the shallow keys specified in the last parameter are shallow copied from source instead of merged
```javascript
const defaults = {
db: {
server: {
host: "localhost",
port: 8000
},
name: 'example'
}
};
const source = { server: { port: 8080 } };
const config = Hoek.applyToDefaults(defaults, source, { shallow: ['db.server'] }); // results in { db: { server: { port: 8080 }, name: 'example' } }
const config = Hoek.applyToDefaults(defaults, source, { shallow: [['db', 'server']] }); // results in { db: { server: { port: 8080 }, name: 'example' } }
```
#### deepEqual(a, b, [options])
Performs a deep comparison of the two values including support for circular dependencies,
prototype, and enumerable properties, where:
- `a` - the first value.
- `b` - the second value.
- `options` - optional settings:
- `deepFunction` - when `true`, function values are deep compared using their source code and
object properties. Defaults to `false`.
- `part` - when `true`, allows a partial match where some of `b` is present in `a`. Defaults to
`false`.
- `prototype` - when `false, prototype comparisons are skipped. Defaults to `true`.
- `skip` - an array of key name strings to skip comparing. The keys can be found in any level
of the object. Note that both values must contain the key - only the value comparison is
skipped. Only applies to plain objects and deep functions (not to map, sets, etc.). Defaults
to no skipping.
- `symbols` - when `false`, symbol properties are ignored. Defaults to `true`.
```javascript
Hoek.deepEqual({ a: [1, 2], b: 'string', c: { d: true } }, { a: [1, 2], b: 'string', c: { d: true } }); //results in true
Hoek.deepEqual(Object.create(null), {}, { prototype: false }); //results in true
Hoek.deepEqual(Object.create(null), {}); //results in false
```
#### intersect(array1, array2, [options])
Find the common unique items betwee two arrays where:
- `array1` - the first array.
- `array2` - the second array.
- `options` - optional settings:
- `first` - if `true`, return only the first intersecting item. Defaults to `false`.
```javascript
const array1 = [1, 2, 3];
const array2 = [1, 4, 5];
const newArray = Hoek.intersect(array1, array2); // results in [1]
```
#### contain(ref, values, [options])
Tests if the reference value contains the provided values where:
- `ref` - the reference string, array, or object.
- `values` - a single or array of values to find within the `ref` value. If `ref` is an object, `values` can be a key name,
an array of key names, or an object with key-value pairs to compare.
- `options` - an optional object with the following optional settings:
- `deep` - if `true`, performed a deep comparison of the values.
- `once` - if `true`, allows only one occurrence of each value.
- `only` - if `true`, does not allow values not explicitly listed.
- `part` - if `true`, allows partial match of the values (at least one must always match).
- `symbols` - clone symbol properties. Defaults to `true`.
Note: comparing a string to overlapping values will result in failed comparison (e.g. `contain('abc', ['ab', 'bc'])`).
Also, if an object key's value does not match the provided value, `false` is returned even when `part` is specified.
```javascript
Hoek.contain('aaa', 'a', { only: true }); // true
Hoek.contain([{ a: 1 }], [{ a: 1 }], { deep: true }); // true
Hoek.contain([1, 2, 2], [1, 2], { once: true }); // false
Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 }, { part: true }); // true
```
#### flatten(array, [target])
Flatten an array
```javascript
const array = [1, [2, 3]];
const flattenedArray = Hoek.flatten(array); // results in [1, 2, 3]
array = [1, [2, 3]];
target = [4, [5]];
flattenedArray = Hoek.flatten(array, target); // results in [4, [5], 1, 2, 3]
```
#### reach(obj, chain, [options])
Converts an object key chain string or array to reference
- `options` - optional settings
- `separator` - string to split chain path on, defaults to '.'
- `default` - value to return if the path or value is not present, default is `undefined`
- `strict` - if `true`, will throw an error on missing member, default is `false`
- `functions` - if `true`, allow traversing functions for properties. `false` will throw an
error if a function is part of the chain. Defaults to `true`.
- `iterables` - if `true`, allows traversing Set and Map objects. `false` will result in
`undefined` return value is the chain contains any Set or Map objects. Note that enabling
`iterables` can impact performance by up to 10% for all calls regardless of the presence of
Set or Map objects. Defaults to `false`.
A chain can be a string that will be split into key names using `separator`,
or an array containing each individual key name.
A chain including negative numbers will work like negative indices on an array.
If chain is `null`, `undefined` or `false`, the object itself will be returned.
```javascript
const chain = 'a.b.c';
const obj = {a : {b : { c : 1}}};
Hoek.reach(obj, chain); // returns 1
const chain = ['a', 'b', -1];
const obj = {a : {b : [2,3,6]}};
Hoek.reach(obj, chain); // returns 6
```
#### reachTemplate(obj, template, [options])
Replaces string parameters (`{name}`) with their corresponding object key values by applying the
[`reach()`](#reachobj-chain-options) method where:
- `obj` - the context object used for key lookup.
- `template` - a string containing `{}` parameters.
- `options` - optional [`reach()`](#reachobj-chain-options) options.
```javascript
const template = '1+{a.b.c}=2';
const obj = { a: { b: { c: 1 } } };
Hoek.reachTemplate(obj, template); // returns '1+1=2'
```
#### stringify(...args)
Converts an object to string using the built-in `JSON.stringify()` method with the difference that any errors are caught
and reported back in the form of the returned string. Used as a shortcut for displaying information to the console (e.g. in
error message) without the need to worry about invalid conversion.
```javascript
const a = {};
a.b = a;
Hoek.stringify(a); // Returns '[Cannot display object: Converting circular structure to JSON]'
```
### Bench
Same as Timer with the exception that `ts` stores the internal node clock which is not related to `Date.now()` and cannot be used to display
human-readable timestamps. More accurate for benchmarking or internal timers.
### Escaping Characters
**hoek** provides convenient methods for escaping html characters. The escaped characters are as followed:
```javascript
internals.htmlEscaped = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`'
};
```
#### escapeHtml(string)
```javascript
const string = ' hey ';
const escapedString = Hoek.escapeHtml(string); // returns <html> hey </html>
```
#### escapeHeaderAttribute(attribute)
Escape attribute value for use in HTTP header
```javascript
const a = Hoek.escapeHeaderAttribute('I said "go w\\o me"'); //returns I said \"go w\\o me\"
```
#### escapeJson(string)
Unicode escapes the characters `<`, `>`, and `&` to prevent mime-sniffing older browsers mistaking JSON as HTML, and escapes line and paragraph separators for JSONP and script contexts.
```javascript
const lineSeparator = String.fromCharCode(0x2028);
const a = Hoek.escapeJson('I said ');
expect(encoded).to.equal('<script>alert(1)</script>');
});
it('encodes < and > as named characters', () => {
const encoded = Hoek.escapeHtml('');
expect(encoded).to.equal('\\u003cscript\\u003ealert(1)\\u003c/script\\u003e');
});
});
hoek-10.0.1/test/esm.js 0000664 0000000 0000000 00000001745 14267037150 0014634 0 ustar 00root root 0000000 0000000 'use strict';
const Code = require('@hapi/code');
const Lab = require('@hapi/lab');
const { before, describe, it } = exports.lab = Lab.script();
const expect = Code.expect;
describe('import()', () => {
let Hoek;
before(async () => {
Hoek = await import('../lib/index.js');
});
it('exposes all methods and classes as named imports', () => {
expect(Object.keys(Hoek)).to.equal([
'Bench',
'Error',
'applyToDefaults',
'assert',
'block',
'clone',
'contain',
'deepEqual',
'default',
'escapeHeaderAttribute',
'escapeHtml',
'escapeJson',
'escapeRegex',
'flatten',
'ignore',
'intersect',
'isPromise',
'merge',
'once',
'reach',
'reachTemplate',
'stringify',
'wait'
]);
});
});
hoek-10.0.1/test/index.js 0000775 0000000 0000000 00000232312 14267037150 0015156 0 ustar 00root root 0000000 0000000 'use strict';
const Util = require('util');
const Code = require('@hapi/code');
const Hoek = require('..');
const Lab = require('@hapi/lab');
const internals = {};
const { describe, it } = exports.lab = Lab.script();
const expect = Code.expect;
const nestedObj = {
v: [7, 8, 9],
w: /^something$/igm,
x: {
a: [1, 2, 3],
b: 123456,
c: new Date(),
d: /hi/igm,
e: /hello/
},
y: 'y',
z: new Date(1378775452757)
};
describe('merge()', () => {
it('deep copies source items', () => {
const sym1 = Symbol('1');
const sym2 = Symbol('2');
const sym3 = Symbol('3');
const target = {
b: 3,
d: [],
[sym1]: true,
[sym3]: true
};
const source = {
c: {
d: 1
},
d: [{ e: 1 }],
[sym2]: true,
[sym3]: false
};
Hoek.merge(target, source);
expect(target.c).to.not.shallow.equal(source.c);
expect(target.c).to.equal(source.c);
expect(target.d).to.not.shallow.equal(source.d);
expect(target.d[0]).to.not.shallow.equal(source.d[0]);
expect(target.d).to.equal(source.d);
expect(target[sym1]).to.be.true();
expect(target[sym2]).to.be.true();
expect(target[sym3]).to.be.false();
});
it('deep copies source items without symbols', () => {
const sym1 = Symbol('1');
const sym2 = Symbol('2');
const sym3 = Symbol('3');
const target = {
b: 3,
d: [],
[sym1]: true,
[sym3]: true
};
const source = {
c: {
d: 1
},
d: [{ e: 1 }],
[sym2]: true,
[sym3]: false
};
Hoek.merge(target, source, { symbols: false });
expect(target.c).to.not.shallow.equal(source.c);
expect(target.c).to.equal(source.c);
expect(target.d).to.not.shallow.equal(source.d);
expect(target.d[0]).to.not.shallow.equal(source.d[0]);
expect(target.d).to.equal(source.d);
expect(target[sym1]).to.be.true();
expect(target[sym2]).to.not.exist();
expect(target[sym3]).to.be.true();
});
it('merges array over an object', () => {
const a = {
x: ['n', 'm']
};
const b = {
x: {
n: '1',
m: '2'
}
};
Hoek.merge(b, a);
expect(a.x[0]).to.equal('n');
expect(a.x.n).to.not.exist();
});
it('merges object over an array', () => {
const a = {
x: ['n', 'm']
};
const b = {
x: {
n: '1',
m: '2'
}
};
Hoek.merge(a, b);
expect(a.x.n).to.equal('1');
expect(a.x[0]).to.not.exist();
});
it('merges from null prototype objects', () => {
const a = {};
const b = Object.create(null);
b.x = true;
Hoek.merge(a, b);
expect(a.x).to.be.true();
});
it('skips non-enumerable properties', () => {
const a = { x: 0 };
const b = {};
Object.defineProperty(b, 'x', {
enumerable: false,
value: 1
});
Hoek.merge(a, b);
expect(a.x).to.equal(0);
});
it('does not throw if source is null', () => {
const a = {};
const b = null;
let c = null;
expect(() => {
c = Hoek.merge(a, b);
}).to.not.throw();
expect(c).to.equal(a);
});
it('does not throw if source is undefined', () => {
const a = {};
const b = undefined;
let c = null;
expect(() => {
c = Hoek.merge(a, b);
}).to.not.throw();
expect(c).to.equal(a);
});
it('throws if source is not an object', () => {
expect(() => {
const a = {};
const b = 0;
Hoek.merge(a, b);
}).to.throw('Invalid source value: must be null, undefined, or an object');
});
it('throws if target is not an object', () => {
expect(() => {
const a = 0;
const b = {};
Hoek.merge(a, b);
}).to.throw('Invalid target value: must be an object');
});
it('throws if target is not an array and source is', () => {
expect(() => {
const a = {};
const b = [1, 2];
Hoek.merge(a, b);
}).to.throw('Cannot merge array onto an object');
});
it('returns the same object when merging arrays', () => {
const a = [];
const b = [1, 2];
expect(Hoek.merge(a, b)).to.equal(a);
});
it('combines an empty object with a non-empty object', () => {
const a = {};
const b = nestedObj;
const c = Hoek.merge(a, b);
expect(a).to.equal(b);
expect(c).to.equal(b);
});
it('overrides values in target', () => {
const a = { x: 1, y: 2, z: 3, v: 5, t: 'test', s: 1, m: 'abc' };
const b = { x: null, z: 4, v: 0, t: { u: 6 }, s: undefined, m: '123' };
const c = Hoek.merge(a, b);
expect(c.x).to.equal(null);
expect(c.y).to.equal(2);
expect(c.z).to.equal(4);
expect(c.v).to.equal(0);
expect(c.m).to.equal('123');
expect(c.t).to.equal({ u: 6 });
expect(c.s).to.equal(undefined);
});
it('overrides values in target (flip)', () => {
const a = { x: 1, y: 2, z: 3, v: 5, t: 'test', s: 1, m: 'abc' };
const b = { x: null, z: 4, v: 0, t: { u: 6 }, s: undefined, m: '123' };
const d = Hoek.merge(b, a);
expect(d.x).to.equal(1);
expect(d.y).to.equal(2);
expect(d.z).to.equal(3);
expect(d.v).to.equal(5);
expect(d.m).to.equal('abc');
expect(d.t).to.equal('test');
expect(d.s).to.equal(1);
});
it('retains Date properties', () => {
const a = { x: new Date(1378776452757) };
const b = Hoek.merge({}, a);
expect(a.x.getTime()).to.equal(b.x.getTime());
});
it('retains Date properties when merging keys', () => {
const a = { x: new Date(1378776452757) };
const b = Hoek.merge({ x: {} }, a);
expect(a.x.getTime()).to.equal(b.x.getTime());
});
it('overrides Buffer', () => {
const a = { x: Buffer.from('abc') };
Hoek.merge({ x: {} }, a);
expect(a.x.toString()).to.equal('abc');
});
it('overrides RegExp', () => {
const a = { x: /test/ };
Hoek.merge({ x: {} }, a);
expect(a.x).to.equal(/test/);
});
it('overrides Symbol properties', () => {
const sym = Symbol();
const a = { [sym]: 1 };
Hoek.merge({ [sym]: {} }, a);
expect(a[sym]).to.equal(1);
});
it('skips __proto__', () => {
const a = '{ "ok": "value", "__proto__": { "test": "value" } }';
const b = Hoek.merge({}, JSON.parse(a));
expect(b).to.equal({ ok: 'value' });
expect(b.test).to.equal(undefined);
});
});
describe('applyToDefaults()', () => {
it('throws when target is null', () => {
expect(() => {
Hoek.applyToDefaults(null, {});
}).to.throw('Invalid defaults value: must be an object');
});
it('throws when options are invalid', () => {
expect(() => {
Hoek.applyToDefaults({}, {}, false);
}).to.throw('Invalid options: must be an object');
expect(() => {
Hoek.applyToDefaults({}, {}, 123);
}).to.throw('Invalid options: must be an object');
});
it('returns null if source is false', () => {
const defaults = {
a: 1,
b: 2,
c: {
d: 3,
e: [5, 6]
},
f: 6,
g: 'test'
};
const result = Hoek.applyToDefaults(defaults, false);
expect(result).to.equal(null);
});
it('returns null if source is null', () => {
const defaults = {
a: 1,
b: 2,
c: {
d: 3,
e: [5, 6]
},
f: 6,
g: 'test'
};
const result = Hoek.applyToDefaults(defaults, null);
expect(result).to.equal(null);
});
it('returns null if source is undefined', () => {
const defaults = {
a: 1,
b: 2,
c: {
d: 3,
e: [5, 6]
},
f: 6,
g: 'test'
};
const result = Hoek.applyToDefaults(defaults, undefined);
expect(result).to.equal(null);
});
it('returns a copy of defaults if source is true', () => {
const defaults = {
a: 1,
b: 2,
c: {
d: 3,
e: [5, 6]
},
f: 6,
g: 'test'
};
const result = Hoek.applyToDefaults(defaults, true);
expect(result).to.equal(defaults);
});
it('applies object to defaults', () => {
const defaults = {
a: 1,
b: 2,
c: {
d: 3,
e: [5, 6]
},
f: 6,
g: 'test'
};
const obj = {
a: null,
c: {
e: [4]
},
f: 0,
g: {
h: 5
}
};
const result = Hoek.applyToDefaults(defaults, obj);
expect(result.c.e).to.equal([4]);
expect(result.a).to.equal(1);
expect(result.b).to.equal(2);
expect(result.f).to.equal(0);
expect(result.g).to.equal({ h: 5 });
});
it('applies object to defaults with null', () => {
const defaults = {
a: 1,
b: 2,
c: {
d: 3,
e: [5, 6]
},
f: 6,
g: 'test'
};
const obj = {
a: null,
c: {
e: [4]
},
f: 0,
g: {
h: 5
}
};
const result = Hoek.applyToDefaults(defaults, obj, { nullOverride: true });
expect(result.c.e).to.equal([4]);
expect(result.a).to.equal(null);
expect(result.b).to.equal(2);
expect(result.f).to.equal(0);
expect(result.g).to.equal({ h: 5 });
});
it('shallow copies the listed keys from source without merging', () => {
const defaults = {
a: {
b: 5,
e: 3
},
c: {
d: 7,
g: 1
}
};
const source = {
a: {
b: 4
},
c: {
d: 6,
f: 7
}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['a'] });
expect(merged).to.equal({ a: { b: 4 }, c: { d: 6, g: 1, f: 7 } });
expect(merged.a).to.shallow.equal(source.a);
expect(merged.a).to.not.equal(defaults.a);
expect(merged.c).to.not.equal(source.c);
expect(merged.c).to.not.equal(defaults.c);
});
it('shallow copies the nested keys (override)', () => {
const defaults = {
a: {
b: 5
},
c: {
d: 7,
g: 1
}
};
const source = {
a: {
b: 4
},
c: {
d: 6,
g: {
h: 8
}
}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['c.g'] });
expect(merged).to.equal({ a: { b: 4 }, c: { d: 6, g: { h: 8 } } });
expect(merged.c.g).to.shallow.equal(source.c.g);
});
it('shallow copies the nested keys (missing)', () => {
const defaults = {
a: {
b: 5
}
};
const source = {
a: {
b: 4
},
c: {
g: {
h: 8
}
}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['c.g'] });
expect(merged).to.equal({ a: { b: 4 }, c: { g: { h: 8 } } });
expect(merged.c.g).to.shallow.equal(source.c.g);
});
it('shallow copies the nested keys (override)', () => {
const defaults = {
a: {
b: 5
},
c: {
g: {
d: 7
}
}
};
const source = {
a: {
b: 4
},
c: {
g: {
h: 8
}
}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['c.g'] });
expect(merged).to.equal({ a: { b: 4 }, c: { g: { h: 8 } } });
expect(merged.c.g).to.shallow.equal(source.c.g);
});
it('shallow copies the nested keys (deeper)', () => {
const defaults = {
a: {
b: 5
}
};
const source = {
a: {
b: 4
},
c: {
g: {
r: {
h: 8
}
}
}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['c.g.r'] });
expect(merged).to.equal({ a: { b: 4 }, c: { g: { r: { h: 8 } } } });
expect(merged.c.g.r).to.shallow.equal(source.c.g.r);
});
it('shallow copies the nested keys (not present)', () => {
const defaults = {
a: {
b: 5
}
};
const source = {
a: {
b: 4
},
c: {
g: {
r: {
h: 8
}
}
}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['x.y'] });
expect(merged).to.equal({ a: { b: 4 }, c: { g: { r: { h: 8 } } } });
});
it('shallow copies the nested keys (non-object)', () => {
const defaults = {
// All falsy values:
_undefined: {
a: 1
},
_null: {
a: 2
},
_false: {
a: 3
},
_emptyString: {
a: 4
},
_zero: {
a: 5
},
_NaN: {
a: 6
},
// Other non-object values:
_string: {
a: 7
},
_number: {
a: 8
},
_true: {
a: 9
},
_function: {
a: 10
}
};
const source = {
_undefined: undefined,
_null: null,
_false: false,
_emptyString: '',
_zero: 0,
_NaN: NaN,
_string: 'foo',
_number: 42,
_true: true,
_function: () => {}
};
const merged = Hoek.applyToDefaults(defaults, source, { shallow: [
'_undefined.a',
'_null.a',
'_false.a',
'_emptyString.a',
'_zero.a',
'_NaN.a',
'_string.a',
'_number.a',
'_true.a',
'_function.a'
] });
expect(merged).to.equal({
_undefined: { a: 1 },
_null: { a: 2 },
_false: false,
_emptyString: '',
_zero: 0,
_NaN: NaN,
_string: 'foo',
_number: 42,
_true: true,
_function: source._function
});
});
it('shallow copies the listed keys in the defaults', () => {
const defaults = {
a: {
b: 1
}
};
const merged = Hoek.applyToDefaults(defaults, {}, { shallow: ['a'] });
expect(merged.a).to.shallow.equal(defaults.a);
});
it('shallow copies the listed keys in the defaults (true)', () => {
const defaults = {
a: {
b: 1
}
};
const merged = Hoek.applyToDefaults(defaults, true, { shallow: ['a'] });
expect(merged.a).to.shallow.equal(defaults.a);
});
it('returns null on false', () => {
const defaults = {
a: {
b: 1
}
};
const merged = Hoek.applyToDefaults(defaults, false, { shallow: ['a'] });
expect(merged).to.equal(null);
});
it('handles missing shallow key in defaults', () => {
const defaults = {
a: {
b: 1
}
};
const options = {
a: {
b: 4
},
c: {
d: 2
}
};
const merged = Hoek.applyToDefaults(defaults, options, { shallow: ['c'] });
expect(merged).to.equal({ a: { b: 4 }, c: { d: 2 } });
expect(merged.c).to.shallow.equal(options.c);
expect(Hoek.applyToDefaults(defaults, true, { shallow: ['c'] })).to.equal({ a: { b: 1 } });
});
it('throws on missing defaults', () => {
expect(() => Hoek.applyToDefaults(null, {}, { shallow: ['a'] })).to.throw('Invalid defaults value: must be an object');
});
it('throws on invalid defaults', () => {
expect(() => Hoek.applyToDefaults('abc', {}, { shallow: ['a'] })).to.throw('Invalid defaults value: must be an object');
});
it('throws on invalid source', () => {
expect(() => Hoek.applyToDefaults({}, 'abc', { shallow: ['a'] })).to.throw('Invalid source value: must be true, falsy or an object');
});
it('throws on missing keys', () => {
expect(() => Hoek.applyToDefaults({}, true, { shallow: 123 })).to.throw('Invalid keys');
});
it('handles array keys', () => {
const sym = Symbol();
const defaults = {
a: {
b: 5,
e: 3
},
c: {
d: 7,
[sym]: {
f: 9
}
}
};
const options = {
a: {
b: 4
},
c: {
d: 6,
[sym]: {
g: 1
}
}
};
const merged = Hoek.applyToDefaults(defaults, options, { shallow: [['c', sym]] });
expect(merged).to.equal({ a: { b: 4, e: 3 }, c: { d: 6, [sym]: { g: 1 } } });
expect(merged.c[sym]).to.shallow.equal(options.c[sym]);
});
it('does not modify shallow entries in source', () => {
const defaults = {
a: {
b: 5
}
};
const source = {};
Object.defineProperty(source, 'a', { value: { b: 4 } });
const merged = Hoek.applyToDefaults(defaults, source, { shallow: ['a'] });
expect(merged).to.equal({ a: { b: 4 } });
expect(merged.a).to.shallow.equal(source.a);
expect(merged.a).to.not.equal(defaults.a);
});
it('should respect nullOverride when shallow is used', () => {
const defaults = { host: 'localhost', port: 8000 };
const source = { host: null, port: 8080 };
const result = Hoek.applyToDefaults(defaults, source, { nullOverride: true, shallow: [] });
expect(result.host).to.equal(null);
expect(result.port).to.equal(8080);
});
});
describe('deepEqual()', () => {
it('compares identical references', () => {
const x = {};
expect(Hoek.deepEqual(x, x)).to.be.true();
});
it('compares simple values', () => {
expect(Hoek.deepEqual('x', 'x')).to.be.true();
expect(Hoek.deepEqual('x', 'y')).to.be.false();
expect(Hoek.deepEqual('x1', 'x')).to.be.false();
expect(Hoek.deepEqual(-0, +0)).to.be.false();
expect(Hoek.deepEqual(-0, -0)).to.be.true();
expect(Hoek.deepEqual(+0, +0)).to.be.true();
expect(Hoek.deepEqual(+0, -0)).to.be.false();
expect(Hoek.deepEqual(1, 1)).to.be.true();
expect(Hoek.deepEqual(0, 0)).to.be.true();
expect(Hoek.deepEqual(-1, 1)).to.be.false();
expect(Hoek.deepEqual(NaN, 0)).to.be.false();
expect(Hoek.deepEqual(NaN, NaN)).to.be.true();
});
it('compares different types', () => {
expect(Hoek.deepEqual([], 5, { prototype: false })).to.be.false();
expect(Hoek.deepEqual(5, [], { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, null, { prototype: false })).to.be.false();
expect(Hoek.deepEqual(null, {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual('abc', {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, 'abc', { prototype: false })).to.be.false();
});
it('compares empty structures', () => {
expect(Hoek.deepEqual([], [])).to.be.true();
expect(Hoek.deepEqual({}, {})).to.be.true();
expect(Hoek.deepEqual([], {})).to.be.false();
expect(Hoek.deepEqual([], {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, [], { prototype: false })).to.be.false();
});
it('compares empty arguments object', () => {
const compare = function () {
expect(Hoek.deepEqual([], arguments)).to.be.false(); // eslint-disable-line prefer-rest-params
};
compare();
});
it('compares empty arguments objects', () => {
const compare = function () {
const arg1 = arguments; // eslint-disable-line prefer-rest-params
const inner = function () {
// callee is not supported in strict mode, was previously false becuse callee was different
expect(Hoek.deepEqual(arg1, arguments)).to.be.true(); // eslint-disable-line prefer-rest-params
};
inner();
};
compare();
});
it('compares symbol object properties', () => {
const sym = Symbol();
const ne = {};
Object.defineProperty(ne, sym, { value: true });
expect(Hoek.deepEqual({ [sym]: { c: true } }, { [sym]: { c: true } })).to.be.true();
expect(Hoek.deepEqual({ [sym]: { c: true } }, { [sym]: { c: false } })).to.be.false();
expect(Hoek.deepEqual({ [sym]: { c: true } }, { [sym]: true })).to.be.false();
expect(Hoek.deepEqual({ [sym]: undefined }, { [sym]: undefined })).to.be.true();
expect(Hoek.deepEqual({ [sym]: undefined }, {})).to.be.false();
expect(Hoek.deepEqual({}, { [sym]: undefined })).to.be.false();
expect(Hoek.deepEqual({}, ne)).to.be.true();
expect(Hoek.deepEqual(ne, {})).to.be.true();
expect(Hoek.deepEqual({ [sym]: true }, ne)).to.be.false();
expect(Hoek.deepEqual(ne, { [sym]: true })).to.be.false();
expect(Hoek.deepEqual(ne, { [Symbol()]: undefined })).to.be.false();
expect(Hoek.deepEqual({ [sym]: true }, { [sym]: true })).to.be.true();
expect(Hoek.deepEqual({ [sym]: true }, {})).to.be.false();
expect(Hoek.deepEqual({ [sym]: true }, {}, { symbols: false })).to.be.true();
});
it('compares dates', () => {
expect(Hoek.deepEqual(new Date(2015, 1, 1), new Date('2015/02/01'))).to.be.true();
expect(Hoek.deepEqual(new Date(100), new Date(101))).to.be.false();
expect(Hoek.deepEqual(new Date(), {})).to.be.false();
expect(Hoek.deepEqual(new Date(2015, 1, 1), new Date('2015/02/01'), { prototype: false })).to.be.true();
expect(Hoek.deepEqual(new Date(), {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, new Date(), { prototype: false })).to.be.false();
});
it('compares regular expressions', () => {
expect(Hoek.deepEqual(/\s/, new RegExp('\\\s'))).to.be.true();
expect(Hoek.deepEqual(/\s/g, /\s/g)).to.be.true();
expect(Hoek.deepEqual(/a/, {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual(/\s/g, /\s/i)).to.be.false();
expect(Hoek.deepEqual(/a/g, /b/g)).to.be.false();
});
it('compares errors', () => {
expect(Hoek.deepEqual(new Error('a'), new Error('a'))).to.be.true();
expect(Hoek.deepEqual(new Error('a'), new Error('b'))).to.be.false();
expect(Hoek.deepEqual(new Error('a'), new TypeError('a'))).to.be.false();
expect(Hoek.deepEqual(new Error('a'), new TypeError('a'), { prototype: false })).to.be.false();
expect(Hoek.deepEqual(new Error(), {})).to.be.false();
expect(Hoek.deepEqual(new Error(), {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, new Error())).to.be.false();
expect(Hoek.deepEqual({}, new Error(), { prototype: false })).to.be.false();
const error = new Error('a');
expect(Hoek.deepEqual(Hoek.clone(error), error)).to.be.true();
expect(Hoek.deepEqual(Hoek.clone(error), error, { prototype: false })).to.be.true();
});
it('compares arrays', () => {
expect(Hoek.deepEqual([[1]], [[1]])).to.be.true();
expect(Hoek.deepEqual([1, 2, 3], [1, 2, 3])).to.be.true();
expect(Hoek.deepEqual([1, 2, 3], [1, 3, 2])).to.be.false();
expect(Hoek.deepEqual([1, 2, 3], [1, 2])).to.be.false();
expect(Hoek.deepEqual([1], [1])).to.be.true();
const item1 = { key: 'value1' };
const item2 = { key: 'value2' };
expect(Hoek.deepEqual([item1, item1], [item1, item2])).to.be.false();
});
it('compares sets', () => {
expect(Hoek.deepEqual(new Set(), new Set())).to.be.true();
expect(Hoek.deepEqual(new Set([1]), new Set([1]))).to.be.true();
expect(Hoek.deepEqual(new Set([]), new Set([]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, 2, 3]), new Set([1, 2, 3]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, 2, 3]), new Set([3, 2, 1]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, 2, 3]), new Set([1, 2, 4]))).to.be.false();
expect(Hoek.deepEqual(new Set([1, 2, 3]), new Set([1, 2]))).to.be.false();
expect(Hoek.deepEqual(new Set([1, 2, 1]), new Set([1, 2]))).to.be.true();
expect(Hoek.deepEqual(new Set([+0]), new Set([-0]))).to.be.true();
expect(Hoek.deepEqual(new Set([NaN]), new Set([NaN]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, {}]), new Set([1, {}]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, {}]), new Set([{}, 1]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, {}, {}]), new Set([{}, 1, {}]))).to.be.true();
expect(Hoek.deepEqual(new Set([1, { a: 1 }]), new Set([{ a: 0 }, 1]))).to.be.false();
expect(Hoek.deepEqual(new WeakSet(), new WeakSet())).to.be.true();
const obj = {};
expect(Hoek.deepEqual(new WeakSet([obj]), new WeakSet())).to.be.true();
expect(Hoek.deepEqual(new WeakSet(), new Set()), { prototype: false }).to.be.false();
const sets = [new Set(), new Set()].map((set) => {
set.modified = true;
return set;
});
expect(Hoek.deepEqual(sets[0], sets[1])).to.be.true();
expect(Hoek.deepEqual(sets[0], new Set())).to.be.false();
});
it('compares extended sets', () => {
class PrivateSet extends Set {
has() {
throw new Error('not allowed');
}
}
const entries = ['a', undefined];
expect(Hoek.deepEqual(new PrivateSet(), new PrivateSet())).to.be.true();
expect(Hoek.deepEqual(new PrivateSet(entries), new PrivateSet(entries))).to.be.true();
expect(Hoek.deepEqual(new PrivateSet(entries), new Set(entries), { prototype: false })).to.be.true();
expect(Hoek.deepEqual(new PrivateSet(entries), new Set(entries), { prototype: true })).to.be.false();
expect(Hoek.deepEqual(new PrivateSet(), new PrivateSet(entries))).to.be.false();
expect(Hoek.deepEqual(new PrivateSet(entries), new PrivateSet())).to.be.false();
class LockableSet extends Set {
constructor(values, locked = true) {
super(values);
this.locked = locked;
}
has() {
if (this.locked) {
throw new Error('not allowed');
}
}
}
expect(Hoek.deepEqual(new LockableSet(), new LockableSet())).to.be.true();
expect(Hoek.deepEqual(new LockableSet(entries), new LockableSet(entries))).to.be.true();
expect(Hoek.deepEqual(new LockableSet(entries, false), new LockableSet(entries, false))).to.be.true();
expect(Hoek.deepEqual(new LockableSet(entries, true), new LockableSet(entries, false))).to.be.false();
expect(Hoek.deepEqual(new LockableSet(entries, false), new LockableSet(entries, true))).to.be.false();
expect(Hoek.deepEqual(new LockableSet(entries), new Set(entries), { prototype: false })).to.be.false();
expect(Hoek.deepEqual(new LockableSet(entries), new PrivateSet(entries), { prototype: false })).to.be.false();
});
it('compares maps', () => {
const item1 = { key: 'value1' };
const item2 = { key: 'value2' };
expect(Hoek.deepEqual(new Map(), new Map())).to.be.true();
expect(Hoek.deepEqual(new Map([[1, {}]]), new Map([[1, {}]]))).to.be.true();
expect(Hoek.deepEqual(new Map([[1, item1]]), new Map([[1, item1]]))).to.be.true();
expect(Hoek.deepEqual(new Map([[1, item1]]), new Map([[1, item2]]))).to.be.false();
expect(Hoek.deepEqual(new Map([[1, undefined]]), new Map([[1, undefined]]))).to.be.true();
expect(Hoek.deepEqual(new Map([[1, undefined]]), new Map([[2, undefined]]))).to.be.false();
expect(Hoek.deepEqual(new Map([[1, {}]]), new Map([[1, {}], [2, {}]]))).to.be.false();
expect(Hoek.deepEqual(new Map([[item1, 1]]), new Map([[item1, 1]]))).to.be.true();
expect(Hoek.deepEqual(new Map([[{}, 1]]), new Map([[{}, 1]]))).to.be.false();
expect(Hoek.deepEqual(new WeakMap(), new WeakMap())).to.be.true();
expect(Hoek.deepEqual(new WeakMap([[item1, 1]]), new WeakMap())).to.be.true();
expect(Hoek.deepEqual(new WeakMap(), new Map()), { prototype: false }).to.be.false();
const maps = [new Map(), new Map()].map((map) => {
map.modified = true;
return map;
});
expect(Hoek.deepEqual(maps[0], maps[1])).to.be.true();
expect(Hoek.deepEqual(maps[0], new Map())).to.be.false();
});
it('compares extended maps', () => {
class PrivateMap extends Map {
get() {
throw new Error('not allowed');
}
}
const entries = [['a', 1], ['b', undefined]];
expect(Hoek.deepEqual(new PrivateMap(), new PrivateMap())).to.be.true();
expect(Hoek.deepEqual(new PrivateMap(entries), new PrivateMap(entries))).to.be.true();
expect(Hoek.deepEqual(new PrivateMap(entries), new Map(entries), { prototype: false })).to.be.true();
expect(Hoek.deepEqual(new PrivateMap(entries), new Map(entries), { prototype: true })).to.be.false();
expect(Hoek.deepEqual(new PrivateMap(), new PrivateMap(entries))).to.be.false();
expect(Hoek.deepEqual(new PrivateMap(entries), new PrivateMap())).to.be.false();
class LockableMap extends Map {
constructor(kvs, locked = true) {
super(kvs);
this.locked = locked;
}
get() {
if (this.locked) {
throw new Error('not allowed');
}
}
}
expect(Hoek.deepEqual(new LockableMap(), new LockableMap())).to.be.true();
expect(Hoek.deepEqual(new LockableMap(entries), new LockableMap(entries))).to.be.true();
expect(Hoek.deepEqual(new LockableMap(entries, false), new LockableMap(entries, false))).to.be.true();
expect(Hoek.deepEqual(new LockableMap(entries, true), new LockableMap(entries, false))).to.be.false();
expect(Hoek.deepEqual(new LockableMap(entries, false), new LockableMap(entries, true))).to.be.false();
expect(Hoek.deepEqual(new LockableMap(entries), new Map(entries), { prototype: false })).to.be.false();
expect(Hoek.deepEqual(new LockableMap(entries), new PrivateMap(entries), { prototype: false })).to.be.false();
});
it('compares promises', () => {
const a = new Promise(() => { });
expect(Hoek.deepEqual(a, a)).to.be.true();
expect(Hoek.deepEqual(a, new Promise(() => { }))).to.be.false();
});
it('compares buffers', () => {
expect(Hoek.deepEqual(Buffer.from([1, 2, 3]), Buffer.from([1, 2, 3]))).to.be.true();
expect(Hoek.deepEqual(Buffer.from([1, 2, 3]), Buffer.from([1, 3, 2]))).to.be.false();
expect(Hoek.deepEqual(Buffer.from([1, 2, 3]), Buffer.from([1, 2]))).to.be.false();
expect(Hoek.deepEqual(Buffer.from([1, 2, 3]), {})).to.be.false();
expect(Hoek.deepEqual(Buffer.from([1, 2, 3]), [1, 2, 3])).to.be.false();
});
it('compares string objects', () => {
/* eslint-disable no-new-wrappers */
expect(Hoek.deepEqual(new String('a'), new String('a'))).to.be.true();
expect(Hoek.deepEqual(new String('a'), new String('b'))).to.be.false();
expect(Hoek.deepEqual(new String(''), {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, new String(''), { prototype: false })).to.be.false();
expect(Hoek.deepEqual(new String('a'), 'a', { prototype: false })).to.be.false();
expect(Hoek.deepEqual('a', new String('a'), { prototype: false })).to.be.false();
/* eslint-enable no-new-wrappers */
});
it('compares number objects', () => {
/* eslint-disable no-new-wrappers */
expect(Hoek.deepEqual(new Number(1), new Number(1))).to.be.true();
expect(Hoek.deepEqual(new Number(1), new Number(2))).to.be.false();
expect(Hoek.deepEqual(new Number(+0), new Number(-0))).to.be.false();
expect(Hoek.deepEqual(new Number(NaN), new Number(NaN))).to.be.true();
expect(Hoek.deepEqual(new Number(0), {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, new Number(0), { prototype: false })).to.be.false();
expect(Hoek.deepEqual(new Number(1), 1, { prototype: false })).to.be.false();
expect(Hoek.deepEqual(1, new Number(1), { prototype: false })).to.be.false();
/* eslint-enable no-new-wrappers */
});
it('compares boolean objects', () => {
/* eslint-disable no-new-wrappers */
expect(Hoek.deepEqual(new Boolean(true), new Boolean(true))).to.be.true();
expect(Hoek.deepEqual(new Boolean(true), new Boolean(false))).to.be.false();
expect(Hoek.deepEqual(new Boolean(false), {}, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({}, new Boolean(false), { prototype: false })).to.be.false();
expect(Hoek.deepEqual(new Boolean(true), true, { prototype: false })).to.be.false();
expect(Hoek.deepEqual(true, new Boolean(true), { prototype: false })).to.be.false();
/* eslint-enable no-new-wrappers */
});
it('compares objects', () => {
expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).to.be.true();
expect(Hoek.deepEqual({ foo: 'bar' }, { foo: 'baz' })).to.be.false();
expect(Hoek.deepEqual({ foo: { bar: 'foo' } }, { foo: { bar: 'baz' } })).to.be.false();
expect(Hoek.deepEqual({ foo: undefined }, {})).to.be.false();
expect(Hoek.deepEqual({}, { foo: undefined })).to.be.false();
expect(Hoek.deepEqual({ foo: undefined }, { bar: undefined })).to.be.false();
});
it('compares functions', () => {
const f1 = () => 1;
const f2 = () => 2;
const f2a = () => 2;
expect(Hoek.deepEqual({ f1 }, { f1 })).to.be.true();
expect(Hoek.deepEqual({ f1 }, { f1: f2 })).to.be.false();
expect(Hoek.deepEqual({ f2 }, { f2: f2a })).to.be.false();
expect(Hoek.deepEqual({ f2 }, { f2: f2a }, { deepFunction: true })).to.be.true();
expect(Hoek.deepEqual({ f2 }, { f2: f1 }, { deepFunction: true })).to.be.false();
const f3 = () => 3;
f3.x = 1;
const f3a = () => 3;
f3a.x = 1;
const f3b = () => 3;
f3b.x = 2;
expect(Hoek.deepEqual({ f3 }, { f3: f3a }, { deepFunction: true })).to.be.true();
expect(Hoek.deepEqual({ f3 }, { f3: f3b }, { deepFunction: true })).to.be.false();
});
it('skips keys', () => {
expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 }, { skip: ['c'] })).to.be.true();
expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }, { skip: ['c'] })).to.be.true();
const sym = Symbol('test');
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3 }, { a: 1, b: 2, [sym]: 4 })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3 }, { a: 1, b: 2, [sym]: 4 }, { skip: [sym] })).to.be.true();
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3 }, { a: 1, b: 2 })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3 }, { a: 1, b: 2 }, { skip: [sym] })).to.be.true();
expect(Hoek.deepEqual({ a: 1, b: 2, [sym]: 3, [Symbol('other')]: true }, { a: 1, b: 2 }, { skip: [sym] })).to.be.false();
expect(Hoek.deepEqual({ a: 1, b: 2 }, { a: 1 }, { skip: ['a'] })).to.be.false();
expect(Hoek.deepEqual({ a: 1 }, { a: 1, b: 2 }, { skip: ['a'] })).to.be.false();
});
it('handles circular dependency', () => {
const a = {};
a.x = a;
const b = Hoek.clone(a);
expect(Hoek.deepEqual(a, b)).to.be.true();
});
it('handles obj only circular dependency', () => {
const a = {};
a.x = a;
const b = { x: {} };
expect(Hoek.deepEqual(a, b)).to.be.false();
expect(Hoek.deepEqual(b, a)).to.be.false();
});
it('handles irregular circular dependency', () => {
const a = {};
a.x = a;
const b = { x: {} };
b.x.x = b;
const c = { x: { x: {} } };
c.x.x.x = c;
expect(Hoek.deepEqual(a, b)).to.be.true();
expect(Hoek.deepEqual(b, a)).to.be.true();
expect(Hoek.deepEqual(a, c)).to.be.true();
expect(Hoek.deepEqual(b, c)).to.be.true();
expect(Hoek.deepEqual(c, a)).to.be.true();
expect(Hoek.deepEqual(c, b)).to.be.true();
b.x.y = 1;
expect(Hoek.deepEqual(a, b)).to.be.false();
expect(Hoek.deepEqual(b, a)).to.be.false();
});
it('handles cross circular dependency', () => {
const a = {};
const b = { x: {}, y: a };
b.x.x = b;
b.x.y = b.x;
a.x = b;
a.y = a;
expect(Hoek.deepEqual(b, a)).to.be.true();
expect(Hoek.deepEqual(a, b)).to.be.true();
b.x.y = 1;
expect(Hoek.deepEqual(b, a)).to.be.false();
expect(Hoek.deepEqual(a, b)).to.be.false();
});
it('handles reuse of objects', () => {
const date1 = { year: 2018, month: 1, day: 1 };
const date2 = { year: 2000, month: 1, day: 1 };
expect(Hoek.deepEqual({ start: date1, end: date1 }, { start: date1, end: date2 })).to.be.false();
});
it('handles valueOf() that throws', () => {
const throwing = class {
constructor(value) {
this.value = value;
}
valueOf() {
throw new Error('failed');
}
};
expect(Hoek.deepEqual(new throwing('a'), new throwing('a'))).to.be.true();
expect(Hoek.deepEqual(new throwing('a'), new throwing('b'))).to.be.false();
expect(Hoek.deepEqual(new throwing('a'), { value: 'a' }, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({ value: 'a' }, new throwing('a'), { prototype: false })).to.be.false();
});
it('handles valueOf() that returns similar value', () => {
const identity = class {
constructor(value) {
this.value = value;
}
valueOf() {
return { value: this.value };
}
};
expect(Hoek.deepEqual(new identity('a'), new identity('a'))).to.be.true();
expect(Hoek.deepEqual(new identity('a'), new identity('b'))).to.be.false();
expect(Hoek.deepEqual(new identity('a'), { value: 'a' }, { prototype: true })).to.be.false();
expect(Hoek.deepEqual(new identity('a'), { value: 'a' }, { prototype: false })).to.be.true();
expect(Hoek.deepEqual({ value: 'a' }, new identity('a'), { prototype: true })).to.be.false();
expect(Hoek.deepEqual({ value: 'a' }, new identity('a'), { prototype: false })).to.be.true();
});
it('skips enumerable properties on prototype chain', () => {
const base = function (value, surprice) {
this.value = value;
if (surprice) {
this.surprice = surprice;
}
};
Object.defineProperty(base.prototype, 'enum', {
enumerable: true,
configurable: true,
value: true
});
expect('enum' in new base('a')).to.be.true();
expect(Hoek.deepEqual(new base('a'), new base('a'))).to.be.true();
expect(Hoek.deepEqual(new base('a'), new base('b'))).to.be.false();
expect(Hoek.deepEqual(new base('a'), { value: 'a' }, { prototype: false })).to.be.true();
expect(Hoek.deepEqual({ value: 'a' }, new base('a'), { prototype: false })).to.be.true();
expect(Hoek.deepEqual(new base('a', 1), { value: 'a', enum: true }, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({ value: 'a', enum: true }, new base('a', 1), { prototype: false })).to.be.false();
});
it('skips non-enumerable properties', () => {
const base = function Base(value, surprice) {
this.value = value;
if (surprice) {
this.surprice = surprice;
}
};
const createObj = (...args) => {
const obj = new base(...args);
Object.defineProperty(obj, 'hidden', {
enumerable: false,
configurable: true,
value: true
});
return obj;
};
expect(Hoek.deepEqual(createObj('a'), createObj('a'))).to.be.true();
expect(Hoek.deepEqual(createObj('a'), createObj('b'))).to.be.false();
expect(Hoek.deepEqual(createObj('a'), { value: 'a' }, { prototype: false })).to.be.true();
expect(Hoek.deepEqual({ value: 'a' }, createObj('a'), { prototype: false })).to.be.true();
expect(Hoek.deepEqual(createObj('a', 1), { value: 'a', hidden: true }, { prototype: false })).to.be.false();
expect(Hoek.deepEqual({ value: 'a', hidden: true }, createObj('a', 1), { prototype: false })).to.be.false();
});
it('compares an object with property getter while executing it', () => {
const obj = {};
const value = 1;
let execCount = 0;
Object.defineProperty(obj, 'test', {
enumerable: true,
configurable: true,
get: function () {
++execCount;
return value;
}
});
const copy = Hoek.clone(obj);
expect(Hoek.deepEqual(obj, copy)).to.be.true();
expect(execCount).to.equal(2);
expect(copy.test).to.equal(1);
expect(execCount).to.equal(3);
});
it('compares objects with property getters', () => {
const obj = {};
Object.defineProperty(obj, 'test', {
enumerable: true,
configurable: true,
get: function () {
return 1;
}
});
const ref = {};
Object.defineProperty(ref, 'test', {
enumerable: true,
configurable: true,
get: function () {
return 2;
}
});
expect(Hoek.deepEqual(obj, ref)).to.be.false();
});
it('compares object prototypes', () => {
const Obj = function () {
this.a = 5;
};
Obj.prototype.b = function () {
return this.a;
};
const Ref = function () {
this.a = 5;
};
Ref.prototype.b = function () {
return this.a;
};
expect(Hoek.deepEqual(new Obj(), new Ref())).to.be.false();
expect(Hoek.deepEqual(new Obj(), new Obj())).to.be.true();
expect(Hoek.deepEqual(new Ref(), new Ref())).to.be.true();
});
it('compares plain objects', () => {
const a = Object.create(null);
const b = Object.create(null);
a.b = 'c';
b.b = 'c';
expect(Hoek.deepEqual(a, b)).to.be.true();
expect(Hoek.deepEqual(a, { b: 'c' })).to.be.false();
});
it('compares an object with an empty object', () => {
const a = { a: 1, b: 2 };
expect(Hoek.deepEqual({}, a)).to.be.false();
expect(Hoek.deepEqual(a, {})).to.be.false();
});
it('compares an object ignoring the prototype', () => {
const a = Object.create(null);
const b = {};
expect(Hoek.deepEqual(a, b, { prototype: false })).to.be.true();
});
it('compares an object ignoring the prototype recursively', () => {
const a = [Object.create(null)];
const b = [{}];
expect(Hoek.deepEqual(a, b, { prototype: false })).to.be.true();
});
});
describe('intersect()', () => {
it('returns the common objects of two arrays', () => {
const array1 = [1, 2, 3, 4, 4, 5, 5];
const array2 = [5, 4, 5, 6, 7];
const common = Hoek.intersect(array1, array2);
expect(common).to.equal([5, 4]);
});
it('returns the common objects of array and set', () => {
const array1 = new Set([1, 2, 3, 4, 4, 5, 5]);
const array2 = [5, 4, 5, 6, 7];
const common = Hoek.intersect(array1, array2);
expect(common).to.equal([5, 4]);
});
it('returns the common objects of set and array', () => {
const array1 = [1, 2, 3, 4, 4, 5, 5];
const array2 = new Set([5, 4, 5, 6, 7]);
const common = Hoek.intersect(array1, array2);
expect(common).to.equal([5, 4]);
});
it('returns the common objects of two sets', () => {
const array1 = new Set([1, 2, 3, 4, 4, 5, 5]);
const array2 = new Set([5, 4, 5, 6, 7]);
const common = Hoek.intersect(array1, array2);
expect(common).to.equal([5, 4]);
});
it('returns just the first common object of two arrays', () => {
const array1 = [1, 2, 3, 4, 4, 5, 5];
const array2 = [5, 4, 5, 6, 7];
const common = Hoek.intersect(array1, array2, { first: true });
expect(common).to.equal(5);
});
it('returns null when no common and returning just the first common object of two arrays', () => {
const array1 = [1, 2, 3, 4, 4, 5, 5];
const array2 = [6, 7];
const common = Hoek.intersect(array1, array2, { first: true });
expect(common).to.equal(null);
});
it('returns an empty array if either input is null', () => {
expect(Hoek.intersect([1], null).length).to.equal(0);
expect(Hoek.intersect(null, [1]).length).to.equal(0);
expect(Hoek.intersect(null, [1], { first: true })).to.be.null();
});
it('returns the common objects of object and array', () => {
const array1 = { 1: true, 2: true, 3: true, 4: true, 5: true };
const array2 = [5, 4, 5, 6, 7];
const common = Hoek.intersect(array1, array2);
expect(common.length).to.equal(2);
});
});
describe('contain()', () => {
it('tests strings', () => {
expect(Hoek.contain('abc', 'ab')).to.be.true();
expect(Hoek.contain('abc', 'abc', { only: true })).to.be.true();
expect(Hoek.contain('aaa', 'a', { only: true })).to.be.true();
expect(Hoek.contain('aaa', 'a', { only: true, once: true })).to.be.false();
expect(Hoek.contain('abc', 'b', { once: true })).to.be.true();
expect(Hoek.contain('abc', ['a', 'c'])).to.be.true();
expect(Hoek.contain('abc', ['a', 'd'], { part: true })).to.be.true();
expect(Hoek.contain('aaa', ['a', 'a'], { only: true, once: true })).to.be.false();
expect(Hoek.contain('aaa', ['a', 'a'], { only: true })).to.be.true();
expect(Hoek.contain('aaa', ['a', 'a', 'a'], { only: true, once: true })).to.be.true();
expect(Hoek.contain('abc', 'ac')).to.be.false();
expect(Hoek.contain('abcd', 'abc', { only: true })).to.be.false();
expect(Hoek.contain('aab', 'a', { only: true })).to.be.false();
expect(Hoek.contain('abb', 'b', { once: true })).to.be.false();
expect(Hoek.contain('abc', ['a', 'd'])).to.be.false();
expect(Hoek.contain('abc', ['ab', 'bc'])).to.be.false(); // Overlapping values not supported
expect(Hoek.contain('', 'a')).to.be.false();
expect(Hoek.contain('', 'a', { only: true })).to.be.false();
expect(Hoek.contain('', '')).to.be.true();
expect(Hoek.contain('', ''), { only: true }).to.be.true();
expect(Hoek.contain('', ''), { once: true }).to.be.true();
expect(Hoek.contain('', ['', ''])).to.be.true();
expect(Hoek.contain('', ['', ''], { only: true })).to.be.true();
expect(Hoek.contain('', ['', ''], { once: true })).to.be.false();
expect(Hoek.contain('a', '')).to.be.true();
expect(Hoek.contain('a', '', { only: true })).to.be.false();
expect(Hoek.contain('a', '', { once: true })).to.be.false();
expect(Hoek.contain('a', ['', ''])).to.be.true();
expect(Hoek.contain('a', ['', ''], { only: true })).to.be.false();
expect(Hoek.contain('a', ['', ''], { once: true })).to.be.false();
expect(Hoek.contain('ab', ['a', 'b', 'c'])).to.be.false();
expect(Hoek.contain('ab', ['a', 'b', 'c'], { only: true })).to.be.false();
expect(Hoek.contain('ab', ['a', 'b', 'c'], { only: true, once: true })).to.be.false();
expect(Hoek.contain('ab', ['c'], { part: true })).to.be.false();
expect(Hoek.contain('ab', ['b'], { part: true })).to.be.true();
});
it('tests arrays', () => {
expect(Hoek.contain([1, 2, 3], 1)).to.be.true();
expect(Hoek.contain([{ a: 1 }], { a: 1 }, { deep: true })).to.be.true();
expect(Hoek.contain([1, 2, 3], [1, 2])).to.be.true();
expect(Hoek.contain([{ a: 1 }], [{ a: 1 }], { deep: true })).to.be.true();
expect(Hoek.contain([1, 1, 2], [1, 2], { only: true })).to.be.true();
expect(Hoek.contain([1, 2], [1, 2], { once: true })).to.be.true();
expect(Hoek.contain([1, 2, 3], [1, 4], { part: true })).to.be.true();
expect(Hoek.contain([null, 2, 3], [null, 4], { part: true })).to.be.true();
expect(Hoek.contain([null], null, { deep: true })).to.be.true();
expect(Hoek.contain([[1], [2]], [[1]], { deep: true })).to.be.true();
expect(Hoek.contain([[1], [2], 3], [[1]], { deep: true })).to.be.true();
expect(Hoek.contain([[1, 2]], [[1]], { deep: true, part: true })).to.be.true();
expect(Hoek.contain([[1, 2]], [[1], 2], { deep: true, part: true })).to.be.true();
expect(Hoek.contain([1, 2, 1], [1, 1, 2], { only: true })).to.be.true();
expect(Hoek.contain([1, 2, 1], [1, 1, 2], { only: true, once: true })).to.be.true();
expect(Hoek.contain([1, 2, 1], [1, 2, 2], { only: true })).to.be.false();
expect(Hoek.contain([1, 2, 1], [1, 2, 2], { only: true, part: true })).to.be.true();
expect(Hoek.contain([1, 1, 1], [1, 1, 1, 1])).to.be.false();
expect(Hoek.contain([1, 1, 1], [1, 1, 1, 1], { part: true })).to.be.true();
expect(Hoek.contain([1, 2, 3], 4)).to.be.false();
expect(Hoek.contain([{ a: 1 }], { a: 2 }, { deep: true })).to.be.false();
expect(Hoek.contain([{ a: 1 }, { a: 1 }], [{ a: 1 }, { a: 1 }], { deep: true, once: true, only: true })).to.be.true();
expect(Hoek.contain([{ a: 1 }, { a: 1 }], [{ a: 1 }, { a: 2 }], { deep: true, once: true, only: true })).to.be.false();
expect(Hoek.contain([{ a: 1 }], { a: 1 })).to.be.false();
expect(Hoek.contain([1, 2, 3], [4, 5])).to.be.false();
expect(Hoek.contain([[3], [2]], [[1]])).to.be.false();
expect(Hoek.contain([[1], [2]], [[1]])).to.be.false();
expect(Hoek.contain([[1, 2]], [[1]], { deep: true })).to.be.false();
expect(Hoek.contain([{ a: 1 }], [{ a: 2 }], { deep: true })).to.be.false();
expect(Hoek.contain([1, 3, 2], [1, 2], { only: true })).to.be.false();
expect(Hoek.contain([1, 2, 2], [1, 2], { once: true })).to.be.false();
expect(Hoek.contain([0, 2, 3], [1, 4], { part: true })).to.be.false();
expect(Hoek.contain([1, 2, 1], [1, 2, 2], { only: true, once: true })).to.be.false();
expect(Hoek.contain([1, 2, 1], [1, 2], { only: true, once: true })).to.be.false();
expect(Hoek.contain([], 1)).to.be.false();
expect(Hoek.contain([], 1, { only: true })).to.be.false();
expect(Hoek.contain(['a', 'b'], ['a', 'b', 'c'])).to.be.false();
expect(Hoek.contain(['a', 'b'], ['a', 'b', 'c'], { only: true })).to.be.false();
expect(Hoek.contain(['a', 'b'], ['a', 'b', 'c'], { only: true, once: true })).to.be.false();
expect(Hoek.contain(['a', 'b'], ['c'], { part: true })).to.be.false();
expect(Hoek.contain(['a', 'b'], ['b'], { part: true })).to.be.true();
expect(Hoek.contain([{ a: 1 }], [1], { deep: true })).to.be.false();
});
it('tests objects', () => {
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, 'a')).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, ['a', 'c'])).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, ['a', 'b', 'c'], { only: true })).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1 })).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, c: 3 })).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 }, { part: true })).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 }, { only: true })).to.be.true();
expect(Hoek.contain({ a: [1], b: [2], c: [3] }, { a: [1], c: [3] }, { deep: true })).to.be.true();
expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true })).to.be.false();
expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, part: true })).to.be.true();
expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, part: false })).to.be.false();
expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, only: true })).to.be.false();
expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, only: false })).to.be.true();
expect(Hoek.contain({ a: [1, 2, 3] }, { a: [2, 4, 6] }, { deep: true, part: true })).to.be.true();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, 'd')).to.be.false();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, ['a', 'd'])).to.be.false();
expect(Hoek.contain({ a: 1, b: 2, c: 3, d: 4 }, ['a', 'b', 'c'], { only: true })).to.be.false();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 2 })).to.be.false();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 2, b: 2 }, { part: true })).to.be.false(); // part does not ignore bad value
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 3 })).to.be.false();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 })).to.be.false();
expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }, { only: true })).to.be.false();
expect(Hoek.contain({ a: [1], b: [2], c: [3] }, { a: [1], c: [3] })).to.be.false();
expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } })).to.be.false();
expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true })).to.be.false();
expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, only: true })).to.be.false();
expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, only: false })).to.be.true();
expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, part: true })).to.be.true();
expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, part: false })).to.be.false();
expect(Hoek.contain({ a: [1, 2, 3] }, { a: [4, 5, 6] }, { deep: true, part: true })).to.be.false();
expect(Hoek.contain({}, 'a')).to.be.false();
expect(Hoek.contain({}, 'a', { only: true })).to.be.false();
expect(Hoek.contain({ a: 'foo', b: 'bar' }, ['a', 'b', 'c'])).to.be.false();
expect(Hoek.contain({ a: 'foo', b: 'bar' }, ['a', 'b', 'c'], { only: true })).to.be.false();
expect(Hoek.contain({ a: 'foo', b: 'bar' }, { a: 'foo', b: 'bar', c: 'x' })).to.be.false();
expect(Hoek.contain({ a: 'foo', b: 'bar' }, { a: 'foo', b: 'bar', c: 'x' }, { only: true })).to.be.false();
expect(Hoek.contain({ a: 1, b: 2 }, ['c'], { part: true })).to.be.false();
expect(Hoek.contain({ a: 1, b: 2 }, ['b'], { part: true })).to.be.true();
// Getter check
{
const Foo = function (bar) {
this.bar = bar;
};
const getBar = function () {
return this.bar;
};
const createFoo = (value) => {
const foo = new Foo(value);
Object.defineProperty(foo, 'baz', {
enumerable: true,
get: getBar
});
return foo;
};
expect(Hoek.contain({ a: createFoo('b') }, { a: createFoo('b') }, { deep: true })).to.be.true();
expect(Hoek.contain({ a: createFoo('b') }, { a: createFoo('b') }, { deep: true, part: true })).to.be.true();
expect(Hoek.contain({ a: createFoo('b') }, { a: { bar: 'b', baz: 'b' } }, { deep: true })).to.be.true();
expect(Hoek.contain({ a: createFoo('b') }, { a: { bar: 'b', baz: 'b' } }, { deep: true, only: true })).to.be.false();
expect(Hoek.contain({ a: createFoo('b') }, { a: { baz: 'b' } }, { deep: true, part: false })).to.be.false();
expect(Hoek.contain({ a: createFoo('b') }, { a: { baz: 'b' } }, { deep: true, part: true })).to.be.true();
expect(Hoek.contain({ a: createFoo('b') }, { a: createFoo('b') }, { deep: true })).to.be.true();
}
// Properties on prototype not visible
{
const Foo = function () {
this.a = 1;
};
Object.defineProperty(Foo.prototype, 'b', {
enumerable: true,
value: 2
});
const Bar = function () {
Foo.call(this);
this.c = 3;
};
Util.inherits(Bar, Foo);
expect((new Bar()).a).to.equal(1);
expect((new Bar()).b).to.equal(2);
expect((new Bar()).c).to.equal(3);
expect(Hoek.contain(new Bar(), { 'a': 1, 'c': 3 }, { only: true })).to.be.true();
expect(Hoek.contain(new Bar(), 'b')).to.be.false();
}
// Non-Enumerable properties
{
const foo = { a: 1, b: 2 };
Object.defineProperty(foo, 'c', {
enumerable: false,
value: 3
});
expect(Hoek.contain(foo, 'c')).to.be.true();
expect(Hoek.contain(foo, { 'c': 3 })).to.be.true();
expect(Hoek.contain(foo, { 'a': 1, 'b': 2, 'c': 3 }, { only: true })).to.be.true();
}
});
it('supports symbols', () => {
const sym = Symbol();
expect(Hoek.contain([sym], sym)).to.be.true();
expect(Hoek.contain({ [sym]: 1 }, sym)).to.be.true();
expect(Hoek.contain({ [sym]: 1, a: 2 }, { [sym]: 1 })).to.be.true();
expect(Hoek.contain([sym], Symbol())).to.be.false();
expect(Hoek.contain({ [sym]: 1 }, Symbol())).to.be.false();
});
it('compares error keys', () => {
const error = new Error('test');
expect(Hoek.contain(error, { x: 1 })).to.be.false();
expect(Hoek.contain(error, { x: 1 }, { part: true })).to.be.false();
error.x = 1;
expect(Hoek.contain(error, { x: 1 })).to.be.true();
expect(Hoek.contain(error, { x: 1 }, { part: true })).to.be.true();
expect(Hoek.contain(error, { x: 1, y: 2 })).to.be.false();
expect(Hoek.contain(error, { x: 1, y: 2 }, { part: true })).to.be.true();
});
});
describe('flatten()', () => {
it('returns a flat array', () => {
const result = Hoek.flatten([1, 2, [3, 4, [5, 6], [7], 8], [9], [10, [11, 12]], 13]);
expect(result.length).to.equal(13);
expect(result).to.equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
});
});
describe('reach()', () => {
const sym = Symbol();
const obj = {
a: {
b: {
c: {
d: 1,
e: 2
},
f: 'hello'
},
g: {
h: 3
},
'-2': true,
[sym]: {
v: true
}
},
i: function () { },
j: null,
k: [4, 8, 9, 1]
};
obj.i.x = 5;
it('returns object itself', () => {
expect(Hoek.reach(obj, null)).to.equal(obj);
expect(Hoek.reach(obj, false)).to.equal(obj);
expect(Hoek.reach(obj)).to.equal(obj);
expect(Hoek.reach(obj, [])).to.equal(obj);
});
it('returns values of array', () => {
expect(Hoek.reach(obj, 'k.0')).to.equal(4);
expect(Hoek.reach(obj, 'k.1')).to.equal(8);
});
it('returns last value of array using negative index', () => {
expect(Hoek.reach(obj, 'k.-1')).to.equal(1);
expect(Hoek.reach(obj, 'k.-2')).to.equal(9);
});
it('returns object property with negative index for non-array', () => {
expect(Hoek.reach(obj, 'a.-2')).to.be.equal(true);
});
it('returns a valid member', () => {
expect(Hoek.reach(obj, 'a.b.c.d')).to.equal(1);
});
it('returns a valid member with separator override', () => {
expect(Hoek.reach(obj, 'a/b/c/d', '/')).to.equal(1);
});
it('returns undefined on null object', () => {
expect(Hoek.reach(null, 'a.b.c.d')).to.equal(undefined);
});
it('returns undefined on missing object member', () => {
expect(Hoek.reach(obj, 'a.b.c.d.x')).to.equal(undefined);
});
it('returns undefined on missing function member', () => {
expect(Hoek.reach(obj, 'i.y', { functions: true })).to.equal(undefined);
});
it('throws on missing member in strict mode', () => {
expect(() => {
Hoek.reach(obj, 'a.b.c.o.x', { strict: true });
}).to.throw('Missing segment o in reach path a.b.c.o.x');
});
it('returns undefined on invalid member', () => {
expect(Hoek.reach(obj, 'a.b.c.d-.x')).to.equal(undefined);
expect(Hoek.reach(obj, 'k.x')).to.equal(undefined);
expect(Hoek.reach(obj, 'k.1000')).to.equal(undefined);
expect(Hoek.reach(obj, 'k/0.5', '/')).to.equal(undefined);
});
it('returns function member', () => {
expect(typeof Hoek.reach(obj, 'i')).to.equal('function');
});
it('returns function property', () => {
expect(Hoek.reach(obj, 'i.x')).to.equal(5);
});
it('returns null', () => {
expect(Hoek.reach(obj, 'j')).to.equal(null);
});
it('throws on function property when functions not allowed', () => {
expect(() => {
Hoek.reach(obj, 'i.x', { functions: false });
}).to.throw('Invalid segment x in reach path i.x');
});
it('will return a default value if property is not found', () => {
expect(Hoek.reach(obj, 'a.b.q', { default: 'defaultValue' })).to.equal('defaultValue');
});
it('will return a default value if path is not found', () => {
expect(Hoek.reach(obj, 'q', { default: 'defaultValue' })).to.equal('defaultValue');
});
it('allows a falsey value to be used as the default value', () => {
expect(Hoek.reach(obj, 'q', { default: '' })).to.equal('');
});
it('allows array-based lookup', () => {
expect(Hoek.reach(obj, ['a', 'b', 'c', 'd'])).to.equal(1);
expect(Hoek.reach(obj, ['k', '1'])).to.equal(8);
expect(Hoek.reach(obj, ['k', 1])).to.equal(8);
expect(Hoek.reach(obj, ['k', '-2'])).to.equal(9);
expect(Hoek.reach(obj, ['k', -2])).to.equal(9);
});
it('allows array-based lookup with symbols', () => {
expect(Hoek.reach(obj, ['a', sym, 'v'])).to.equal(true);
expect(Hoek.reach(obj, ['a', Symbol(), 'v'])).to.equal(undefined);
});
it('returns character in string', () => {
expect(Hoek.reach(['abc'], [0])).to.equal('abc');
expect(Hoek.reach(['abc'], ['0'])).to.equal('abc');
});
it('reaches sets and maps', () => {
const value = {
a: {
b: new Set([
{ x: 1 },
{ x: 2 },
{
y: new Map([
['v', 4],
['w', 5]
])
}
])
}
};
expect(Hoek.reach(value, 'a.b.2.y.w')).to.not.exist();
expect(Hoek.reach(value, 'a.b.2.y.w', { iterables: true })).to.equal(5);
});
});
describe('reachTemplate()', () => {
it('applies object to template', () => {
const obj = {
a: {
b: {
c: {
d: 1
}
}
},
j: null,
k: [4, 8, 9, 1]
};
const template = '{k.0}:{k.-2}:{a.b.c.d}:{x.y}:{j}';
expect(Hoek.reachTemplate(obj, template)).to.equal('4:9:1::');
});
it('applies object to template (options)', () => {
const obj = {
a: {
b: {
c: {
d: 1
}
}
},
j: null,
k: [4, 8, 9, 1]
};
const template = '{k/0}:{k/-2}:{a/b/c/d}:{x/y}:{j}';
expect(Hoek.reachTemplate(obj, template, '/')).to.equal('4:9:1::');
});
it('isn\'t prone to ReDoS given an adversarial template', () => {
const sizes = [0, 1, 2, 3, 4]; // Should be evenly-spaced
const times = [];
const diffs = [];
for (const size of sizes) {
const start = Date.now();
Hoek.reachTemplate({}, '{'.repeat(size * 10000));
times.push(Date.now() - start);
}
for (let i = 1; i < times.length; ++i) {
diffs.push(times[i] - times[i - 1]);
}
// Under ReDoS, as the size of the input increases the timing accelerates upwards,
// i.e. each timing diff would be greater than the last.
const diffsMonotonic = diffs[0] < diffs[1] && diffs[1] < diffs[2] && diffs[2] < diffs[3];
expect(diffsMonotonic, 'Timing diffs monotonic').to.be.false();
});
});
describe('assert()', () => {
it('throws an Error when using assert in a test', () => {
expect(() => {
Hoek.assert(false, 'my error message');
}).to.throw('my error message');
});
it('throws an Error when using assert in a test with no message', () => {
expect(() => {
Hoek.assert(false);
}).to.throw('Unknown error');
});
it('throws an Error when using assert in a test with multipart message', () => {
expect(() => {
Hoek.assert(false, 'This', 'is', 'my message');
}).to.throw('This is my message');
});
it('throws an Error when using assert in a test with multipart message (empty)', () => {
expect(() => {
Hoek.assert(false, 'This', 'is', '', 'my message');
}).to.throw('This is my message');
});
it('throws an Error when using assert in a test with object message', () => {
expect(() => {
Hoek.assert(false, 'This', 'is', { spinal: 'tap' });
}).to.throw('This is {"spinal":"tap"}');
});
it('throws an Error when using assert in a test with multipart string and error messages', () => {
expect(() => {
Hoek.assert(false, new Error('This'), 'is', 'spinal', new Error('tap'));
}).to.throw('This is spinal tap');
});
it('throws an Error when using assert in a test with error object message', () => {
const err = new Error('This is spinal tap');
const got = expect(() => Hoek.assert(false, err)).to.throw('This is spinal tap');
expect(got).to.shallow.equal(err);
});
it('throws the same Error that is passed to it if there is only one error passed', () => {
const error = new Error('ruh roh');
const error2 = new Error('ruh roh');
const fn = function () {
Hoek.assert(false, error);
};
try {
fn();
}
catch (err) {
expect(err).to.equal(error); // should be the same reference
expect(err).to.not.shallow.equal(error2); // error with the same message should not match
}
});
});
describe('Bench', () => {
it('returns time elapsed', async () => {
const timer = new Hoek.Bench();
await Hoek.wait(12);
expect(timer.elapsed()).to.be.above(9);
});
});
describe('escapeRegex()', () => {
it('escapes all special regular expression characters', () => {
const a = Hoek.escapeRegex('4^f$s.4*5+-_?%=#!:@|~\\/`"(>)[<]d{}s,');
expect(a).to.equal('4\\^f\\$s\\.4\\*5\\+\\-_\\?%\\=#\\!\\:@\\|~\\\\\\/`"\\(>\\)\\[<\\]d\\{\\}s\\,');
});
});
describe('escapeHeaderAttribute()', () => {
it('should not alter ascii values', () => {
const a = Hoek.escapeHeaderAttribute('My Value');
expect(a).to.equal('My Value');
});
it('escapes all special HTTP header attribute characters', () => {
const a = Hoek.escapeHeaderAttribute('I said go!!!#"' + String.fromCharCode(92));
expect(a).to.equal('I said go!!!#\\"\\\\');
});
it('throws on large unicode characters', () => {
expect(() => {
Hoek.escapeHeaderAttribute('this is a test' + String.fromCharCode(500) + String.fromCharCode(300));
}).to.throw(Error);
});
it('throws on CRLF to prevent response splitting', () => {
expect(() => {
Hoek.escapeHeaderAttribute('this is a test\r\n');
}).to.throw(Error);
});
});
describe('escapeHtml()', () => {
it('escapes all special HTML characters', () => {
const a = Hoek.escapeHtml('&<>"\'`');
expect(a).to.equal('&<>"'`');
});
it('returns empty string on falsy input', () => {
const a = Hoek.escapeHtml('');
expect(a).to.equal('');
});
it('returns unchanged string on no reserved input', () => {
const a = Hoek.escapeHtml('abc');
expect(a).to.equal('abc');
});
});
describe('once()', () => {
it('allows function to only execute once', () => {
let gen = 0;
let add = function (x) {
gen += x;
};
add(5);
expect(gen).to.equal(5);
add = Hoek.once(add);
add(5);
expect(gen).to.equal(10);
add(5);
expect(gen).to.equal(10);
});
it('double once wraps one time', () => {
let method = function () { };
method = Hoek.once(method);
method.x = 1;
method = Hoek.once(method);
expect(method.x).to.equal(1);
});
});
describe('ignore()', () => {
it('exists', () => {
expect(Hoek.ignore).to.exist();
expect(typeof Hoek.ignore).to.equal('function');
});
});
describe('stringify()', () => {
it('converts object to string', () => {
const obj = { a: 1 };
expect(Hoek.stringify(obj)).to.equal('{"a":1}');
});
it('returns error in result string', () => {
const obj = { a: 1 };
obj.b = obj;
expect(Hoek.stringify(obj)).to.contain('Cannot display object');
});
});
describe('isPromise()', () => {
it('determines if an object is a promise', async () => {
expect(Hoek.isPromise({})).to.be.false();
expect(Hoek.isPromise(null)).to.be.false();
expect(Hoek.isPromise(false)).to.be.false();
expect(Hoek.isPromise(0)).to.be.false();
expect(Hoek.isPromise('')).to.be.false();
expect(Hoek.isPromise({ then: 1 })).to.be.false();
expect(Hoek.isPromise([])).to.be.false();
const items = [
Promise.resolve(),
Promise.reject()
];
expect(Hoek.isPromise(items[0])).to.be.true();
expect(Hoek.isPromise(items[1])).to.be.true();
expect(Hoek.isPromise(new Promise(Hoek.ignore))).to.be.true();
expect(Hoek.isPromise({ then: Hoek.ignore })).to.be.true();
try {
await Promise.all(items);
}
catch (err) { }
});
});
describe('wait()', () => {
it('delays for timeout ms', async () => {
const timeout = {};
setTimeout(() => (timeout.before = true), 10);
const wait = Hoek.wait(10);
setTimeout(() => (timeout.after = true), 10);
await wait;
expect(timeout.before).to.be.true();
expect(timeout.after).to.be.undefined();
});
it('delays for timeout ms as bigint', async () => {
const timeout = {};
setTimeout(() => (timeout.before = true), 10);
const wait = Hoek.wait(10n);
setTimeout(() => (timeout.after = true), 10);
await wait;
expect(timeout.before).to.be.true();
expect(timeout.after).to.be.undefined();
});
it('handles timeouts >= 2^31', async () => {
const flow = [];
let no = 0;
const fakeTimeout = function (cb, time) {
const timer = ++no;
flow.push(`CALL(${timer}): ${time}`);
setImmediate(() => {
flow.push(`PRE(${timer})`);
cb();
flow.push(`POST(${timer})`);
});
};
await Hoek.wait(2 ** 31, null, { setTimeout: fakeTimeout });
flow.push('DONE1');
await Hoek.wait(2 ** 32 + 2 ** 30, null, { setTimeout: fakeTimeout });
flow.push('DONE2');
expect(flow).to.equal([
'CALL(1): 2147483647',
'PRE(1)',
'CALL(2): 1',
'POST(1)',
'PRE(2)',
'POST(2)',
'DONE1',
'CALL(3): 2147483647',
'PRE(3)',
'CALL(4): 2147483647',
'POST(3)',
'PRE(4)',
'CALL(5): 1073741826',
'POST(4)',
'PRE(5)',
'POST(5)',
'DONE2'
]);
});
it('returns never resolving promise when timeout >= Number.MAX_SAFE_INTEGER', async () => {
let calls = 0;
const fakeTimeout = function (cb) {
++calls;
process.nextTick(cb);
};
await Hoek.wait(2 ** 31 - 1, null, { setTimeout: fakeTimeout });
expect(calls).to.equal(1);
const waited = Symbol('waited');
const result = await Promise.race([
Hoek.wait(1, waited),
Hoek.wait(Number.MAX_SAFE_INTEGER, null, { setTimeout: fakeTimeout }),
Hoek.wait(Infinity, null, { setTimeout: fakeTimeout })
]);
expect(result).to.be.equal(waited);
expect(calls).to.equal(1);
});
it('handles a return value', async () => {
const uniqueValue = {};
const timeout = {};
setTimeout(() => (timeout.before = true), 10);
const wait = Hoek.wait(10, uniqueValue);
setTimeout(() => (timeout.after = true), 10);
expect(await wait).to.shallow.equal(uniqueValue);
expect(timeout.before).to.be.true();
expect(timeout.after).to.be.undefined();
});
it('undefined timeout resolves immediately', async () => {
const waited = Symbol('waited');
const result = await Promise.race([
Hoek.wait(undefined, waited),
Hoek.wait(0)
]);
expect(result).to.equal(waited);
});
it('NaN timeout resolves immediately', async () => {
const waited = Symbol('waited');
const result = await Promise.race([
Hoek.wait(Number.NaN, waited),
Hoek.wait(0)
]);
expect(result).to.equal(waited);
});
it('rejects on weird timeout values', async () => {
await expect(() => Hoek.wait({})).to.throw();
await expect(() => Hoek.wait(Symbol('hi'))).to.throw();
});
});
describe('block()', () => {
it('returns a promise', () => {
expect(Hoek.block()).to.be.instanceOf(Promise);
});
it('does not immediately reject or resolve', async () => {
const promise = Hoek.block();
const waited = Symbol('waited');
const result = await Promise.race([
Hoek.wait(1, waited),
promise
]);
expect(result).to.be.equal(waited);
});
});
hoek-10.0.1/test/index.ts 0000775 0000000 0000000 00000021554 14267037150 0015174 0 ustar 00root root 0000000 0000000 import * as Hoek from '..';
import * as Lab from '@hapi/lab';
const { expect } = Lab.types;
interface Foo {
a?: number;
b?: string;
};
interface Bar {
b?: string;
c?: boolean;
};
// deepEqual()
Hoek.deepEqual('some', 'some');
Hoek.deepEqual('some', 3);
Hoek.deepEqual({}, {});
Hoek.deepEqual({}, {}, { prototype: false, symbols: true, part: false, deepFunction: true });
Hoek.deepEqual({}, {}, { skip: ['a', 'b', Symbol('test')] });
expect.type(Hoek.deepEqual(1, 2));
expect.error(Hoek.deepEqual());
expect.error(Hoek.deepEqual(1, 2, {}, 'x'));
expect.error(Hoek.deepEqual({}, {}, { unknown: true }));
expect.error(Hoek.deepEqual({}, {}, { skip: [1] }))
// clone()
Hoek.clone('string');
Hoek.clone(123);
Hoek.clone({ a: 1 });
Hoek.clone({ a: 1 }, { prototype: true, symbols: true });
Hoek.clone({}, { shallow: [] });
Hoek.clone(1, { shallow: [] });
Hoek.clone(null, { shallow: [] });
Hoek.clone({ a: 1, b: { c: [2] } }, { shallow: true });
Hoek.clone({ a: 1, b: { c: [2] } }, { shallow: ['b'] });
Hoek.clone({ a: 1, b: { c: [2] } }, { shallow: [['b']] });
Hoek.clone({ a: 1, b: { c: [2] } }, { shallow: ['b'], prototype: true, symbols: true });
expect.type(Hoek.clone('string'));
expect.type