var treeify = require('../treeify'), vows = require('vows'), assert = require('assert'), events = require('events'); // - helper functions ----------------- function treeifyByLineGuts(args) { var emitter = new events.EventEmitter(), lineNum = 0; args.push(function(line) { emitter.emit('success', line); emitter.emit('line ' + (++lineNum), line); }); treeify.asLines.apply(this, args); return emitter; } function treeifyByLine(obj) { return function(showValues) { var arguments = [ obj, showValues ]; return treeifyByLineGuts(arguments); }; } function treeifyByLineWithHideFunctionsArgument(obj) { return function(showValues, hideFunctions) { var arguments = [ obj, showValues, hideFunctions ]; return treeifyByLineGuts(arguments); }; } function treeifyEntirely(obj) { return function(showValues, hideFunctions) { return treeify.asTree(obj, showValues, hideFunctions); }; } function withValuesShown(showValues) { return function(func){ return func(showValues, false) }; } function withValuesShownFunctionsHidden() { return function(func){ return func(true, true) }; } function is(content, arrayIndex) { return function(lines) { var toCheck = lines; if (arrayIndex !== undefined) { toCheck = lines[arrayIndex]; } assert.strictEqual(toCheck, content, 'should be "' + content + '" but was "' + toCheck + '"'); }; } function checkLines(/* ... */) { var ret = {}, entry; for (var line = 1; line <= arguments.length; line++) { if ( ! arguments[line - 1]) continue; entry = {}; entry['branches correctly on line '+line] = is(arguments[line - 1]); ret['line '+line] = entry; } return ret; } // - the beautiful test suite --------- vows.describe('tree-test').addBatch({ 'A tree created from an empty object': { topic: {}, 'when returned as a whole tree': { topic: treeifyEntirely, 'with values hidden': { topic: withValuesShown(false), 'is an empty string': is('') }, 'with values shown': { topic: withValuesShown(true), 'is an empty string': is('') } } }, 'A tree created from a single-level object': { topic: { apples: 'gala', // ├─ apples: gala oranges: 'mandarin' // └─ oranges: mandarin }, 'when returned line-by-line': { topic: treeifyByLine, 'with values hidden': { topic: withValuesShown(false), 'is two lines long': function(err, line) { this.expect(2); }, on: checkLines('├─ apples', '└─ oranges') }, 'with values shown': { topic: withValuesShown(true), 'is two lines long': function(err, line) { this.expect(2); }, on: checkLines('├─ apples: gala', '└─ oranges: mandarin') } }, 'when returned as a whole tree': { topic: treeifyEntirely, 'with values hidden': { topic: withValuesShown(false), 'is not empty': function(tree) { assert.notEqual(tree, '', 'should not be empty'); }, 'contains 2 line breaks': function(tree) { assert.strictEqual(tree.match(/\n/g).length, 2, 'should contain 2 x \n'); }, '(split into an array of lines)': { topic: function(tree) { return tree.split(/\n/g) }, 'has a correct first line': is('├─ apples', 0), 'has a correct second line': is('└─ oranges', 1), 'has nothing at the end': is('', 2) } }, 'with values shown': { topic: withValuesShown(true), 'is not empty': function(tree) { assert.notEqual(tree, '', 'should not be empty'); }, 'contains 2 line breaks': function(tree) { assert.strictEqual(tree.match(/\n/g).length, 2, 'should contain 2 x \n'); }, '(split into an array of lines)': { topic: function(tree) { return tree.split(/\n/g) }, 'has a correct first line': is('├─ apples: gala', 0), 'has a correct second line': is('└─ oranges: mandarin', 1), 'has nothing at the end': is('', 2) } } } }, 'A tree created from a multi-level object': { topic: { oranges: { // ├─ oranges 'mandarin': { // │ └─ mandarin clementine: null, // │ ├─ clementine tangerine: // │ └─ tangerine 'so cheap and juicy!' } }, apples: { // └─ apples 'gala': null, // ├─ gala 'pink lady': null // └─ pink lady } }, 'when returned line-by-line': { topic: treeifyByLine, 'with values hidden': { topic: withValuesShown(false), 'is seven lines long': function(err, line) { this.expect(7); }, on: checkLines('├─ oranges', '│ └─ mandarin', '│ ├─ clementine', '│ └─ tangerine', '└─ apples', ' ├─ gala', ' └─ pink lady') }, 'with values shown': { topic: withValuesShown(true), on: checkLines(null, null, null, '│ └─ tangerine: so cheap and juicy!') } }, 'when returned as a whole tree': { topic: treeifyEntirely, 'with values shown': { topic: withValuesShown(true), '(split into an array of lines)': { topic: function(tree) { return tree.split(/\n/g) }, 'has a correct first line': is('├─ oranges', 0), 'has a correct third line': is('│ └─ tangerine: so cheap and juicy!', 3), 'has nothing at the end': is('', 7) } } } }, 'A tree created from an object with not so circular references': { topic: function() { var obj = { one: 'one', two: { four: 'four' } }; obj['three'] = obj.two; return obj; }, 'when returned line-by-line': { topic: treeifyByLine, 'with values shown': { topic: withValuesShown(true), on: checkLines('├─ one: one', '├─ two', '│ └─ four: four', '└─ three', ' └─ four: four') } } }, 'A tree created from an object with circular references': { topic: function() { var obj = { one: 'one', two: 'two' }; obj['three'] = obj; return obj; }, 'when returned line-by-line': { topic: treeifyByLine, 'with values shown': { topic: withValuesShown(true), on: checkLines('├─ one: one', '├─ two: two', '└─ three (circular ref.)') } } }, 'A tree created from an object containing various types': { topic: { array: [ 'one', 'two' ], numeric: 42, decimal: 42.24, bool: false, nil: null, undef: undefined, date: new Date(2018,0,1) }, 'when returned line-by-line': { topic: treeifyByLine, 'with values shown': { topic: withValuesShown(true), on: checkLines('├─ array', '│ ├─ 0: one', '│ └─ 1: two', '├─ numeric: 42', '├─ decimal: 42.24', '├─ bool: false', '├─ nil', '├─ undef: undefined', '└─ date: Mon Jan 01 2018 00:00:00 GMT+0000 (UTC)') } } }, 'A tree created from an object with prototyped functions': { topic: function() { var func = function(){ this.Friendly = 'stuff'; } func.prototype.Nasty = function(){} return new func(); }, 'when returned as a whole tree': { topic: treeifyEntirely, 'with values shown': { topic: withValuesShown(true), 'and split into an array of lines': { topic: function(tree) { return tree.split(/\n/g) }, 'is a one liner output (with a following blank line)': function(lines) { assert.equal(lines.length, 2); }, 'has a correct first line': is('└─ Friendly: stuff', 0) } } } }, 'A tree with functions': { topic: { func:function(){}, Friendly:"stuff", Another:"stuff" }, 'when returned line-by-line': { topic: treeifyByLineWithHideFunctionsArgument, 'with values shown, but functions hidden': { topic: withValuesShownFunctionsHidden(), 'is two lines long': function(err, line) { this.expect(2); }, on: checkLines('├─ Friendly: stuff', '└─ Another: stuff') } }, 'when returned as a whole tree': { topic: treeifyEntirely, 'with values shown, but functions hidden': { topic: withValuesShownFunctionsHidden(), 'and split into an array of lines': { topic: function(tree) { console.error(tree); return tree.split(/\n/g) }, 'is a one liner output (with a following blank line)': function(lines) { assert.equal(lines.length, 3); }, 'has a correct first line': is('├─ Friendly: stuff', 0) } } } } }).export(module);