From f4caf2720849faa4fc3ce1eaacd975f0476fb5f6 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 29 Nov 2017 08:56:12 -0800 Subject: [PATCH 1/8] Fixed `pending()` for `async`/promise-returning specs Fixes #1449. Fixes #1450. --- lib/jasmine-core/jasmine.js | 7 ++++++- spec/core/QueueRunnerSpec.js | 14 +++++++++----- spec/core/integration/EnvSpec.js | 29 +++++++++++++++++++++++++++++ src/core/QueueRunner.js | 7 ++++++- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index f30b7b00..2bf8f793 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4353,7 +4353,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { var maybeThenable = queueableFn.fn.call(self.userContext); if (maybeThenable && j$.isFunction_(maybeThenable.then)) { - maybeThenable.then(next, next.fail); + maybeThenable.then(next, onPromiseRejection); completedSynchronously = false; return { completedSynchronously: false }; } @@ -4375,6 +4375,11 @@ getJasmineRequireObj().QueueRunner = function(j$) { errored = true; } + function onPromiseRejection(e) { + onException(e); + next(); + } + function handleException(e, queueableFn) { onException(e); if (!self.catchException(e)) { diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index a237135b..6b682ea2 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -375,27 +375,31 @@ describe("QueueRunner", function() { expect(onComplete).toHaveBeenCalled(); }); - it("fails the function when the promise is rejected", function() { + it("handles a rejected promise like an unhandled exception", function() { var promise = new StubPromise(), queueableFn1 = { fn: function() { - setTimeout(function() { promise.rejectHandler('foo'); }, 100); + setTimeout(function() { + debugger; + promise.rejectHandler('foo') + }, 100); return promise; } }, queueableFn2 = { fn: jasmine.createSpy('fn2') }, failFn = jasmine.createSpy('fail'), + onExceptionCallback = jasmine.createSpy('on exception callback'), queueRunner = new jasmineUnderTest.QueueRunner({ queueableFns: [queueableFn1, queueableFn2], - fail: failFn + onException: onExceptionCallback }); queueRunner.execute(); - expect(failFn).not.toHaveBeenCalled(); + expect(onExceptionCallback).not.toHaveBeenCalled(); expect(queueableFn2.fn).not.toHaveBeenCalled(); jasmine.clock().tick(100); - expect(failFn).toHaveBeenCalledWith('foo'); + expect(onExceptionCallback).toHaveBeenCalledWith('foo'); expect(queueableFn2.fn).toHaveBeenCalled(); }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index f77b6265..7793d168 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1485,6 +1485,7 @@ describe("Env integration", function() { reporter.jasmineDone.and.callFake(function() { var specStatus = reporter.specDone.calls.argsFor(0)[0]; + expect(specStatus.status).toBe('pending'); expect(specStatus.pendingReason).toBe('with a message'); done(); @@ -1499,6 +1500,34 @@ describe("Env integration", function() { env.execute(); }); + it('should report pending spec messages from async functions', function(done) { + jasmine.getEnv().requireAsyncAwait(); + + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('fakeReporter', [ + 'specDone', + 'jasmineDone' + ]); + + reporter.jasmineDone.and.callFake(function() { + var specStatus = reporter.specDone.calls.argsFor(0)[0]; + + expect(specStatus.status).toBe('pending'); + expect(specStatus.pendingReason).toBe('with a message'); + + done(); + }); + + env.addReporter(reporter); + + env.it('will be pending', async function() { + debugger; + env.pending('with a message'); + }); + + env.execute(); + }); + it('should report using fallback reporter', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 8af19fa1..f422b49d 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -125,7 +125,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { var maybeThenable = queueableFn.fn.call(self.userContext); if (maybeThenable && j$.isFunction_(maybeThenable.then)) { - maybeThenable.then(next, next.fail); + maybeThenable.then(next, onPromiseRejection); completedSynchronously = false; return { completedSynchronously: false }; } @@ -147,6 +147,11 @@ getJasmineRequireObj().QueueRunner = function(j$) { errored = true; } + function onPromiseRejection(e) { + onException(e); + next(); + } + function handleException(e, queueableFn) { onException(e); if (!self.catchException(e)) { From 18ed34e9be95f3253aac5216df9157e989856576 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 29 Nov 2017 09:32:12 -0800 Subject: [PATCH 2/8] Fixed failing specs --- spec/core/integration/EnvSpec.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 7793d168..0adbad94 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1500,8 +1500,18 @@ describe("Env integration", function() { env.execute(); }); - it('should report pending spec messages from async functions', function(done) { - jasmine.getEnv().requireAsyncAwait(); + it('should report pending spec messages from promise-returning functions', function(done) { + function StubPromise(fn) { + try { + fn(); + } catch (e) { + this.exception = e; + } + } + + StubPromise.prototype.then = function(resolve, reject) { + reject(this.exception); + }; var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ @@ -1520,9 +1530,10 @@ describe("Env integration", function() { env.addReporter(reporter); - env.it('will be pending', async function() { - debugger; - env.pending('with a message'); + env.it('will be pending', function() { + return new StubPromise(function() { + env.pending('with a message'); + }); }); env.execute(); From 0183b1642d49df7cadc02d803eaaebb648356c4b Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Thu, 30 Nov 2017 07:53:59 -0800 Subject: [PATCH 3/8] Removed stray debugger statement --- spec/core/QueueRunnerSpec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index 6b682ea2..3059b50b 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -379,7 +379,6 @@ describe("QueueRunner", function() { var promise = new StubPromise(), queueableFn1 = { fn: function() { setTimeout(function() { - debugger; promise.rejectHandler('foo') }, 100); return promise; From 0b5dce7d84fd869e11950620b6359b1e5c59ad82 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Thu, 7 Dec 2017 21:13:53 -0800 Subject: [PATCH 4/8] Removed CodeClimate badge & configuration CodeClimate's recommendations have been a mix of irrelevant and actively harmful for some time. The latest problem is that it applies length and complexity requirements equally to all functions whether they act as privacy scopes, namespaces, classes or just regular old functions. Static analysis tools are only worthwhile if their recommendations add enough value to offset the cost of slogging through them -- in other words, if the signal to noise ratio is high enough. CodeClimate hasn't carried its own weight for a long time, and it's only gotten worse with each recent update. --- .codeclimate.yml | 17 ----------------- README.md | 1 - 2 files changed, 18 deletions(-) delete mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 468ed1e4..00000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,17 +0,0 @@ -languages: - JavaScript: true -ratings: - paths: - - "src/**/*.js" -exclude_paths: -- "spec/**/*.js" -- "lib/**" -- "dist/*" -- "grunt/**" -- "images/*" -- "**/*.md" -- "**/*.yml" -- "**/*.json" -- "**/*.scss" -- "**/*.erb" -- "*.sh" diff --git a/README.md b/README.md index 01196ada..456991d2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [](http://jasmine.github.io) [![Build Status](https://travis-ci.org/jasmine/jasmine.svg?branch=master)](https://travis-ci.org/jasmine/jasmine) -[![Code Climate](https://codeclimate.com/github/jasmine/jasmine.svg)](https://codeclimate.com/github/jasmine/jasmine) ======= From 8ea4c6d3a1c8655209602dce71223d80a8f2ad61 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 11 Dec 2017 17:24:38 -0800 Subject: [PATCH 5/8] Update contributing for new naming of `jasmineUnderTest` --- .github/CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8bd2f92a..c8d6ea55 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -41,9 +41,9 @@ Once you've pushed a feature branch to your forked repo, you're ready to open a ### Self-testing -Note that Jasmine tests itself. The files in `lib` are loaded first, defining the reference `jasmine`. Then the files in `src` are loaded, defining the reference `j$`. So there are two copies of the code loaded under test. +Note that Jasmine tests itself. The files in `lib` are loaded first, defining the reference `jasmine`. Then the files in `src` are loaded, defining the reference `jasmineUnderTest`. So there are two copies of the code loaded under test. -The tests should always use `j$` to refer to the objects and functions that are being tested. But the tests can use functions on `jasmine` as needed. _Be careful how you structure any new test code_. Copy the patterns you see in the existing code - this ensures that the code you're testing is not leaking into the `jasmine` reference and vice-versa. +The tests should always use `jasmineUnderTest` to refer to the objects and functions that are being tested. But the tests can use functions on `jasmine` as needed. _Be careful how you structure any new test code_. Copy the patterns you see in the existing code - this ensures that the code you're testing is not leaking into the `jasmine` reference and vice-versa. ### `boot.js` From c74af1d900207af05a623839822262f0bff3d891 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 12 Dec 2017 08:39:07 -0800 Subject: [PATCH 6/8] Reduced pretty printer limits to much smaller values This should help prevent out of memory errors when printing large object graphs. See #1291 and #1422. [#153257532] --- lib/jasmine-core/jasmine.js | 4 ++-- src/core/base.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 2bf8f793..842cbb97 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -138,14 +138,14 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH */ - j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_DEPTH = 8; /** * Maximum number of array elements to display when pretty printing objects. * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH */ - j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL diff --git a/src/core/base.js b/src/core/base.js index ea09cb34..28bb1dbb 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -8,14 +8,14 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH */ - j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_DEPTH = 8; /** * Maximum number of array elements to display when pretty printing objects. * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH */ - j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL From 86a38fceb9afd3b630709b9a00480886f0c1dc54 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 12 Dec 2017 09:53:24 -0800 Subject: [PATCH 7/8] Truncate pretty printer output that is more than j$.MAX_PRETTY_PRINT_CHARS long --- lib/jasmine-core/jasmine.js | 22 +++++++++++++++++++++- spec/core/PrettyPrintSpec.js | 15 +++++++++++++++ src/core/PrettyPrinter.js | 16 +++++++++++++++- src/core/base.js | 6 ++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 842cbb97..b8c7e593 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -146,6 +146,12 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; + /** + * Maximum number of charasters to display when pretty printing objects. + * Characters past this number will be ellipised. + * @name jasmine.MAX_PRETTY_PRINT_CHARS + */ + j$.MAX_PRETTY_PRINT_CHARS = 1000; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL @@ -4044,6 +4050,7 @@ getJasmineRequireObj().pp = function(j$) { function StringPrettyPrinter() { PrettyPrinter.call(this); + this.length = 0; this.stringParts = []; } @@ -4187,9 +4194,22 @@ getJasmineRequireObj().pp = function(j$) { }; StringPrettyPrinter.prototype.append = function(value) { - this.stringParts.push(value); + if (this.length < j$.MAX_PRETTY_PRINT_CHARS) { + value = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); + this.length += value.length; + this.stringParts.push(value); + } }; + function truncate(s, maxlen) { + if (s.length <= maxlen) { + return s; + } + + s = s.substring(0, maxlen - 4); + return s + ' ...'; + } + function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index d15aae10..745a5619 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -131,6 +131,21 @@ describe("jasmineUnderTest.pp", function () { } }); + it("should truncate outputs that are too long", function() { + var originalMaxChars = jasmineUnderTest.MAX_PRETTY_PRINT_CHARS; + var big = [ + { a: 1, b: "a long string" }, + {} + ]; + + try { + jasmineUnderTest.MAX_PRETTY_PRINT_CHARS = 34; + expect(jasmineUnderTest.pp(big)).toEqual("[ Object({ a: 1, b: 'a long st ..."); + } finally { + jasmineUnderTest.MAX_PRETTY_PRINT_CHARS = originalMaxChars; + } + }); + it("should print 'null' as the constructor of an object with its own constructor property", function() { expect(jasmineUnderTest.pp({constructor: function() {}})).toContain("null({"); expect(jasmineUnderTest.pp({constructor: 'foo'})).toContain("null({"); diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 16b66074..04f85078 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -92,6 +92,7 @@ getJasmineRequireObj().pp = function(j$) { function StringPrettyPrinter() { PrettyPrinter.call(this); + this.length = 0; this.stringParts = []; } @@ -235,9 +236,22 @@ getJasmineRequireObj().pp = function(j$) { }; StringPrettyPrinter.prototype.append = function(value) { - this.stringParts.push(value); + if (this.length < j$.MAX_PRETTY_PRINT_CHARS) { + value = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); + this.length += value.length; + this.stringParts.push(value); + } }; + function truncate(s, maxlen) { + if (s.length <= maxlen) { + return s; + } + + s = s.substring(0, maxlen - 4); + return s + ' ...'; + } + function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { diff --git a/src/core/base.js b/src/core/base.js index 28bb1dbb..da39f762 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -16,6 +16,12 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; + /** + * Maximum number of charasters to display when pretty printing objects. + * Characters past this number will be ellipised. + * @name jasmine.MAX_PRETTY_PRINT_CHARS + */ + j$.MAX_PRETTY_PRINT_CHARS = 1000; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL From c0d051319990858de86c46a9697711bf80a2ef7c Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 15 Dec 2017 13:23:59 -0800 Subject: [PATCH 8/8] Stop serializing objects after hitting jasmine.MAX_PRETTY_PRINT_CHARS --- lib/jasmine-core/jasmine.js | 27 ++++++++++++++++++++------- spec/core/PrettyPrintSpec.js | 32 ++++++++++++++++++++++++++------ src/core/PrettyPrinter.js | 27 ++++++++++++++++++++------- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index b8c7e593..260bb289 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4015,6 +4015,10 @@ getJasmineRequireObj().pp = function(j$) { } else { this.emitScalar(value.toString()); } + } catch (e) { + if (this.ppNestLevel_ > 1 || !(e instanceof MaxCharsReachedError)) { + throw e; + } } finally { this.ppNestLevel_--; } @@ -4194,22 +4198,31 @@ getJasmineRequireObj().pp = function(j$) { }; StringPrettyPrinter.prototype.append = function(value) { - if (this.length < j$.MAX_PRETTY_PRINT_CHARS) { - value = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); - this.length += value.length; - this.stringParts.push(value); + var result = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); + this.length += result.value.length; + this.stringParts.push(result.value); + + if (result.truncated) { + throw new MaxCharsReachedError(); } }; function truncate(s, maxlen) { if (s.length <= maxlen) { - return s; + return { value: s, truncated: false }; } - s = s.substring(0, maxlen - 4); - return s + ' ...'; + s = s.substring(0, maxlen - 4) + ' ...'; + return { value: s, truncated: true }; } + function MaxCharsReachedError() { + this.message = 'Exceeded ' + j$.MAX_PRETTY_PRINT_CHARS + + ' characters while pretty-printing a value'; + } + + MaxCharsReachedError.prototype = new Error(); + function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 745a5619..65578260 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -131,19 +131,39 @@ describe("jasmineUnderTest.pp", function () { } }); - it("should truncate outputs that are too long", function() { + function withMaxChars(maxChars, fn) { var originalMaxChars = jasmineUnderTest.MAX_PRETTY_PRINT_CHARS; + + try { + jasmineUnderTest.MAX_PRETTY_PRINT_CHARS = maxChars; + fn(); + } finally { + jasmineUnderTest.MAX_PRETTY_PRINT_CHARS = originalMaxChars; + } + } + + it("should truncate outputs that are too long", function() { var big = [ { a: 1, b: "a long string" }, {} ]; - try { - jasmineUnderTest.MAX_PRETTY_PRINT_CHARS = 34; + withMaxChars(34, function() { expect(jasmineUnderTest.pp(big)).toEqual("[ Object({ a: 1, b: 'a long st ..."); - } finally { - jasmineUnderTest.MAX_PRETTY_PRINT_CHARS = originalMaxChars; - } + }); + }); + + it("should not serialize more objects after hitting MAX_PRETTY_PRINT_CHARS", function() { + var a = { jasmineToString: function() { return 'object a'; } }, + b = { jasmineToString: function() { return 'object b'; } }, + c = { jasmineToString: jasmine.createSpy('c jasmineToString').and.returnValue('') }, + d = { jasmineToString: jasmine.createSpy('d jasmineToString').and.returnValue('') }; + + withMaxChars(30, function() { + jasmineUnderTest.pp([{a: a, b: b, c: c}, d]); + expect(c.jasmineToString).not.toHaveBeenCalled(); + expect(d.jasmineToString).not.toHaveBeenCalled(); + }); }); it("should print 'null' as the constructor of an object with its own constructor property", function() { diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 04f85078..0685fa24 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -57,6 +57,10 @@ getJasmineRequireObj().pp = function(j$) { } else { this.emitScalar(value.toString()); } + } catch (e) { + if (this.ppNestLevel_ > 1 || !(e instanceof MaxCharsReachedError)) { + throw e; + } } finally { this.ppNestLevel_--; } @@ -236,22 +240,31 @@ getJasmineRequireObj().pp = function(j$) { }; StringPrettyPrinter.prototype.append = function(value) { - if (this.length < j$.MAX_PRETTY_PRINT_CHARS) { - value = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); - this.length += value.length; - this.stringParts.push(value); + var result = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length); + this.length += result.value.length; + this.stringParts.push(result.value); + + if (result.truncated) { + throw new MaxCharsReachedError(); } }; function truncate(s, maxlen) { if (s.length <= maxlen) { - return s; + return { value: s, truncated: false }; } - s = s.substring(0, maxlen - 4); - return s + ' ...'; + s = s.substring(0, maxlen - 4) + ' ...'; + return { value: s, truncated: true }; } + function MaxCharsReachedError() { + this.message = 'Exceeded ' + j$.MAX_PRETTY_PRINT_CHARS + + ' characters while pretty-printing a value'; + } + + MaxCharsReachedError.prototype = new Error(); + function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) {