@fluffy-spoon/substitute
Advanced tools
Comparing version 1.0.65 to 1.0.66
@@ -82,8 +82,67 @@ "use strict"; | ||
ava_1.default.beforeEach(function () { | ||
console.log(''); | ||
console.log('Ava: beforeEach'); | ||
console.log(''); | ||
instance = new Example(); | ||
substitute = Index_1.Substitute.for(); | ||
}); | ||
ava_1.default('class with method called "received" can be used for call count verification when proxies are suspended', function (t) { | ||
Index_1.Substitute.disableFor(substitute).received(2); | ||
t.throws(function () { return substitute.received(2).received(2); }); | ||
t.notThrows(function () { return substitute.received(1).received(2); }); | ||
}); | ||
ava_1.default('class with method called "received" can be used for call count verification', function (t) { | ||
Index_1.Substitute.disableFor(substitute).received('foo'); | ||
t.notThrows(function () { return substitute.received(1).received('foo'); }); | ||
t.throws(function () { return substitute.received(2).received('foo'); }); | ||
}); | ||
ava_1.default('partial mocks using function mimicks with all args', function (t) { | ||
substitute.c(Index_1.Arg.all()).mimicks(instance.c); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
ava_1.default('can call received twice', function (t) { | ||
substitute.c('blah', 'fuzz'); | ||
t.throws(function () { return substitute.received(1337).c('foo', 'bar'); }, "Expected 1337 calls to the method c with arguments [foo, bar], but received none of such calls.\nAll calls received to method c:\n-> 1 call with arguments [blah, fuzz]"); | ||
t.throws(function () { return substitute.received(2117).c('foo', 'bar'); }, "Expected 2117 calls to the method c with arguments [foo, bar], but received none of such calls.\nAll calls received to method c:\n-> 1 call with arguments [blah, fuzz]"); | ||
}); | ||
ava_1.default('class string field get received', function (t) { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(function () { return substitute.received(3).a; }); | ||
t.notThrows(function () { return substitute.received().a; }); | ||
t.notThrows(function () { return substitute.received(4).a; }); | ||
}); | ||
ava_1.default('class string field set received', function (t) { | ||
substitute.v = undefined; | ||
substitute.v = null; | ||
substitute.v = 'hello'; | ||
substitute.v = 'hello'; | ||
substitute.v = 'world'; | ||
t.notThrows(function () { return substitute.received().v = 'hello'; }); | ||
t.notThrows(function () { return substitute.received(5).v = Index_1.Arg.any(); }); | ||
t.notThrows(function () { return substitute.received().v = Index_1.Arg.any(); }); | ||
t.notThrows(function () { return substitute.received(2).v = 'hello'; }); | ||
t.notThrows(function () { return substitute.received(2).v = Index_1.Arg.is(function (x) { return x && x.indexOf('ll') > -1; }); }); | ||
t.throws(function () { return substitute.received(2).v = Index_1.Arg.any(); }); | ||
t.throws(function () { return substitute.received(1).v = Index_1.Arg.any(); }); | ||
t.throws(function () { return substitute.received(1).v = Index_1.Arg.is(function (x) { return x && x.indexOf('ll') > -1; }); }); | ||
t.throws(function () { return substitute.received(3).v = 'hello'; }); | ||
}); | ||
ava_1.default('class method returns with placeholder args', function (t) { | ||
substitute.c(Index_1.Arg.any(), "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is(substitute.c("his", "there"), 'haha'); | ||
t.is(substitute.c("his", "there"), void 0); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('partial mocks using function mimicks with specific args', function (t) { | ||
substitute.c('a', 'b').mimicks(instance.c); | ||
t.is(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
ava_1.default('class method returns with specific args', function (t) { | ||
substitute.c("hi", "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is(substitute.c("hi", "there"), 'haha'); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('returning other fake from promise works', function (t) { return __awaiter(_this, void 0, void 0, function () { | ||
@@ -120,39 +179,2 @@ var otherSubstitute, _a, _b, _c; | ||
}); }); | ||
ava_1.default('class string field set received', function (t) { | ||
substitute.v = undefined; | ||
substitute.v = null; | ||
substitute.v = 'hello'; | ||
substitute.v = 'hello'; | ||
substitute.v = 'world'; | ||
t.notThrows(function () { return substitute.received().v = 'hello'; }); | ||
t.notThrows(function () { return substitute.received(5).v = Index_1.Arg.any(); }); | ||
t.notThrows(function () { return substitute.received().v = Index_1.Arg.any(); }); | ||
t.notThrows(function () { return substitute.received(2).v = 'hello'; }); | ||
t.notThrows(function () { return substitute.received(2).v = Index_1.Arg.is(function (x) { return x && x.indexOf('ll') > -1; }); }); | ||
t.throws(function () { return substitute.received(2).v = Index_1.Arg.any(); }); | ||
t.throws(function () { return substitute.received(1).v = Index_1.Arg.any(); }); | ||
t.throws(function () { return substitute.received(1).v = Index_1.Arg.is(function (x) { return x && x.indexOf('ll') > -1; }); }); | ||
t.throws(function () { return substitute.received(3).v = 'hello'; }); | ||
}); | ||
ava_1.default('class method returns with specific args', function (t) { | ||
substitute.c("hi", "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is(substitute.c("hi", "the1re"), substitute); | ||
t.deepEqual(substitute.c("hi", "there"), 'haha'); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('partial mocks using function mimicks with specific args', function (t) { | ||
substitute.c('a', 'b').mimicks(instance.c); | ||
t.is(substitute.c('c', 'b'), substitute); | ||
t.is(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
ava_1.default('class method returns with placeholder args', function (t) { | ||
substitute.c(Index_1.Arg.any(), "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is(substitute.c("hi", "the1re"), substitute); | ||
t.is(substitute.c("his", "there"), 'haha'); | ||
t.is(substitute.c("his", "there"), void 0); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('class void returns', function (t) { | ||
@@ -183,25 +205,2 @@ substitute.foo().returns(void 0, null); | ||
}); | ||
ava_1.default('can call received twice', function (t) { | ||
t.throws(function () { return substitute.received(1337).c('foo', 'bar'); }, "Expected 1337 calls to the method c with arguments [foo, bar], but received none of such calls.\nAll calls received to method c: (no calls)"); | ||
t.throws(function () { return substitute.received(2117).c('foo', 'bar'); }, "Expected 2117 calls to the method c with arguments [foo, bar], but received none of such calls.\nAll calls received to method c: (no calls)"); | ||
}); | ||
ava_1.default('class string field get received', function (t) { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(function () { return substitute.received(3).a; }); | ||
t.notThrows(function () { return substitute.received().a; }); | ||
t.notThrows(function () { return substitute.received(4).a; }); | ||
}); | ||
ava_1.default('class with method called "received" can be used for call count verification when proxies are suspended', function (t) { | ||
Index_1.Substitute.disableFor(substitute).received(2); | ||
t.throws(function () { return substitute.received(2).received(2); }); | ||
t.notThrows(function () { return substitute.received(1).received(2); }); | ||
}); | ||
ava_1.default('class with method called "received" can be used for call count verification', function (t) { | ||
Index_1.Substitute.disableFor(substitute).received('foo'); | ||
t.notThrows(function () { return substitute.received(1).received('foo'); }); | ||
t.throws(function () { return substitute.received(2).received('foo'); }); | ||
}); | ||
ava_1.default('partial mocks using property instance mimicks', function (t) { | ||
@@ -211,6 +210,2 @@ substitute.d.mimicks(function () { return instance.d; }); | ||
}); | ||
ava_1.default('partial mocks using function mimicks with all args', function (t) { | ||
substitute.c(Index_1.Arg.all()).mimicks(instance.c); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
ava_1.default('are arguments equal', function (t) { | ||
@@ -217,0 +212,0 @@ t.true(Utilities_1.areArgumentsEqual(Index_1.Arg.any(), 'hi')); |
@@ -1,53 +0,16 @@ | ||
export declare abstract class ProxyPropertyContextBase { | ||
name: string; | ||
type: 'function' | 'object'; | ||
import { ContextState } from "./states/ContextState"; | ||
import { InitialState } from "./states/InitialState"; | ||
export declare class Context { | ||
private _initialState; | ||
private _proxy; | ||
private _rootProxy; | ||
private _state; | ||
constructor(); | ||
apply(args: any[]): any; | ||
set(property: PropertyKey, value: any): void; | ||
get(property: PropertyKey): any; | ||
readonly proxy: any; | ||
readonly rootProxy: any; | ||
readonly initialState: InitialState; | ||
state: ContextState; | ||
} | ||
export declare class ProxyPropertyContext extends ProxyPropertyContextBase { | ||
type: 'object'; | ||
mimicks: Function; | ||
returnValues: any[]; | ||
constructor(); | ||
promoteToMethod(): ProxyMethodPropertyContext; | ||
} | ||
export declare class ProxyMethodPropertyContext extends ProxyPropertyContextBase { | ||
method: ProxyMethodContext; | ||
type: 'function'; | ||
constructor(); | ||
} | ||
export declare class ProxyMethodContext { | ||
arguments: any[]; | ||
returnValues: any[]; | ||
mimicks: Function; | ||
constructor(); | ||
} | ||
export declare class ProxyCallRecords { | ||
expected: ProxyExpectation; | ||
actual: ProxyCallRecord[]; | ||
constructor(); | ||
} | ||
export declare class ProxyExpectation { | ||
callCount: number; | ||
negated: boolean; | ||
propertyName: string; | ||
arguments: Array<any>; | ||
constructor(); | ||
} | ||
export declare class ProxyObjectContext { | ||
property: ProxyPropertyContext | ProxyMethodPropertyContext; | ||
calls: ProxyCallRecords; | ||
constructor(); | ||
setExpectations(count: number, negated: boolean): void; | ||
findActualPropertyCalls(propertyName: string): ProxyCallRecord[]; | ||
findActualMethodCalls(propertyName: string, args?: any[]): ProxyCallRecord[]; | ||
getLastCall(): ProxyCallRecord; | ||
removeActualPropertyCall(call: ProxyCallRecord): void; | ||
addActualPropertyCall(): ProxyCallRecord; | ||
fixExistingCallArguments(): void; | ||
} | ||
export declare class ProxyCallRecord { | ||
callCount: number; | ||
property: ProxyPropertyContext | ProxyMethodPropertyContext; | ||
argumentsSnapshot: any[]; | ||
constructor(property?: ProxyPropertyContext | ProxyMethodPropertyContext); | ||
} |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
} | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var __read = (this && this.__read) || function (o, n) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator]; | ||
if (!m) return o; | ||
var i = m.call(o), r, ar = [], e; | ||
try { | ||
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); | ||
} | ||
catch (error) { e = { error: error }; } | ||
finally { | ||
try { | ||
if (r && !r.done && (m = i["return"])) m.call(i); | ||
} | ||
finally { if (e) throw e.error; } | ||
} | ||
return ar; | ||
}; | ||
var __spread = (this && this.__spread) || function () { | ||
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); | ||
return ar; | ||
}; | ||
var __values = (this && this.__values) || function (o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Utilities_1 = require("./Utilities"); | ||
var Arguments_1 = require("./Arguments"); | ||
var ProxyPropertyContextBase = /** @class */ (function () { | ||
function ProxyPropertyContextBase() { | ||
this.name = null; | ||
this.type = null; | ||
} | ||
return ProxyPropertyContextBase; | ||
}()); | ||
exports.ProxyPropertyContextBase = ProxyPropertyContextBase; | ||
var ProxyPropertyContext = /** @class */ (function (_super) { | ||
__extends(ProxyPropertyContext, _super); | ||
function ProxyPropertyContext() { | ||
return _super.call(this) || this; | ||
} | ||
ProxyPropertyContext.prototype.promoteToMethod = function () { | ||
var methodContext = this; | ||
methodContext.method = new ProxyMethodContext(); | ||
methodContext.type = 'function'; | ||
return methodContext; | ||
}; | ||
return ProxyPropertyContext; | ||
}(ProxyPropertyContextBase)); | ||
exports.ProxyPropertyContext = ProxyPropertyContext; | ||
var ProxyMethodPropertyContext = /** @class */ (function (_super) { | ||
__extends(ProxyMethodPropertyContext, _super); | ||
function ProxyMethodPropertyContext() { | ||
var _this = _super.call(this) || this; | ||
_this.method = new ProxyMethodContext(); | ||
return _this; | ||
} | ||
return ProxyMethodPropertyContext; | ||
}(ProxyPropertyContextBase)); | ||
exports.ProxyMethodPropertyContext = ProxyMethodPropertyContext; | ||
var ProxyMethodContext = /** @class */ (function () { | ||
function ProxyMethodContext() { | ||
this.arguments = []; | ||
} | ||
return ProxyMethodContext; | ||
}()); | ||
exports.ProxyMethodContext = ProxyMethodContext; | ||
var ProxyCallRecords = /** @class */ (function () { | ||
function ProxyCallRecords() { | ||
this.expected = null; | ||
this.actual = []; | ||
} | ||
return ProxyCallRecords; | ||
}()); | ||
exports.ProxyCallRecords = ProxyCallRecords; | ||
var ProxyExpectation = /** @class */ (function () { | ||
function ProxyExpectation() { | ||
this.callCount = void 0; | ||
this.negated = false; | ||
} | ||
return ProxyExpectation; | ||
}()); | ||
exports.ProxyExpectation = ProxyExpectation; | ||
var ProxyObjectContext = /** @class */ (function () { | ||
function ProxyObjectContext() { | ||
this.calls = new ProxyCallRecords(); | ||
this.property = new ProxyPropertyContext(); | ||
} | ||
ProxyObjectContext.prototype.setExpectations = function (count, negated) { | ||
var call = new ProxyExpectation(); | ||
call.callCount = count; | ||
call.negated = negated; | ||
call.propertyName = null; | ||
this.calls.expected = call; | ||
}; | ||
ProxyObjectContext.prototype.findActualPropertyCalls = function (propertyName) { | ||
return this.calls.actual.filter(function (x) { | ||
return x.property.name === propertyName; | ||
var InitialState_1 = require("./states/InitialState"); | ||
var Substitute_1 = require("./Substitute"); | ||
var Context = /** @class */ (function () { | ||
function Context() { | ||
var _this_1 = this; | ||
this._initialState = new InitialState_1.InitialState(); | ||
this._state = this._initialState; | ||
this._proxy = new Proxy(function () { }, { | ||
apply: function (_target, _this, args) { | ||
return _this_1.apply(args); | ||
}, | ||
set: function (_target, property, value) { | ||
_this_1.set(property, value); | ||
return true; | ||
}, | ||
get: function (_target, property) { | ||
return _this_1.get(property); | ||
} | ||
}); | ||
}; | ||
ProxyObjectContext.prototype.findActualMethodCalls = function (propertyName, args) { | ||
var result = this.calls | ||
.actual | ||
.filter(function (x) { return x.property.name === propertyName; }) | ||
.filter(function (x) { | ||
if (args === void 0) | ||
this._rootProxy = new Proxy(function () { }, { | ||
apply: function (_target, _this, args) { | ||
return _this_1.initialState.apply(_this_1, args); | ||
}, | ||
set: function (_target, property, value) { | ||
_this_1.initialState.set(_this_1, property, value); | ||
return true; | ||
if (x.property.type !== 'function') | ||
return false; | ||
var args1 = x.argumentsSnapshot; | ||
var args2 = args; | ||
if (!args1 || !args2) | ||
return false; | ||
var firstArg1 = args1[0]; | ||
var firstArg2 = args2[0]; | ||
if (firstArg1 instanceof Arguments_1.AllArguments || firstArg2 instanceof Arguments_1.AllArguments) | ||
return true; | ||
if (args1.length !== args2.length) | ||
return false; | ||
for (var i = 0; i < args1.length; i++) { | ||
var arg1 = args1[i]; | ||
var arg2 = args2[i]; | ||
if (!Utilities_1.areArgumentsEqual(arg1, arg2)) | ||
return false; | ||
}, | ||
get: function (_target, property) { | ||
return _this_1.initialState.get(_this_1, property); | ||
} | ||
return true; | ||
}); | ||
return result; | ||
} | ||
Context.prototype.apply = function (args) { | ||
// console.log('apply', args); | ||
return this._state.apply(this, args); | ||
}; | ||
ProxyObjectContext.prototype.getLastCall = function () { | ||
return this.calls.actual[this.calls.actual.length - 1]; | ||
Context.prototype.set = function (property, value) { | ||
// console.log('set', property, value); | ||
return this._state.set(this, property, value); | ||
}; | ||
ProxyObjectContext.prototype.removeActualPropertyCall = function (call) { | ||
var callIndex = this.calls.actual.indexOf(call); | ||
this.calls.actual.splice(callIndex, 1); | ||
Context.prototype.get = function (property) { | ||
if (property === Substitute_1.HandlerKey) | ||
return this; | ||
// const uninterestingProperties = [ | ||
// '$$typeof', | ||
// 'constructor', | ||
// 'name', | ||
// 'call' | ||
// ]; | ||
// if(typeof property !== 'symbol' && uninterestingProperties.indexOf(property.toString()) === -1) | ||
// console.log('get', property); | ||
return this._state.get(this, property); | ||
}; | ||
ProxyObjectContext.prototype.addActualPropertyCall = function () { | ||
var _this = this; | ||
var existingCall; | ||
var existingCallCandidates = this.calls.actual.filter(function (x) { | ||
return x.property.name === _this.property.name; | ||
}); | ||
var thisProperty = this.property; | ||
if (thisProperty.type === 'function') { | ||
existingCall = existingCallCandidates.filter(function (x) { | ||
return x.property.type === thisProperty.type && | ||
Utilities_1.areArgumentsEqual(x.argumentsSnapshot, thisProperty.method.arguments); | ||
})[0]; | ||
} | ||
else { | ||
existingCall = existingCallCandidates[0]; | ||
} | ||
if (!existingCall) { | ||
existingCall = new ProxyCallRecord(this.property); | ||
this.calls.actual.push(existingCall); | ||
} | ||
else if (thisProperty.type === 'function') { | ||
existingCall.argumentsSnapshot = thisProperty.method.arguments; | ||
} | ||
existingCall.callCount++; | ||
return existingCall; | ||
}; | ||
ProxyObjectContext.prototype.fixExistingCallArguments = function () { | ||
var e_1, _a; | ||
var actualCalls = this.calls.actual; | ||
try { | ||
for (var _b = __values(__spread(actualCalls)), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var existingCall = _c.value; | ||
var existingCallProperty = existingCall.property; | ||
if (existingCallProperty.type === 'function' && existingCall.argumentsSnapshot === null) | ||
this.calls.actual.splice(this.calls.actual.indexOf(existingCall), 1); | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
}; | ||
return ProxyObjectContext; | ||
Object.defineProperty(Context.prototype, "proxy", { | ||
get: function () { | ||
return this._proxy; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(Context.prototype, "rootProxy", { | ||
get: function () { | ||
return this._rootProxy; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(Context.prototype, "initialState", { | ||
get: function () { | ||
return this._initialState; | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
Object.defineProperty(Context.prototype, "state", { | ||
set: function (state) { | ||
if (this._state === state) | ||
return; | ||
this._state = state; | ||
if (state.onSwitchedTo) | ||
state.onSwitchedTo(this); | ||
// console.log('state', state); | ||
}, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
return Context; | ||
}()); | ||
exports.ProxyObjectContext = ProxyObjectContext; | ||
var ProxyCallRecord = /** @class */ (function () { | ||
function ProxyCallRecord(property) { | ||
this.callCount = 0; | ||
this.property = property || null; | ||
this.argumentsSnapshot = property && property.type === 'function' ? property.method.arguments : null; | ||
} | ||
return ProxyCallRecord; | ||
}()); | ||
exports.ProxyCallRecord = ProxyCallRecord; | ||
exports.Context = Context; | ||
//# sourceMappingURL=Context.js.map |
import { ObjectSubstitute, OmitProxyMethods, DisabledSubstituteObject } from "./Transformations"; | ||
export declare const HandlerKey: unique symbol; | ||
export declare const AreProxiesDisabledKey: unique symbol; | ||
export declare class Substitute { | ||
static isSubstitute<T>(instance: T): any; | ||
static for<T>(): ObjectSubstitute<OmitProxyMethods<T>, T>; | ||
static disableFor<T extends ObjectSubstitute<OmitProxyMethods<any>>>(substitute: T): DisabledSubstituteObject<T>; | ||
static for<T>(): ObjectSubstitute<OmitProxyMethods<T>, T>; | ||
private static assertCallMatchCount; | ||
} |
@@ -24,21 +24,19 @@ "use strict"; | ||
var Context_1 = require("./Context"); | ||
var Utilities_1 = require("./Utilities"); | ||
var areProxiesDisabledKey = Symbol.for('areProxiesDisabled'); | ||
var handlerKey = Symbol.for('handler'); | ||
var isFake = Symbol.for('isFake'); | ||
var internalSymbols = [areProxiesDisabledKey, handlerKey, isFake]; | ||
exports.HandlerKey = Symbol(); | ||
exports.AreProxiesDisabledKey = Symbol(); | ||
var Substitute = /** @class */ (function () { | ||
function Substitute() { | ||
} | ||
Substitute.isSubstitute = function (instance) { | ||
return instance[isFake]; | ||
Substitute.for = function () { | ||
var objectContext = new Context_1.Context(); | ||
return objectContext.rootProxy; | ||
}; | ||
Substitute.disableFor = function (substitute) { | ||
var thisProxy = substitute; | ||
var thisExposedProxy = thisProxy[handlerKey]; | ||
var thisExposedProxy = thisProxy[exports.HandlerKey]; | ||
var disableProxy = function (f) { | ||
return function () { | ||
thisProxy[areProxiesDisabledKey] = true; | ||
thisProxy[exports.AreProxiesDisabledKey] = true; | ||
var returnValue = f.call.apply(f, __spread([thisExposedProxy], arguments)); | ||
thisProxy[areProxiesDisabledKey] = false; | ||
thisProxy[exports.AreProxiesDisabledKey] = false; | ||
return returnValue; | ||
@@ -53,259 +51,2 @@ }; | ||
}; | ||
Substitute.for = function () { | ||
var _this = this; | ||
var objectContext = new Context_1.ProxyObjectContext(); | ||
var thisProxy; | ||
var isProxyDisabled = function () { return thisProxy[areProxiesDisabledKey]; }; | ||
var isFluffySpoonProperty = function (p) { return internalSymbols.indexOf(p) !== -1; }; | ||
var internalStore = Object.create(null); | ||
var thisExposedProxy = { | ||
apply: function (_target, _thisArg, argumentsList) { | ||
var propertyContext = objectContext.property; | ||
if (!propertyContext) | ||
throw new Error('The property context could not be determined while invoking a proxy method.'); | ||
if (!propertyContext.name) | ||
throw new Error('The name of the current method could not be found.'); | ||
var expected = objectContext.calls.expected; | ||
if (propertyContext.type !== 'function') { | ||
var newMethodPropertyContext = propertyContext.promoteToMethod(); | ||
newMethodPropertyContext.method.arguments = argumentsList; | ||
newMethodPropertyContext.method.returnValues = null; | ||
} | ||
var existingCalls = objectContext.findActualMethodCalls(propertyContext.name, argumentsList); | ||
var existingCall = existingCalls[0]; | ||
var allCalls = objectContext.findActualMethodCalls(propertyContext.name); | ||
if (propertyContext.type === 'object') | ||
throw new Error('An error occured while promoting a property to a method.'); | ||
var hasExpected = expected && expected.callCount !== void 0; | ||
if (hasExpected) { | ||
expected.arguments = argumentsList; | ||
expected.propertyName = propertyContext.name; | ||
objectContext.fixExistingCallArguments(); | ||
_this.assertCallMatchCount('method', thisProxy, objectContext, allCalls, existingCalls); | ||
return void 0; | ||
} | ||
else { | ||
if (existingCall) { | ||
existingCall.callCount++; | ||
if (existingCall.property.type === 'function') { | ||
var mimicks = existingCall.property.method.mimicks; | ||
if (mimicks) | ||
return mimicks.call.apply(mimicks, __spread([_target], argumentsList)); | ||
} | ||
if (propertyContext.method.returnValues) | ||
return propertyContext.method.returnValues[existingCall.callCount - 1]; | ||
} | ||
else { | ||
propertyContext.method.arguments = argumentsList; | ||
if (!expected) | ||
objectContext.addActualPropertyCall(); | ||
if (isProxyDisabled()) | ||
return void 0; | ||
} | ||
} | ||
return thisProxy; | ||
}, | ||
set: function (_target, property, value) { | ||
if (isFluffySpoonProperty(property)) { | ||
internalStore[property] = value; | ||
return true; | ||
} | ||
var expected = objectContext.calls.expected; | ||
var argumentsList = [value]; | ||
var existingCalls = objectContext.findActualMethodCalls(property.toString(), argumentsList); | ||
if (expected && expected.callCount !== void 0) { | ||
expected.arguments = argumentsList; | ||
expected.propertyName = property.toString(); | ||
_this.assertCallMatchCount('property', thisProxy, objectContext, objectContext.findActualMethodCalls(property.toString()), existingCalls); | ||
return true; | ||
} | ||
var propertyContext = objectContext.property; | ||
if (propertyContext) { | ||
if (existingCalls.length > 0 && propertyContext.type === 'function') { | ||
var existingCall = existingCalls[0]; | ||
if (!expected) | ||
existingCall.callCount++; | ||
return true; | ||
} | ||
} | ||
var newMethodPropertyContext = new Context_1.ProxyMethodPropertyContext(); | ||
newMethodPropertyContext.name = property.toString(); | ||
newMethodPropertyContext.type = 'function'; | ||
newMethodPropertyContext.method.arguments = argumentsList; | ||
newMethodPropertyContext.method.returnValues = argumentsList; | ||
objectContext.property = newMethodPropertyContext; | ||
if (!expected) | ||
objectContext.addActualPropertyCall(); | ||
return true; | ||
}, | ||
get: function (target, property) { | ||
if (isFluffySpoonProperty(property)) | ||
return internalStore[property]; | ||
if (typeof property === 'symbol') { | ||
if (property === Symbol.toPrimitive) | ||
return function () { return void 0; }; | ||
if (property === Symbol.toStringTag) | ||
return void 0; | ||
} | ||
if (property === 'valueOf') | ||
return void 0; | ||
if (property === 'toString') | ||
return (target[property] || '').toString(); | ||
if (property === 'inspect') | ||
return function () { return '{SubstituteJS fake}'; }; | ||
if (property === 'constructor') | ||
return function () { return thisProxy; }; | ||
if (property === 'then') | ||
return void 0; | ||
var currentPropertyContext = objectContext.property; | ||
var addPropertyToObjectContext = function () { | ||
var expected = objectContext.calls.expected; | ||
var existingCall = objectContext.findActualPropertyCalls(property.toString())[0] || null; | ||
if (existingCall) { | ||
var existingCallProperty = existingCall.property; | ||
if (existingCallProperty.type === 'function') { | ||
objectContext.property = existingCallProperty; | ||
return thisProxy; | ||
} | ||
if (expected && expected.callCount !== void 0) { | ||
expected.propertyName = existingCallProperty.name; | ||
_this.assertCallMatchCount('property', thisProxy, objectContext, [existingCall], [existingCall]); | ||
return thisProxy; | ||
} | ||
existingCall.callCount++; | ||
if (existingCallProperty.returnValues) | ||
return existingCallProperty.returnValues[existingCall.callCount - 1]; | ||
var mimicks = existingCallProperty.mimicks; | ||
if (mimicks) | ||
return mimicks(); | ||
return void 0; | ||
} | ||
var newPropertyContext = new Context_1.ProxyPropertyContext(); | ||
newPropertyContext.name = property.toString(); | ||
newPropertyContext.type = 'object'; | ||
newPropertyContext.returnValues = null; | ||
objectContext.property = newPropertyContext; | ||
if (!expected) | ||
objectContext.addActualPropertyCall(); | ||
return thisProxy; | ||
}; | ||
if (property === 'returns' && !isProxyDisabled()) { | ||
var createReturnsFunction = function (context) { | ||
return function () { | ||
var args = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
args[_i] = arguments[_i]; | ||
} | ||
context.returnValues = args; | ||
context.mimicks = void 0; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
}; | ||
if (currentPropertyContext.type === 'object') | ||
return createReturnsFunction(currentPropertyContext); | ||
if (currentPropertyContext.type === 'function') | ||
return createReturnsFunction(currentPropertyContext.method); | ||
} | ||
if (property === 'mimicks' && !isProxyDisabled()) { | ||
var createMimicksFunction = function (context) { | ||
return function (value) { | ||
objectContext.property = null; | ||
objectContext.calls.expected = null; | ||
context.returnValues = void 0; | ||
context.mimicks = value; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
}; | ||
if (currentPropertyContext.type === 'object') | ||
return createMimicksFunction(currentPropertyContext); | ||
if (currentPropertyContext.type === 'function') | ||
return createMimicksFunction(currentPropertyContext.method); | ||
} | ||
if (!isProxyDisabled() && (property === 'received' || property === 'didNotReceive')) { | ||
return function (count) { | ||
var args = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
args[_i - 1] = arguments[_i]; | ||
} | ||
var shouldForwardCall = (typeof count !== 'number' && typeof count !== 'undefined') || args.length > 0 || isProxyDisabled(); | ||
if (shouldForwardCall) { | ||
addPropertyToObjectContext(); | ||
return thisExposedProxy.apply(target, target, __spread([count], args)); | ||
} | ||
if (count === void 0) | ||
count = null; | ||
objectContext.setExpectations(count, property === 'didNotReceive'); | ||
thisProxy[areProxiesDisabledKey] = true; | ||
return thisProxy; | ||
}; | ||
} | ||
return addPropertyToObjectContext(); | ||
} | ||
}; | ||
thisProxy = new Proxy(function () { }, thisExposedProxy); | ||
thisProxy[areProxiesDisabledKey] = false; | ||
thisProxy[isFake] = true; | ||
thisProxy[handlerKey] = thisExposedProxy; | ||
return thisProxy; | ||
}; | ||
Substitute.assertCallMatchCount = function (type, thisProxy, objectContext, allCalls, matchingCalls) { | ||
var expected = objectContext.calls.expected; | ||
objectContext.property = null; | ||
objectContext.calls.expected = null; | ||
thisProxy[areProxiesDisabledKey] = false; | ||
var getCallCounts = function (calls) { | ||
var callCounts = calls.map(function (x) { return x.callCount; }); | ||
var totalCallCount = callCounts.length === 0 ? 0 : callCounts.reduce(function (accumulator, value) { return accumulator + value; }); | ||
return totalCallCount; | ||
}; | ||
var matchingCallsCount = getCallCounts(matchingCalls); | ||
var isMatch = !((!expected.negated && ((expected.callCount === null && matchingCallsCount === 0) || | ||
(expected.callCount !== null && expected.callCount !== matchingCallsCount))) || | ||
(expected.negated && ((expected.callCount === null && matchingCallsCount !== 0) || | ||
(expected.callCount !== null && expected.callCount === matchingCallsCount)))); | ||
if (!isMatch) { | ||
var errorMessage = ''; | ||
errorMessage += expected.negated ? 'Did not expect' : 'Expected'; | ||
errorMessage += ' '; | ||
errorMessage += expected.callCount === null ? 'one or more' : expected.callCount; | ||
errorMessage += ' call'; | ||
errorMessage += (expected.callCount === null || expected.callCount !== 1) ? 's' : ''; | ||
errorMessage += ' to the '; | ||
errorMessage += type; | ||
errorMessage += ' '; | ||
errorMessage += expected.propertyName; | ||
if (expected.arguments) { | ||
if (type === 'property') { | ||
errorMessage += ' with value '; | ||
var value = expected.arguments[0]; | ||
if (value === null) | ||
errorMessage += 'null'; | ||
if (value === void 0) | ||
errorMessage += 'undefined'; | ||
if (value) | ||
errorMessage += value; | ||
} | ||
else if (type === 'method') { | ||
errorMessage += ' with '; | ||
errorMessage += Utilities_1.stringifyArguments(expected.arguments); | ||
} | ||
} | ||
errorMessage += ', but received '; | ||
errorMessage += matchingCallsCount === 0 ? 'none' : matchingCallsCount; | ||
if (expected.arguments) { | ||
errorMessage += ' of such call'; | ||
errorMessage += matchingCallsCount !== 1 ? 's' : ''; | ||
} | ||
errorMessage += '.'; | ||
if (expected.arguments) { | ||
errorMessage += '\nAll calls received to '; | ||
errorMessage += type; | ||
errorMessage += ' '; | ||
errorMessage += expected.propertyName; | ||
errorMessage += ':'; | ||
errorMessage += Utilities_1.stringifyCalls(allCalls); | ||
} | ||
throw new Error(errorMessage); | ||
} | ||
}; | ||
return Substitute; | ||
@@ -312,0 +53,0 @@ }()); |
@@ -1,4 +0,8 @@ | ||
import { ProxyCallRecord } from "./Context"; | ||
export declare type Call = { | ||
callCount: number; | ||
arguments?: any[]; | ||
}; | ||
export declare function stringifyArguments(args: any[]): string; | ||
export declare function stringifyCalls(calls: ProxyCallRecord[]): string; | ||
export declare function areArgumentArraysEqual(a: any[], b: any[]): boolean; | ||
export declare function stringifyCalls(calls: Call[]): string; | ||
export declare function areArgumentsEqual(a: any, b: any): boolean; |
@@ -19,2 +19,10 @@ "use strict"; | ||
; | ||
function areArgumentArraysEqual(a, b) { | ||
for (var i = 0; i < Math.min(b.length, a.length); i++) { | ||
if (!areArgumentsEqual(b[i], a[i])) | ||
return false; | ||
} | ||
return true; | ||
} | ||
exports.areArgumentArraysEqual = areArgumentArraysEqual; | ||
function stringifyCalls(calls) { | ||
@@ -31,4 +39,4 @@ var e_1, _a; | ||
output += call.callCount !== 1 ? 's' : ''; | ||
if (call.property.type === 'function') | ||
output += ' with ' + stringifyArguments(call.argumentsSnapshot); | ||
if (call.arguments) | ||
output += ' with ' + stringifyArguments(call.arguments); | ||
} | ||
@@ -35,0 +43,0 @@ } |
{ | ||
"name": "@fluffy-spoon/substitute", | ||
"version": "1.0.65", | ||
"version": "1.0.66", | ||
"description": "An NSubstitute port to TypeScript called substitute.js.", | ||
@@ -17,7 +17,7 @@ "main": "dist/src/Index.js", | ||
"ts-node": "^7.0.1", | ||
"typescript": "^3.1.1" | ||
"typescript": "^3.1.3" | ||
}, | ||
"ava": { | ||
"files": [ | ||
"./dist/spec/*.js" | ||
"./dist/spec/**/*.js" | ||
], | ||
@@ -24,0 +24,0 @@ "sources": [ |
@@ -43,6 +43,2 @@ import test from 'ava'; | ||
test.beforeEach(() => { | ||
console.log(''); | ||
console.log('Ava: beforeEach') | ||
console.log(''); | ||
instance = new Example(); | ||
@@ -52,15 +48,47 @@ substitute = Substitute.for<Example>(); | ||
test('returning other fake from promise works', async t => { | ||
const otherSubstitute = Substitute.for<Dummy>(); | ||
substitute.returnPromise().returns(Promise.resolve(otherSubstitute)); | ||
test('class with method called "received" can be used for call count verification when proxies are suspended', t => { | ||
Substitute.disableFor(substitute).received(2); | ||
t.is(otherSubstitute, await substitute.returnPromise()); | ||
t.throws(() => substitute.received(2).received(2)); | ||
t.notThrows(() => substitute.received(1).received(2)); | ||
}); | ||
test('returning resolved promises works', async t => { | ||
substitute.returnPromise().returns(Promise.resolve(1338)); | ||
test('class with method called "received" can be used for call count verification', t => { | ||
Substitute.disableFor(substitute).received('foo'); | ||
t.is(1338, await substitute.returnPromise()); | ||
t.notThrows(() => substitute.received(1).received('foo')); | ||
t.throws(() => substitute.received(2).received('foo')); | ||
}); | ||
test('partial mocks using function mimicks with all args', t => { | ||
substitute.c(Arg.all()).mimicks(instance.c); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
test('can call received twice', t => { | ||
substitute.c('blah', 'fuzz'); | ||
t.throws(() => substitute.received(1337).c('foo', 'bar'), | ||
`Expected 1337 calls to the method c with arguments [foo, bar], but received none of such calls. | ||
All calls received to method c: | ||
-> 1 call with arguments [blah, fuzz]`); | ||
t.throws(() => substitute.received(2117).c('foo', 'bar'), | ||
`Expected 2117 calls to the method c with arguments [foo, bar], but received none of such calls. | ||
All calls received to method c: | ||
-> 1 call with arguments [blah, fuzz]`); | ||
}); | ||
test('class string field get received', t => { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(() => substitute.received(3).a); | ||
t.notThrows(() => substitute.received().a); | ||
t.notThrows(() => substitute.received(4).a); | ||
}); | ||
test('class string field set received', t => { | ||
@@ -85,2 +113,17 @@ substitute.v = undefined; | ||
test('class method returns with placeholder args', t => { | ||
substitute.c(Arg.any(), "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is(substitute.c("his", "there"), 'haha'); | ||
t.is<any>(substitute.c("his", "there"), void 0); | ||
t.is<any>(substitute.c("hi", "there"), void 0); | ||
}); | ||
test('partial mocks using function mimicks with specific args', t => { | ||
substitute.c('a', 'b').mimicks(instance.c); | ||
t.is(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
test('class method returns with specific args', t => { | ||
@@ -90,4 +133,3 @@ substitute.c("hi", "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is<any>(substitute.c("hi", "the1re"), substitute); | ||
t.deepEqual(substitute.c("hi", "there"), 'haha'); | ||
t.is(substitute.c("hi", "there"), 'haha'); | ||
t.is(substitute.c("hi", "there"), void 0); | ||
@@ -97,17 +139,13 @@ t.is(substitute.c("hi", "there"), void 0); | ||
test('partial mocks using function mimicks with specific args', t => { | ||
substitute.c('a', 'b').mimicks(instance.c); | ||
test('returning other fake from promise works', async t => { | ||
const otherSubstitute = Substitute.for<Dummy>(); | ||
substitute.returnPromise().returns(Promise.resolve(otherSubstitute)); | ||
t.is<any>(substitute.c('c', 'b'), substitute); | ||
t.is(substitute.c('a', 'b'), 'hello a world (b)'); | ||
t.is(otherSubstitute, await substitute.returnPromise()); | ||
}); | ||
test('class method returns with placeholder args', t => { | ||
substitute.c(Arg.any(), "there").returns("blah", "haha"); | ||
t.is(substitute.c("hi", "there"), 'blah'); | ||
t.is<any>(substitute.c("hi", "the1re"), substitute); | ||
t.is(substitute.c("his", "there"), 'haha'); | ||
t.is<any>(substitute.c("his", "there"), void 0); | ||
t.is<any>(substitute.c("hi", "there"), void 0); | ||
test('returning resolved promises works', async t => { | ||
substitute.returnPromise().returns(Promise.resolve(1338)); | ||
t.is(1338, await substitute.returnPromise()); | ||
}); | ||
@@ -156,37 +194,2 @@ | ||
test('can call received twice', t => { | ||
t.throws(() => substitute.received(1337).c('foo', 'bar'), | ||
`Expected 1337 calls to the method c with arguments [foo, bar], but received none of such calls. | ||
All calls received to method c: (no calls)`); | ||
t.throws(() => substitute.received(2117).c('foo', 'bar'), | ||
`Expected 2117 calls to the method c with arguments [foo, bar], but received none of such calls. | ||
All calls received to method c: (no calls)`); | ||
}); | ||
test('class string field get received', t => { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(() => substitute.received(3).a); | ||
t.notThrows(() => substitute.received().a); | ||
t.notThrows(() => substitute.received(4).a); | ||
}); | ||
test('class with method called "received" can be used for call count verification when proxies are suspended', t => { | ||
Substitute.disableFor(substitute).received(2); | ||
t.throws(() => substitute.received(2).received(2)); | ||
t.notThrows(() => substitute.received(1).received(2)); | ||
}); | ||
test('class with method called "received" can be used for call count verification', t => { | ||
Substitute.disableFor(substitute).received('foo'); | ||
t.notThrows(() => substitute.received(1).received('foo')); | ||
t.throws(() => substitute.received(2).received('foo')); | ||
}); | ||
test('partial mocks using property instance mimicks', t => { | ||
@@ -198,8 +201,2 @@ substitute.d.mimicks(() => instance.d); | ||
test('partial mocks using function mimicks with all args', t => { | ||
substitute.c(Arg.all()).mimicks(instance.c); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
test('are arguments equal', t => { | ||
@@ -206,0 +203,0 @@ t.true(areArgumentsEqual(Arg.any(), 'hi')); |
@@ -1,196 +0,92 @@ | ||
import { areArgumentsEqual } from "./Utilities"; | ||
import { AllArguments } from "./Arguments"; | ||
import { ContextState } from "./states/ContextState"; | ||
import { InitialState } from "./states/InitialState"; | ||
import { HandlerKey } from "./Substitute"; | ||
export abstract class ProxyPropertyContextBase { | ||
name: string; | ||
export class Context { | ||
private _initialState: InitialState; | ||
type: 'function' | 'object'; | ||
constructor() { | ||
this.name = null; | ||
this.type = null; | ||
} | ||
} | ||
export class ProxyPropertyContext extends ProxyPropertyContextBase { | ||
type: 'object'; | ||
private _proxy: any; | ||
private _rootProxy: any; | ||
mimicks: Function; | ||
returnValues: any[]; | ||
private _state: ContextState; | ||
constructor() { | ||
super(); | ||
} | ||
this._initialState = new InitialState(); | ||
this._state = this._initialState; | ||
promoteToMethod(): ProxyMethodPropertyContext { | ||
const methodContext = this as any as ProxyMethodPropertyContext; | ||
methodContext.method = new ProxyMethodContext(); | ||
methodContext.type = 'function'; | ||
this._proxy = new Proxy(() => { }, { | ||
apply: (_target, _this, args) => { | ||
return this.apply(args); | ||
}, | ||
set: (_target, property, value) => { | ||
this.set(property, value); | ||
return true; | ||
}, | ||
get: (_target, property) => { | ||
return this.get(property); | ||
} | ||
}); | ||
return methodContext; | ||
this._rootProxy = new Proxy(() => { }, { | ||
apply: (_target, _this, args) => { | ||
return this.initialState.apply(this, args); | ||
}, | ||
set: (_target, property, value) => { | ||
this.initialState.set(this, property, value); | ||
return true; | ||
}, | ||
get: (_target, property) => { | ||
return this.initialState.get(this, property); | ||
} | ||
}); | ||
} | ||
} | ||
export class ProxyMethodPropertyContext extends ProxyPropertyContextBase { | ||
method: ProxyMethodContext; | ||
type: 'function'; | ||
constructor() { | ||
super(); | ||
this.method = new ProxyMethodContext(); | ||
apply(args: any[]) { | ||
// console.log('apply', args); | ||
return this._state.apply(this, args); | ||
} | ||
} | ||
export class ProxyMethodContext { | ||
arguments: any[]; | ||
returnValues: any[]; | ||
mimicks: Function; | ||
constructor() { | ||
this.arguments = []; | ||
set(property: PropertyKey, value: any) { | ||
// console.log('set', property, value); | ||
return this._state.set(this, property, value); | ||
} | ||
} | ||
export class ProxyCallRecords { | ||
expected: ProxyExpectation; | ||
actual: ProxyCallRecord[]; | ||
get(property: PropertyKey) { | ||
if(property === HandlerKey) | ||
return this; | ||
constructor() { | ||
this.expected = null; | ||
this.actual = []; | ||
} | ||
} | ||
// const uninterestingProperties = [ | ||
// '$$typeof', | ||
// 'constructor', | ||
// 'name', | ||
// 'call' | ||
// ]; | ||
// if(typeof property !== 'symbol' && uninterestingProperties.indexOf(property.toString()) === -1) | ||
// console.log('get', property); | ||
export class ProxyExpectation { | ||
callCount: number; | ||
negated: boolean; | ||
propertyName: string; | ||
arguments: Array<any>; | ||
constructor() { | ||
this.callCount = void 0; | ||
this.negated = false; | ||
return this._state.get(this, property); | ||
} | ||
} | ||
export class ProxyObjectContext { | ||
property: ProxyPropertyContext|ProxyMethodPropertyContext; | ||
calls: ProxyCallRecords; | ||
constructor() { | ||
this.calls = new ProxyCallRecords(); | ||
this.property = new ProxyPropertyContext(); | ||
public get proxy() { | ||
return this._proxy; | ||
} | ||
setExpectations(count: number, negated: boolean) { | ||
const call = new ProxyExpectation(); | ||
call.callCount = count; | ||
call.negated = negated; | ||
call.propertyName = null; | ||
this.calls.expected = call; | ||
public get rootProxy() { | ||
return this._rootProxy; | ||
} | ||
findActualPropertyCalls(propertyName: string) { | ||
return this.calls.actual.filter(x => | ||
x.property.name === propertyName); | ||
public get initialState() { | ||
return this._initialState; | ||
} | ||
findActualMethodCalls(propertyName: string, args?: any[]) { | ||
let result = this.calls | ||
.actual | ||
.filter(x => x.property.name === propertyName) | ||
.filter(x => { | ||
if(args === void 0) | ||
return true; | ||
public set state(state: ContextState) { | ||
if(this._state === state) | ||
return; | ||
if(x.property.type !== 'function') return false; | ||
const args1 = x.argumentsSnapshot; | ||
const args2 = args; | ||
this._state = state; | ||
if(state.onSwitchedTo) | ||
state.onSwitchedTo(this); | ||
if(!args1 || !args2) | ||
return false; | ||
const firstArg1 = args1[0]; | ||
const firstArg2 = args2[0]; | ||
if(firstArg1 instanceof AllArguments || firstArg2 instanceof AllArguments) | ||
return true; | ||
if(args1.length !== args2.length) | ||
return false; | ||
for(let i=0;i<args1.length;i++) { | ||
const arg1 = args1[i]; | ||
const arg2 = args2[i]; | ||
if(!areArgumentsEqual(arg1, arg2)) | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return result; | ||
// console.log('state', state); | ||
} | ||
getLastCall() { | ||
return this.calls.actual[this.calls.actual.length-1]; | ||
} | ||
removeActualPropertyCall(call: ProxyCallRecord) { | ||
const callIndex = this.calls.actual.indexOf(call); | ||
this.calls.actual.splice(callIndex, 1); | ||
} | ||
addActualPropertyCall() { | ||
let existingCall: ProxyCallRecord; | ||
const existingCallCandidates = this.calls.actual.filter(x => | ||
x.property.name === this.property.name); | ||
const thisProperty = this.property; | ||
if(thisProperty.type === 'function') { | ||
existingCall = existingCallCandidates.filter(x => | ||
x.property.type === thisProperty.type && | ||
areArgumentsEqual(x.argumentsSnapshot, thisProperty.method.arguments))[0]; | ||
} else { | ||
existingCall = existingCallCandidates[0]; | ||
} | ||
if(!existingCall) { | ||
existingCall = new ProxyCallRecord(this.property); | ||
this.calls.actual.push(existingCall); | ||
} else if(thisProperty.type === 'function') { | ||
existingCall.argumentsSnapshot = thisProperty.method.arguments; | ||
} | ||
existingCall.callCount++; | ||
return existingCall; | ||
} | ||
fixExistingCallArguments() { | ||
const actualCalls = this.calls.actual; | ||
for(let existingCall of [...actualCalls]) { | ||
const existingCallProperty = existingCall.property; | ||
if(existingCallProperty.type === 'function' && existingCall.argumentsSnapshot === null) | ||
this.calls.actual.splice(this.calls.actual.indexOf(existingCall), 1); | ||
} | ||
} | ||
} | ||
export class ProxyCallRecord { | ||
callCount: number; | ||
property: ProxyPropertyContext | ProxyMethodPropertyContext; | ||
argumentsSnapshot: any[]; | ||
constructor(property?: ProxyPropertyContext | ProxyMethodPropertyContext) { | ||
this.callCount = 0; | ||
this.property = property || null; | ||
this.argumentsSnapshot = property && property.type === 'function' ? property.method.arguments : null; | ||
} | ||
} |
@@ -0,14 +1,11 @@ | ||
import { Context } from "./Context"; | ||
import { ObjectSubstitute, OmitProxyMethods, DisabledSubstituteObject } from "./Transformations"; | ||
import { ProxyObjectContext, ProxyPropertyContext, ProxyMethodPropertyContext, ProxyCallRecord, ProxyExpectation } from "./Context"; | ||
import { stringifyCalls, stringifyArguments } from "./Utilities"; | ||
const areProxiesDisabledKey = Symbol.for('areProxiesDisabled'); | ||
const handlerKey = Symbol.for('handler'); | ||
const isFake = Symbol.for('isFake'); | ||
export const HandlerKey = Symbol(); | ||
export const AreProxiesDisabledKey = Symbol(); | ||
const internalSymbols = [areProxiesDisabledKey, handlerKey, isFake]; | ||
export class Substitute { | ||
static isSubstitute<T>(instance: T) { | ||
return instance[isFake]; | ||
static for<T>(): ObjectSubstitute<OmitProxyMethods<T>, T> { | ||
const objectContext = new Context(); | ||
return objectContext.rootProxy; | ||
} | ||
@@ -18,9 +15,9 @@ | ||
const thisProxy = substitute as any; | ||
const thisExposedProxy = thisProxy[handlerKey]; | ||
const thisExposedProxy = thisProxy[HandlerKey]; | ||
const disableProxy = <K extends Function>(f: K): K => { | ||
return function() { | ||
thisProxy[areProxiesDisabledKey] = true; | ||
thisProxy[AreProxiesDisabledKey] = true; | ||
const returnValue = f.call(thisExposedProxy, ...arguments); | ||
thisProxy[areProxiesDisabledKey] = false; | ||
thisProxy[AreProxiesDisabledKey] = false; | ||
return returnValue; | ||
@@ -36,349 +33,2 @@ } as any; | ||
} | ||
static for<T>(): ObjectSubstitute<OmitProxyMethods<T>, T> { | ||
const objectContext = new ProxyObjectContext(); | ||
let thisProxy: ObjectSubstitute<T>; | ||
const isProxyDisabled = () => thisProxy[areProxiesDisabledKey]; | ||
const isFluffySpoonProperty = (p: symbol) => internalSymbols.indexOf(p) !== -1; | ||
const internalStore = Object.create(null) as any; | ||
var thisExposedProxy: ProxyHandler<any> = { | ||
apply: (_target, _thisArg, argumentsList) => { | ||
const propertyContext = objectContext.property; | ||
if(!propertyContext) | ||
throw new Error('The property context could not be determined while invoking a proxy method.'); | ||
if(!propertyContext.name) | ||
throw new Error('The name of the current method could not be found.'); | ||
const expected = objectContext.calls.expected; | ||
if(propertyContext.type !== 'function') { | ||
const newMethodPropertyContext = propertyContext.promoteToMethod(); | ||
newMethodPropertyContext.method.arguments = argumentsList; | ||
newMethodPropertyContext.method.returnValues = null; | ||
} | ||
const existingCalls = objectContext.findActualMethodCalls(propertyContext.name, argumentsList); | ||
const existingCall = existingCalls[0]; | ||
const allCalls = objectContext.findActualMethodCalls(propertyContext.name); | ||
if(propertyContext.type === 'object') | ||
throw new Error('An error occured while promoting a property to a method.'); | ||
const hasExpected = expected && expected.callCount !== void 0; | ||
if(hasExpected) { | ||
expected.arguments = argumentsList; | ||
expected.propertyName = propertyContext.name; | ||
objectContext.fixExistingCallArguments(); | ||
this.assertCallMatchCount('method', thisProxy, objectContext, | ||
allCalls, | ||
existingCalls); | ||
return void 0; | ||
} else { | ||
if(existingCall) { | ||
existingCall.callCount++; | ||
if(existingCall.property.type === 'function') { | ||
const mimicks = existingCall.property.method.mimicks; | ||
if(mimicks) | ||
return mimicks.call(_target, ...argumentsList); | ||
} | ||
if(propertyContext.method.returnValues) | ||
return propertyContext.method.returnValues[existingCall.callCount - 1]; | ||
} else { | ||
propertyContext.method.arguments = argumentsList; | ||
if(!expected) | ||
objectContext.addActualPropertyCall(); | ||
if(isProxyDisabled()) | ||
return void 0; | ||
} | ||
} | ||
return thisProxy; | ||
}, | ||
set: (_target, property, value) => { | ||
if(isFluffySpoonProperty(property as symbol)) { | ||
internalStore[property] = value; | ||
return true; | ||
} | ||
const expected = objectContext.calls.expected; | ||
const argumentsList = [value]; | ||
let existingCalls = objectContext.findActualMethodCalls(property.toString(), argumentsList); | ||
if (expected && expected.callCount !== void 0) { | ||
expected.arguments = argumentsList; | ||
expected.propertyName = property.toString(); | ||
this.assertCallMatchCount('property', thisProxy, objectContext, | ||
objectContext.findActualMethodCalls(property.toString()), | ||
existingCalls); | ||
return true; | ||
} | ||
const propertyContext = objectContext.property; | ||
if(propertyContext) { | ||
if (existingCalls.length > 0 && propertyContext.type === 'function') { | ||
const existingCall = existingCalls[0]; | ||
if(!expected) | ||
existingCall.callCount++; | ||
return true; | ||
} | ||
} | ||
const newMethodPropertyContext = new ProxyMethodPropertyContext(); | ||
newMethodPropertyContext.name = property.toString(); | ||
newMethodPropertyContext.type = 'function'; | ||
newMethodPropertyContext.method.arguments = argumentsList; | ||
newMethodPropertyContext.method.returnValues = argumentsList; | ||
objectContext.property = newMethodPropertyContext; | ||
if(!expected) | ||
objectContext.addActualPropertyCall(); | ||
return true; | ||
}, | ||
get: (target, property) => { | ||
if(isFluffySpoonProperty(property as symbol)) | ||
return internalStore[property]; | ||
if (typeof property === 'symbol') { | ||
if (property === Symbol.toPrimitive) | ||
return () => void 0; | ||
if(property === Symbol.toStringTag) | ||
return void 0; | ||
} | ||
if (property === 'valueOf') | ||
return void 0; | ||
if (property === 'toString') | ||
return (target[property] || '').toString(); | ||
if (property === 'inspect') | ||
return () => '{SubstituteJS fake}'; | ||
if (property === 'constructor') | ||
return () => thisProxy; | ||
if(property === 'then') | ||
return void 0; | ||
const currentPropertyContext = objectContext.property; | ||
const addPropertyToObjectContext = () => { | ||
const expected = objectContext.calls.expected; | ||
const existingCall = objectContext.findActualPropertyCalls(property.toString())[0] || null; | ||
if (existingCall) { | ||
const existingCallProperty = existingCall.property; | ||
if (existingCallProperty.type === 'function') { | ||
objectContext.property = existingCallProperty; | ||
return thisProxy; | ||
} | ||
if (expected && expected.callCount !== void 0) { | ||
expected.propertyName = existingCallProperty.name; | ||
this.assertCallMatchCount('property', thisProxy, objectContext, [existingCall], [existingCall]); | ||
return thisProxy; | ||
} | ||
existingCall.callCount++; | ||
if (existingCallProperty.returnValues) | ||
return existingCallProperty.returnValues[existingCall.callCount - 1]; | ||
const mimicks = existingCallProperty.mimicks; | ||
if(mimicks) | ||
return mimicks(); | ||
return void 0; | ||
} | ||
const newPropertyContext = new ProxyPropertyContext(); | ||
newPropertyContext.name = property.toString(); | ||
newPropertyContext.type = 'object'; | ||
newPropertyContext.returnValues = null; | ||
objectContext.property = newPropertyContext; | ||
if(!expected) | ||
objectContext.addActualPropertyCall(); | ||
return thisProxy; | ||
}; | ||
if (property === 'returns' && !isProxyDisabled()) { | ||
const createReturnsFunction = (context: {returnValues, mimicks}) => { | ||
return (...args: any[]) => { | ||
context.returnValues = args; | ||
context.mimicks = void 0; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
}; | ||
if (currentPropertyContext.type === 'object') | ||
return createReturnsFunction(currentPropertyContext); | ||
if (currentPropertyContext.type === 'function') | ||
return createReturnsFunction(currentPropertyContext.method); | ||
} | ||
if(property === 'mimicks' && !isProxyDisabled()) { | ||
const createMimicksFunction = (context: {returnValues, mimicks}) => { | ||
return (value: Function) => { | ||
objectContext.property = null; | ||
objectContext.calls.expected = null; | ||
context.returnValues = void 0; | ||
context.mimicks = value; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
}; | ||
if(currentPropertyContext.type === 'object') | ||
return createMimicksFunction(currentPropertyContext); | ||
if(currentPropertyContext.type === 'function') | ||
return createMimicksFunction(currentPropertyContext.method); | ||
} | ||
if (!isProxyDisabled() && (property === 'received' || property === 'didNotReceive')) { | ||
return (count?: number, ...args) => { | ||
const shouldForwardCall = (typeof count !== 'number' && typeof count !== 'undefined') || args.length > 0 || isProxyDisabled(); | ||
if(shouldForwardCall) { | ||
addPropertyToObjectContext(); | ||
return thisExposedProxy.apply(target, target, [count, ...args]); | ||
} | ||
if (count === void 0) | ||
count = null; | ||
objectContext.setExpectations(count, property === 'didNotReceive'); | ||
thisProxy[areProxiesDisabledKey] = true; | ||
return thisProxy; | ||
}; | ||
} | ||
return addPropertyToObjectContext(); | ||
} | ||
}; | ||
thisProxy = new Proxy(() => { }, thisExposedProxy) as any; | ||
thisProxy[areProxiesDisabledKey] = false; | ||
thisProxy[isFake] = true; | ||
thisProxy[handlerKey] = thisExposedProxy; | ||
return thisProxy; | ||
} | ||
private static assertCallMatchCount( | ||
type: 'property' | 'method', | ||
thisProxy: any, | ||
objectContext: ProxyObjectContext, | ||
allCalls: ProxyCallRecord[], | ||
matchingCalls: ProxyCallRecord[]): void | ||
{ | ||
const expected = objectContext.calls.expected; | ||
objectContext.property = null; | ||
objectContext.calls.expected = null; | ||
thisProxy[areProxiesDisabledKey] = false; | ||
const getCallCounts = (calls: ProxyCallRecord[]) => { | ||
const callCounts = calls.map(x => x.callCount); | ||
const totalCallCount = callCounts.length === 0 ? 0 : callCounts.reduce((accumulator, value) => accumulator + value); | ||
return totalCallCount; | ||
} | ||
const matchingCallsCount = getCallCounts(matchingCalls); | ||
const isMatch = | ||
!( | ||
( | ||
!expected.negated && ( | ||
(expected.callCount === null && matchingCallsCount === 0) || | ||
(expected.callCount !== null && expected.callCount !== matchingCallsCount) | ||
) | ||
) || | ||
( | ||
expected.negated && ( | ||
(expected.callCount === null && matchingCallsCount !== 0) || | ||
(expected.callCount !== null && expected.callCount === matchingCallsCount) | ||
) | ||
) | ||
); | ||
if (!isMatch) { | ||
let errorMessage = ''; | ||
errorMessage += expected.negated ? 'Did not expect' : 'Expected'; | ||
errorMessage += ' '; | ||
errorMessage += expected.callCount === null ? 'one or more' : expected.callCount; | ||
errorMessage += ' call'; | ||
errorMessage += (expected.callCount === null || expected.callCount !== 1) ? 's' : ''; | ||
errorMessage += ' to the '; | ||
errorMessage += type; | ||
errorMessage += ' '; | ||
errorMessage += expected.propertyName; | ||
if(expected.arguments) { | ||
if(type === 'property') { | ||
errorMessage += ' with value '; | ||
const value = expected.arguments[0]; | ||
if(value === null) | ||
errorMessage += 'null'; | ||
if(value === void 0) | ||
errorMessage += 'undefined'; | ||
if(value) | ||
errorMessage += value; | ||
} else if(type === 'method') { | ||
errorMessage += ' with '; | ||
errorMessage += stringifyArguments(expected.arguments); | ||
} | ||
} | ||
errorMessage += ', but received '; | ||
errorMessage += matchingCallsCount === 0 ? 'none' : matchingCallsCount; | ||
if(expected.arguments) { | ||
errorMessage += ' of such call'; | ||
errorMessage += matchingCallsCount !== 1 ? 's' : ''; | ||
} | ||
errorMessage += '.'; | ||
if(expected.arguments) { | ||
errorMessage += '\nAll calls received to '; | ||
errorMessage += type; | ||
errorMessage += ' '; | ||
errorMessage += expected.propertyName; | ||
errorMessage += ':'; | ||
errorMessage += stringifyCalls(allCalls); | ||
} | ||
throw new Error(errorMessage); | ||
} | ||
} | ||
} |
@@ -1,4 +0,5 @@ | ||
import { ProxyCallRecord } from "./Context"; | ||
import { Argument, AllArguments } from "./Arguments"; | ||
export type Call = {callCount: number, arguments?: any[]}; | ||
export function stringifyArguments(args: any[]) { | ||
@@ -8,3 +9,12 @@ return args && args.length > 0 ? 'arguments [' + args.join(', ') + ']' : 'no arguments'; | ||
export function stringifyCalls(calls: ProxyCallRecord[]) { | ||
export function areArgumentArraysEqual(a: any[], b: any[]) { | ||
for(var i=0;i<Math.min(b.length, a.length);i++) { | ||
if(!areArgumentsEqual(b[i], a[i])) | ||
return false; | ||
} | ||
return true; | ||
} | ||
export function stringifyCalls(calls: Call[]) { | ||
calls = calls.filter(x => x.callCount > 0); | ||
@@ -20,4 +30,4 @@ | ||
if(call.property.type === 'function') | ||
output += ' with ' + stringifyArguments(call.argumentsSnapshot); | ||
if(call.arguments) | ||
output += ' with ' + stringifyArguments(call.arguments); | ||
} | ||
@@ -24,0 +34,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
55
1873
142902
1