├── .gitmodules ├── .travis.yml ├── History.md ├── License.md ├── Readme.md ├── assert.js ├── logger.js ├── package.json ├── test.js ├── test ├── assert.js ├── async.js ├── custom-asserts.js ├── fail-fast.js ├── index.js └── utils │ └── logger.js └── utils.js /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "engines/node/ansi-font"] 2 | path = engines/node/ansi-font 3 | url = https://github.com/Gozala/ansi-font.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 0.6.0 / 2012-11-24 4 | 5 | - Fix assertion for error objects to ignore stack traces on PhantomJS 6 | & to make node does asserts them properly. 7 | 8 | ## 0.5.2 / 2012-10-31 9 | 10 | - Exit only when `process.exit` is defined. This enables use of browseryfied 11 | tests in browser. 12 | - Start testing with PanthomJS in a browser. 13 | 14 | ## 0.5.1 / 2012-10-31 15 | 16 | - Fix bug introduced in 0.5.0 that exited process with a wrong code. 17 | - Add `assert.end` function as an alternative to `done` callback. 18 | - Change module layout to match better node conventions. 19 | 20 | ## 0.5.0 / 2012-10-31 21 | 22 | - Switch to logging via `console.log` instead of `process.stdout` for 23 | better compatibility with browserify. 24 | - Exit process with error code if test fails, or with `0` if not. 25 | 26 | ## 0.4.4 / 2012-01-15 27 | 28 | - Improve `assert.throws` API to accept exception as an argument. 29 | 30 | ## 0.4.3 / 2011-11-15 31 | 32 | - Use newer version of ansi-font library. 33 | 34 | ## 0.4.2 / 2011-11-15 35 | 36 | - Use bug.url to avoid warning form NPM. 37 | 38 | ## 0.4.1 / 2011-07-18 ## 39 | 40 | - Improved error reporting. 41 | 42 | ## 0.4.0 / 2011-07-10 ## 43 | 44 | - Support for browser runtime. 45 | 46 | ## 0.3.0 / 2011-07-10 ## 47 | 48 | - Switching to Uncommonjs spec. 49 | - Simplifying implementation. 50 | 51 | ## 0.2.1 / 2011-06-10 ## 52 | 53 | - RegExp support for `assert.throws`. 54 | 55 | ## 0.2.0 / 2011-06-07 ## 56 | 57 | - Code refactoring & package restructuring. 58 | - Processing code through jsbeautifier for better readability. 59 | 60 | ## 0.1.1 / 2011-04-02 ## 61 | 62 | - Bugfix for `assert.throws` regression. 63 | 64 | ## 0.1.0 / 2011-02-24 ## 65 | 66 | - Package restructuring to support node@0.4.0 67 | 68 | ## 0.0.11 / 2011-02-16 ## 69 | 70 | - Removing 'use strict' where octal escape sequences were used. 71 | 72 | ## 0.0.10 / 2010-11-11 ## 73 | 74 | - Improved error reporting. 75 | 76 | ## 0.0.9 / 2010-11-01 ## 77 | 78 | - Switching to MIT license. 79 | - Fix for `deepEqual` 80 | 81 | ## 0.0.8 / 2010-10-23 ## 82 | 83 | - Even more improved fail reports. 84 | 85 | ## 0.0.7 / 2010-10-23 ## 86 | 87 | - Improved fail reports. 88 | 89 | ## 0.0.6 / 2010-10-08 ## 90 | 91 | - Improving project documentation. 92 | 93 | ## 0.0.5 / 2010-10-07 ## 94 | 95 | - Improved fail and error messages. 96 | 97 | ## 0.0.4 / 2010-10-07 ## 98 | 99 | - Adding more tests for custom asserts. 100 | 101 | ## 0.0.3 / 2010-10-06 ## 102 | 103 | - Rename `raises` bach to `throws` to follow the spec. 104 | 105 | ## 0.0.2 / 2010-09-29 ## 106 | 107 | - Bugfix. 108 | - Adding test. 109 | - Support for async tests. 110 | 111 | ## 0.0.1 / 2010-09-25 ## 112 | 113 | - Unit test runner port from narwhal. 114 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2011 Irakli Gozalishvili. All rights reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # (Un)commonJS unit test runner 2 | 3 | Implementation of [(Un)commonJS unit test runner][UncommonJS unit test runner]. 4 | 5 | [![build status](https://secure.travis-ci.org/Gozala/test-commonjs.png)](http://travis-ci.org/Gozala/test-commonjs) 6 | 7 | ## Testing 8 | 9 | In order to make your package testable from [npm] you should: 10 | 11 | - Create a directory in your package root. 12 | - Define test directory in package descriptor under `directories` section. 13 | - Define test script in package descriptor under `scripts` section. 14 | - Define dependency on this package (It's name is "test" in [npm] registry). 15 | - Write your tests 16 | - Test your package by running all tests `npm test` 17 | or run individual tests `node ./path/to/test/group.js` 18 | 19 | ## Example 20 | 21 | ### package.json 22 | 23 | ```js 24 | { 25 | "name": "mypackage", 26 | "version": "0.7.0", 27 | "description": "Sample package", 28 | "scripts": { "test": "node test/all.js" }, 29 | "devDependencies": { "test": ">=0.0.5" } 30 | } 31 | ``` 32 | 33 | ### Async test 34 | 35 | ```js 36 | // if test function expects second named argument it will be executed 37 | // in async mode and test will be complete only after callback is called 38 | exports['test my async foo'] = function(assert, done) { 39 | var http = require('http') 40 | var google = http.createClient(80, 'www.jeditoolkit.com') 41 | var request = google.request('GET', '/', {'host': 'www.jeditoolkit.com'}) 42 | request.end() 43 | request.on('response', function (response) { 44 | assert.equal(response.statusCode, 302, 'must redirect') // will log result 45 | response.setEncoding('utf8') 46 | response.on('data', function (chunk) { 47 | assert.notEqual(chunk, 'helo world', 'must be something more inteligent') 48 | done() // telling test runner that we're done with this test 49 | }) 50 | }) 51 | } 52 | 53 | if (module == require.main) require('test').run(exports) 54 | ``` 55 | 56 | ### Sync test 57 | 58 | ```js 59 | // using assert passed to the test function that just logs failures 60 | exports['test that logs all failures'] = function(assert) { 61 | assert.equal(2 + 2, 5, 'assert failure is logged') 62 | assert.equal(3 + 2, 5, 'assert pass is logged') 63 | } 64 | 65 | if (module == require.main) require('test').run(exports) 66 | ``` 67 | 68 | ### Fast fail 69 | 70 | ```js 71 | // using nodejs's build in asserts that throw on failure 72 | var assert = require('assert') 73 | 74 | exports['test that stops execution on first failure'] = function() { 75 | assert.equal(2 + 2, 5, 'assert fails and test execution stop here') 76 | assert.equal(3 + 2, 5, 'will never pass this since test failed above') 77 | } 78 | 79 | if (module == require.main) require('test').run(exports) 80 | ``` 81 | 82 | ### Custom assertions 83 | 84 | ```js 85 | var AssertBase = require('assert').Assert 86 | var AssertDescriptor = { 87 | constructor: { value: Assert }, 88 | inRange: { value: function (lower, inner, upper, message) { 89 | if (lower < inner && inner < upper) { 90 | this.fail({ 91 | actual: inner, 92 | expected: lower + '> ' + ' < ' + upper, 93 | operator: "inRange", 94 | message: message 95 | }) 96 | } else { 97 | this.pass(message); 98 | } 99 | }, enumerable: true } 100 | } 101 | function Assert() { 102 | return Object.create(AssertBase.apply(null, arguments), AssertDescriptor) 103 | } 104 | 105 | // bundling custom asserts with test suite 106 | exports.Assert = Assert 107 | exports['test with custom asserts'] = function(assert) { 108 | assert.inRange(2, 3, 5, 'passes assert and logs') 109 | assert.equal(3 + 2, 5, 'assert pass is logged') 110 | } 111 | 112 | if (module == require.main) require('test').run(exports) 113 | ``` 114 | 115 | For more examples checkout tests for this package and for more details see 116 | the [UncommonJS unit test runner] specification. 117 | 118 | [UncommonJS unit test runner]:https://github.com/kriskowal/uncommonjs/blob/master/tests/specification.md 119 | [npm]:http://npmjs.org/ 120 | -------------------------------------------------------------------------------- /assert.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var utils = require("./utils") 4 | 5 | 6 | /** 7 | * The `AssertionError` is defined in assert. 8 | * @extends Error 9 | * @example 10 | * new assert.AssertionError({ 11 | * message: message, 12 | * actual: actual, 13 | * expected: expected 14 | * }) 15 | */ 16 | function AssertionError(options) { 17 | var assertionError = Object.create(AssertionError.prototype); 18 | 19 | if (utils.isString(options)) 20 | options = { message: options }; 21 | if ("actual" in options) 22 | assertionError.actual = options.actual; 23 | if ("expected" in options) 24 | assertionError.expected = options.expected; 25 | if ("operator" in options) 26 | assertionError.operator = options.operator; 27 | 28 | assertionError.message = options.message; 29 | assertionError.stack = new Error().stack; 30 | return assertionError; 31 | } 32 | AssertionError.prototype = Object.create(Error.prototype, { 33 | constructor: { value: AssertionError }, 34 | name: { value: "AssertionError", enumerable: true }, 35 | toString: { value: function toString() { 36 | var value; 37 | if (this.message) { 38 | value = this.name + " : " + this.message; 39 | } 40 | else { 41 | value = [ 42 | this.name + " : ", 43 | utils.source(this.expected), 44 | this.operator, 45 | utils.source(this.actual) 46 | ].join(" "); 47 | } 48 | return value; 49 | }} 50 | }); 51 | exports.AssertionError = AssertionError; 52 | 53 | function Assert(logger) { 54 | return Object.create(Assert.prototype, { _log: { value: logger }}); 55 | } 56 | Assert.prototype = { 57 | fail: function fail(e) { 58 | this._log.fail(e); 59 | }, 60 | pass: function pass(message) { 61 | this._log.pass(message); 62 | }, 63 | error: function error(e) { 64 | this._log.error(e); 65 | }, 66 | ok: function ok(value, message) { 67 | if (!!!value) { 68 | this.fail({ 69 | actual: value, 70 | expected: true, 71 | message: message, 72 | operator: "==" 73 | }); 74 | } 75 | else { 76 | this.pass(message); 77 | } 78 | }, 79 | 80 | /** 81 | * The equality assertion tests shallow, coercive equality with `==`. 82 | * @example 83 | * assert.equal(1, 1, "one is one"); 84 | */ 85 | equal: function equal(actual, expected, message) { 86 | if (actual == expected) { 87 | this.pass(message); 88 | } 89 | else { 90 | this.fail({ 91 | actual: actual, 92 | expected: expected, 93 | message: message, 94 | operator: "==" 95 | }); 96 | } 97 | }, 98 | 99 | /** 100 | * The non-equality assertion tests for whether two objects are not equal 101 | * with `!=`. 102 | * @example 103 | * assert.notEqual(1, 2, "one is not two"); 104 | */ 105 | notEqual: function notEqual(actual, expected, message) { 106 | if (actual != expected) { 107 | this.pass(message); 108 | } 109 | else { 110 | this.fail({ 111 | actual: actual, 112 | expected: expected, 113 | message: message, 114 | operator: "!=", 115 | }); 116 | } 117 | }, 118 | 119 | /** 120 | * The equivalence assertion tests a deep (with `===`) equality relation. 121 | * @example 122 | * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects") 123 | */ 124 | deepEqual: function deepEqual(actual, expected, message) { 125 | if (isDeepEqual(actual, expected)) { 126 | this.pass(message); 127 | } 128 | else { 129 | this.fail({ 130 | actual: actual, 131 | expected: expected, 132 | message: message, 133 | operator: "deepEqual" 134 | }); 135 | } 136 | }, 137 | 138 | /** 139 | * The non-equivalence assertion tests for any deep (with `===`) inequality. 140 | * @example 141 | * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }), 142 | * "object"s inherit from different prototypes"); 143 | */ 144 | notDeepEqual: function notDeepEqual(actual, expected, message) { 145 | if (!isDeepEqual(actual, expected)) { 146 | this.pass(message); 147 | } 148 | else { 149 | this.fail({ 150 | actual: actual, 151 | expected: expected, 152 | message: message, 153 | operator: "notDeepEqual" 154 | }); 155 | } 156 | }, 157 | 158 | /** 159 | * The strict equality assertion tests strict equality, as determined by 160 | * `===`. 161 | * @example 162 | * assert.strictEqual(null, null, "`null` is `null`") 163 | */ 164 | strictEqual: function strictEqual(actual, expected, message) { 165 | if (actual === expected) { 166 | this.pass(message); 167 | } 168 | else { 169 | this.fail({ 170 | actual: actual, 171 | expected: expected, 172 | message: message, 173 | operator: "===" 174 | }); 175 | } 176 | }, 177 | 178 | /** 179 | * The strict non-equality assertion tests for strict inequality, as 180 | * determined by `!==`. 181 | * @example 182 | * assert.notStrictEqual(null, undefined, "`null` is not `undefined`"); 183 | */ 184 | notStrictEqual: function notStrictEqual(actual, expected, message) { 185 | if (actual !== expected) { 186 | this.pass(message); 187 | } 188 | else { 189 | this.fail({ 190 | actual: actual, 191 | expected: expected, 192 | message: message, 193 | operator: "!==" 194 | }) 195 | } 196 | }, 197 | 198 | /** 199 | * The assertion whether or not given `block` throws an exception. If optional 200 | * `Error` argument is provided and it"s type of function thrown error is 201 | * asserted to be an instance of it, if type of `Error` is string then message 202 | * of throw exception is asserted to contain it. 203 | * @param {Function} block 204 | * Function that is expected to throw. 205 | * @param {Error|RegExp} [Error] 206 | * Error constructor that is expected to be thrown or a string that 207 | * must be contained by a message of the thrown exception, or a RegExp 208 | * matching a message of the thrown exception. 209 | * @param {String} message 210 | * Description message 211 | * 212 | * @examples 213 | * 214 | * assert.throws(function block() { 215 | * doSomething(4) 216 | * }, "Object is expected", "Incorrect argument is passed"); 217 | * 218 | * assert.throws(function block() { 219 | * Object.create(5) 220 | * }, TypeError, "TypeError is thrown"); 221 | */ 222 | throws: function throws(block, Error, message) { 223 | var threw = false; 224 | var exception = null; 225 | 226 | // If third argument is not provided and second argument is a string it 227 | // means that optional `Error` argument was not passed, so we shift 228 | // arguments. 229 | if (utils.isString(Error) && utils.isUndefined(message)) { 230 | message = Error; 231 | Error = undefined; 232 | } 233 | 234 | // Executing given `block`. 235 | try { 236 | block(); 237 | } 238 | catch (e) { 239 | threw = true; 240 | exception = e; 241 | } 242 | 243 | // If exception was thrown and `Error` argument was not passed assert is 244 | // passed. 245 | if (threw && (utils.isUndefined(Error) || 246 | // If Error is thrown exception 247 | (Error == exception) || 248 | // If passed `Error` is RegExp using it"s test method to 249 | // assert thrown exception message. 250 | (utils.isRegExp(Error) && Error.test(exception.message)) || 251 | // If passed `Error` is a constructor function testing if 252 | // thrown exception is an instance of it. 253 | (utils.isFunction(Error) && utils.instanceOf(exception, Error)))) 254 | { 255 | this.pass(message); 256 | } 257 | 258 | // Otherwise we report assertion failure. 259 | else { 260 | var failure = { 261 | message: message, 262 | operator: "throws" 263 | }; 264 | 265 | if (exception) 266 | failure.actual = exception; 267 | 268 | if (Error) 269 | failure.expected = Error; 270 | 271 | this.fail(failure); 272 | } 273 | } 274 | }; 275 | exports.Assert = Assert; 276 | 277 | function isDeepEqual(actual, expected) { 278 | // 7.1. All identical values are equivalent, as determined by ===. 279 | if (actual === expected) { 280 | return true; 281 | } 282 | 283 | // 7.2. If the expected value is a Date object, the actual value is 284 | // equivalent if it is also a Date object that refers to the same time. 285 | else if (utils.isDate(actual) && utils.isDate(expected)) { 286 | return actual.getTime() === expected.getTime(); 287 | } 288 | 289 | // XXX specification bug: this should be specified 290 | else if (utils.isPrimitive(actual) || utils.isPrimitive(expected)) { 291 | return expected === actual; 292 | } 293 | 294 | else if (utils.instanceOf(actual, Error) || 295 | utils.instanceOf(expected, Error)) { 296 | return actual.message === expected.message && 297 | actual.type === expected.type && 298 | actual.name === expected.name && 299 | (actual.constructor && expected.constructor && 300 | actual.constructor.name === expected.constructor.name) 301 | } 302 | 303 | // 7.3. Other pairs that do not both pass typeof value == "object", 304 | // equivalence is determined by ==. 305 | else if (!utils.isObject(actual) && !utils.isObject(expected)) { 306 | return actual == expected; 307 | } 308 | 309 | // 7.4. For all other Object pairs, including Array objects, equivalence is 310 | // determined by having the same number of owned properties (as verified 311 | // with Object.prototype.hasOwnProperty.call), the same set of keys 312 | // (although not necessarily the same order), equivalent values for every 313 | // corresponding key, and an identical "prototype" property. Note: this 314 | // accounts for both named and indexed properties on Arrays. 315 | else { 316 | return actual.prototype === expected.prototype && 317 | isEquivalent(actual, expected); 318 | } 319 | } 320 | 321 | function isEquivalent(a, b, stack) { 322 | return isArrayEquivalent(Object.keys(a).sort(), 323 | Object.keys(b).sort()) && 324 | Object.keys(a).every(function(key) { 325 | return isDeepEqual(a[key], b[key], stack) 326 | }); 327 | } 328 | 329 | function isArrayEquivalent(a, b, stack) { 330 | return utils.isArray(a) && utils.isArray(b) && a.length === b.length && 331 | a.every(function(value, index) { 332 | return isDeepEqual(value, b[index]); 333 | }); 334 | } 335 | -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var font = require("ansi-font/index") 4 | var toSource = require("./utils").source 5 | 6 | var INDENT = " " 7 | 8 | var report = console.log.bind(console) 9 | 10 | function passed(message) { 11 | return font.green("\u2713 " + message) 12 | } 13 | function failed(message) { 14 | return font.red("\u2717 " + message) 15 | } 16 | function errored(message) { 17 | return font.magenta("\u26A1 " + message) 18 | } 19 | 20 | function indent(message, indentation) { 21 | indentation = undefined === indentation ? INDENT : indentation 22 | message = message || "" 23 | return message.replace(/^/gm, indentation) 24 | } 25 | 26 | function Logger(options) { 27 | if (!(this instanceof Logger)) return new Logger(options) 28 | 29 | options = options || {} 30 | var print = options.print || report 31 | var indentation = options.indentation || "" 32 | var results = options.results || { passes: [], fails: [], errors: [] } 33 | this.passes = results.passes 34 | this.fails = results.fails 35 | this.errors = results.errors 36 | results = this 37 | 38 | 39 | this.pass = function pass(message) { 40 | results.passes.push(message) 41 | print(indent(passed(message), indentation)) 42 | } 43 | 44 | this.fail = function fail(error) { 45 | results.fails.push(error) 46 | var message = error.message 47 | if ("expected" in error) 48 | message += "\n Expected: \n" + toSource(error.expected, INDENT) 49 | if ("actual" in error) 50 | message += "\n Actual: \n" + toSource(error.actual, INDENT) 51 | if ("operator" in error) 52 | message += "\n Operator: " + toSource(error.operator, INDENT) 53 | print(indent(failed(message), indentation)) 54 | } 55 | 56 | this.error = function error(exception) { 57 | results.errors.push(exception) 58 | print(indent(errored(exception.stack || exception), indentation)) 59 | } 60 | 61 | this.section = function section(title) { 62 | print(indent(title, indentation)) 63 | return new Logger({ 64 | print: print, 65 | indentation: indent(indentation), 66 | results: results 67 | }) 68 | } 69 | 70 | this.report = function report() { 71 | print("Passed:" + results.passes.length + 72 | " Failed:" + results.fails.length + 73 | " Errors:" + results.errors.length) 74 | } 75 | } 76 | 77 | Logger.Logger = Logger 78 | module.exports = Logger 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.6.0", 4 | "description": "(Un)CommonJS test runner.", 5 | "keywords": [ 6 | "test", 7 | "commonjs", 8 | "uncommonjs", 9 | "unit" 10 | ], 11 | "homepage": "https://github.com/Gozala/test-commonjs/", 12 | "author": "Irakli Gozalishvili (http://jeditoolkit.com)", 13 | "contributors": [ 14 | "Irakli Gozalishvili (http://jeditoolkit.com)", 15 | "Kris Kowal (http://github.com/kriskowal/)", 16 | "Zach Carter", 17 | "Felix Geisendörfer", 18 | "Karl Guertin", 19 | "Ash Berlin", 20 | "Francois Lafortune" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/Gozala/test-commonjs.git", 25 | "web": "https//github.com/Gozala/test-commonjs" 26 | }, 27 | "bugs": { 28 | "url": "http://github.com/Gozala/test-commonjs/issues/" 29 | }, 30 | "scripts": { 31 | "test": "npm run test-node && npm run test-browser", 32 | "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/index.js", 33 | "test-node": "node ./test/index.js" 34 | }, 35 | "main": "./test.js", 36 | "licenses": [ 37 | { 38 | "type": "MIT", 39 | "url": "https://github.com/Gozala/test-commonjs/License.md" 40 | } 41 | ], 42 | "dependencies": { 43 | "ansi-font": "0.0.2" 44 | }, 45 | "devDependencies": { 46 | "phantomify": "~0.x.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Assert = require("./assert").Assert 4 | var Logger = require("./logger").Logger 5 | 6 | 7 | var ERR_COMPLETED_ASSERT = "Assert in completed test" 8 | var ERR_COMPLETED_COMPLETE = "Attemt to complete test more then one times" 9 | var ERR_EXPECT = "AssertionError" 10 | 11 | 12 | /** 13 | * Creates a test function. 14 | */ 15 | function Test(name, unit, logger, Assert) { 16 | var isSync = unit.length <= 1 17 | var isFailFast = !unit.length 18 | var isDone = false 19 | return function test(next) { 20 | logger = logger.section(name) 21 | var assert = Assert(logger) 22 | assert.end = function end() { 23 | if (isDone) return logger.error(Error(ERR_COMPLETED_COMPLETE)) 24 | isDone = true 25 | next() 26 | } 27 | 28 | try { 29 | var result = unit(assert, assert.end) 30 | // If it"s async test that returns a promise. 31 | if (result && typeof(result.then) === "function") { 32 | result.then(function passed() { 33 | logger.pass("passed") 34 | assert.end() 35 | }, function failed(reason) { 36 | logger.fail(reason) 37 | assert.end() 38 | }) 39 | } else { 40 | if (isFailFast) logger.pass("passed") 41 | if (isSync) assert.end() 42 | } 43 | } catch (exception) { 44 | if (ERR_EXPECT === exception.name) assert.fail(exception) 45 | else logger.error(exception) 46 | assert.end() 47 | } 48 | } 49 | } 50 | 51 | function isTest(name) { return name.indexOf("test") === 0 } 52 | 53 | /** 54 | * Creates a test suite / group. Calling returned function will execute 55 | * all test in the given suite. 56 | */ 57 | function Suite(name, units, logger, Assert) { 58 | // Collecting properties that represent test functions or suits. 59 | var names = Object.keys(units).filter(isTest) 60 | Assert = units.Assert || Assert 61 | // Returning a function that executes all test in this suite and all it"s 62 | // sub-suits. 63 | return function suite(end) { 64 | // Chaining test / suits so that each is executed after last is done. 65 | function next() { 66 | if (!names.length) return end() 67 | var name = names.shift() 68 | var unit = Unit(name, units[name], logger, units.Assert || Assert) 69 | unit(next) 70 | } 71 | next((logger = logger.section(name))) 72 | } 73 | } 74 | function Unit(name, units, logger, Assert) { 75 | return typeof(units) === "function" ? Test(name, units, logger, Assert) 76 | : Suite(name, units, logger, Assert) 77 | } 78 | 79 | 80 | /** 81 | * Test runner function. 82 | */ 83 | exports.run = function run(units, logger) { 84 | var exit = logger ? false : true 85 | logger = logger || new Logger() 86 | var unit = Unit("Running all tests:", units, logger, Assert) 87 | unit(function done() { 88 | logger.report() 89 | var failed = logger.errors.length !== 0 || logger.fails.length !== 0 90 | // Exit only if `process.exit` exist and if no logger was provided. 91 | if (exit && process.exit) process.exit(failed ? 1 : 0) 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /test/assert.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var run = require("../test").run 4 | var Logger = require("./utils/logger").Logger 5 | 6 | exports["test existence of all assert methods on assert"] = function (assert) { 7 | var functionType, methods; 8 | functionType = "function" 9 | methods = ["ok", "equal", "notEqual", "deepEqual", "notDeepEqual", "throws"] 10 | run({ 11 | "test fixture": function (assert) { 12 | methods.forEach(function (name) { 13 | assert.equal(typeof assert[name], functionType, 14 | "`" + name + "` must be method of `assert`") 15 | }) 16 | } 17 | }, new Logger(function(passes) { 18 | assert.equal(passes.length, methods.length, "all methdos were found") 19 | })) 20 | } 21 | 22 | exports["test correctness of `assert.ok`"] = function (assert) { 23 | var valid, invalid, report 24 | valid = [1, -9, true, ",.", {}, function () {}, ["1"], Infinity] 25 | invalid = [null, undefined, false, "", 0, NaN] 26 | report = null 27 | 28 | run({ 29 | "test fixture": function (assert) { 30 | valid.forEach(function (value, index) { 31 | assert.ok(value, value + " is valid") 32 | }) 33 | 34 | invalid.forEach(function (value, index) { 35 | assert.ok(value, value + " is invalid") 36 | }) 37 | } 38 | }, Logger(function(passes, fails, errors) { 39 | assert.equal(passes.length, valid.length, 40 | "Amount of passed test must match amount of valid inputs") 41 | assert.equal(fails.length, invalid.length, 42 | "Amount of failed test must match amount of invalid inputs") 43 | assert.equal(errors.length, 0, "Must be no errors") 44 | })) 45 | } 46 | 47 | exports["test correctness of `assert.equal`"] = function (assert) { 48 | var valid, invalid 49 | 50 | valid = [ 51 | [1, 1], 52 | [450, 450], 53 | ["string", "" + "s" + "tring"], 54 | [undefined, 55 | {}.oops], 56 | [null, null], 57 | [String("test"), "test"], 58 | [String("test"), String("test")], 59 | [null, undefined], 60 | [1, true], 61 | [2 / 0, Infinity], 62 | [JSON.stringify({ 63 | bla: 3 64 | }), JSON.stringify({ 65 | bla: 3 66 | })] 67 | ] 68 | invalid = [ 69 | [0, 4], 70 | [0, null], 71 | [undefined, 0], 72 | [{}, {}], 73 | [NaN, NaN], // wtfjs 74 | [JSON.parse('{ "bla": 3 }'), JSON.parse('{ "bla": 3 }')] 75 | ] 76 | 77 | run({ 78 | "test fixture": function (assert) { 79 | valid.forEach(function (value, index) { 80 | var message = "`" + value[0] + "` is equal to `" + value[1] + "`" 81 | assert.equal(value[0], value[1], message) 82 | }) 83 | 84 | invalid.forEach(function (value, index) { 85 | var message = "`" + value[0] + "` is not equal to `" + value[1] + "`" 86 | assert.equal(value[0], value[1], message) 87 | }) 88 | } 89 | }, Logger(function(passes, fails, errors) { 90 | assert.equal(passes.length, valid.length, 91 | "Amount of passed test must match amount of valid inputs") 92 | assert.equal(fails.length, invalid.length, 93 | "Amount of failed test must match amount of invalid inputs") 94 | assert.equal(errors.length, 0, "Must be no errors") 95 | })) 96 | } 97 | 98 | exports["test correctness of `assert.deepEqual`"] = function (assert) { 99 | var valid, invalid, report 100 | valid = [ 101 | [ [], [] ], 102 | [ {}, {} ], 103 | ] 104 | invalid = [ 105 | [ [], undefined ], 106 | [ [], [1] ] 107 | ] 108 | report = null 109 | 110 | run({ 111 | "test fixture": function (assert) { 112 | valid.forEach(function (value, index) { 113 | var message = "`" + value[0] + "` is deepEqual of `" + value[1] + "`" 114 | assert.deepEqual(value[0], value[1], message) 115 | }) 116 | 117 | invalid.forEach(function (value, index) { 118 | var message = "`" + value[0] + "` is not deepEqual of `" + value[1] + "`" 119 | assert.deepEqual(value[0], value[1], message) 120 | }) 121 | } 122 | }, Logger(function(passes, fails, errors) { 123 | assert.equal(passes.length, valid.length, 124 | "Amount of passed test must match amount of valid inputs") 125 | assert.equal(fails.length, invalid.length, 126 | "Amount of failed test must match amount of invalid inputs") 127 | assert.equal(errors.length, 0, "Must be no errors") 128 | })) 129 | } 130 | 131 | if (require.main === module) 132 | require("../test").run(exports) 133 | -------------------------------------------------------------------------------- /test/async.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var run = require("../test").run 4 | var Logger = require("./utils/logger").Logger 5 | 6 | exports["test must call callback to complete it"] = function (assert, done) { 7 | 8 | var isDone, isTimerCalled, report, completeInnerTest 9 | 10 | isDone = isTimerCalled = false 11 | report = null 12 | 13 | run({ 14 | "test:must throw": function (assert, done) { 15 | completeInnerTest = done 16 | assert.equal(1, 1, "Must be equal") 17 | } 18 | }, Logger(function (passes, fails, errors) { 19 | isDone = true 20 | assert.equal(isTimerCalled, true, "timer should be called already") 21 | assert.equal(passes.length, 1, "Must contain one pass") 22 | assert.equal(fails.length, 0, "No fails") 23 | assert.equal(errors.length, 0, "No errors") 24 | done() 25 | })) 26 | 27 | setTimeout(function () { 28 | assert.equal(isDone, false, "callback must not be called") 29 | isTimerCalled = true 30 | completeInnerTest() 31 | }, 0) 32 | } 33 | 34 | exports["test multiple tests with timeout"] = function (assert, done) { 35 | var tests = 0 36 | 37 | run({ 38 | "test async": function (assert, done) { 39 | tests ++ 40 | setTimeout(function () { 41 | assert.ok(true) 42 | assert.ok(false) 43 | done() 44 | }, 100) 45 | }, 46 | "test throws": function (assert) { 47 | tests ++ 48 | throw new Error("boom") 49 | }, 50 | "test fail fast": function (assert) { 51 | tests ++ 52 | // TODO: Find a better solution for browser. 53 | //require("assert").ok(0) 54 | assert.ok(0) 55 | }, 56 | "ignore if does not starts with test": function () { 57 | tests ++ 58 | }, 59 | "test sync pass": function (assert) { 60 | tests ++ 61 | assert.equal(1, 2) 62 | assert.equal(2, 2) 63 | } 64 | }, Logger(function (passes, fails, errors) { 65 | assert.equal(tests, 4, "All test were executed") 66 | assert.equal(passes.length, 2, "Must pass two tests") 67 | assert.equal(fails.length, 3, "Must fail tree tests") 68 | assert.equal(errors.length, 1, "Must report one error") 69 | done() 70 | })) 71 | } 72 | 73 | if (require.main === module) 74 | require("../test").run(exports) 75 | -------------------------------------------------------------------------------- /test/custom-asserts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var AssertBase = require("../assert").Assert 4 | /** 5 | * Generates custom assertion constructors that may be bundled with a test 6 | * suite. 7 | * @params {String} 8 | * names of assertion function to be added to the generated Assert. 9 | */ 10 | function Assert() { 11 | var descriptorMap = {} 12 | Array.prototype.forEach.call(arguments, function (name) { 13 | descriptorMap[name] = { 14 | value: function (message) { 15 | this.pass(message) 16 | } 17 | } 18 | }) 19 | return function Assert() { 20 | return Object.create(AssertBase.apply(null, arguments), descriptorMap) 21 | } 22 | } 23 | 24 | exports["test suite"] = { 25 | Assert: Assert("foo"), 26 | "test that custom assertor is passed to test function": function (assert) { 27 | assert.ok("foo" in assert, "custom assertion function `foo` is defined") 28 | assert.foo("custom assertion function `foo` is called") 29 | }, 30 | "test sub suite": { 31 | "test that `Assert` is inherited by sub suits": function (assert) { 32 | assert.ok("foo" in assert, "assertion function `foo` is defined") 33 | }, 34 | "test sub sub suite": { 35 | Assert: Assert("bar"), 36 | "test that custom assertor is passed to test function": function (assert) { 37 | assert.ok("bar" in assert, "custom assertion function `bar` is defined") 38 | assert.bar("custom assertion function `bar` is called") 39 | }, 40 | "test that `Assert` is not inherited by sub sub suits": function (assert) { 41 | assert.ok(!("foo" in assert), "assertion function `foo` is not defined") 42 | } 43 | } 44 | } 45 | } 46 | 47 | if (require.main === module) 48 | require("../test").run(exports) 49 | -------------------------------------------------------------------------------- /test/fail-fast.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _assert = require("assert") 4 | var run = require("../test").run 5 | var Logger = require("./utils/logger").Logger 6 | 7 | 8 | 9 | exports["test that passes"] = function (assert) { 10 | run({ 11 | "test fixture": function () { 12 | _assert.equal(1, 1, "Must be equal") 13 | } 14 | }, Logger(function (passes, fails, errors) { 15 | assert.equal(passes.length, 1, "Must pass one test") 16 | assert.equal(fails.length, 0, "Must not fail any test") 17 | assert.equal(errors.length, 0, "Must not contain any errors") 18 | })) 19 | } 20 | 21 | exports["test that fails"] = function (assert) { 22 | run({ 23 | "test fixture": function () { 24 | _assert.equal(1, 2, "Must be two") 25 | } 26 | }, Logger(function (passes, fails, errors) { 27 | assert.equal(passes.length, 0, "Must not pass any test") 28 | assert.equal(fails.length, 1, "Must fail one test") 29 | assert.equal(errors.length, 0, "Must not contain any errors") 30 | })) 31 | } 32 | 33 | exports["test that throws error"] = function (assert) { 34 | run({ 35 | "test fixture": function () { 36 | throw new Error("Boom!!") 37 | } 38 | }, Logger(function (passes, fails, errors) { 39 | assert.equal(passes.length, 0, "Must not pass any test") 40 | assert.equal(fails.length, 0, "Must not fail any test") 41 | assert.equal(errors.length, 1, "Must contain one error") 42 | })) 43 | } 44 | 45 | exports["test that passes one assert and fails fast"] = function (assert) { 46 | run({ 47 | "test fixture": function (assert) { 48 | assert.equal(1, 1, "Must be equal") 49 | _assert.equal(1, 2, "Must fail test") 50 | } 51 | }, Logger(function (passes, fails, errors) { 52 | assert.equal(passes.length, 1, "Must pass one test") 53 | assert.equal(fails.length, 1, "Must fail one test") 54 | assert.equal(errors.length, 0, "Must not contain any errors") 55 | })) 56 | } 57 | 58 | exports["test async with fast fail"] = function (assert) { 59 | run({ 60 | "test:must throw": function (assert, done) { 61 | throw new Error("Boom!!") 62 | } 63 | }, Logger(function (passes, fails, errors) { 64 | assert.equal(passes.length, 0, "Must not pass any test") 65 | assert.equal(fails.length, 0, "Must not fail any test") 66 | assert.equal(errors.length, 1, "Must contain one error") 67 | })) 68 | } 69 | 70 | if (require.main === module) 71 | require("../test").run(exports) 72 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports["test fail fast"] = require("./fail-fast") 4 | exports["test async"] = require("./async") 5 | exports["test assertions"] = require("./assert") 6 | exports["test custom `Assert`'s"] = require("./custom-asserts") 7 | 8 | require("../test").run(exports) 9 | -------------------------------------------------------------------------------- /test/utils/logger.js: -------------------------------------------------------------------------------- 1 | exports.Logger = function Logger(callback) { 2 | if (!(this instanceof Logger)) return new Logger(callback) 3 | this.passes = [] 4 | this.fails = [] 5 | this.errors = [] 6 | this.pass = function (message) { 7 | this.passes.push(message) 8 | } 9 | this.fail = function fail(assertion) { 10 | this.fails.push(assertion) 11 | } 12 | this.error = function error(exception) { 13 | this.errors.push(exception) 14 | } 15 | this.section = function section() { 16 | return this 17 | } 18 | this.report = function report() { 19 | if (callback) 20 | callback(this.passes, this.fails, this.errors) 21 | } 22 | return this 23 | } 24 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Returns `true` if `value` is `undefined`. 5 | * @examples 6 | * var foo; isUndefined(foo); // true 7 | * isUndefined(0); // false 8 | */ 9 | function isUndefined(value) { 10 | return value === undefined; 11 | } 12 | exports.isUndefined = isUndefined; 13 | 14 | /** 15 | * Returns `true` if value is `null`. 16 | * @examples 17 | * isNull(null); // true 18 | * isNull(undefined); // false 19 | */ 20 | function isNull(value) { 21 | return value === null; 22 | } 23 | exports.isNull = isNull; 24 | 25 | /** 26 | * Returns `true` if value is a string. 27 | * @examples 28 | * isString("moe"); // true 29 | */ 30 | function isString(value) { 31 | return typeof value === "string"; 32 | } 33 | exports.isString = isString; 34 | 35 | /** 36 | * Returns `true` if `value` is a number. 37 | * @examples 38 | * isNumber(8.4 * 5); // true 39 | */ 40 | function isNumber(value) { 41 | return typeof value === "number"; 42 | } 43 | exports.isNumber = isNumber; 44 | 45 | /** 46 | * Returns `true` if `value` is a `RegExp`. 47 | * @examples 48 | * isRegExp(/moe/); // true 49 | */ 50 | function isRegExp(value) { 51 | return instanceOf(value, RegExp); 52 | } 53 | exports.isRegExp = isRegExp; 54 | 55 | /** 56 | * Returns true if `value` is a `Date`. 57 | * @examples 58 | * isDate(new Date()); // true 59 | */ 60 | function isDate(value) { 61 | return isObject(value) && instanceOf(value, Date); 62 | } 63 | exports.isDate = isDate; 64 | 65 | /** 66 | * Returns true if object is a Function. 67 | * @examples 68 | * isFunction(function foo(){}) // true 69 | */ 70 | function isFunction(value) { 71 | return typeof value === "function" && value.call && value.apply; 72 | } 73 | exports.isFunction = isFunction; 74 | 75 | /** 76 | * Returns `true` if `value` is an object (please note that `null` is considered 77 | * to be an atom and not an object). 78 | * @examples 79 | * isObject({}) // true 80 | * isObject(null) // false 81 | */ 82 | function isObject(value) { 83 | return typeof value === "object" && value !== null; 84 | } 85 | exports.isObject = isObject; 86 | 87 | /** 88 | * Returns true if `value` is an Array. 89 | * @examples 90 | * isArray([1, 2, 3]) // true 91 | * isArray({ 0: "foo", length: 1 }) // false 92 | */ 93 | var isArray = Array.isArray || function isArray(value) { 94 | return Object.prototype.toString.call(value) === "[object Array]"; 95 | } 96 | exports.isArray = isArray; 97 | 98 | /** 99 | * Returns `true` if `value` is an Arguments object. 100 | * @examples 101 | * (function(){ return isArguments(arguments); })(1, 2, 3); // true 102 | * isArguments([1,2,3]); // false 103 | */ 104 | function isArguments(value) { 105 | return Object.prototype.toString.call(value) === "[object Arguments]"; 106 | } 107 | exports.isArguments = isArguments; 108 | 109 | /** 110 | * Returns true if it is a primitive `value`. (null, undefined, number, 111 | * boolean, string) 112 | * @examples 113 | * isPrimitive(3) // true 114 | * isPrimitive("foo") // true 115 | * isPrimitive({ bar: 3 }) // false 116 | */ 117 | function isPrimitive(value) { 118 | return !isFunction(value) && !isObject(value); 119 | } 120 | exports.isPrimitive = isPrimitive; 121 | 122 | /** 123 | * Returns `true` if given `object` is flat (it is direct decedent of 124 | * `Object.prototype` or `null`). 125 | * @examples 126 | * isFlat({}) // true 127 | * isFlat(new Type()) // false 128 | */ 129 | function isFlat(object) { 130 | return isObject(object) && (isNull(Object.getPrototypeOf(object)) || 131 | isNull(Object.getPrototypeOf( 132 | Object.getPrototypeOf(object)))); 133 | } 134 | exports.isFlat = isFlat; 135 | 136 | /** 137 | * Returns `true` if object contains no values. 138 | */ 139 | function isEmpty(object) { 140 | if (isObject(object)) { 141 | for (var key in object) 142 | return false; 143 | return true; 144 | } 145 | return false; 146 | } 147 | exports.isEmpty = isEmpty; 148 | 149 | /** 150 | * Returns `true` if `value` is an array / flat object containing only atomic 151 | * values and other flat objects. 152 | */ 153 | function isJSON(value, visited) { 154 | // Adding value to array of visited values. 155 | (visited || (visited = [])).push(value); 156 | // If `value` is an atom return `true` cause it"s valid JSON. 157 | return isPrimitive(value) || 158 | // If `value` is an array of JSON values that has not been visited 159 | // yet. 160 | (isArray(value) && value.every(function(element) { 161 | return isJSON(element, visited); 162 | })) || 163 | // If `value` is a plain object containing properties with a JSON 164 | // values it"s a valid JSON. 165 | (isFlat(value) && Object.keys(value).every(function(key) { 166 | var $ = Object.getOwnPropertyDescriptor(value, key); 167 | // Check every proprety of a plain object to verify that 168 | // it"s neither getter nor setter, but a JSON value, that 169 | // has not been visited yet. 170 | return ((!isObject($.value) || !~visited.indexOf($.value)) && 171 | !("get" in $) && !("set" in $) && 172 | isJSON($.value, visited)); 173 | })); 174 | } 175 | exports.isJSON = function (value) { 176 | return isJSON(value); 177 | }; 178 | 179 | /** 180 | * Returns if `value` is an instance of a given `Type`. This is exactly same as 181 | * `value instanceof Type` with a difference that `Type` can be from a scope 182 | * that has a different top level object. (Like in case where `Type` is a 183 | * function from different iframe / jetpack module / sandbox). 184 | */ 185 | function instanceOf(value, Type) { 186 | var isConstructorNameSame; 187 | var isConstructorSourceSame; 188 | 189 | // If `instanceof` returned `true` we know result right away. 190 | var isInstanceOf = value instanceof Type; 191 | 192 | // If `instanceof` returned `false` we do ducktype check since `Type` may be 193 | // from a different sandbox. If a constructor of the `value` or a constructor 194 | // of the value"s prototype has same name and source we assume that it"s an 195 | // instance of the Type. 196 | if (!isInstanceOf && value) { 197 | isConstructorNameSame = value.constructor.name === Type.name; 198 | isConstructorSourceSame = String(value.constructor) == String(Type); 199 | isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) || 200 | instanceOf(Object.getPrototypeOf(value), Type); 201 | } 202 | return isInstanceOf; 203 | } 204 | exports.instanceOf = instanceOf; 205 | 206 | /** 207 | * Function returns textual representation of a value passed to it. Function 208 | * takes additional `indent` argument that is used for indentation. Also 209 | * optional `limit` argument may be passed to limit amount of detail returned. 210 | * @param {Object} value 211 | * @param {String} [indent=" "] 212 | * @param {Number} [limit] 213 | */ 214 | function source(value, indent, limit, offset, visited) { 215 | var result; 216 | var names; 217 | var nestingIndex; 218 | var isCompact = !isUndefined(limit); 219 | 220 | indent = indent || " "; 221 | offset = (offset || ""); 222 | result = ""; 223 | visited = visited || []; 224 | 225 | if (isUndefined(value)) { 226 | result += "undefined"; 227 | } 228 | else if (isNull(value)) { 229 | result += "null"; 230 | } 231 | else if (isString(value)) { 232 | result += "\"" + value + "\""; 233 | } 234 | else if (isFunction(value)) { 235 | value = String(value).split("\n"); 236 | if (isCompact && value.length > 2) { 237 | value = value.splice(0, 2); 238 | value.push("...}"); 239 | } 240 | result += value.join("\n" + offset); 241 | } 242 | else if (isArray(value)) { 243 | if ((nestingIndex = (visited.indexOf(value) + 1))) { 244 | result = "#" + nestingIndex + "#"; 245 | } 246 | else { 247 | visited.push(value); 248 | 249 | if (isCompact) 250 | value = value.slice(0, limit); 251 | 252 | result += "[\n"; 253 | result += value.map(function(value) { 254 | return offset + indent + source(value, indent, limit, offset + indent, 255 | visited); 256 | }).join(",\n"); 257 | result += isCompact && value.length > limit ? 258 | ",\n" + offset + "...]" : "\n" + offset + "]"; 259 | } 260 | } 261 | else if (isObject(value)) { 262 | if ((nestingIndex = (visited.indexOf(value) + 1))) { 263 | result = "#" + nestingIndex + "#" 264 | } 265 | else { 266 | visited.push(value) 267 | 268 | names = Object.keys(value); 269 | 270 | result += "{ // " + value + "\n"; 271 | result += (isCompact ? names.slice(0, limit) : names).map(function(name) { 272 | var _limit = isCompact ? limit - 1 : limit; 273 | var descriptor = Object.getOwnPropertyDescriptor(value, name); 274 | var result = offset + indent + "// "; 275 | var accessor; 276 | if (0 <= name.indexOf(" ")) 277 | name = "\"" + name + "\""; 278 | 279 | if (descriptor.writable) 280 | result += "writable "; 281 | if (descriptor.configurable) 282 | result += "configurable "; 283 | if (descriptor.enumerable) 284 | result += "enumerable "; 285 | 286 | result += "\n"; 287 | if ("value" in descriptor) { 288 | result += offset + indent + name + ": "; 289 | result += source(descriptor.value, indent, _limit, indent + offset, 290 | visited); 291 | } 292 | else { 293 | 294 | if (descriptor.get) { 295 | result += offset + indent + "get " + name + " "; 296 | accessor = source(descriptor.get, indent, _limit, indent + offset, 297 | visited); 298 | result += accessor.substr(accessor.indexOf("{")); 299 | } 300 | 301 | if (descriptor.set) { 302 | if (descriptor.get) result += ",\n"; 303 | result += offset + indent + "set " + name + " "; 304 | accessor = source(descriptor.set, indent, _limit, indent + offset, 305 | visited); 306 | result += accessor.substr(accessor.indexOf("{")); 307 | } 308 | } 309 | return result; 310 | }).join(",\n"); 311 | 312 | if (isCompact) { 313 | if (names.length > limit && limit > 0) { 314 | result += ",\n" + offset + indent + "//..."; 315 | } 316 | } 317 | else { 318 | if (names.length) 319 | result += ","; 320 | 321 | result += "\n" + offset + indent + "\"__proto__\": "; 322 | result += source(Object.getPrototypeOf(value), indent, 0, 323 | offset + indent); 324 | } 325 | 326 | result += "\n" + offset + "}"; 327 | } 328 | } 329 | else { 330 | result += String(value); 331 | } 332 | return result; 333 | } 334 | exports.source = function (value, indentation, limit) { 335 | return source(value, indentation, limit); 336 | }; 337 | --------------------------------------------------------------------------------