Initial commit
[yaffs-website] / node_modules / hoek / test / index.js
1 // Load modules
2
3 var Fs = require('fs');
4 var Path = require('path');
5 var Code = require('code');
6 var Hoek = require('../lib');
7 var Lab = require('lab');
8
9
10 // Declare internals
11
12 var internals = {};
13
14
15 // Test shortcuts
16
17 var lab = exports.lab = Lab.script();
18 var describe = lab.experiment;
19 var it = lab.test;
20 var expect = Code.expect;
21
22
23 var nestedObj = {
24     v: [7, 8, 9],
25     w: /^something$/igm,
26     x: {
27         a: [1, 2, 3],
28         b: 123456,
29         c: new Date(),
30         d: /hi/igm,
31         e: /hello/
32     },
33     y: 'y',
34     z: new Date(1378775452757)
35 };
36
37 var dupsArray = [nestedObj, { z: 'z' }, nestedObj];
38 var reducedDupsArray = [nestedObj, { z: 'z' }];
39
40 describe('clone()', function () {
41
42     it('clones a nested object', function (done) {
43
44         var a = nestedObj;
45         var b = Hoek.clone(a);
46
47         expect(a).to.deep.equal(b);
48         expect(a.z.getTime()).to.equal(b.z.getTime());
49         done();
50     });
51
52     it('clones a null object', function (done) {
53
54         var b = Hoek.clone(null);
55
56         expect(b).to.equal(null);
57         done();
58     });
59
60     it('should not convert undefined properties to null', function (done) {
61
62         var obj = { something: undefined };
63         var b = Hoek.clone(obj);
64
65         expect(typeof b.something).to.equal('undefined');
66         done();
67     });
68
69     it('should not throw on circular reference', function (done) {
70
71         var a = {};
72         a.x = a;
73
74         var test = function () {
75
76             var b = Hoek.clone(a);
77         };
78
79         expect(test).to.not.throw();
80         done();
81     });
82
83     it('clones circular reference', function (done) {
84
85         var x = {
86             'z': new Date()
87         };
88         x.y = x;
89
90         var b = Hoek.clone(x);
91         expect(Object.keys(b.y)).to.deep.equal(Object.keys(x));
92         expect(b.z).to.not.equal(x.z);
93         expect(b.y).to.not.equal(x.y);
94         expect(b.y.z).to.not.equal(x.y.z);
95         expect(b.y).to.equal(b);
96         expect(b.y.y.y.y).to.equal(b);
97         done();
98     });
99
100     it('clones an object with a null prototype', function (done) {
101
102         var obj = Object.create(null);
103         var b = Hoek.clone(obj);
104
105         expect(b).to.deep.equal(obj);
106         done();
107     });
108
109     it('clones deeply nested object', function (done) {
110
111         var a = {
112             x: {
113                 y: {
114                     a: [1, 2, 3],
115                     b: 123456,
116                     c: new Date(),
117                     d: /hi/igm,
118                     e: /hello/
119                 }
120             }
121         };
122
123         var b = Hoek.clone(a);
124
125         expect(a).to.deep.equal(b);
126         expect(a.x.y.c.getTime()).to.equal(b.x.y.c.getTime());
127         done();
128     });
129
130     it('clones arrays', function (done) {
131
132         var a = [1, 2, 3];
133
134         var b = Hoek.clone(a);
135
136         expect(a).to.deep.equal(b);
137         done();
138     });
139
140     it('performs actual copy for shallow keys (no pass by reference)', function (done) {
141
142         var x = Hoek.clone(nestedObj);
143         var y = Hoek.clone(nestedObj);
144
145         // Date
146         expect(x.z).to.not.equal(nestedObj.z);
147         expect(x.z).to.not.equal(y.z);
148
149         // Regex
150         expect(x.w).to.not.equal(nestedObj.w);
151         expect(x.w).to.not.equal(y.w);
152
153         // Array
154         expect(x.v).to.not.equal(nestedObj.v);
155         expect(x.v).to.not.equal(y.v);
156
157         // Immutable(s)
158         x.y = 5;
159         expect(x.y).to.not.equal(nestedObj.y);
160         expect(x.y).to.not.equal(y.y);
161
162         done();
163     });
164
165     it('performs actual copy for deep keys (no pass by reference)', function (done) {
166
167         var x = Hoek.clone(nestedObj);
168         var y = Hoek.clone(nestedObj);
169
170         expect(x.x.c).to.not.equal(nestedObj.x.c);
171         expect(x.x.c).to.not.equal(y.x.c);
172
173         expect(x.x.c.getTime()).to.equal(nestedObj.x.c.getTime());
174         expect(x.x.c.getTime()).to.equal(y.x.c.getTime());
175         done();
176     });
177
178     it('copies functions with properties', function (done) {
179
180         var a = {
181             x: function () {
182
183                 return 1;
184             },
185             y: {}
186         };
187         a.x.z = 'string in function';
188         a.x.v = function () {
189
190             return 2;
191         };
192         a.y.u = a.x;
193
194         var b = Hoek.clone(a);
195         expect(b.x()).to.equal(1);
196         expect(b.x.v()).to.equal(2);
197         expect(b.y.u).to.equal(b.x);
198         expect(b.x.z).to.equal('string in function');
199         done();
200     });
201
202     it('should copy a buffer', function (done) {
203
204         var tls = {
205             key: new Buffer([1, 2, 3, 4, 5]),
206             cert: new Buffer([1, 2, 3, 4, 5, 6, 10])
207         };
208
209         var copiedTls = Hoek.clone(tls);
210         expect(Buffer.isBuffer(copiedTls.key)).to.equal(true);
211         expect(JSON.stringify(copiedTls.key)).to.equal(JSON.stringify(tls.key));
212         expect(Buffer.isBuffer(copiedTls.cert)).to.equal(true);
213         expect(JSON.stringify(copiedTls.cert)).to.equal(JSON.stringify(tls.cert));
214         done();
215     });
216
217     it('clones an object with a prototype', function (done) {
218
219         var Obj = function () {
220
221             this.a = 5;
222         };
223
224         Obj.prototype.b = function () {
225
226             return 'c';
227         };
228
229         var a = new Obj();
230         var b = Hoek.clone(a);
231
232         expect(b.a).to.equal(5);
233         expect(b.b()).to.equal('c');
234         expect(a).to.deep.equal(b);
235         done();
236     });
237
238     it('reuses cloned Date object', function (done) {
239
240         var obj = {
241             a: new Date()
242         };
243
244         obj.b = obj.a;
245
246         var copy = Hoek.clone(obj);
247         expect(copy.a).to.equal(copy.b);
248         done();
249     });
250
251     it('shallow copies an object with a prototype and isImmutable flag', function (done) {
252
253         var Obj = function () {
254
255             this.value = 5;
256         };
257
258         Obj.prototype.b = function () {
259
260             return 'c';
261         };
262
263         Obj.prototype.isImmutable = true;
264
265         var obj = {
266             a: new Obj()
267         };
268
269         var copy = Hoek.clone(obj);
270
271         expect(obj.a.value).to.equal(5);
272         expect(copy.a.value).to.equal(5);
273         expect(copy.a.b()).to.equal('c');
274         expect(obj.a).to.equal(copy.a);
275         done();
276     });
277
278     it('clones an object with property getter without executing it', function (done) {
279
280         var obj = {};
281         var value = 1;
282         var execCount = 0;
283
284         Object.defineProperty(obj, 'test', {
285             enumerable: true,
286             configurable: true,
287             get: function () {
288
289                 ++execCount;
290                 return value;
291             }
292         });
293
294         var copy = Hoek.clone(obj);
295         expect(execCount).to.equal(0);
296         expect(copy.test).to.equal(1);
297         expect(execCount).to.equal(1);
298         done();
299     });
300
301     it('clones an object with property getter and setter', function (done) {
302
303         var obj = {
304             _test: 0
305         };
306
307         Object.defineProperty(obj, 'test', {
308             enumerable: true,
309             configurable: true,
310             get: function () {
311
312                 return this._test;
313             },
314             set: function (value) {
315
316                 this._test = value - 1;
317             }
318         });
319
320         var copy = Hoek.clone(obj);
321         expect(copy.test).to.equal(0);
322         copy.test = 5;
323         expect(copy.test).to.equal(4);
324         done();
325     });
326
327     it('clones an object with only property setter', function (done) {
328
329         var obj = {
330             _test: 0
331         };
332
333         Object.defineProperty(obj, 'test', {
334             enumerable: true,
335             configurable: true,
336             set: function (value) {
337
338                 this._test = value - 1;
339             }
340         });
341
342         var copy = Hoek.clone(obj);
343         expect(copy._test).to.equal(0);
344         copy.test = 5;
345         expect(copy._test).to.equal(4);
346         done();
347     });
348
349     it('clones an object with non-enumerable properties', function (done) {
350
351         var obj = {
352             _test: 0
353         };
354
355         Object.defineProperty(obj, 'test', {
356             enumerable: false,
357             configurable: true,
358             set: function (value) {
359
360                 this._test = value - 1;
361             }
362         });
363
364         var copy = Hoek.clone(obj);
365         expect(copy._test).to.equal(0);
366         copy.test = 5;
367         expect(copy._test).to.equal(4);
368         done();
369     });
370
371     it('clones an object where getOwnPropertyDescriptor returns undefined', function (done) {
372
373         var oldGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
374         var obj = { a: 'b' };
375         Object.getOwnPropertyDescriptor = function () {
376
377             return undefined;
378         };
379
380         var copy = Hoek.clone(obj);
381         Object.getOwnPropertyDescriptor = oldGetOwnPropertyDescriptor;
382         expect(copy).to.deep.equal(obj);
383         done();
384     });
385 });
386
387 describe('merge()', function () {
388
389     it('deep copies source items', function (done) {
390
391         var target = {
392             b: 3,
393             d: []
394         };
395
396         var source = {
397             c: {
398                 d: 1
399             },
400             d: [{ e: 1 }]
401         };
402
403         Hoek.merge(target, source);
404         expect(target.c).to.not.equal(source.c);
405         expect(target.c).to.deep.equal(source.c);
406         expect(target.d).to.not.equal(source.d);
407         expect(target.d[0]).to.not.equal(source.d[0]);
408         expect(target.d).to.deep.equal(source.d);
409         done();
410     });
411
412     it('merges array over an object', function (done) {
413
414         var a = {
415             x: ['n', 'm']
416         };
417
418         var b = {
419             x: {
420                 n: '1',
421                 m: '2'
422             }
423         };
424
425         Hoek.merge(b, a);
426         expect(a.x[0]).to.equal('n');
427         expect(a.x.n).to.not.exist();
428         done();
429     });
430
431     it('merges object over an array', function (done) {
432
433         var a = {
434             x: ['n', 'm']
435         };
436
437         var b = {
438             x: {
439                 n: '1',
440                 m: '2'
441             }
442         };
443
444         Hoek.merge(a, b);
445         expect(a.x.n).to.equal('1');
446         expect(a.x[0]).to.not.exist();
447         done();
448     });
449
450     it('does not throw if source is null', function (done) {
451
452         var a = {};
453         var b = null;
454         var c = null;
455
456         expect(function () {
457
458             c = Hoek.merge(a, b);
459         }).to.not.throw();
460
461         expect(c).to.equal(a);
462         done();
463     });
464
465     it('does not throw if source is undefined', function (done) {
466
467         var a = {};
468         var b;
469         var c = null;
470
471         expect(function () {
472
473             c = Hoek.merge(a, b);
474         }).to.not.throw();
475
476         expect(c).to.equal(a);
477         done();
478     });
479
480     it('throws if source is not an object', function (done) {
481
482         expect(function () {
483
484             var a = {};
485             var b = 0;
486
487             Hoek.merge(a, b);
488         }).to.throw('Invalid source value: must be null, undefined, or an object');
489         done();
490     });
491
492     it('throws if target is not an object', function (done) {
493
494         expect(function () {
495
496             var a = 0;
497             var b = {};
498
499             Hoek.merge(a, b);
500         }).to.throw('Invalid target value: must be an object');
501         done();
502     });
503
504     it('throws if target is not an array and source is', function (done) {
505
506         expect(function () {
507
508             var a = {};
509             var b = [1, 2];
510
511             Hoek.merge(a, b);
512         }).to.throw('Cannot merge array onto an object');
513         done();
514     });
515
516     it('returns the same object when merging arrays', function (done) {
517
518         var a = [];
519         var b = [1, 2];
520
521         expect(Hoek.merge(a, b)).to.equal(a);
522         done();
523     });
524
525     it('combines an empty object with a non-empty object', function (done) {
526
527         var a = {};
528         var b = nestedObj;
529
530         var c = Hoek.merge(a, b);
531         expect(a).to.deep.equal(b);
532         expect(c).to.deep.equal(b);
533         done();
534     });
535
536     it('overrides values in target', function (done) {
537
538         var a = { x: 1, y: 2, z: 3, v: 5, t: 'test', m: 'abc' };
539         var b = { x: null, z: 4, v: 0, t: { u: 6 }, m: '123' };
540
541         var c = Hoek.merge(a, b);
542         expect(c.x).to.equal(null);
543         expect(c.y).to.equal(2);
544         expect(c.z).to.equal(4);
545         expect(c.v).to.equal(0);
546         expect(c.m).to.equal('123');
547         expect(c.t).to.deep.equal({ u: 6 });
548         done();
549     });
550
551     it('overrides values in target (flip)', function (done) {
552
553         var a = { x: 1, y: 2, z: 3, v: 5, t: 'test', m: 'abc' };
554         var b = { x: null, z: 4, v: 0, t: { u: 6 }, m: '123' };
555
556         var d = Hoek.merge(b, a);
557         expect(d.x).to.equal(1);
558         expect(d.y).to.equal(2);
559         expect(d.z).to.equal(3);
560         expect(d.v).to.equal(5);
561         expect(d.m).to.equal('abc');
562         expect(d.t).to.deep.equal('test');
563         done();
564     });
565
566     it('retains Date properties', function (done) {
567
568         var a = { x: new Date(1378776452757) };
569
570         var b = Hoek.merge({}, a);
571         expect(a.x.getTime()).to.equal(b.x.getTime());
572         done();
573     });
574
575     it('retains Date properties when merging keys', function (done) {
576
577         var a = { x: new Date(1378776452757) };
578
579         var b = Hoek.merge({ x: {} }, a);
580         expect(a.x.getTime()).to.equal(b.x.getTime());
581         done();
582     });
583
584     it('overrides Buffer', function (done) {
585
586         var a = { x: new Buffer('abc') };
587
588         var b = Hoek.merge({ x: {} }, a);
589         expect(a.x.toString()).to.equal('abc');
590         done();
591     });
592 });
593
594 describe('applyToDefaults()', function () {
595
596     var defaults = {
597         a: 1,
598         b: 2,
599         c: {
600             d: 3,
601             e: [5, 6]
602         },
603         f: 6,
604         g: 'test'
605     };
606
607     it('throws when target is null', function (done) {
608
609         expect(function () {
610
611             Hoek.applyToDefaults(null, {});
612         }).to.throw('Invalid defaults value: must be an object');
613         done();
614     });
615
616     it('returns null if options is false', function (done) {
617
618         var result = Hoek.applyToDefaults(defaults, false);
619         expect(result).to.equal(null);
620         done();
621     });
622
623     it('returns null if options is null', function (done) {
624
625         var result = Hoek.applyToDefaults(defaults, null);
626         expect(result).to.equal(null);
627         done();
628     });
629
630     it('returns null if options is undefined', function (done) {
631
632         var result = Hoek.applyToDefaults(defaults, undefined);
633         expect(result).to.equal(null);
634         done();
635     });
636
637     it('returns a copy of defaults if options is true', function (done) {
638
639         var result = Hoek.applyToDefaults(defaults, true);
640         expect(result).to.deep.equal(defaults);
641         done();
642     });
643
644     it('applies object to defaults', function (done) {
645
646         var obj = {
647             a: null,
648             c: {
649                 e: [4]
650             },
651             f: 0,
652             g: {
653                 h: 5
654             }
655         };
656
657         var result = Hoek.applyToDefaults(defaults, obj);
658         expect(result.c.e).to.deep.equal([4]);
659         expect(result.a).to.equal(1);
660         expect(result.b).to.equal(2);
661         expect(result.f).to.equal(0);
662         expect(result.g).to.deep.equal({ h: 5 });
663         done();
664     });
665
666     it('applies object to defaults with null', function (done) {
667
668         var obj = {
669             a: null,
670             c: {
671                 e: [4]
672             },
673             f: 0,
674             g: {
675                 h: 5
676             }
677         };
678
679         var result = Hoek.applyToDefaults(defaults, obj, true);
680         expect(result.c.e).to.deep.equal([4]);
681         expect(result.a).to.equal(null);
682         expect(result.b).to.equal(2);
683         expect(result.f).to.equal(0);
684         expect(result.g).to.deep.equal({ h: 5 });
685         done();
686     });
687 });
688
689 describe('cloneWithShallow()', function () {
690
691     it('deep clones except for listed keys', function (done) {
692
693         var source = {
694             a: {
695                 b: 5
696             },
697             c: {
698                 d: 6
699             }
700         };
701
702         var copy = Hoek.cloneWithShallow(source, ['c']);
703         expect(copy).to.deep.equal(source);
704         expect(copy).to.not.equal(source);
705         expect(copy.a).to.not.equal(source.a);
706         expect(copy.b).to.equal(source.b);
707         done();
708     });
709
710     it('returns immutable value', function (done) {
711
712         expect(Hoek.cloneWithShallow(5)).to.equal(5);
713         done();
714     });
715
716     it('returns null value', function (done) {
717
718         expect(Hoek.cloneWithShallow(null)).to.equal(null);
719         done();
720     });
721
722     it('returns undefined value', function (done) {
723
724         expect(Hoek.cloneWithShallow(undefined)).to.equal(undefined);
725         done();
726     });
727
728     it('deep clones except for listed keys (including missing keys)', function (done) {
729
730         var source = {
731             a: {
732                 b: 5
733             },
734             c: {
735                 d: 6
736             }
737         };
738
739         var copy = Hoek.cloneWithShallow(source, ['c', 'v']);
740         expect(copy).to.deep.equal(source);
741         expect(copy).to.not.equal(source);
742         expect(copy.a).to.not.equal(source.a);
743         expect(copy.b).to.equal(source.b);
744         done();
745     });
746 });
747
748 describe('applyToDefaultsWithShallow()', function () {
749
750     it('shallow copies the listed keys from options without merging', function (done) {
751
752         var defaults = {
753             a: {
754                 b: 5,
755                 e: 3
756             },
757             c: {
758                 d: 7,
759                 g: 1
760             }
761         };
762
763         var options = {
764             a: {
765                 b: 4
766             },
767             c: {
768                 d: 6,
769                 f: 7
770             }
771         };
772
773         var merged = Hoek.applyToDefaultsWithShallow(defaults, options, ['a']);
774         expect(merged).to.deep.equal({ a: { b: 4 }, c: { d: 6, g: 1, f: 7 } });
775         expect(merged.a).to.equal(options.a);
776         expect(merged.a).to.not.equal(defaults.a);
777         expect(merged.c).to.not.equal(options.c);
778         expect(merged.c).to.not.equal(defaults.c);
779         done();
780     });
781
782     it('shallow copies the nested keys (override)', function (done) {
783
784         var defaults = {
785             a: {
786                 b: 5
787             },
788             c: {
789                 d: 7,
790                 g: 1
791             }
792         };
793
794         var options = {
795             a: {
796                 b: 4
797             },
798             c: {
799                 d: 6,
800                 g: {
801                     h: 8
802                 }
803             }
804         };
805
806         var merged = Hoek.applyToDefaultsWithShallow(defaults, options, ['c.g']);
807         expect(merged).to.deep.equal({ a: { b: 4 }, c: { d: 6, g: { h: 8 } } });
808         expect(merged.c.g).to.equal(options.c.g);
809         done();
810     });
811
812     it('shallow copies the nested keys (missing)', function (done) {
813
814         var defaults = {
815             a: {
816                 b: 5
817             }
818         };
819
820         var options = {
821             a: {
822                 b: 4
823             },
824             c: {
825                 g: {
826                     h: 8
827                 }
828             }
829         };
830
831         var merged = Hoek.applyToDefaultsWithShallow(defaults, options, ['c.g']);
832         expect(merged).to.deep.equal({ a: { b: 4 }, c: { g: { h: 8 } } });
833         expect(merged.c.g).to.equal(options.c.g);
834         done();
835     });
836
837     it('shallow copies the nested keys (override)', function (done) {
838
839         var defaults = {
840             a: {
841                 b: 5
842             },
843             c: {
844                 g: {
845                     d: 7
846                 }
847             }
848         };
849
850         var options = {
851             a: {
852                 b: 4
853             },
854             c: {
855                 g: {
856                     h: 8
857                 }
858             }
859         };
860
861         var merged = Hoek.applyToDefaultsWithShallow(defaults, options, ['c.g']);
862         expect(merged).to.deep.equal({ a: { b: 4 }, c: { g: { h: 8 } } });
863         expect(merged.c.g).to.equal(options.c.g);
864         done();
865     });
866
867     it('shallow copies the nested keys (deeper)', function (done) {
868
869         var defaults = {
870             a: {
871                 b: 5
872             }
873         };
874
875         var options = {
876             a: {
877                 b: 4
878             },
879             c: {
880                 g: {
881                     r: {
882                         h: 8
883                     }
884                 }
885             }
886         };
887
888         var merged = Hoek.applyToDefaultsWithShallow(defaults, options, ['c.g.r']);
889         expect(merged).to.deep.equal({ a: { b: 4 }, c: { g: { r: { h: 8 } } } });
890         expect(merged.c.g.r).to.equal(options.c.g.r);
891         done();
892     });
893
894     it('shallow copies the nested keys (not present)', function (done) {
895
896         var defaults = {
897             a: {
898                 b: 5
899             }
900         };
901
902         var options = {
903             a: {
904                 b: 4
905             },
906             c: {
907                 g: {
908                     r: {
909                         h: 8
910                     }
911                 }
912             }
913         };
914
915         var merged = Hoek.applyToDefaultsWithShallow(defaults, options, ['x.y']);
916         expect(merged).to.deep.equal({ a: { b: 4 }, c: { g: { r: { h: 8 } } } });
917         done();
918     });
919
920     it('shallow copies the listed keys in the defaults', function (done) {
921
922         var defaults = {
923             a: {
924                 b: 1
925             }
926         };
927
928         var merged = Hoek.applyToDefaultsWithShallow(defaults, {}, ['a']);
929         expect(merged.a).to.equal(defaults.a);
930         done();
931     });
932
933     it('shallow copies the listed keys in the defaults (true)', function (done) {
934
935         var defaults = {
936             a: {
937                 b: 1
938             }
939         };
940
941         var merged = Hoek.applyToDefaultsWithShallow(defaults, true, ['a']);
942         expect(merged.a).to.equal(defaults.a);
943         done();
944     });
945
946     it('returns null on false', function (done) {
947
948         var defaults = {
949             a: {
950                 b: 1
951             }
952         };
953
954         var merged = Hoek.applyToDefaultsWithShallow(defaults, false, ['a']);
955         expect(merged).to.equal(null);
956         done();
957     });
958
959     it('throws on missing defaults', function (done) {
960
961         expect(function () {
962
963             Hoek.applyToDefaultsWithShallow(null, {}, ['a']);
964         }).to.throw('Invalid defaults value: must be an object');
965         done();
966     });
967
968     it('throws on invalid defaults', function (done) {
969
970         expect(function () {
971
972             Hoek.applyToDefaultsWithShallow('abc', {}, ['a']);
973         }).to.throw('Invalid defaults value: must be an object');
974         done();
975     });
976
977     it('throws on invalid options', function (done) {
978
979         expect(function () {
980
981             Hoek.applyToDefaultsWithShallow({}, 'abc', ['a']);
982         }).to.throw('Invalid options value: must be true, falsy or an object');
983         done();
984     });
985
986     it('throws on missing keys', function (done) {
987
988         expect(function () {
989
990             Hoek.applyToDefaultsWithShallow({}, true);
991         }).to.throw('Invalid keys');
992         done();
993     });
994
995     it('throws on invalid keys', function (done) {
996
997         expect(function () {
998
999             Hoek.applyToDefaultsWithShallow({}, true, 'a');
1000         }).to.throw('Invalid keys');
1001         done();
1002     });
1003 });
1004
1005 describe('deepEqual()', function () {
1006
1007     it('compares simple values', function (done) {
1008
1009         expect(Hoek.deepEqual('x', 'x')).to.be.true();
1010         expect(Hoek.deepEqual('x', 'y')).to.be.false();
1011         expect(Hoek.deepEqual('x1', 'x')).to.be.false();
1012         expect(Hoek.deepEqual(-0, +0)).to.be.false();
1013         expect(Hoek.deepEqual(-0, -0)).to.be.true();
1014         expect(Hoek.deepEqual(+0, +0)).to.be.true();
1015         expect(Hoek.deepEqual(+0, -0)).to.be.false();
1016         expect(Hoek.deepEqual(1, 1)).to.be.true();
1017         expect(Hoek.deepEqual(0, 0)).to.be.true();
1018         expect(Hoek.deepEqual(-1, 1)).to.be.false();
1019         expect(Hoek.deepEqual(NaN, 0)).to.be.false();
1020         expect(Hoek.deepEqual(NaN, NaN)).to.be.true();
1021         done();
1022     });
1023
1024     it('compares different types', function (done) {
1025
1026         expect(Hoek.deepEqual([], 5)).to.be.false();
1027         expect(Hoek.deepEqual(5, [])).to.be.false();
1028         expect(Hoek.deepEqual({}, null)).to.be.false();
1029         expect(Hoek.deepEqual(null, {})).to.be.false();
1030         expect(Hoek.deepEqual('abc', {})).to.be.false();
1031         expect(Hoek.deepEqual({}, 'abc')).to.be.false();
1032         done();
1033     });
1034
1035     it('compares empty structures', function (done) {
1036
1037         expect(Hoek.deepEqual([], [])).to.be.true();
1038         expect(Hoek.deepEqual({}, {})).to.be.true();
1039         expect(Hoek.deepEqual([], {})).to.be.false();
1040         done();
1041     });
1042
1043     it('compares empty arguments object', function (done) {
1044
1045         var compare = function () {
1046
1047             expect(Hoek.deepEqual([], arguments)).to.be.false();
1048         };
1049
1050         compare();
1051         done();
1052     });
1053
1054     it('compares empty arguments objects', function (done) {
1055
1056         var compare = function () {
1057
1058             var arg1 = arguments;
1059
1060             var inner = function () {
1061
1062                 expect(Hoek.deepEqual(arg1, arguments)).to.be.false(); // callee is not the same
1063             };
1064
1065             inner();
1066         };
1067
1068         compare();
1069         done();
1070     });
1071
1072     it('compares dates', function (done) {
1073
1074         expect(Hoek.deepEqual(new Date(2015, 1, 1), new Date(2015, 1, 1))).to.be.true();
1075         expect(Hoek.deepEqual(new Date(100), new Date(101))).to.be.false();
1076         expect(Hoek.deepEqual(new Date(), {})).to.be.false();
1077         done();
1078     });
1079
1080     it('compares regular expressions', function (done) {
1081
1082         expect(Hoek.deepEqual(/\s/, new RegExp('\\\s'))).to.be.true();
1083         expect(Hoek.deepEqual(/\s/g, /\s/g)).to.be.true();
1084         expect(Hoek.deepEqual(/a/, {})).to.be.false();
1085         expect(Hoek.deepEqual(/\s/g, /\s/i)).to.be.false();
1086         expect(Hoek.deepEqual(/a/g, /b/g)).to.be.false();
1087         done();
1088     });
1089
1090     it('compares arrays', function (done) {
1091
1092         expect(Hoek.deepEqual([[1]], [[1]])).to.be.true();
1093         expect(Hoek.deepEqual([1, 2, 3], [1, 2, 3])).to.be.true();
1094         expect(Hoek.deepEqual([1, 2, 3], [1, 3, 2])).to.be.false();
1095         expect(Hoek.deepEqual([1, 2, 3], [1, 2])).to.be.false();
1096         expect(Hoek.deepEqual([1], [1])).to.be.true();
1097         done();
1098     });
1099
1100     it('compares buffers', function (done) {
1101
1102         expect(Hoek.deepEqual(new Buffer([1, 2, 3]), new Buffer([1, 2, 3]))).to.be.true();
1103         expect(Hoek.deepEqual(new Buffer([1, 2, 3]), new Buffer([1, 3, 2]))).to.be.false();
1104         expect(Hoek.deepEqual(new Buffer([1, 2, 3]), new Buffer([1, 2]))).to.be.false();
1105         expect(Hoek.deepEqual(new Buffer([1, 2, 3]), {})).to.be.false();
1106         expect(Hoek.deepEqual(new Buffer([1, 2, 3]), [1, 2, 3])).to.be.false();
1107         done();
1108     });
1109
1110     it('compares objects', function (done) {
1111
1112         expect(Hoek.deepEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).to.be.true();
1113         expect(Hoek.deepEqual({ foo: 'bar' }, { foo: 'baz' })).to.be.false();
1114         expect(Hoek.deepEqual({ foo: { bar: 'foo' } }, { foo: { bar: 'baz' } })).to.be.false();
1115         done();
1116     });
1117
1118     it('handles circular dependency', function (done) {
1119
1120         var a = {};
1121         a.x = a;
1122
1123         var b = Hoek.clone(a);
1124         expect(Hoek.deepEqual(a, b)).to.be.true();
1125         done();
1126     });
1127
1128     it('compares an object with property getter without executing it', function (done) {
1129
1130         var obj = {};
1131         var value = 1;
1132         var execCount = 0;
1133
1134         Object.defineProperty(obj, 'test', {
1135             enumerable: true,
1136             configurable: true,
1137             get: function () {
1138
1139                 ++execCount;
1140                 return value;
1141             }
1142         });
1143
1144         var copy = Hoek.clone(obj);
1145         expect(Hoek.deepEqual(obj, copy)).to.be.true();
1146         expect(execCount).to.equal(0);
1147         expect(copy.test).to.equal(1);
1148         expect(execCount).to.equal(1);
1149         done();
1150     });
1151
1152     it('compares objects with property getters', function (done) {
1153
1154         var obj = {};
1155         Object.defineProperty(obj, 'test', {
1156             enumerable: true,
1157             configurable: true,
1158             get: function () {
1159
1160                 return 1;
1161             }
1162         });
1163
1164         var ref = {};
1165         Object.defineProperty(ref, 'test', {
1166             enumerable: true,
1167             configurable: true,
1168             get: function () {
1169
1170                 return 2;
1171             }
1172         });
1173
1174         expect(Hoek.deepEqual(obj, ref)).to.be.false();
1175         done();
1176     });
1177
1178     it('compares object prototypes', function (done) {
1179
1180         var Obj = function () {
1181
1182             this.a = 5;
1183         };
1184
1185         Obj.prototype.b = function () {
1186
1187             return this.a;
1188         };
1189
1190         var Ref = function () {
1191
1192             this.a = 5;
1193         };
1194
1195         Ref.prototype.b = function () {
1196
1197             return this.a;
1198         };
1199
1200         expect(Hoek.deepEqual(new Obj(), new Ref())).to.be.false();
1201         expect(Hoek.deepEqual(new Obj(), new Obj())).to.be.true();
1202         expect(Hoek.deepEqual(new Ref(), new Ref())).to.be.true();
1203         done();
1204     });
1205
1206     it('compares plain objects', function (done) {
1207
1208         var a = Object.create(null);
1209         var b = Object.create(null);
1210
1211         a.b = 'c';
1212         b.b = 'c';
1213
1214         expect(Hoek.deepEqual(a, b)).to.be.true();
1215         expect(Hoek.deepEqual(a, { b: 'c' })).to.be.false();
1216         done();
1217     });
1218
1219     it('compares an object with an empty object', function (done) {
1220
1221         var a = { a: 1, b: 2 };
1222
1223         expect(Hoek.deepEqual({}, a)).to.be.false();
1224         expect(Hoek.deepEqual(a, {})).to.be.false();
1225         done();
1226     });
1227
1228     it('compares an object ignoring the prototype', function (done) {
1229
1230         var a = Object.create(null);
1231         var b = {};
1232
1233         expect(Hoek.deepEqual(a, b, { prototype: false })).to.be.true();
1234         done();
1235     });
1236
1237     it('compares an object ignoring the prototype recursively', function (done) {
1238
1239         var a = [Object.create(null)];
1240         var b = [{}];
1241
1242         expect(Hoek.deepEqual(a, b, { prototype: false })).to.be.true();
1243         done();
1244     });
1245 });
1246
1247 describe('unique()', function () {
1248
1249     it('ensures uniqueness within array of objects based on subkey', function (done) {
1250
1251         var a = Hoek.unique(dupsArray, 'x');
1252         expect(a).to.deep.equal(reducedDupsArray);
1253         done();
1254     });
1255
1256     it('removes duplicated without key', function (done) {
1257
1258         expect(Hoek.unique([1, 2, 3, 4, 2, 1, 5])).to.deep.equal([1, 2, 3, 4, 5]);
1259         done();
1260     });
1261 });
1262
1263 describe('mapToObject()', function () {
1264
1265     it('returns null on null array', function (done) {
1266
1267         var a = Hoek.mapToObject(null);
1268         expect(a).to.equal(null);
1269         done();
1270     });
1271
1272     it('converts basic array to existential object', function (done) {
1273
1274         var keys = [1, 2, 3, 4];
1275         var a = Hoek.mapToObject(keys);
1276         for (var i in keys) {
1277             expect(a[keys[i]]).to.equal(true);
1278         }
1279         done();
1280     });
1281
1282     it('converts array of objects to existential object', function (done) {
1283
1284         var keys = [{ x: 1 }, { x: 2 }, { x: 3 }, { y: 4 }];
1285         var subkey = 'x';
1286         var a = Hoek.mapToObject(keys, subkey);
1287         expect(a).to.deep.equal({ 1: true, 2: true, 3: true });
1288         done();
1289     });
1290 });
1291
1292 describe('intersect()', function () {
1293
1294     it('returns the common objects of two arrays', function (done) {
1295
1296         var array1 = [1, 2, 3, 4, 4, 5, 5];
1297         var array2 = [5, 4, 5, 6, 7];
1298         var common = Hoek.intersect(array1, array2);
1299         expect(common.length).to.equal(2);
1300         done();
1301     });
1302
1303     it('returns just the first common object of two arrays', function (done) {
1304
1305         var array1 = [1, 2, 3, 4, 4, 5, 5];
1306         var array2 = [5, 4, 5, 6, 7];
1307         var common = Hoek.intersect(array1, array2, true);
1308         expect(common).to.equal(5);
1309         done();
1310     });
1311
1312     it('returns null when no common and returning just the first common object of two arrays', function (done) {
1313
1314         var array1 = [1, 2, 3, 4, 4, 5, 5];
1315         var array2 = [6, 7];
1316         var common = Hoek.intersect(array1, array2, true);
1317         expect(common).to.equal(null);
1318         done();
1319     });
1320
1321     it('returns an empty array if either input is null', function (done) {
1322
1323         expect(Hoek.intersect([1], null).length).to.equal(0);
1324         expect(Hoek.intersect(null, [1]).length).to.equal(0);
1325         done();
1326     });
1327
1328     it('returns the common objects of object and array', function (done) {
1329
1330         var array1 = [1, 2, 3, 4, 4, 5, 5];
1331         var array2 = [5, 4, 5, 6, 7];
1332         var common = Hoek.intersect(Hoek.mapToObject(array1), array2);
1333         expect(common.length).to.equal(2);
1334         done();
1335     });
1336 });
1337
1338 describe('contain()', function () {
1339
1340     it('tests strings', function (done) {
1341
1342         expect(Hoek.contain('abc', 'ab')).to.be.true();
1343         expect(Hoek.contain('abc', 'abc', { only: true })).to.be.true();
1344         expect(Hoek.contain('aaa', 'a', { only: true })).to.be.true();
1345         expect(Hoek.contain('abc', 'b', { once: true })).to.be.true();
1346         expect(Hoek.contain('abc', ['a', 'c'])).to.be.true();
1347         expect(Hoek.contain('abc', ['a', 'd'], { part: true })).to.be.true();
1348
1349         expect(Hoek.contain('abc', 'ac')).to.be.false();
1350         expect(Hoek.contain('abcd', 'abc', { only: true })).to.be.false();
1351         expect(Hoek.contain('aab', 'a', { only: true })).to.be.false();
1352         expect(Hoek.contain('abb', 'b', { once: true })).to.be.false();
1353         expect(Hoek.contain('abc', ['a', 'd'])).to.be.false();
1354         expect(Hoek.contain('abc', ['ab', 'bc'])).to.be.false();                      // Overlapping values not supported
1355         done();
1356     });
1357
1358     it('tests arrays', function (done) {
1359
1360         expect(Hoek.contain([1, 2, 3], 1)).to.be.true();
1361         expect(Hoek.contain([{ a: 1 }], { a: 1 }, { deep: true })).to.be.true();
1362         expect(Hoek.contain([1, 2, 3], [1, 2])).to.be.true();
1363         expect(Hoek.contain([{ a: 1 }], [{ a: 1 }], { deep: true })).to.be.true();
1364         expect(Hoek.contain([1, 1, 2], [1, 2], { only: true })).to.be.true();
1365         expect(Hoek.contain([1, 2], [1, 2], { once: true })).to.be.true();
1366         expect(Hoek.contain([1, 2, 3], [1, 4], { part: true })).to.be.true();
1367         expect(Hoek.contain([[1], [2]], [[1]], { deep: true })).to.be.true();
1368
1369         expect(Hoek.contain([1, 2, 3], 4)).to.be.false();
1370         expect(Hoek.contain([{ a: 1 }], { a: 2 }, { deep: true })).to.be.false();
1371         expect(Hoek.contain([{ a: 1 }], { a: 1 })).to.be.false();
1372         expect(Hoek.contain([1, 2, 3], [4, 5])).to.be.false();
1373         expect(Hoek.contain([[3], [2]], [[1]])).to.be.false();
1374         expect(Hoek.contain([[1], [2]], [[1]])).to.be.false();
1375         expect(Hoek.contain([{ a: 1 }], [{ a: 2 }], { deep: true })).to.be.false();
1376         expect(Hoek.contain([1, 3, 2], [1, 2], { only: true })).to.be.false();
1377         expect(Hoek.contain([1, 2, 2], [1, 2], { once: true })).to.be.false();
1378         expect(Hoek.contain([0, 2, 3], [1, 4], { part: true })).to.be.false();
1379         done();
1380     });
1381
1382     it('tests objects', function (done) {
1383
1384         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, 'a')).to.be.true();
1385         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, ['a', 'c'])).to.be.true();
1386         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, ['a', 'b', 'c'], { only: true })).to.be.true();
1387         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1 })).to.be.true();
1388         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, c: 3 })).to.be.true();
1389         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 }, { part: true })).to.be.true();
1390         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 }, { only: true })).to.be.true();
1391         expect(Hoek.contain({ a: [1], b: [2], c: [3] }, { a: [1], c: [3] }, { deep: true })).to.be.true();
1392         expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true })).to.be.true();
1393         expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, part: true })).to.be.true();
1394         expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, part: false })).to.be.false();
1395         expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, only: true })).to.be.false();
1396         expect(Hoek.contain({ a: [{ b: 1 }, { c: 2 }, { d: 3, e: 4 }] }, { a: [{ b: 1 }, { d: 3 }] }, { deep: true, only: false })).to.be.true();
1397
1398         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, 'd')).to.be.false();
1399         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, ['a', 'd'])).to.be.false();
1400         expect(Hoek.contain({ a: 1, b: 2, c: 3, d: 4 }, ['a', 'b', 'c'], { only: true })).to.be.false();
1401         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 2 })).to.be.false();
1402         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 2, b: 2 }, { part: true })).to.be.false();             // part does not ignore bad value
1403         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 3 })).to.be.false();
1404         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 })).to.be.false();
1405         expect(Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }, { only: true })).to.be.false();
1406         expect(Hoek.contain({ a: [1], b: [2], c: [3] }, { a: [1], c: [3] })).to.be.false();
1407         expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } })).to.be.false();
1408         expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true })).to.be.true();
1409         expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, only: true })).to.be.false();
1410         expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, only: false })).to.be.true();
1411         expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, part: true })).to.be.true();
1412         expect(Hoek.contain({ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 1 } } }, { deep: true, part: false })).to.be.false();
1413
1414         // Getter check
1415         var Foo = function (bar) {
1416
1417             this.bar = bar;
1418         };
1419
1420         Object.defineProperty(Foo.prototype, 'baz', {
1421             enumerable: true,
1422             get: function () {
1423
1424                 return this.bar;
1425             }
1426         });
1427
1428         expect(Hoek.contain({ a: new Foo('b') }, { a: new Foo('b') }, { deep: true })).to.be.true();
1429         expect(Hoek.contain({ a: new Foo('b') }, { a: new Foo('b') }, { deep: true, part: true })).to.be.true();
1430         expect(Hoek.contain({ a: new Foo('b') }, { a: { baz: 'b' } }, { deep: true })).to.be.true();
1431         expect(Hoek.contain({ a: new Foo('b') }, { a: { baz: 'b' } }, { deep: true, only: true })).to.be.false();
1432         expect(Hoek.contain({ a: new Foo('b') }, { a: { baz: 'b' } }, { deep: true, part: false })).to.be.false();
1433         expect(Hoek.contain({ a: new Foo('b') }, { a: { baz: 'b' } }, { deep: true, part: true })).to.be.true();
1434
1435         done();
1436     });
1437 });
1438
1439 describe('flatten()', function () {
1440
1441     it('returns a flat array', function (done) {
1442
1443         var result = Hoek.flatten([1, 2, [3, 4, [5, 6], [7], 8], [9], [10, [11, 12]], 13]);
1444         expect(result.length).to.equal(13);
1445         expect(result).to.deep.equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
1446         done();
1447     });
1448 });
1449
1450 describe('reach()', function () {
1451
1452     var obj = {
1453         a: {
1454             b: {
1455                 c: {
1456                     d: 1,
1457                     e: 2
1458                 },
1459                 f: 'hello'
1460             },
1461             g: {
1462                 h: 3
1463             }
1464         },
1465         i: function () { },
1466         j: null,
1467         k: [4, 8, 9, 1]
1468     };
1469
1470     obj.i.x = 5;
1471
1472     it('returns object itself', function (done) {
1473
1474         expect(Hoek.reach(obj, null)).to.equal(obj);
1475         expect(Hoek.reach(obj, false)).to.equal(obj);
1476         expect(Hoek.reach(obj)).to.equal(obj);
1477         done();
1478     });
1479
1480     it('returns first value of array', function (done) {
1481
1482         expect(Hoek.reach(obj, 'k.0')).to.equal(4);
1483         done();
1484     });
1485
1486     it('returns last value of array using negative index', function (done) {
1487
1488         expect(Hoek.reach(obj, 'k.-2')).to.equal(9);
1489         done();
1490     });
1491
1492     it('returns a valid member', function (done) {
1493
1494         expect(Hoek.reach(obj, 'a.b.c.d')).to.equal(1);
1495         done();
1496     });
1497
1498     it('returns a valid member with separator override', function (done) {
1499
1500         expect(Hoek.reach(obj, 'a/b/c/d', '/')).to.equal(1);
1501         done();
1502     });
1503
1504     it('returns undefined on null object', function (done) {
1505
1506         expect(Hoek.reach(null, 'a.b.c.d')).to.equal(undefined);
1507         done();
1508     });
1509
1510     it('returns undefined on missing object member', function (done) {
1511
1512         expect(Hoek.reach(obj, 'a.b.c.d.x')).to.equal(undefined);
1513         done();
1514     });
1515
1516     it('returns undefined on missing function member', function (done) {
1517
1518         expect(Hoek.reach(obj, 'i.y', { functions: true })).to.equal(undefined);
1519         done();
1520     });
1521
1522     it('throws on missing member in strict mode', function (done) {
1523
1524         expect(function () {
1525
1526             Hoek.reach(obj, 'a.b.c.o.x', { strict: true });
1527         }).to.throw('Missing segment o in reach path  a.b.c.o.x');
1528
1529         done();
1530     });
1531
1532     it('returns undefined on invalid member', function (done) {
1533
1534         expect(Hoek.reach(obj, 'a.b.c.d-.x')).to.equal(undefined);
1535         done();
1536     });
1537
1538     it('returns function member', function (done) {
1539
1540         expect(typeof Hoek.reach(obj, 'i')).to.equal('function');
1541         done();
1542     });
1543
1544     it('returns function property', function (done) {
1545
1546         expect(Hoek.reach(obj, 'i.x')).to.equal(5);
1547         done();
1548     });
1549
1550     it('returns null', function (done) {
1551
1552         expect(Hoek.reach(obj, 'j')).to.equal(null);
1553         done();
1554     });
1555
1556     it('throws on function property when functions not allowed', function (done) {
1557
1558         expect(function () {
1559
1560             Hoek.reach(obj, 'i.x', { functions: false });
1561         }).to.throw('Invalid segment x in reach path  i.x');
1562
1563         done();
1564     });
1565
1566     it('will return a default value if property is not found', function (done) {
1567
1568         expect(Hoek.reach(obj, 'a.b.q', { default: 'defaultValue' })).to.equal('defaultValue');
1569         done();
1570     });
1571
1572     it('will return a default value if path is not found', function (done) {
1573
1574         expect(Hoek.reach(obj, 'q', { default: 'defaultValue' })).to.equal('defaultValue');
1575         done();
1576     });
1577
1578     it('allows a falsey value to be used as the default value', function (done) {
1579
1580         expect(Hoek.reach(obj, 'q', { default: '' })).to.equal('');
1581         done();
1582     });
1583 });
1584
1585 describe('reachTemplate()', function () {
1586
1587     it('applies object to template', function (done) {
1588
1589         var obj = {
1590             a: {
1591                 b: {
1592                     c: {
1593                         d: 1
1594                     }
1595                 }
1596             },
1597             j: null,
1598             k: [4, 8, 9, 1]
1599         };
1600
1601         var template = '{k.0}:{k.-2}:{a.b.c.d}:{x.y}:{j}';
1602
1603         expect(Hoek.reachTemplate(obj, template)).to.equal('4:9:1::');
1604         done();
1605     });
1606
1607     it('applies object to template (options)', function (done) {
1608
1609         var obj = {
1610             a: {
1611                 b: {
1612                     c: {
1613                         d: 1
1614                     }
1615                 }
1616             },
1617             j: null,
1618             k: [4, 8, 9, 1]
1619         };
1620
1621         var template = '{k/0}:{k/-2}:{a/b/c/d}:{x/y}:{j}';
1622
1623         expect(Hoek.reachTemplate(obj, template, '/')).to.equal('4:9:1::');
1624         done();
1625     });
1626 });
1627
1628 describe('callStack()', function () {
1629
1630     it('returns the full call stack', function (done) {
1631
1632         var stack = Hoek.callStack();
1633         expect(stack[0][0]).to.contain('index.js');
1634         expect(stack[0][2]).to.equal(26);
1635         done();
1636     });
1637 });
1638
1639 describe('displayStack ()', function () {
1640
1641     it('returns the full call stack for display', function (done) {
1642
1643         var stack = Hoek.displayStack();
1644         expect(stack[0]).to.contain(Path.normalize('/test/index.js') + ':');
1645         done();
1646     });
1647
1648     it('includes constructor functions correctly', function (done) {
1649
1650         var Something = function (next) {
1651
1652             next();
1653         };
1654
1655         var something = new Something(function () {
1656
1657             var stack = Hoek.displayStack();
1658             expect(stack[1]).to.contain('new Something');
1659             done();
1660         });
1661     });
1662 });
1663
1664 describe('abort()', function () {
1665
1666     it('exits process when not in test mode', function (done) {
1667
1668         var env = process.env.NODE_ENV;
1669         var write = process.stdout.write;
1670         var exit = process.exit;
1671
1672         process.env.NODE_ENV = 'nottatest';
1673         process.stdout.write = function () { };
1674         process.exit = function (state) {
1675
1676             process.exit = exit;
1677             process.env.NODE_ENV = env;
1678             process.stdout.write = write;
1679
1680             expect(state).to.equal(1);
1681             done();
1682         };
1683
1684         Hoek.abort('Boom');
1685     });
1686
1687     it('throws when not in test mode and abortThrow is true', function (done) {
1688
1689         var env = process.env.NODE_ENV;
1690         process.env.NODE_ENV = 'nottatest';
1691         Hoek.abortThrow = true;
1692
1693         var fn = function () {
1694
1695             Hoek.abort('my error message');
1696         };
1697
1698         expect(fn).to.throw('my error message');
1699         Hoek.abortThrow = false;
1700         process.env.NODE_ENV = env;
1701
1702         done();
1703     });
1704
1705     it('respects hideStack argument', function (done) {
1706
1707         var env = process.env.NODE_ENV;
1708         var write = process.stdout.write;
1709         var exit = process.exit;
1710         var output = '';
1711
1712         process.exit = function () { };
1713         process.env.NODE_ENV = '';
1714         process.stdout.write = function (message) {
1715
1716             output = message;
1717         };
1718
1719         Hoek.abort('my error message', true);
1720
1721         process.env.NODE_ENV = env;
1722         process.stdout.write = write;
1723         process.exit = exit;
1724
1725         expect(output).to.equal('ABORT: my error message\n\t\n');
1726
1727         done();
1728     });
1729
1730     it('throws in test mode', function (done) {
1731
1732         var env = process.env.NODE_ENV;
1733         process.env.NODE_ENV = 'test';
1734
1735         expect(function () {
1736
1737             Hoek.abort('my error message', true);
1738         }).to.throw('my error message');
1739
1740         process.env.NODE_ENV = env;
1741         done();
1742     });
1743
1744     it('throws in test mode with default message', function (done) {
1745
1746         var env = process.env.NODE_ENV;
1747         process.env.NODE_ENV = 'test';
1748
1749         expect(function () {
1750
1751             Hoek.abort('', true);
1752         }).to.throw('Unknown error');
1753
1754         process.env.NODE_ENV = env;
1755         done();
1756     });
1757
1758     it('defaults to showing stack', function (done) {
1759
1760         var env = process.env.NODE_ENV;
1761         var write = process.stdout.write;
1762         var exit = process.exit;
1763         var output = '';
1764
1765         process.exit = function () { };
1766         process.env.NODE_ENV = '';
1767         process.stdout.write = function (message) {
1768
1769             output = message;
1770         };
1771
1772         Hoek.abort('my error message');
1773
1774         process.env.NODE_ENV = env;
1775         process.stdout.write = write;
1776         process.exit = exit;
1777
1778         expect(output).to.contain('index.js');
1779
1780         done();
1781     });
1782 });
1783
1784 describe('assert()', function () {
1785
1786     it('throws an Error when using assert in a test', function (done) {
1787
1788         var fn = function () {
1789
1790             Hoek.assert(false, 'my error message');
1791         };
1792
1793         expect(fn).to.throw('my error message');
1794         done();
1795     });
1796
1797     it('throws an Error when using assert in a test with no message', function (done) {
1798
1799         var fn = function () {
1800
1801             Hoek.assert(false);
1802         };
1803
1804         expect(fn).to.throw('Unknown error');
1805         done();
1806     });
1807
1808     it('throws an Error when using assert in a test with multipart message', function (done) {
1809
1810         var fn = function () {
1811
1812             Hoek.assert(false, 'This', 'is', 'my message');
1813         };
1814
1815         expect(fn).to.throw('This is my message');
1816         done();
1817     });
1818
1819     it('throws an Error when using assert in a test with multipart message (empty)', function (done) {
1820
1821         var fn = function () {
1822
1823             Hoek.assert(false, 'This', 'is', '', 'my message');
1824         };
1825
1826         expect(fn).to.throw('This is my message');
1827         done();
1828     });
1829
1830     it('throws an Error when using assert in a test with object message', function (done) {
1831
1832         var fn = function () {
1833
1834             Hoek.assert(false, 'This', 'is', { spinal: 'tap' });
1835         };
1836
1837         expect(fn).to.throw('This is {"spinal":"tap"}');
1838         done();
1839     });
1840
1841     it('throws an Error when using assert in a test with multipart string and error messages', function (done) {
1842
1843         var fn = function () {
1844
1845             Hoek.assert(false, 'This', 'is', new Error('spinal'), new Error('tap'));
1846         };
1847
1848         expect(fn).to.throw('This is spinal tap');
1849         done();
1850     });
1851
1852     it('throws an Error when using assert in a test with error object message', function (done) {
1853
1854         var fn = function () {
1855
1856             Hoek.assert(false, new Error('This is spinal tap'));
1857         };
1858
1859         expect(fn).to.throw('This is spinal tap');
1860         done();
1861     });
1862
1863     it('throws the same Error that is passed to it if there is only one error passed', function (done) {
1864
1865         var error = new Error('ruh roh');
1866         var error2 = new Error('ruh roh');
1867
1868         var fn = function () {
1869
1870             Hoek.assert(false, error);
1871         };
1872
1873         try {
1874             fn();
1875         } catch (err) {
1876             expect(error).to.equal(error);  // should be the same reference
1877             expect(error).to.not.equal(error2); // error with the same message should not match
1878         }
1879
1880         done();
1881     });
1882 });
1883
1884 describe('Timer', function () {
1885
1886     it('returns time elapsed', function (done) {
1887
1888         var timer = new Hoek.Timer();
1889         setTimeout(function () {
1890
1891             expect(timer.elapsed()).to.be.above(9);
1892             done();
1893         }, 12);
1894     });
1895 });
1896
1897 describe('Bench', function () {
1898
1899     it('returns time elapsed', function (done) {
1900
1901         var timer = new Hoek.Bench();
1902         setTimeout(function () {
1903
1904             expect(timer.elapsed()).to.be.above(9);
1905             done();
1906         }, 12);
1907     });
1908 });
1909
1910 describe('escapeRegex()', function () {
1911
1912     it('escapes all special regular expression characters', function (done) {
1913
1914         var a = Hoek.escapeRegex('4^f$s.4*5+-_?%=#!:@|~\\/`"(>)[<]d{}s,');
1915         expect(a).to.equal('4\\^f\\$s\\.4\\*5\\+\\-_\\?%\\=#\\!\\:@\\|~\\\\\\/`"\\(>\\)\\[<\\]d\\{\\}s\\,');
1916         done();
1917     });
1918 });
1919
1920 describe('Base64Url', function () {
1921
1922     var base64str = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w';
1923     var str = unescape('%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29*+%2C-./0123456789%3A%3B%3C%3D%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF');
1924
1925     describe('base64urlEncode()', function () {
1926
1927         it('should base64 URL-safe a string', function (done) {
1928
1929             expect(Hoek.base64urlEncode(str)).to.equal(base64str);
1930             done();
1931         });
1932
1933         it('encodes a buffer', function (done) {
1934
1935             expect(Hoek.base64urlEncode(new Buffer(str, 'binary'))).to.equal(base64str);
1936             done();
1937         });
1938
1939         it('should base64 URL-safe a hex string', function (done) {
1940
1941             var buffer = new Buffer(str, 'binary');
1942             expect(Hoek.base64urlEncode(buffer.toString('hex'), 'hex')).to.equal(base64str);
1943             done();
1944         });
1945
1946         it('works on larger input strings', function (done) {
1947
1948             var input = Fs.readFileSync(Path.join(__dirname, 'index.js')).toString();
1949             var encoded = Hoek.base64urlEncode(input);
1950
1951             expect(encoded).to.not.contain('+');
1952             expect(encoded).to.not.contain('/');
1953
1954             var decoded = Hoek.base64urlDecode(encoded);
1955
1956             expect(decoded).to.equal(input);
1957             done();
1958         });
1959     });
1960
1961     describe('base64urlDecode()', function () {
1962
1963         it('should un-base64 URL-safe a string', function (done) {
1964
1965             expect(Hoek.base64urlDecode(base64str)).to.equal(str);
1966             done();
1967         });
1968
1969         it('should un-base64 URL-safe a string into hex', function (done) {
1970
1971             expect(Hoek.base64urlDecode(base64str, 'hex')).to.equal(new Buffer(str, 'binary').toString('hex'));
1972             done();
1973         });
1974
1975         it('should un-base64 URL-safe a string and return a buffer', function (done) {
1976
1977             var buf = Hoek.base64urlDecode(base64str, 'buffer');
1978             expect(buf instanceof Buffer).to.equal(true);
1979             expect(buf.toString('binary')).to.equal(str);
1980             done();
1981         });
1982
1983         it('returns error on undefined input', function (done) {
1984
1985             expect(Hoek.base64urlDecode().message).to.exist();
1986             done();
1987         });
1988
1989         it('returns error on invalid input', function (done) {
1990
1991             expect(Hoek.base64urlDecode('*').message).to.exist();
1992             done();
1993         });
1994     });
1995 });
1996
1997 describe('escapeHeaderAttribute()', function () {
1998
1999     it('should not alter ascii values', function (done) {
2000
2001         var a = Hoek.escapeHeaderAttribute('My Value');
2002         expect(a).to.equal('My Value');
2003         done();
2004     });
2005
2006     it('escapes all special HTTP header attribute characters', function (done) {
2007
2008         var a = Hoek.escapeHeaderAttribute('I said go!!!#"' + String.fromCharCode(92));
2009         expect(a).to.equal('I said go!!!#\\"\\\\');
2010         done();
2011     });
2012
2013     it('throws on large unicode characters', function (done) {
2014
2015         var fn = function () {
2016
2017             Hoek.escapeHeaderAttribute('this is a test' + String.fromCharCode(500) + String.fromCharCode(300));
2018         };
2019
2020         expect(fn).to.throw(Error);
2021         done();
2022     });
2023
2024     it('throws on CRLF to prevent response splitting', function (done) {
2025
2026         var fn = function () {
2027
2028             Hoek.escapeHeaderAttribute('this is a test\r\n');
2029         };
2030
2031         expect(fn).to.throw(Error);
2032         done();
2033     });
2034 });
2035
2036 describe('escapeHtml()', function () {
2037
2038     it('escapes all special HTML characters', function (done) {
2039
2040         var a = Hoek.escapeHtml('&<>"\'`');
2041         expect(a).to.equal('&amp;&lt;&gt;&quot;&#x27;&#x60;');
2042         done();
2043     });
2044
2045     it('returns empty string on falsy input', function (done) {
2046
2047         var a = Hoek.escapeHtml('');
2048         expect(a).to.equal('');
2049         done();
2050     });
2051
2052     it('returns unchanged string on no reserved input', function (done) {
2053
2054         var a = Hoek.escapeHtml('abc');
2055         expect(a).to.equal('abc');
2056         done();
2057     });
2058 });
2059
2060 describe('nextTick()', function () {
2061
2062     it('calls the provided callback on nextTick', function (done) {
2063
2064         var a = 0;
2065
2066         var inc = function (step, next) {
2067
2068             a += step;
2069             next();
2070         };
2071
2072         var ticked = Hoek.nextTick(inc);
2073
2074         ticked(5, function () {
2075
2076             expect(a).to.equal(6);
2077             done();
2078         });
2079
2080         expect(a).to.equal(0);
2081         inc(1, function () {
2082
2083             expect(a).to.equal(1);
2084         });
2085     });
2086 });
2087
2088 describe('once()', function () {
2089
2090     it('allows function to only execute once', function (done) {
2091
2092         var gen = 0;
2093         var add = function (x) {
2094
2095             gen += x;
2096         };
2097
2098         add(5);
2099         expect(gen).to.equal(5);
2100         add = Hoek.once(add);
2101         add(5);
2102         expect(gen).to.equal(10);
2103         add(5);
2104         expect(gen).to.equal(10);
2105         done();
2106     });
2107
2108     it('double once wraps one time', function (done) {
2109
2110         var method = function () { };
2111         method = Hoek.once(method);
2112         method.x = 1;
2113         method = Hoek.once(method);
2114         expect(method.x).to.equal(1);
2115         done();
2116     });
2117 });
2118
2119 describe('isAbsoltePath()', function () {
2120
2121     it('identifies if path is absolute on Unix without node support', { parallel: false }, function (done) {
2122
2123         var orig = Path.isAbsolute;
2124         Path.isAbsolute = undefined;
2125
2126         expect(Hoek.isAbsolutePath('')).to.equal(false);
2127         expect(Hoek.isAbsolutePath('a')).to.equal(false);
2128         expect(Hoek.isAbsolutePath('./a')).to.equal(false);
2129         expect(Hoek.isAbsolutePath('/a')).to.equal(true);
2130         expect(Hoek.isAbsolutePath('/')).to.equal(true);
2131
2132         Path.isAbsolute = orig;
2133
2134         done();
2135     });
2136
2137     it('identifies if path is absolute with fake node support', { parallel: false }, function (done) {
2138
2139         var orig = Path.isAbsolute;
2140         Path.isAbsolute = function (path) {
2141
2142             return path[0] === '/';
2143         };
2144
2145         expect(Hoek.isAbsolutePath('', 'linux')).to.equal(false);
2146         expect(Hoek.isAbsolutePath('a', 'linux')).to.equal(false);
2147         expect(Hoek.isAbsolutePath('./a', 'linux')).to.equal(false);
2148         expect(Hoek.isAbsolutePath('/a', 'linux')).to.equal(true);
2149         expect(Hoek.isAbsolutePath('/', 'linux')).to.equal(true);
2150
2151         Path.isAbsolute = orig;
2152
2153         done();
2154     });
2155
2156     it('identifies if path is absolute on Windows without node support', { parallel: false }, function (done) {
2157
2158         var orig = Path.isAbsolute;
2159         Path.isAbsolute = undefined;
2160
2161         expect(Hoek.isAbsolutePath('//server/file', 'win32')).to.equal(true);
2162         expect(Hoek.isAbsolutePath('//server/file', 'win32')).to.equal(true);
2163         expect(Hoek.isAbsolutePath('\\\\server\\file', 'win32')).to.equal(true);
2164         expect(Hoek.isAbsolutePath('C:/Users/', 'win32')).to.equal(true);
2165         expect(Hoek.isAbsolutePath('C:\\Users\\', 'win32')).to.equal(true);
2166         expect(Hoek.isAbsolutePath('C:cwd/another', 'win32')).to.equal(false);
2167         expect(Hoek.isAbsolutePath('C:cwd\\another', 'win32')).to.equal(false);
2168         expect(Hoek.isAbsolutePath('directory/directory', 'win32')).to.equal(false);
2169         expect(Hoek.isAbsolutePath('directory\\directory', 'win32')).to.equal(false);
2170
2171         Path.isAbsolute = orig;
2172
2173         done();
2174     });
2175 });
2176
2177 describe('isInteger()', function () {
2178
2179     it('validates integers', function (done) {
2180
2181         expect(Hoek.isInteger(0)).to.equal(true);
2182         expect(Hoek.isInteger(1)).to.equal(true);
2183         expect(Hoek.isInteger(1394035612500)).to.equal(true);
2184         expect(Hoek.isInteger('0')).to.equal(false);
2185         expect(Hoek.isInteger(1.0)).to.equal(true);
2186         expect(Hoek.isInteger(1.1)).to.equal(false);
2187         done();
2188     });
2189 });
2190
2191 describe('ignore()', function () {
2192
2193     it('exists', function (done) {
2194
2195         expect(Hoek.ignore).to.exist();
2196         expect(typeof Hoek.ignore).to.equal('function');
2197         done();
2198     });
2199 });
2200
2201 describe('inherits()', function () {
2202
2203     it('exists', function (done) {
2204
2205         expect(Hoek.inherits).to.exist();
2206         expect(typeof Hoek.inherits).to.equal('function');
2207         done();
2208     });
2209 });
2210
2211 describe('format()', function () {
2212
2213     it('exists', function (done) {
2214
2215         expect(Hoek.format).to.exist();
2216         expect(typeof Hoek.format).to.equal('function');
2217         done();
2218     });
2219
2220     it('is a reference to Util.format', function (done) {
2221
2222         expect(Hoek.format('hello %s', 'world')).to.equal('hello world');
2223         done();
2224     });
2225 });
2226
2227 describe('transform()', function () {
2228
2229     var source = {
2230         address: {
2231             one: '123 main street',
2232             two: 'PO Box 1234'
2233         },
2234         zip: {
2235             code: 3321232,
2236             province: null
2237         },
2238         title: 'Warehouse',
2239         state: 'CA'
2240     };
2241
2242     var sourcesArray = [{
2243         address: {
2244             one: '123 main street',
2245             two: 'PO Box 1234'
2246         },
2247         zip: {
2248             code: 3321232,
2249             province: null
2250         },
2251         title: 'Warehouse',
2252         state: 'CA'
2253     }, {
2254         address: {
2255             one: '456 market street',
2256             two: 'PO Box 5678'
2257         },
2258         zip: {
2259             code: 9876,
2260             province: null
2261         },
2262         title: 'Garage',
2263         state: 'NY'
2264     }];
2265
2266     it('transforms an object based on the input object', function (done) {
2267
2268         var result = Hoek.transform(source, {
2269             'person.address.lineOne': 'address.one',
2270             'person.address.lineTwo': 'address.two',
2271             'title': 'title',
2272             'person.address.region': 'state',
2273             'person.address.zip': 'zip.code',
2274             'person.address.location': 'zip.province'
2275         });
2276
2277         expect(result).to.deep.equal({
2278             person: {
2279                 address: {
2280                     lineOne: '123 main street',
2281                     lineTwo: 'PO Box 1234',
2282                     region: 'CA',
2283                     zip: 3321232,
2284                     location: null
2285                 }
2286             },
2287             title: 'Warehouse'
2288         });
2289
2290         done();
2291     });
2292
2293     it('transforms an array of objects based on the input object', function (done) {
2294
2295         var result = Hoek.transform(sourcesArray, {
2296             'person.address.lineOne': 'address.one',
2297             'person.address.lineTwo': 'address.two',
2298             'title': 'title',
2299             'person.address.region': 'state',
2300             'person.address.zip': 'zip.code',
2301             'person.address.location': 'zip.province'
2302         });
2303
2304         expect(result).to.deep.equal([
2305             {
2306                 person: {
2307                     address: {
2308                         lineOne: '123 main street',
2309                         lineTwo: 'PO Box 1234',
2310                         region: 'CA',
2311                         zip: 3321232,
2312                         location: null
2313                     }
2314                 },
2315                 title: 'Warehouse'
2316             },
2317             {
2318                 person: {
2319                     address: {
2320                         lineOne: '456 market street',
2321                         lineTwo: 'PO Box 5678',
2322                         region: 'NY',
2323                         zip: 9876,
2324                         location: null
2325                     }
2326                 },
2327                 title: 'Garage'
2328             }
2329         ]);
2330
2331         done();
2332     });
2333
2334     it('uses the reach options passed into it', function (done) {
2335
2336         var schema = {
2337             'person.address.lineOne': 'address-one',
2338             'person.address.lineTwo': 'address-two',
2339             'title': 'title',
2340             'person.address.region': 'state',
2341             'person.prefix': 'person-title',
2342             'person.zip': 'zip-code'
2343         };
2344         var options = {
2345             separator: '-',
2346             default: 'unknown'
2347         };
2348         var result = Hoek.transform(source, schema, options);
2349
2350         expect(result).to.deep.equal({
2351             person: {
2352                 address: {
2353                     lineOne: '123 main street',
2354                     lineTwo: 'PO Box 1234',
2355                     region: 'CA'
2356                 },
2357                 prefix: 'unknown',
2358                 zip: 3321232
2359             },
2360             title: 'Warehouse'
2361         });
2362
2363         done();
2364     });
2365
2366     it('works to create shallow objects', function (done) {
2367
2368         var result = Hoek.transform(source, {
2369             lineOne: 'address.one',
2370             lineTwo: 'address.two',
2371             title: 'title',
2372             region: 'state',
2373             province: 'zip.province'
2374         });
2375
2376         expect(result).to.deep.equal({
2377             lineOne: '123 main street',
2378             lineTwo: 'PO Box 1234',
2379             title: 'Warehouse',
2380             region: 'CA',
2381             province: null
2382         });
2383
2384         done();
2385     });
2386
2387     it('only allows strings in the map', function (done) {
2388
2389         expect(function () {
2390
2391             var result = Hoek.transform(source, {
2392                 lineOne: {}
2393             });
2394         }).to.throw('All mappings must be "." delineated strings');
2395
2396         done();
2397     });
2398
2399     it('throws an error on invalid arguments', function (done) {
2400
2401         expect(function () {
2402
2403             var result = Hoek.transform(NaN, {});
2404         }).to.throw('Invalid source object: must be null, undefined, an object, or an array');
2405
2406         done();
2407     });
2408
2409     it('is safe to pass null', function (done) {
2410
2411         var result = Hoek.transform(null, {});
2412         expect(result).to.deep.equal({});
2413
2414         done();
2415     });
2416
2417     it('is safe to pass undefined', function (done) {
2418
2419         var result = Hoek.transform(undefined, {});
2420         expect(result).to.deep.equal({});
2421
2422         done();
2423     });
2424 });
2425
2426 describe('uniqueFilename()', function () {
2427
2428     it('generates a random file path', function (done) {
2429
2430         var result = Hoek.uniqueFilename('./test/modules');
2431
2432         expect(result).to.exist();
2433         expect(result).to.be.a.string();
2434         expect(result).to.contain('test/modules');
2435         done();
2436     });
2437
2438     it('is random enough to use in fast loops', function (done) {
2439
2440         var results = [];
2441
2442         for (var i = 0; i < 10; ++i) {
2443             results[i] = Hoek.uniqueFilename('./test/modules');
2444         }
2445
2446         var filter = results.filter(function (item, index, array) {
2447
2448             return array.indexOf(item) === index;
2449         });
2450
2451         expect(filter.length).to.equal(10);
2452         expect(results.length).to.equal(10);
2453         done();
2454
2455     });
2456
2457     it('combines the random elements with a supplied character', function (done) {
2458
2459         var result = Hoek.uniqueFilename('./test', 'txt');
2460
2461         expect(result).to.contain('test/');
2462         expect(result).to.contain('.txt');
2463
2464         done();
2465     });
2466
2467     it('accepts extensions with a "." in it', function (done) {
2468
2469         var result = Hoek.uniqueFilename('./test', '.mp3');
2470
2471         expect(result).to.contain('test/');
2472         expect(result).to.contain('.mp3');
2473
2474         done();
2475     });
2476 });
2477
2478 describe('stringify()', function (done) {
2479
2480     it('converts object to string', function (done) {
2481
2482         var obj = { a: 1 };
2483         expect(Hoek.stringify(obj)).to.equal('{"a":1}');
2484         done();
2485     });
2486
2487     it('returns error in result string', function (done) {
2488
2489         var obj = { a: 1 };
2490         obj.b = obj;
2491         expect(Hoek.stringify(obj)).to.equal('[Cannot display object: Converting circular structure to JSON]');
2492         done();
2493     });
2494 });
2495
2496 describe('shallow()', function (done) {
2497
2498     it('shallow copies an object', function (done) {
2499
2500         var obj = {
2501             a: 5,
2502             b: {
2503                 c: 6
2504             }
2505         };
2506
2507         var shallow = Hoek.shallow(obj);
2508         expect(shallow).to.not.equal(obj);
2509         expect(shallow).to.deep.equal(obj);
2510         expect(shallow.b).to.equal(obj.b);
2511         done();
2512     });
2513 });