pax_global_header00006660000000000000000000000064142670371500014517gustar00rootroot0000000000000052 comment=941a932b8be873bd119c6680c368116c52845e4a hoek-10.0.1/000077500000000000000000000000001426703715000125245ustar00rootroot00000000000000hoek-10.0.1/.github/000077500000000000000000000000001426703715000140645ustar00rootroot00000000000000hoek-10.0.1/.github/workflows/000077500000000000000000000000001426703715000161215ustar00rootroot00000000000000hoek-10.0.1/.github/workflows/ci-module.yml000066400000000000000000000003141426703715000205200ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000001541426703715000145140ustar00rootroot00000000000000**/node_modules **/package-lock.json coverage.* **/.DS_Store **/._* **/*.pem **/.vs **/.vscode **/.idea hoek-10.0.1/API.md000077500000000000000000000315441426703715000134710ustar00rootroot00000000000000 ### 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.js000066400000000000000000000017451426703715000146340ustar00rootroot00000000000000'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.js000077500000000000000000002323121426703715000151560ustar00rootroot00000000000000'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.ts000077500000000000000000000215541426703715000151740ustar00rootroot00000000000000import * 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(Hoek.clone({})); expect.type(Hoek.clone({} as Bar)); expect.type(Hoek.clone({ a: 1 } as Foo, { shallow: ['b'] })); expect.error(Hoek.clone({}, { unknown: true })); expect.error(Hoek.clone({}, { shallow: [1] })); expect.error(Hoek.clone({}, { shallow: 1 })); // merge() Hoek.merge({ a: 1 } as Foo, { b: 'x' } as Bar); Hoek.merge({ a: 1 }, { a: null }); Hoek.merge({ a: 1 }, { a: null }, { mergeArrays: true, nullOverride: true, symbols: false }); expect.type(Hoek.merge({}, {})); expect.type(Hoek.merge({ a: 1 } as Foo, { b: 'x' } as Bar)); expect.error(Hoek.merge(1, 2)); expect.error(Hoek.merge({ a: 1 }, { a: null }, true)); expect.error(Hoek.merge({ a: 1 }, { a: null }, { unknown: true })); // applyToDefaults() Hoek.applyToDefaults({}, {}); Hoek.applyToDefaults({}, true); Hoek.applyToDefaults({}, false); Hoek.applyToDefaults({}, null); Hoek.applyToDefaults({ a: 1 } as Foo, { b: 'x' }); Hoek.applyToDefaults({ a: 1 } as object, { b: 'x' }); Hoek.applyToDefaults({ a: 1 } as Foo, { b: 'x' }, { shallow: ['b'] }); Hoek.applyToDefaults({ a: 1 } as object, { b: 'x' }, { shallow: ['c'] }); expect.type(Hoek.applyToDefaults({}, {})); expect.type(Hoek.applyToDefaults({ a: 1 } as Foo, { b: 'x' })); expect.type(Hoek.applyToDefaults({}, {}, { shallow: [] })); expect.error(Hoek.applyToDefaults({} as Foo, 0)); expect.error(Hoek.applyToDefaults({} as Foo, 0, { shallow: [] })); // intersect() Hoek.intersect([1], [1, 2]); Hoek.intersect(['a'], ['b', 'b']); Hoek.intersect([1], [1, 2], { first: true }); Hoek.intersect([1], [1, 2], { first: false }); Hoek.intersect([1], null); Hoek.intersect(null, [1, 2]); Hoek.intersect([1], null, { first: true }); Hoek.intersect(null, [1, 2], { first: true }); Hoek.intersect(new Set([1]), new Set([1, 2])); Hoek.intersect([1], new Set([1, 2])); Hoek.intersect(new Set([1]), [1, 2]); expect.type(Hoek.intersect([1], [1, 2])); expect.type(Hoek.intersect([1], [1, 2], { first: false })); expect.type(Hoek.intersect([1], [1, 2], { first: true })); expect.error(Hoek.intersect()); expect.error(Hoek.intersect(1, 2)); expect.error(Hoek.intersect([1], 2)); expect.error(Hoek.intersect([1], [2], 'x')); expect.error(Hoek.intersect(new Map(), [2])); // contain() Hoek.contain('abc', 'a'); Hoek.contain('abc', ['a', 'x']); Hoek.contain('abc', ['a', 'd'], { once: true, part: true, deep: true, symbols: true, only: true }); Hoek.contain({ a: 1 }, 'a'); Hoek.contain({ a: 1 }, { a: 1 }); Hoek.contain({ a: 1, b: 2 }, ['a', 'x'], { part: true, deep: true, symbols: true, only: true }); Hoek.contain([1], 1); Hoek.contain([1], [1]); Hoek.contain([1], [1], { once: true, part: true, deep: true, symbols: true, only: true }); expect.type(Hoek.contain('abc', 'a')); expect.type(Hoek.contain({ a: 1 }, 'a')); expect.type(Hoek.contain([1], 1)); expect.error(Hoek.contain('abc', 'a', { unknown: true })); expect.error(Hoek.contain(['a'], 'a', { unknown: true })); expect.error(Hoek.contain({ a: 1 }, 'a', { unknown: true })); expect.error(Hoek.contain('abc', 1)); expect.error(Hoek.contain('abc', [1])); expect.error(Hoek.contain('abc', [{}])); expect.error(Hoek.contain('abc', {})); expect.error(Hoek.contain({}, 1)); expect.error(Hoek.contain({ a: 1, b: 2 }, ['a', 'x'], { once: true })); // flatten() Hoek.flatten([1, [2, 3]]); Hoek.flatten([1, [2, 3]], [4, 5]); expect.type(Hoek.flatten([1, [2, 3]])); expect.type(Hoek.flatten([1, [2, 3]], [])); expect.error(Hoek.flatten()); expect.error(Hoek.flatten(1)); expect.error(Hoek.flatten([], 1)); // reach() Hoek.reach(null, false); Hoek.reach(null, '0'); Hoek.reach(null, ['0']); Hoek.reach(['abc'], false); Hoek.reach(['abc'], null); Hoek.reach(['abc'], undefined); Hoek.reach(['abc'], [0]); Hoek.reach(['abc'], ['0']); Hoek.reach(['abc'], '0'); Hoek.reach({ a: { b: { c: 3 } } }, 'a.b.c'); Hoek.reach({ a: { b: { c: 3 } } }, ['a', 'b', 'c']); Hoek.reach({ a: { b: { c: 3 } } }, 'a/b/c', { separator: '/', default: 4, strict: true, functions: true, iterables: true }); expect.type(Hoek.reach(['abc'], [0])); expect.type(Hoek.reach({ a: { b: { c: 3 } } }, 'a.b.c')); expect.error(Hoek.reach()); expect.error(Hoek.reach([])); expect.error(Hoek.reach(1, '0')); expect.error(Hoek.reach('abc', '0')); expect.error(Hoek.reach([0], 0)); expect.error(Hoek.reach(['abc'], '0', { unknown: false })); expect.error(Hoek.reach(['abc'], '0', { separator: false })); // reachTemplate() Hoek.reachTemplate(null, 'a{b}c'); Hoek.reachTemplate([1, 2], 'a{1}c'); Hoek.reachTemplate({ b: 2 }, 'a{b}c'); Hoek.reachTemplate({ a: { b: { c: 3 } } }, '{a/b/c}', { separator: '/', default: 4, strict: true, functions: true }); expect.type(Hoek.reachTemplate([1, 2], 'a{1}c')); expect.error(Hoek.reachTemplate()); expect.error(Hoek.reachTemplate([])); expect.error(Hoek.reachTemplate(1, '0')); expect.error(Hoek.reachTemplate('abc', '0')); expect.error(Hoek.reachTemplate([0], 0)); expect.error(Hoek.reachTemplate(['abc'], '{0}', { unknown: false })); expect.error(Hoek.reachTemplate(['abc'], '{0}', { separator: false })); // assert() Hoek.assert(true); Hoek.assert(true, 'some', 'message', 10); Hoek.assert(1, 'error'); Hoek.assert(true, new Error('message')); expect.type(Hoek.assert(true)); // Bench const bench = new Hoek.Bench(); expect.type(bench.ts); expect.type(bench.reset()); expect.type(bench.elapsed()); expect.type(Hoek.Bench.now()); expect.error(new Hoek.Bench({})); expect.error(bench.reset(true)); expect.error(bench.elapsed(true)); expect.error(Hoek.Bench.now(true)); // escapeRegex() Hoek.escapeRegex('something?'); expect.type(Hoek.escapeRegex('^?')); expect.error(Hoek.escapeRegex()); expect.error(Hoek.escapeRegex(true)); expect.error(Hoek.escapeRegex({})); // escapeHeaderAttribute() Hoek.escapeHeaderAttribute('something?'); expect.type(Hoek.escapeHeaderAttribute('^?')); expect.error(Hoek.escapeHeaderAttribute()); expect.error(Hoek.escapeHeaderAttribute(true)); expect.error(Hoek.escapeHeaderAttribute({})); // escapeHtml() Hoek.escapeHtml('something?'); expect.type(Hoek.escapeHtml('^?')); expect.error(Hoek.escapeHtml()); expect.error(Hoek.escapeHtml(true)); expect.error(Hoek.escapeHtml({})); // escapeJson() Hoek.escapeJson('something?'); expect.type(Hoek.escapeJson('^?')); expect.error(Hoek.escapeJson()); expect.error(Hoek.escapeJson(true)); expect.error(Hoek.escapeJson({})); // once() Hoek.once(() => 4); Hoek.once(() => undefined); expect.type<() => void>(Hoek.once(() => 'x')); expect.error(Hoek.once()); expect.error(Hoek.once('x')); expect.error(Hoek.once({})); // ignore() Hoek.ignore(); Hoek.ignore(1, 2, 'x'); expect.type<() => void>(Hoek.ignore); expect.type(Hoek.ignore()); // stringify() Hoek.stringify(123); Hoek.stringify({}, null, 4); expect.type(Hoek.stringify(123)); expect.error(Hoek.stringify()); // wait() // $lab:types:off$ await Hoek.wait(); await Hoek.wait(123); expect.type>(Hoek.wait()); expect.type(await Hoek.wait(100)); expect.type>(Hoek.wait(100, 'ok')); expect.error(Hoek.wait({})); // $lab:types:on$ // block() // $lab:types:off$ Hoek.wait(); expect.type>(Hoek.block()); expect.type(await Hoek.block()); expect.error(Hoek.block(123)); // $lab:types:on$ // isPromise() Hoek.isPromise(1); Hoek.isPromise({}); Hoek.isPromise(null); expect.type(Hoek.isPromise(1)); expect.error(Hoek.isPromise()); expect.error(Hoek.isPromise(1, 2)); // ts interface X { a: number; }; interface Y { b: number; }; function xor(input: Hoek.ts.XOR): number { return input.a || input.b || 10; } xor({ a: 1 }); xor({ b: 2 }); expect.error(xor({ a: 1, b: 2 })); hoek-10.0.1/test/modules/000077500000000000000000000000001426703715000151535ustar00rootroot00000000000000hoek-10.0.1/test/modules/ignore.txt000066400000000000000000000000001426703715000171650ustar00rootroot00000000000000hoek-10.0.1/test/modules/test1.js000077500000000000000000000000361426703715000165530ustar00rootroot00000000000000'use strict'; exports.x = 1; hoek-10.0.1/test/modules/test2.js000077500000000000000000000000361426703715000165540ustar00rootroot00000000000000'use strict'; exports.y = 2; hoek-10.0.1/test/modules/test3.js000077500000000000000000000000361426703715000165550ustar00rootroot00000000000000'use strict'; exports.z = 3;