Add error constructor generator
This produces the constructor used for Error subtypes. * lib/ctor/ErrorCtor.js: Added * test/ctor/ErrorCtorTest.js: Addedmaster
parent
37a459a25a
commit
c69a42945c
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
* Handles the stupid-complicated error subtyping situation in JavaScript
|
||||
*
|
||||
* Copyright (C) 2016 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU ease.js.
|
||||
*
|
||||
* ease.js is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Before you wonder why this is so stupid-complicated and question this
|
||||
* effort: ease.js supports ECMAScript 3 and later environments.
|
||||
*
|
||||
* Unless you continue to question because this is JavaScript and,
|
||||
* regardless of ECMAScript version, it's still stupid. Then you'd be
|
||||
* right.
|
||||
*
|
||||
* See test case for comprehensive examples.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructor generator for Error subtypes
|
||||
*
|
||||
* BASE should be the supertype of all error prototypes in the environment;
|
||||
* this is usually `Error'. BASE is used to determine what features are
|
||||
* available in the particular environment (e.g. `Error.captureStackTrace').
|
||||
*
|
||||
* The goal is to generate error constructors that will produce errors as
|
||||
* close to the form of the environment of BASE as possible: this is _not_
|
||||
* an attempt to provide a unified Error interface across all environments;
|
||||
* even if we know about certain data line line numbers, if an error from
|
||||
* BASE would not normally produce them, then neither will we.
|
||||
*
|
||||
* @param {Function} base supertype of all error prototypes
|
||||
*
|
||||
* @return {ErrorCtor}
|
||||
*/
|
||||
function ErrorCtor( base )
|
||||
{
|
||||
if ( !( this instanceof ErrorCtor ) )
|
||||
{
|
||||
return new ErrorCtor( base );
|
||||
}
|
||||
|
||||
if ( typeof base !== 'function' )
|
||||
{
|
||||
throw TypeError( "Expected constructor for error base" );
|
||||
}
|
||||
|
||||
this._base = base;
|
||||
this._initDataSupport( base );
|
||||
};
|
||||
|
||||
|
||||
ErrorCtor.prototype = {
|
||||
/**
|
||||
* Stack parser-guesser
|
||||
*
|
||||
* This recognizes Mozilla- and V8-style stack traces containing our
|
||||
* unique identifier; other formats might work by chance, but their
|
||||
* support is not intentional.
|
||||
*
|
||||
* There are four match groups, as noted in the regex itself below:
|
||||
* 1. The entire stripped stack (if recognized);
|
||||
* 2. Filename;
|
||||
* 3. Line number; and
|
||||
* 4. Column number (might not exist).
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
_stackre: new RegExp(
|
||||
'^' +
|
||||
'(?:' +
|
||||
'.+?\\n\\s+at ' + // V8-style 'at' on second line
|
||||
')?' +
|
||||
|
||||
'.*?__\\$\\$ector\\$\\$__' + // our unique identifier
|
||||
'.*(?:\\n|$)' + // ignore rest of line
|
||||
|
||||
'(' + // (stripped stack)
|
||||
'(?:' +
|
||||
'.*?[@(]' + // skip Mozilla/V8 frame name
|
||||
'(.*?)' + // (filename)
|
||||
':(\\d+)' + // (line)
|
||||
'(?::(\\d+))?' + // (column)
|
||||
'.*?\\n' + // ignore rest of line
|
||||
')?' +
|
||||
'(?:.|\\n)*' + // include rest of stack
|
||||
')?' +
|
||||
'$'
|
||||
),
|
||||
|
||||
/**
|
||||
* Base error constructor (usually Error)
|
||||
* @type {Function}
|
||||
*/
|
||||
_base: {},
|
||||
|
||||
|
||||
/**
|
||||
* Create error constructor
|
||||
*
|
||||
* Note that, as this is intended for use as a constructor for ease.js
|
||||
* classes, this will _not_ set up the prototype as a subtype of
|
||||
* SUPERTYPE---the caller is expected to do so.
|
||||
*
|
||||
* @param {Function} supertype parent error constructor
|
||||
* @param {string} name error subtype name
|
||||
*
|
||||
* @return {function(string)} error constructor
|
||||
*/
|
||||
createCtor: function( supertype, name )
|
||||
{
|
||||
if ( typeof supertype !== 'function' )
|
||||
{
|
||||
throw TypeError( "Expected constructor for supertype" );
|
||||
}
|
||||
|
||||
var _self = this;
|
||||
|
||||
// yes, this name is important, as we use it as an identifier for
|
||||
// stack stripping (see `#_parseStack')
|
||||
function __$$ector$$__( message )
|
||||
{
|
||||
this.message = message;
|
||||
_self._setStackTrace( this, _self._base, supertype );
|
||||
}
|
||||
|
||||
// it's important to let the name fall through if not provided
|
||||
if ( name !== undefined )
|
||||
{
|
||||
__$$ector$$__.prototype.name = name;
|
||||
}
|
||||
|
||||
return __$$ector$$__;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Create stack trace using appropriate method for environment
|
||||
*
|
||||
* If BASE has a `captureStackStrace' method, then it will be used with
|
||||
* DEST as the destination and SUPERTYPE as the relative object for the
|
||||
* stack frames. Otherwise, `DEST.stack' will be overwritten with the
|
||||
* `stack' produces by instantiating SUPERTYPE, which is the
|
||||
* conventional approach.
|
||||
*
|
||||
* @param {Object} dest destination object for values
|
||||
* @param {Function} base supertype of all errors
|
||||
* @param {Function} supertype supertype of new error
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
_setStackTrace: function( dest, base, supertype )
|
||||
{
|
||||
if ( typeof base.captureStackTrace === 'function' )
|
||||
{
|
||||
base.captureStackTrace( dest, dest.constructor );
|
||||
return;
|
||||
}
|
||||
|
||||
var super_inst = new supertype(),
|
||||
stack_data = this._parseStack( super_inst.stack );
|
||||
|
||||
dest.stack = stack_data.stripped;
|
||||
|
||||
if ( this._lineSupport )
|
||||
{
|
||||
dest.lineNumber = stack_data.line;
|
||||
}
|
||||
|
||||
if ( this._columnSupport )
|
||||
{
|
||||
dest.columnNumber = stack_data.column;
|
||||
}
|
||||
|
||||
if ( this._filenameSupport )
|
||||
{
|
||||
dest.fileName = stack_data.filename;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to extract stack frames below self, as well as the line and
|
||||
* column numbers (if available)
|
||||
*
|
||||
* The provided string STACK should be the full stack trace. It will be
|
||||
* parsed to ensure that the first stack frame matches a unique
|
||||
* identifier for the error constructor, and then return the following:
|
||||
*
|
||||
* `full': original STACK;
|
||||
* `stripped': stack trace with first frame stripped, if matching;
|
||||
* `filename': filename from the first non-error frame, if matching;
|
||||
* `line': line number from first non-error frame, if matching;
|
||||
* `column': column number from first non-error frame, if matching.
|
||||
*
|
||||
* @param {string} stack full stack trace
|
||||
*
|
||||
* @return {Object} full, stripped, line, column
|
||||
*/
|
||||
_parseStack: function( stack )
|
||||
{
|
||||
var match = ( typeof stack === 'string' )
|
||||
? stack.match( this._stackre )
|
||||
: null;
|
||||
|
||||
if ( match )
|
||||
{
|
||||
// these undefined defaults deal with older environments
|
||||
// (e.g. IE<9) returning an empty string rather than undefined
|
||||
// for non-matches (note that !!"0"===true, so we're okay)
|
||||
return {
|
||||
full: stack,
|
||||
stripped: match[ 1 ] || '',
|
||||
filename: match[ 2 ] || undefined,
|
||||
line: match[ 3 ] || undefined,
|
||||
column: match[ 4 ] || undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
full: stack,
|
||||
stripped: stack
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Initialize with whether line, column, and/or filenames are supported
|
||||
* by the environment (of BASE)
|
||||
*
|
||||
* Some environments (e.g. GNU IceCat) support line and column
|
||||
* numbers. Others (like older versions of a certain proprietary
|
||||
* browser) only support line numbers. Others support neither.
|
||||
*
|
||||
* The reason for this very specific distinction is strict consistency:
|
||||
* we want to produce errors of the exact same form as those created by
|
||||
* BASE.
|
||||
*
|
||||
* Below, we check for the value on the prototype chain first and, upon
|
||||
* failing to find anything, then check to see if the field exists at
|
||||
* all on an instance of BASE.
|
||||
*
|
||||
* This method sets `_{line,column,filename}Support`.
|
||||
*
|
||||
* @param {Function} base supertype of all errors
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
_initDataSupport: function( base )
|
||||
{
|
||||
var chk = new base(),
|
||||
hasOwn = Object.hasOwnProperty;
|
||||
|
||||
this._lineSupport = ( chk.lineNumber !== undefined )
|
||||
|| hasOwn.call( chk, 'lineNumber' );
|
||||
|
||||
this._columnSupport = ( chk.columnNumber !== undefined )
|
||||
|| hasOwn.call( chk, 'columnNumber' );
|
||||
|
||||
this._filenameSupport = ( chk.fileName !== undefined )
|
||||
|| hasOwn.call( chk, 'fileName' );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Whether the given TYPE is our base error constructor or a subtype
|
||||
*
|
||||
* @param {Function} type constructor to check against our base
|
||||
*
|
||||
* @return {boolean} whether TYPE is our base constructor or a subtype
|
||||
*/
|
||||
isError: function( type )
|
||||
{
|
||||
return ( type === this._base )
|
||||
|| ( type.prototype instanceof this._base );
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
module.exports = ErrorCtor;
|
|
@ -0,0 +1,510 @@
|
|||
/**
|
||||
* Tests error constructor generation
|
||||
*
|
||||
* Copyright (C) 2016 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU ease.js.
|
||||
*
|
||||
* ease.js is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function DummyError() {};
|
||||
|
||||
function SubDummyError() {};
|
||||
SubDummyError.prototype = new DummyError();
|
||||
SubDummyError.prototype.name = 'sub dummy name';
|
||||
|
||||
|
||||
require( 'common' ).testCase(
|
||||
{
|
||||
caseSetUp: function()
|
||||
{
|
||||
this.Sut = this.require( 'ctor/ErrorCtor' );
|
||||
|
||||
// no need to be comprehensive
|
||||
this.bases = [ '', {} ];
|
||||
|
||||
// space is intentional, since in IceCat (for example) you get
|
||||
// '@debugger eval code' if you get an error in the console
|
||||
this.frames = [
|
||||
// average case, multiple frames, Mozilla-style
|
||||
{
|
||||
frames: [
|
||||
"__$$ector$$__@foo.js:1:2",
|
||||
"bar@first-frame.js:1:2",
|
||||
"baz@second other frame:2:2",
|
||||
],
|
||||
|
||||
strip: 1,
|
||||
fileName: 'first-frame.js',
|
||||
lineNumber: 1,
|
||||
columnNumber: 2,
|
||||
},
|
||||
// average case, multiple frames, Mozilla-style, no-match
|
||||
{
|
||||
frames: [
|
||||
"__$$no$$__@foo.js:1:2",
|
||||
"bar@first-frame2.js:1:2",
|
||||
"baz@second other frame:2:2",
|
||||
],
|
||||
|
||||
strip: 0,
|
||||
fileName: undefined,
|
||||
lineNumber: undefined,
|
||||
columnNumber: undefined,
|
||||
},
|
||||
// average case, Mozilla-style, no column number
|
||||
{
|
||||
frames: [
|
||||
"__$$ector$$__@foo.js:1",
|
||||
"bar@first-frame2.js:2",
|
||||
"baz@second other frame:3",
|
||||
],
|
||||
|
||||
strip: 1,
|
||||
fileName: 'first-frame2.js',
|
||||
lineNumber: 2,
|
||||
columnNumber: undefined,
|
||||
},
|
||||
// average case, Mozilla-style, no frame name
|
||||
{
|
||||
frames: [
|
||||
"__$$ector$$__@foo.js:1:1",
|
||||
"@first-frame3.js:2:2",
|
||||
"baz@second other frame:3:3",
|
||||
],
|
||||
|
||||
strip: 1,
|
||||
fileName: 'first-frame3.js',
|
||||
lineNumber: 2,
|
||||
columnNumber: 2,
|
||||
},
|
||||
// no other frames (but notice the trailing
|
||||
// newline), Mozilla-style
|
||||
{
|
||||
frames: [
|
||||
"__$$ector$$__@foo.js:1:2\n",
|
||||
],
|
||||
|
||||
strip: 1,
|
||||
fileName: undefined,
|
||||
lineNumber: undefined,
|
||||
columnNumber: undefined,
|
||||
},
|
||||
|
||||
// average case, multiple frames, V8-style
|
||||
{
|
||||
frames: [
|
||||
"SomeError",
|
||||
" at __$$ector$$__ (foo.js:1:2)",
|
||||
" at bar (first-frame.js:1:2)",
|
||||
" at baz (second other frame:2:2)",
|
||||
],
|
||||
|
||||
strip: 2,
|
||||
fileName: 'first-frame.js',
|
||||
lineNumber: 1,
|
||||
columnNumber: 2,
|
||||
},
|
||||
// average case, V8-style, multiple frames, no-match
|
||||
{
|
||||
frames: [
|
||||
"SomeError",
|
||||
" at __$$nomatch$$__ (foo.js:1:2)",
|
||||
" at bar (first-frame.js:1:2)",
|
||||
" at baz (second other frame:2:2)",
|
||||
],
|
||||
|
||||
strip: 0,
|
||||
fileName: undefined,
|
||||
lineNumber: undefined,
|
||||
columnNumber: undefined,
|
||||
},
|
||||
// average case, V8-style, no column number
|
||||
{
|
||||
frames: [
|
||||
"SomeError",
|
||||
" at __$$ector$$__ (foo.js:1)",
|
||||
" at bar (first-frame2.js:1)",
|
||||
" at baz (second other frame:2)",
|
||||
],
|
||||
|
||||
strip: 2,
|
||||
fileName: 'first-frame2.js',
|
||||
lineNumber: 1,
|
||||
columnNumber: undefined,
|
||||
},
|
||||
// average case, V8-style, no frame name
|
||||
{
|
||||
frames: [
|
||||
"SomeError",
|
||||
" at __$$ector$$__ (foo.js:1:2)",
|
||||
" at (first-frame.js:1:2)",
|
||||
" at (second other frame:2:2)",
|
||||
],
|
||||
|
||||
strip: 2,
|
||||
fileName: 'first-frame.js',
|
||||
lineNumber: 1,
|
||||
columnNumber: 2,
|
||||
},
|
||||
// no other frames, V8-style
|
||||
{
|
||||
frames: [
|
||||
"SomeError",
|
||||
" at __$$ector$$__ (foo.js:1:2)"
|
||||
],
|
||||
|
||||
strip: 2,
|
||||
fileName: 'first-frame.js',
|
||||
lineNumber: undefined,
|
||||
columnNumber: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
// whether line, column, or filename are available in environment
|
||||
this.linecolf = [
|
||||
{ lineNumber: false, columnNumber: false, fileName: true },
|
||||
{ lineNumber: true, columnNumber: false, fileName: true },
|
||||
{ lineNumber: false, columnNumber: true, fileName: true },
|
||||
{ lineNumber: true , columnNumber: true, fileName: true },
|
||||
{ lineNumber: false, columnNumber: false, fileName: false },
|
||||
{ lineNumber: true, columnNumber: false, fileName: false },
|
||||
{ lineNumber: false, columnNumber: true, fileName: false },
|
||||
{ lineNumber: true , columnNumber: true, fileName: false },
|
||||
];
|
||||
},
|
||||
|
||||
|
||||
'@each(bases) Throws error if base is not a function': function( obj )
|
||||
{
|
||||
this.assertThrows( function()
|
||||
{
|
||||
this.Sut( obj );
|
||||
}, TypeError );
|
||||
},
|
||||
|
||||
|
||||
'@each(bases) Throws error if supertype is not a function': function( obj )
|
||||
{
|
||||
this.assertThrows( function()
|
||||
{
|
||||
this.Sut( DummyError, obj );
|
||||
}, TypeError );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Error messages are set by a `message' property.
|
||||
*/
|
||||
'Sets message via constructor': function()
|
||||
{
|
||||
var expected = 'foo message';
|
||||
|
||||
this.assertEqual(
|
||||
new ( this.Sut( DummyError ).createCtor( DummyError ) )
|
||||
( expected ).message,
|
||||
expected
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* The build-in ECMAScript Error constructors don't cast MESSAGE to a
|
||||
* string, so we shouldn't either.
|
||||
*/
|
||||
'Does not cast message to string': function()
|
||||
{
|
||||
var expected = {};
|
||||
|
||||
this.assertStrictEqual(
|
||||
new ( this.Sut( DummyError ).createCtor( DummyError ) )
|
||||
( expected ).message,
|
||||
expected
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* The name of the error is derived from the `name' property.
|
||||
*/
|
||||
'Sets name to class name': function( Type )
|
||||
{
|
||||
var expected = 'MyError';
|
||||
|
||||
this.assertEqual(
|
||||
new (
|
||||
this.Sut( DummyError ).createCtor( DummyError, expected )
|
||||
)().name,
|
||||
expected
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* ...unless one is not provided, in which case we should retain the
|
||||
* parent's.
|
||||
*
|
||||
* Since the constructor generator doesn't set up the constructor's
|
||||
* supertype, this amounts to seeing if it'll fall through if we set the
|
||||
* supertype. We don't want to just check whether `name' is or is not
|
||||
* defined on the prototype, since we only care that it works, not how
|
||||
* it's done.
|
||||
*/
|
||||
'Defaults name to supertype': function( Type )
|
||||
{
|
||||
var ctor = this.Sut( DummyError )
|
||||
.createCtor( SubDummyError );
|
||||
|
||||
ctor.prototype = new SubDummyError();
|
||||
|
||||
this.assertEqual(
|
||||
new ctor().name,
|
||||
new SubDummyError().name
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* JavaScript doesn't make extending Error pleasent---we need to take
|
||||
* care of our own stack trace, and that trace isn't going to be
|
||||
* entirely correct (because we have an extra stack frame, being in the
|
||||
* Error itself). Furthermore, not all environments support stack.
|
||||
*
|
||||
* To make matters worse, the proper method of obtaining or overwriting
|
||||
* a stack trace also varies. So, let's emulate some environments.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Certain browsers (like Chromium) support `Error.captureStackTrace',
|
||||
* which sets the `stack' property on the given object to either a
|
||||
* complete stack trace, or a stack trace below a reference to a given
|
||||
* object. The `stack' property is also defined as a getter, which
|
||||
* makes for confusing and frustrating development when you are
|
||||
* wondering why setting it does nothing. (Personal experience
|
||||
* perhaps?)
|
||||
*
|
||||
* Note that the previous tests will implicitly test that
|
||||
* `captureStackTrace' is _not_ called when unavailable, because they
|
||||
* will fail to call an undefined function.
|
||||
*/
|
||||
'Uses Error.captureStackTrace when available': function()
|
||||
{
|
||||
var _self = this,
|
||||
expected = 'as expected',
|
||||
capture_args;
|
||||
|
||||
function DummyErrorCapture() {};
|
||||
DummyErrorCapture.captureStackTrace = function()
|
||||
{
|
||||
capture_args = arguments;
|
||||
};
|
||||
|
||||
var given_ctor = this.Sut( DummyErrorCapture )
|
||||
.createCtor( DummyError );
|
||||
|
||||
// if the stack trace were generated now, then that would be bad (as
|
||||
// it would be incorrect when the error is actually instantiated)
|
||||
this.assertEqual( undefined, capture_args );
|
||||
|
||||
var inst = new given_ctor();
|
||||
|
||||
// destination for `stack' property set
|
||||
this.assertStrictEqual( capture_args[ 0 ], inst );
|
||||
|
||||
// relative stack frame
|
||||
this.assertStrictEqual( capture_args[ 1 ], given_ctor );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* If `Error.captureStackTrace' is _not_ available, we fall back to the
|
||||
* good-ol'-fashion overwrite-stack-with-a-super-instance-stack
|
||||
* approach, which is conventional.
|
||||
*/
|
||||
'Overwrites `stack\' property if no `captureStackTrace\'': function()
|
||||
{
|
||||
var expected = 'as expected',
|
||||
allow = false;
|
||||
|
||||
// just something that we can mock the stack on
|
||||
function SubDummyError()
|
||||
{
|
||||
if ( !allow ) return;
|
||||
|
||||
this.stack = expected;
|
||||
}
|
||||
|
||||
// this is why stack traces are traditionally a problem unless you
|
||||
// remember to explicitly set it; ease.js does it for you
|
||||
SubDummyError.prototype = new DummyError();
|
||||
SubDummyError.prototype.stack = 'stack not set';
|
||||
|
||||
var given = this.Sut( DummyError )
|
||||
.createCtor( SubDummyError );
|
||||
|
||||
// ensures that this stack is actually from a new object, not the
|
||||
// stack that was set on the prototype
|
||||
allow = true;
|
||||
var result = new given().stack;
|
||||
|
||||
this.assertEqual( result, expected );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* If `Error.captureStackTrace' is available and used, then the error
|
||||
* constructor itself will not appear in the stack trace. If we have to
|
||||
* set it, however, then it will---this is a consequence of
|
||||
* instantiating the supertype within the error constructor in order to
|
||||
* get a proper stack trace.
|
||||
*
|
||||
* We will attempt to strip ourselves from the string if the stack trace
|
||||
* string meets certain critiera.
|
||||
*
|
||||
* Also make sure we don't strip if there is a non-match. Generally
|
||||
* speaking, this won't often (if ever) be the case in practice, but
|
||||
* let's never make assumptions.
|
||||
*/
|
||||
'@each(frames) Strips self from stack if no `captureStackTrace\'':
|
||||
function( framedata )
|
||||
{
|
||||
var lines = Array.prototype.slice.call( framedata.frames );
|
||||
|
||||
function SubDummyError()
|
||||
{
|
||||
this.stack = lines.join( '\n' );
|
||||
}
|
||||
|
||||
var ctor = this.Sut( DummyError )
|
||||
.createCtor( SubDummyError );
|
||||
|
||||
var result = new ctor().stack;
|
||||
|
||||
this.assertEqual(
|
||||
lines.slice( framedata.strip ).join( '\n' ),
|
||||
result
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Certain browsers (like GNU IceCat) support `{line,column}Number` and
|
||||
* `fileName`; if those are defined, we will propagate them.
|
||||
*
|
||||
* ...but there's a caveat: the values set on the supertype's error
|
||||
* object aren't going to be correct, because the first frame is not our
|
||||
* own. That means we have to do some string parsing on the second
|
||||
* frame; this will only happen if stack stripping was successful, since
|
||||
* we otherwise have no idea if the second frame is actually what we
|
||||
* want.
|
||||
*
|
||||
* Even if we do happen to know the values, if the environment in which
|
||||
* we are running does not normally provide those data, then neither
|
||||
* will we (for strict consistency).
|
||||
*/
|
||||
'@each(linecolf) Sets line, column, and filename data if available':
|
||||
function( linecolf )
|
||||
{
|
||||
var expected = {
|
||||
lineNumber: 5,
|
||||
columnNumber: 3,
|
||||
fileName: 'foofile.js',
|
||||
}
|
||||
|
||||
var lines = [
|
||||
"@__$$ector$$__ foo:1:1",
|
||||
"@" + expected.fileName + ":" + expected.lineNumber
|
||||
+ ":" + expected.columnNumber,
|
||||
"@second other frame:2:2"
|
||||
];
|
||||
|
||||
function LineColDummyError()
|
||||
{
|
||||
this.stack = lines.join( '\n' );
|
||||
|
||||
for ( var prop in linecolf )
|
||||
{
|
||||
if ( !linecolf[ prop ] ) continue;
|
||||
|
||||
// purposefully not an integer; should apply if the key
|
||||
// exists at all
|
||||
this[ prop ] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var ctor = this.Sut( LineColDummyError )
|
||||
.createCtor( LineColDummyError );
|
||||
|
||||
var errobj = new ctor();
|
||||
|
||||
for ( var prop in linecolf )
|
||||
{
|
||||
if ( linecolf[ prop ] )
|
||||
{
|
||||
this.assertEqual( expected[ prop ], errobj[ prop ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
this.assertOk(
|
||||
!Object.hasOwnProperty.call( errobj, prop )
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* This tests various situations with regards to the data available in
|
||||
* stack traces; see `this.frames` for those cases.
|
||||
*/
|
||||
'@each(frames) Recognizes line, column, and filename when available':
|
||||
function( framedata )
|
||||
{
|
||||
function LineColDummyError()
|
||||
{
|
||||
this.stack = framedata.frames.join( '\n' );
|
||||
this.lineNumber = undefined;
|
||||
this.columnNumber = undefined;
|
||||
this.fileName = undefined;
|
||||
}
|
||||
|
||||
var ctor = this.Sut( LineColDummyError )
|
||||
.createCtor( LineColDummyError );
|
||||
|
||||
var errobj = new ctor();
|
||||
|
||||
this.assertEqual( framedata.lineNumber, errobj.lineNumber );
|
||||
this.assertEqual( framedata.columnNumber, errobj.columnNumber );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* A predicate is provided to allow callers to determine if the given
|
||||
* object is our base constructor or a subtype thereof.
|
||||
*/
|
||||
'Provides predicate to recognize base match': function()
|
||||
{
|
||||
var sut = this.Sut( DummyError );
|
||||
|
||||
this.assertOk( sut.isError( DummyError ) );
|
||||
this.assertOk( !sut.isError( new DummyError() ) );
|
||||
|
||||
this.assertOk( sut.isError( SubDummyError ) );
|
||||
this.assertOk( !sut.isError( new SubDummyError() ) );
|
||||
|
||||
this.assertOk( !sut.isError( function() {} ) );
|
||||
},
|
||||
} );
|
Loading…
Reference in New Issue