-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: rewrite factory to be Higher order function
split spec files
- Loading branch information
1 parent
dd7086b
commit 720940c
Showing
5 changed files
with
105 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,25 @@ | ||
import { checkProtoChain, checkProperties } from './spec.utils' | ||
import { CustomError } from './custom-error' | ||
import { factory } from './factory' | ||
|
||
const checkProtoChain = (contructor, ...chain) => () => { | ||
const error = new contructor() | ||
expect(error).toBeInstanceOf(contructor) | ||
chain.forEach(c => expect(error).toBeInstanceOf(c)) | ||
} | ||
test('Instance', () => checkProtoChain(CustomError, Error)) | ||
|
||
const checkProperties = (contructor, message = 'foo') => () => { | ||
const error = new contructor(message) | ||
expect(error.message).toBe(message) | ||
expect(error).toHaveProperty('stack') | ||
} | ||
|
||
test('Instance', checkProtoChain(CustomError, Error)) | ||
|
||
test( | ||
'Extended', | ||
checkProtoChain(class SubError extends CustomError {}, CustomError, Error), | ||
) | ||
|
||
test('Basic properties', checkProperties(CustomError)) | ||
|
||
test('Factory instance', checkProtoChain(factory('TestError'), Error)) | ||
|
||
const TestError = factory('TestError') | ||
|
||
test( | ||
'Factory extended', | ||
checkProtoChain(factory('SubError', {}, TestError), TestError, Error), | ||
) | ||
|
||
test( | ||
'Factory extended by class', | ||
checkProtoChain(class SubError extends TestError {}, TestError, Error), | ||
) | ||
|
||
test( | ||
'Factory properties', | ||
checkProperties(factory('TestError', { message: 'default', code: 1 })), | ||
) | ||
test('Instance pre ES6 environment', () => { | ||
const O = <any>Object | ||
const E = <any>Error | ||
const setPrototypeOf = O.setPrototypeOf | ||
const captureStackTrace = E.captureStackTrace | ||
delete O.setPrototypeOf | ||
delete E.captureStackTrace | ||
|
||
test('Factory exended properties', () => { | ||
const TestError = factory('TestError', { message: 'foo', code: 1 }) | ||
const message = 'my message' | ||
const code = 42 | ||
const ArgsError = factory<{ | ||
name: 'ArgsError' | ||
message: string | ||
code: number | ||
}>('ArgsError', { message: 'bar', code: 2 }, TestError) | ||
const argsError = new ArgsError(message, code) | ||
expect(argsError.message).toBe(message) | ||
expect(argsError.code).toBe(code) | ||
checkProtoChain(CustomError, Error) | ||
|
||
const defaultArgsError = new ArgsError() | ||
expect(defaultArgsError.message).toBe('bar') | ||
expect(defaultArgsError.code).toBe(2) | ||
O.setPrototypeOf = setPrototypeOf | ||
E.captureStackTrace = captureStackTrace | ||
}) | ||
|
||
test('Instance pre ES6 environment', () => { | ||
delete (<any>Object).setPrototypeOf | ||
delete (<any>Error).captureStackTrace | ||
checkProtoChain(CustomError, Error)() | ||
test('Extended', () => { | ||
class SubError extends CustomError {} | ||
checkProtoChain(SubError, CustomError, Error) | ||
}) | ||
|
||
// call toString & inspect methods to display examples and for coverage | ||
console.log('%o\n%o', TestError, Error) | ||
console.log('%o\n%o', TestError.toString(), Error.toString()) | ||
console.log('%o\n%o', TestError('test'), Error('test')) | ||
console.log('%o\n%o', TestError('msg').toString(), Error('msg').toString()) | ||
test('Basic properties', () => checkProperties(CustomError)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { checkProtoChain, checkProperties } from './spec.utils' | ||
import { factory } from './factory' | ||
|
||
const TestError = factory(function TestError() {}) | ||
|
||
test('Factory instance', () => checkProtoChain(TestError, Error)) | ||
|
||
test('Factory extended', () => { | ||
const SubError = factory(function SubError() {}, TestError) | ||
checkProtoChain(SubError, TestError, Error) | ||
checkProtoChain(factory(SubError, RangeError), RangeError, Error) | ||
}) | ||
|
||
test('Factory extended by class', () => { | ||
const TestError = <ErrorConstructor>factory(function MyError() {}, | ||
RangeError) | ||
class SubError extends TestError {} | ||
checkProtoChain(SubError, TestError, RangeError, Error) | ||
}) | ||
|
||
test('Factory properties', () => { | ||
function TestError(code = 1, message = 'foo') { | ||
this.code = code | ||
this.message = message | ||
} | ||
checkProperties(factory(TestError), 'foo') | ||
|
||
function AnotherError(code = 2, message = 'bar') { | ||
this.code = code | ||
this.message = message | ||
} | ||
checkProperties(factory(AnotherError), 'bar') | ||
|
||
const ArgsError = factory<{ code: number }>( | ||
AnotherError, | ||
factory(TestError), | ||
) | ||
const argsError = ArgsError(3, 'baz') | ||
expect(argsError.message).toBe('baz') | ||
expect(argsError.code).toBe(3) | ||
|
||
const defaultArgsError = ArgsError() | ||
expect(defaultArgsError.message).toBe('bar') | ||
expect(defaultArgsError.code).toBe(2) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,33 @@ | ||
import { fixStack } from './utils' | ||
|
||
export interface CustomErrorConstructor<CustomError extends Error> | ||
extends ErrorConstructor { | ||
new (...args): CustomError | ||
(...args): CustomError | ||
readonly prototype: CustomError | ||
interface CustomError extends Error {} | ||
|
||
interface CustomProperties { | ||
[property: string]: any | ||
} | ||
|
||
export const factory = <CustomError extends Error = Error>( | ||
name: string, | ||
props: { [prop: string]: any } = { message: undefined }, | ||
parent: CustomErrorConstructor<CustomError> | ErrorConstructor = Error, | ||
): CustomErrorConstructor<CustomError> => { | ||
const properties = Object.keys(props) | ||
interface CustomConstructor<Properties> extends ErrorConstructor { | ||
new (...args): CustomError & Properties | ||
(...args): CustomError & Properties | ||
readonly prototype: CustomError | ||
} | ||
|
||
const constructor = function(this: Error, ...args: any[]): void { | ||
export function factory<Properties = CustomError>( | ||
fn: (...Arguments) => void, | ||
parent: ErrorConstructor = Error, | ||
): CustomConstructor<Properties> { | ||
function CustomError(this: CustomError, ...args: any[]): void { | ||
// allow simple function call | ||
if (!(this instanceof constructor)) return new constructor(...args) | ||
// call super | ||
if (!(this instanceof CustomError)) return new CustomError(...args) | ||
// apply super | ||
parent.apply(this, args) | ||
// set properties from arguments or default values | ||
properties.forEach((key, index) => | ||
Object.defineProperty(this, key, { | ||
value: index in args ? args[index] : props[key], | ||
writable: true, | ||
configurable: true, | ||
}), | ||
) | ||
// apply custom fn | ||
fn.apply(this, args) | ||
// try to remove contructor from stack trace | ||
fixStack(this, constructor) | ||
fixStack(this, CustomError) | ||
} | ||
|
||
return Object.defineProperties(constructor, { | ||
prototype: { | ||
value: Object.create(parent.prototype, { | ||
name: { value: name }, | ||
toString: { | ||
value: function() { | ||
return `${this.name}: ${this.message}` | ||
}, | ||
}, | ||
inspect: { | ||
value: function() { | ||
return this.stack | ||
}, | ||
}, | ||
}), | ||
}, | ||
toString: { | ||
value: () => `function ${name}(${properties}) { [custom code] }`, | ||
}, | ||
inspect: { value: () => `{ [Function: ${name}] }` }, | ||
return Object.defineProperties(CustomError, { | ||
prototype: { value: Object.create(parent.prototype) }, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export const checkProtoChain = (contructor, ...chain) => { | ||
const error = new contructor() | ||
expect(error).toBeInstanceOf(contructor) | ||
chain.forEach(c => expect(error).toBeInstanceOf(c)) | ||
} | ||
|
||
export const checkProperties = (contructor, message = 'foo') => { | ||
const error = new contructor(message) | ||
expect(error.message).toBe(message) | ||
expect(error).toHaveProperty('stack') | ||
} |