@fluffy-spoon/substitute
Advanced tools
Comparing version 2.0.0-beta.1 to 2.0.0-beta.2
@@ -9,3 +9,3 @@ declare type PredicateFunction<T> = (arg: T) => boolean; | ||
private _options?; | ||
constructor(_description: string, _matchingFunction: PredicateFunction<T>, _options?: ArgumentOptions); | ||
constructor(_description: string, _matchingFunction: PredicateFunction<T>, _options?: ArgumentOptions | undefined); | ||
matches(arg: T): boolean; | ||
@@ -12,0 +12,0 @@ toString(): string; |
import { Substitute, SubstituteOf } from './Substitute'; | ||
export { Arg } from './Arguments'; | ||
export { Substitute, SubstituteOf }; | ||
export { ClearType } from './Utilities'; | ||
export default Substitute; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Substitute = exports.Arg = void 0; | ||
exports.ClearType = exports.Substitute = exports.Arg = void 0; | ||
const Substitute_1 = require("./Substitute"); | ||
@@ -8,2 +8,4 @@ Object.defineProperty(exports, "Substitute", { enumerable: true, get: function () { return Substitute_1.Substitute; } }); | ||
Object.defineProperty(exports, "Arg", { enumerable: true, get: function () { return Arguments_1.Arg; } }); | ||
var Utilities_1 = require("./Utilities"); | ||
Object.defineProperty(exports, "ClearType", { enumerable: true, get: function () { return Utilities_1.ClearType; } }); | ||
exports.default = Substitute_1.Substitute; |
@@ -5,5 +5,4 @@ /// <reference types="node" /> | ||
export declare class RecordedArguments { | ||
private _argumentsClass; | ||
private _argumentsClass?; | ||
private _value?; | ||
private readonly _hasNoArguments; | ||
private constructor(); | ||
@@ -15,5 +14,8 @@ static from(rawArguments: any[]): RecordedArguments; | ||
}>(objectWithArguments: T[]): T[]; | ||
get argumentsClass(): ArgumentsClass; | ||
get argumentsClass(): ArgumentsClass | undefined; | ||
get value(): any[] | undefined; | ||
get hasNoArguments(): boolean; | ||
hasArguments(): this is this & { | ||
argumentsClass: ArgumentsClass; | ||
value: any[]; | ||
}; | ||
match(other: RecordedArguments): boolean; | ||
@@ -20,0 +22,0 @@ private classifyArguments; |
@@ -8,7 +8,4 @@ "use strict"; | ||
constructor(rawArguments) { | ||
this._hasNoArguments = false; | ||
if (typeof rawArguments === 'undefined') { | ||
this._hasNoArguments = true; | ||
if (typeof rawArguments === 'undefined') | ||
return this; | ||
} | ||
this._argumentsClass = this.classifyArguments(rawArguments); | ||
@@ -48,4 +45,4 @@ this._value = rawArguments; | ||
} | ||
get hasNoArguments() { | ||
return this._hasNoArguments; | ||
hasArguments() { | ||
return Array.isArray(this._value); | ||
} | ||
@@ -55,4 +52,4 @@ match(other) { | ||
return true; | ||
if (this.hasNoArguments || other.hasNoArguments) | ||
return this.hasNoArguments && other.hasNoArguments; | ||
if (!this.hasArguments() || !other.hasArguments()) | ||
return !this.hasArguments() && !other.hasArguments(); | ||
if (this.value.length !== other.value.length) | ||
@@ -59,0 +56,0 @@ return false; |
/// <reference types="node" /> | ||
import { inspect, InspectOptions } from 'util'; | ||
import { SubstituteNodeBase } from './SubstituteNodeBase'; | ||
import { FilterFunction } from './Types'; | ||
import { RecordsSet } from './RecordsSet'; | ||
export declare class Recorder { | ||
export declare class Recorder<TRecord> { | ||
private _identity; | ||
private _records; | ||
private _indexedRecords; | ||
constructor(); | ||
get records(): RecordsSet<SubstituteNodeBase>; | ||
get indexedRecords(): Map<PropertyKey, RecordsSet<SubstituteNodeBase>>; | ||
addIndexedRecord(node: SubstituteNodeBase): void; | ||
addRecord(node: SubstituteNodeBase): void; | ||
getSiblingsOf(node: SubstituteNodeBase): RecordsSet<SubstituteNodeBase>; | ||
clearRecords(filterFunction: (node: SubstituteNodeBase) => boolean): void; | ||
private constructor(); | ||
static withIdentityProperty<TRecord>(identity: keyof TRecord): Recorder<TRecord>; | ||
get records(): RecordsSet<TRecord>; | ||
get indexedRecords(): Map<PropertyKey, RecordsSet<TRecord>>; | ||
addIndexedRecord(record: TRecord): void; | ||
addRecord(record: TRecord): void; | ||
getSiblingsOf(record: TRecord): RecordsSet<TRecord>; | ||
clearRecords(filterFunction: FilterFunction<TRecord>): void; | ||
[inspect.custom](_: number, options: InspectOptions): string; | ||
private getIdentity; | ||
private clearIndexedRecord; | ||
} |
@@ -7,6 +7,10 @@ "use strict"; | ||
class Recorder { | ||
constructor() { | ||
constructor(identity) { | ||
this._identity = identity; | ||
this._records = new RecordsSet_1.RecordsSet(); | ||
this._indexedRecords = new Map(); | ||
} | ||
static withIdentityProperty(identity) { | ||
return new this(identity); | ||
} | ||
get records() { | ||
@@ -18,17 +22,18 @@ return this._records; | ||
} | ||
addIndexedRecord(node) { | ||
this.addRecord(node); | ||
const existingNodes = this.indexedRecords.get(node.key); | ||
addIndexedRecord(record) { | ||
const id = this.getIdentity(record); | ||
this.addRecord(record); | ||
const existingNodes = this.indexedRecords.get(id); | ||
if (typeof existingNodes === 'undefined') | ||
this.indexedRecords.set(node.key, new RecordsSet_1.RecordsSet([node])); | ||
this.indexedRecords.set(id, new RecordsSet_1.RecordsSet([record])); | ||
else | ||
existingNodes.add(node); | ||
existingNodes.add(record); | ||
} | ||
addRecord(node) { | ||
this._records.add(node); | ||
addRecord(record) { | ||
this._records.add(record); | ||
} | ||
getSiblingsOf(node) { | ||
getSiblingsOf(record) { | ||
var _a; | ||
const siblingNodes = (_a = this.indexedRecords.get(node.key)) !== null && _a !== void 0 ? _a : new RecordsSet_1.RecordsSet(); | ||
return siblingNodes.filter(siblingNode => siblingNode !== node); | ||
const siblings = (_a = this.indexedRecords.get(this.getIdentity(record))) !== null && _a !== void 0 ? _a : new RecordsSet_1.RecordsSet(); | ||
return siblings.filter(sibling => sibling !== record); | ||
} | ||
@@ -38,6 +43,3 @@ clearRecords(filterFunction) { | ||
for (const record of recordsToRemove) { | ||
const indexedRecord = this.indexedRecords.get(record.key); | ||
indexedRecord.delete(record); | ||
if (indexedRecord.size === 0) | ||
this.indexedRecords.delete(record.key); | ||
this.clearIndexedRecord(record); | ||
this.records.delete(record); | ||
@@ -50,3 +52,16 @@ } | ||
} | ||
getIdentity(record) { | ||
// for typescript < 4.6, we need to intersect PropertyKey with the default type | ||
return record[this._identity]; | ||
} | ||
clearIndexedRecord(record) { | ||
const id = this.getIdentity(record); | ||
const indexedRecord = this.indexedRecords.get(id); | ||
if (typeof indexedRecord === 'undefined') | ||
return; | ||
indexedRecord.delete(record); | ||
if (indexedRecord.size === 0) | ||
this.indexedRecords.delete(id); | ||
} | ||
} | ||
exports.Recorder = Recorder; |
@@ -0,8 +1,10 @@ | ||
import { FilterFunction } from './Types'; | ||
declare type MapperFunction<T, R> = (item: T) => R; | ||
export declare class RecordsSet<T> extends Set<T> { | ||
private _transformer; | ||
private _transformer?; | ||
private readonly _prevIter?; | ||
constructor(value?: Iterable<T> | readonly T[]); | ||
get size(): number; | ||
filter(predicate: (item: T) => boolean): RecordsSet<T>; | ||
map<R>(predicate: (item: T) => R): RecordsSet<R>; | ||
filter(predicate: FilterFunction<T>): RecordsSet<T>; | ||
map<R>(predicate: MapperFunction<T, R>): RecordsSet<R>; | ||
has(value: T): boolean; | ||
@@ -15,1 +17,2 @@ delete(value: T): boolean; | ||
} | ||
export {}; |
@@ -6,4 +6,5 @@ "use strict"; | ||
constructor(value) { | ||
super(value instanceof RecordsSet ? undefined : value); | ||
if (value instanceof RecordsSet) | ||
const isRecordSet = value instanceof RecordsSet; | ||
super(isRecordSet ? undefined : value); | ||
if (isRecordSet) | ||
this._prevIter = value; | ||
@@ -19,3 +20,3 @@ } | ||
const newInstance = new RecordsSet(this); | ||
newInstance._transformer = { type: 'filter', fnc: predicate }; | ||
newInstance._transformer = { type: 'filter', predicate }; | ||
return newInstance; | ||
@@ -25,3 +26,3 @@ } | ||
const newInstance = new RecordsSet(this); | ||
newInstance._transformer = { type: 'mapper', fnc: predicate }; | ||
newInstance._transformer = { type: 'mapper', predicate }; | ||
return newInstance; | ||
@@ -41,3 +42,6 @@ } | ||
clear() { | ||
Object.defineProperty(this, '_prevIter', { value: undefined }); | ||
if (this._prevIter instanceof RecordsSet) { | ||
this._prevIter.clear(); | ||
Object.defineProperty(this, '_prevIter', { value: undefined }); | ||
} | ||
super.clear(); | ||
@@ -58,6 +62,11 @@ } | ||
for (const value of itarable) { | ||
if (transform.type === 'mapper') | ||
yield transform.fnc(value); | ||
if (transform.type === 'filter' && transform.fnc(value)) | ||
yield value; | ||
switch (transform.type) { | ||
case 'filter': | ||
if (transform.predicate(value)) | ||
yield value; | ||
break; | ||
case 'mapper': | ||
yield transform.predicate(value); | ||
break; | ||
} | ||
} | ||
@@ -64,0 +73,0 @@ } |
@@ -1,24 +0,7 @@ | ||
/// <reference types="node" /> | ||
import { InspectOptions } from 'util'; | ||
import { SubstituteBase } from './SubstituteBase'; | ||
import { Recorder } from './Recorder'; | ||
import { DisabledSubstituteObject, ObjectSubstitute, OmitProxyMethods } from './Transformations'; | ||
export declare type SubstituteOf<T extends Object> = ObjectSubstitute<OmitProxyMethods<T>, T> & T; | ||
declare type Instantiable<T> = { | ||
[SubstituteBase.instance]?: T; | ||
}; | ||
export declare class Substitute extends SubstituteBase { | ||
private _proxy; | ||
private _recorder; | ||
private _context; | ||
constructor(); | ||
import { DisabledSubstituteObject, ObjectSubstitute } from './Transformations'; | ||
export declare type SubstituteOf<T> = ObjectSubstitute<T> & T; | ||
export declare class Substitute { | ||
static for<T>(): SubstituteOf<T>; | ||
static disableFor<T extends SubstituteOf<unknown> & Instantiable<Substitute>>(substituteProxy: T): DisabledSubstituteObject<T>; | ||
get proxy(): Substitute; | ||
get recorder(): Recorder; | ||
get context(): { | ||
disableAssertions: boolean; | ||
}; | ||
protected printableForm(_: number, options: InspectOptions): string; | ||
static disableFor<T extends SubstituteOf<unknown>>(substituteProxy: T): DisabledSubstituteObject<T>; | ||
private static extractSubstituteNodeFromSubstitute; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Substitute = void 0; | ||
const util_1 = require("util"); | ||
const SubstituteBase_1 = require("./SubstituteBase"); | ||
const SubstituteProxy_1 = require("./SubstituteProxy"); | ||
const Recorder_1 = require("./Recorder"); | ||
class Substitute extends SubstituteBase_1.SubstituteBase { | ||
constructor() { | ||
super(); | ||
this._recorder = new Recorder_1.Recorder(); | ||
this._context = { disableAssertions: false }; | ||
this._proxy = (0, SubstituteProxy_1.createSubstituteProxy)(this, { | ||
get: (target, _property, _, node) => { | ||
if (target.context.disableAssertions) | ||
node.disableAssertions(); | ||
} | ||
// apply: (target, _, args, __, proxy) => { | ||
// const rootProperty = proxy.get(target, '()', proxy) TODO: Implement to support callable interfaces | ||
// return Reflect.apply(rootProperty, rootProperty, args) | ||
// } | ||
}); | ||
} | ||
const SubstituteNode_1 = require("./SubstituteNode"); | ||
class Substitute { | ||
static for() { | ||
const substitute = new this(); | ||
const substitute = SubstituteNode_1.SubstituteNode.createRoot(); | ||
return substitute.proxy; | ||
} | ||
static disableFor(substituteProxy) { | ||
const substitute = substituteProxy[SubstituteBase_1.SubstituteBase.instance]; | ||
const substitute = this.extractSubstituteNodeFromSubstitute(substituteProxy); | ||
const disableProxy = (reflection) => (...args) => { | ||
substitute.context.disableAssertions = true; | ||
substitute.rootContext.substituteMethodsEnabled = false; | ||
const reflectionResult = reflection(...args); | ||
substitute.context.disableAssertions = false; | ||
substitute.rootContext.substituteMethodsEnabled = true; | ||
return reflectionResult; | ||
@@ -48,17 +30,6 @@ }; | ||
} | ||
get proxy() { | ||
return this._proxy; | ||
static extractSubstituteNodeFromSubstitute(substitute) { | ||
return substitute[SubstituteNode_1.SubstituteNode.instance]; | ||
} | ||
get recorder() { | ||
return this._recorder; | ||
} | ||
get context() { | ||
return this._context; | ||
} | ||
printableForm(_, options) { | ||
const records = (0, util_1.inspect)(this.recorder, options); | ||
const instanceName = 'Substitute'; // Substitute<FooThing> | ||
return instanceName + ' {' + records + '\n}'; | ||
} | ||
} | ||
exports.Substitute = Substitute; |
@@ -0,3 +1,3 @@ | ||
import { PropertyType } from './Types'; | ||
import { RecordedArguments } from './RecordedArguments'; | ||
import { PropertyType } from './Utilities'; | ||
declare enum SubstituteExceptionTypes { | ||
@@ -8,6 +8,6 @@ CallCountMissMatch = "CallCountMissMatch", | ||
export declare class SubstituteException extends Error { | ||
type: SubstituteExceptionTypes; | ||
type?: SubstituteExceptionTypes; | ||
constructor(msg: string, exceptionType?: SubstituteExceptionTypes); | ||
static forCallCountMissMatch(count: { | ||
expected: number | null; | ||
expected: number | undefined; | ||
received: number; | ||
@@ -14,0 +14,0 @@ }, property: { |
@@ -20,3 +20,3 @@ "use strict"; | ||
const commonMessage = `Expected ${Utilities_1.textModifier.bold(count.expected === undefined ? '1 or more' : count.expected.toString())} ${(0, Utilities_1.plurify)('call', count.expected)} to the ${Utilities_1.textModifier.italic(property.type)} ${propertyValue}`; | ||
const messageForMethods = property.type === Utilities_1.PropertyType.method ? ` with ${(0, Utilities_1.stringifyArguments)(calls.expected)}` : ''; // should also apply for setters (instead of methods only) | ||
const messageForMethods = property.type === Utilities_1.PropertyType.Method ? ` with ${(0, Utilities_1.stringifyArguments)(calls.expected)}` : ''; // should also apply for setters (instead of methods only) | ||
const receivedMessage = `, but received ${Utilities_1.textModifier.bold(count.received < 1 ? 'none' : count.received.toString())} of such calls.`; | ||
@@ -23,0 +23,0 @@ const callTrace = calls.received.length > 0 |
/// <reference types="node" /> | ||
import { InspectOptions } from 'util'; | ||
import { PropertyType, AssertionMethod, SubstitutionMethod, ConfigurationMethod } from './Utilities'; | ||
import { inspect, InspectOptions } from 'util'; | ||
import { SubstituteNodeBase } from './SubstituteNodeBase'; | ||
import { RecordedArguments } from './RecordedArguments'; | ||
import { SubstituteNodeBase } from './SubstituteNodeBase'; | ||
import { SubstituteBase } from './SubstituteBase'; | ||
declare type SubstituteContext = SubstitutionMethod | AssertionMethod | ConfigurationMethod | 'none'; | ||
export declare class SubstituteNode extends SubstituteNodeBase<SubstituteNode> { | ||
import type { SubstituteContext, PropertyType } from './Types'; | ||
declare const instance: unique symbol; | ||
declare type RootContext = { | ||
substituteMethodsEnabled: boolean; | ||
}; | ||
export declare class SubstituteNode extends SubstituteNodeBase { | ||
private _proxy; | ||
private _rootContext; | ||
private _propertyType; | ||
@@ -14,5 +17,9 @@ private _accessorType; | ||
private _context; | ||
private _disabledAssertions; | ||
constructor(property: PropertyKey, parent: SubstituteNode | SubstituteBase); | ||
private _disabledSubstituteMethods; | ||
private constructor(); | ||
static instance: typeof instance; | ||
static createRoot(): SubstituteNode; | ||
static createChild(key: PropertyKey, parent: SubstituteNode): SubstituteNode; | ||
get proxy(): SubstituteNode; | ||
get rootContext(): RootContext; | ||
get context(): SubstituteContext; | ||
@@ -26,5 +33,5 @@ get hasContext(): boolean; | ||
get recordedArguments(): RecordedArguments; | ||
get disabledAssertions(): boolean; | ||
get disabledSubstituteMethods(): boolean; | ||
assignContext(context: SubstituteContext): void; | ||
disableAssertions(): void; | ||
disableSubstituteMethods(): void; | ||
read(): SubstituteNode | void | never; | ||
@@ -36,5 +43,12 @@ write(value: any): void; | ||
handleMethod(rawArguments: any[]): void; | ||
private tryToAssignContext; | ||
private handleSpecialContext; | ||
private getMostSuitableSubstitution; | ||
protected printableForm(_: number, options: InspectOptions): string; | ||
private isSpecialProperty; | ||
private evaluateSpecialProperty; | ||
[inspect.custom](...args: [_: number, options: InspectOptions]): string; | ||
private printableForm; | ||
private printRootNode; | ||
private printNode; | ||
} | ||
export {}; |
@@ -5,7 +5,7 @@ "use strict"; | ||
const util_1 = require("util"); | ||
const SubstituteNodeBase_1 = require("./SubstituteNodeBase"); | ||
const RecordedArguments_1 = require("./RecordedArguments"); | ||
const Utilities_1 = require("./Utilities"); | ||
const SubstituteException_1 = require("./SubstituteException"); | ||
const RecordedArguments_1 = require("./RecordedArguments"); | ||
const SubstituteNodeBase_1 = require("./SubstituteNodeBase"); | ||
const SubstituteProxy_1 = require("./SubstituteProxy"); | ||
const instance = Symbol('Substitute:Instance'); | ||
const clearTypeToFilterMap = { | ||
@@ -17,30 +17,52 @@ all: () => true, | ||
class SubstituteNode extends SubstituteNodeBase_1.SubstituteNodeBase { | ||
constructor(property, parent) { | ||
super(property, parent); | ||
this._propertyType = Utilities_1.PropertyType.property; | ||
constructor(key, parent) { | ||
super(key, parent); | ||
this._propertyType = Utilities_1.PropertyType.Property; | ||
this._accessorType = 'get'; | ||
this._recordedArguments = RecordedArguments_1.RecordedArguments.none(); | ||
this._context = 'none'; | ||
this._disabledAssertions = false; | ||
this._proxy = (0, SubstituteProxy_1.createSubstituteProxy)(this, { | ||
get: (node, _, __, nextNode) => { | ||
if (node.isAssertion) | ||
nextNode.executeAssertion(); | ||
this._disabledSubstituteMethods = false; | ||
if (this.isRoot()) | ||
this._rootContext = { substituteMethodsEnabled: true }; | ||
else | ||
this._rootContext = this.root.rootContext; | ||
this._proxy = new Proxy(this, { | ||
get: function (target, property) { | ||
if (target.isSpecialProperty(property)) | ||
return target.evaluateSpecialProperty(property); | ||
const newNode = SubstituteNode.createChild(property, target); | ||
if (target.isRoot() && !target.rootContext.substituteMethodsEnabled) | ||
newNode.disableSubstituteMethods(); | ||
if (target.isIntermediateNode() && target.isAssertion) | ||
newNode.executeAssertion(); | ||
return newNode.read(); | ||
}, | ||
set: (node, _, __, ___, nextNode) => { | ||
if (node.isAssertion) | ||
nextNode.executeAssertion(); | ||
set: function (target, property, value) { | ||
const newNode = SubstituteNode.createChild(property, target); | ||
newNode.write(value); | ||
if (target.isAssertion) | ||
newNode.executeAssertion(); | ||
return true; | ||
}, | ||
apply: (node, _, rawArguments) => { | ||
apply: function (target, _thisArg, rawArguments) { | ||
var _a, _b; | ||
node.handleMethod(rawArguments); | ||
if (node.context === 'clearSubstitute') | ||
return node.clear(); | ||
return ((_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.isAssertion) !== null && _b !== void 0 ? _b : false) ? node.executeAssertion() : node.read(); | ||
target.handleMethod(rawArguments); | ||
if (target.hasContext) | ||
target.handleSpecialContext(); | ||
return ((_b = (_a = target.parent) === null || _a === void 0 ? void 0 : _a.isAssertion) !== null && _b !== void 0 ? _b : false) ? target.executeAssertion() : target.read(); | ||
} | ||
}); | ||
} | ||
static createRoot() { | ||
return new this('*Substitute<Root>'); | ||
} | ||
static createChild(key, parent) { | ||
return new this(key, parent); | ||
} | ||
get proxy() { | ||
return this._proxy; | ||
} | ||
get rootContext() { | ||
return this._rootContext; | ||
} | ||
get context() { | ||
@@ -70,4 +92,4 @@ return this._context; | ||
} | ||
get disabledAssertions() { | ||
return this._disabledAssertions; | ||
get disabledSubstituteMethods() { | ||
return this._disabledSubstituteMethods; | ||
} | ||
@@ -77,8 +99,8 @@ assignContext(context) { | ||
} | ||
disableAssertions() { | ||
this._disabledAssertions = true; | ||
disableSubstituteMethods() { | ||
this._disabledSubstituteMethods = true; | ||
} | ||
read() { | ||
var _a, _b; | ||
if ((_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.isSubstitution) !== null && _b !== void 0 ? _b : false) | ||
if (((_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.isSubstitution) !== null && _b !== void 0 ? _b : false) || this.context === 'clearSubstitute') | ||
return; | ||
@@ -98,10 +120,17 @@ if (this.isAssertion) | ||
var _a; | ||
const clearType = (_a = this.recordedArguments.value[0]) !== null && _a !== void 0 ? _a : 'all'; | ||
if (!this.recordedArguments.hasArguments()) | ||
throw new TypeError('No args'); | ||
const clearType = (_a = this.recordedArguments.value[0]) !== null && _a !== void 0 ? _a : Utilities_1.ClearType.All; | ||
const filter = clearTypeToFilterMap[clearType]; | ||
this.root.recorder.clearRecords(filter); | ||
this.recorder.clearRecords(filter); | ||
} | ||
executeSubstitution(contextArguments) { | ||
var _a; | ||
if (!this.hasChild()) | ||
throw new TypeError('Substitue node has no child'); | ||
if (!this.child.recordedArguments.hasArguments()) | ||
throw new TypeError('Child args'); | ||
const substitutionMethod = this.context; | ||
const substitutionValue = this.child.recordedArguments.value.length > 1 | ||
? this.child.recordedArguments.value.shift() | ||
? (_a = this.child.recordedArguments.value) === null || _a === void 0 ? void 0 : _a.shift() | ||
: this.child.recordedArguments.value[0]; | ||
@@ -112,4 +141,7 @@ switch (substitutionMethod) { | ||
case 'mimicks': | ||
const argumentsToApply = this.propertyType === Utilities_1.PropertyType.property ? [] : contextArguments.value; | ||
return substitutionValue(...argumentsToApply); | ||
if (this.propertyType === Utilities_1.PropertyType.Property) | ||
return substitutionValue(); | ||
if (!contextArguments.hasArguments()) | ||
throw new TypeError('Context arguments cannot be undefined'); | ||
return substitutionValue(...contextArguments.value); | ||
case 'resolves': | ||
@@ -127,5 +159,7 @@ return Promise.resolve(substitutionValue); | ||
var _a; | ||
const siblings = [...this.getAllSiblings().filter(n => !n.hasContext && n.accessorType === this.accessorType)]; | ||
if (!this.isIntermediateNode()) | ||
throw new Error('Not possible'); | ||
if (!this.parent.recordedArguments.hasArguments()) | ||
throw new TypeError('Parent args'); | ||
const siblings = [...this.getAllSiblings().filter(n => !n.hasContext && n.accessorType === this.accessorType)]; | ||
const expectedCount = (_a = this.parent.recordedArguments.value[0]) !== null && _a !== void 0 ? _a : undefined; | ||
@@ -149,4 +183,7 @@ const finiteExpectation = expectedCount !== undefined; | ||
handleMethod(rawArguments) { | ||
this._propertyType = Utilities_1.PropertyType.method; | ||
this._propertyType = Utilities_1.PropertyType.Method; | ||
this._recordedArguments = RecordedArguments_1.RecordedArguments.from(rawArguments); | ||
this.tryToAssignContext(); | ||
} | ||
tryToAssignContext() { | ||
if (!(0, Utilities_1.isSubstituteMethod)(this.property)) | ||
@@ -156,5 +193,9 @@ return; | ||
return this.parent.assignContext(this.property); | ||
if (this.disabledAssertions || !this.isHead()) | ||
if (this.disabledSubstituteMethods) | ||
return; | ||
this.assignContext(this.property); | ||
} | ||
handleSpecialContext() { | ||
if (this.context === 'clearSubstitute') | ||
return this.clear(); | ||
if (this.context === 'didNotReceive') | ||
@@ -170,4 +211,30 @@ this._recordedArguments = RecordedArguments_1.RecordedArguments.from([0]); | ||
} | ||
isSpecialProperty(property) { | ||
return property === SubstituteNode.instance || property === util_1.inspect.custom || property === 'then'; | ||
} | ||
evaluateSpecialProperty(property) { | ||
switch (property) { | ||
case SubstituteNode.instance: | ||
return this; | ||
case util_1.inspect.custom: | ||
return this.printableForm.bind(this); | ||
case 'then': | ||
return; | ||
default: | ||
throw SubstituteException_1.SubstituteException.generic(`Evaluation of special property ${property} is not implemented`); | ||
} | ||
} | ||
[util_1.inspect.custom](...args) { | ||
return util_1.types.isProxy(this) ? this[util_1.inspect.custom](...args) : this.printableForm(...args); | ||
} | ||
printableForm(_, options) { | ||
var _a; | ||
return this.isRoot() ? this.printRootNode(options) : this.printNode(options); | ||
} | ||
printRootNode(options) { | ||
const records = (0, util_1.inspect)(this.recorder, options); | ||
const instanceName = '*Substitute<Root>'; // Substitute<FooThing> | ||
return instanceName + ' {' + records + '\n}'; | ||
} | ||
printNode(options) { | ||
var _a, _b; | ||
const hasContext = this.hasContext; | ||
@@ -178,6 +245,6 @@ const args = (0, util_1.inspect)(this.recordedArguments, options); | ||
: this.isAssertion | ||
? `${this.child.property.toString()}` | ||
? `${(_a = this.child) === null || _a === void 0 ? void 0 : _a.property.toString()}` | ||
: ''; | ||
const s = hasContext | ||
? ` ${label}${(0, util_1.inspect)((_a = this.child) === null || _a === void 0 ? void 0 : _a.recordedArguments, options)}` | ||
? ` ${label}${(0, util_1.inspect)((_b = this.child) === null || _b === void 0 ? void 0 : _b.recordedArguments, options)}` | ||
: ''; | ||
@@ -189,1 +256,2 @@ const printableNode = `${this.propertyType}<${this.property.toString()}>: ${args}${s}`; | ||
exports.SubstituteNode = SubstituteNode; | ||
SubstituteNode.instance = instance; |
@@ -1,29 +0,33 @@ | ||
import { SubstituteBase } from './SubstituteBase'; | ||
import { Substitute } from './Substitute'; | ||
import { Recorder } from './Recorder'; | ||
import { RecordsSet } from './RecordsSet'; | ||
export declare abstract class SubstituteNodeBase<T extends SubstituteNodeBase = SubstituteNodeBase<any>> extends SubstituteBase { | ||
export declare abstract class SubstituteNodeBase extends Function { | ||
private _key; | ||
private _root; | ||
private _parent?; | ||
private _child?; | ||
private _head; | ||
private _root; | ||
constructor(_key: PropertyKey, caller: SubstituteBase); | ||
private _depth; | ||
private _recorder; | ||
constructor(_key: PropertyKey, parent?: SubstituteNodeBase); | ||
private isNode; | ||
get key(): PropertyKey; | ||
set parent(parent: T | undefined); | ||
get parent(): T | undefined; | ||
set child(child: T); | ||
get child(): T; | ||
get head(): T & { | ||
get recorder(): Recorder<this>; | ||
protected get parent(): this | undefined; | ||
protected get child(): this | undefined; | ||
protected get root(): this & { | ||
parent: undefined; | ||
}; | ||
protected get root(): Substitute; | ||
protected isHead(): this is T & { | ||
protected get depth(): number; | ||
private assignChild; | ||
protected isRoot(): this is this & { | ||
parent: undefined; | ||
}; | ||
protected isIntermediateNode(): this is T & { | ||
parent: T; | ||
protected isIntermediateNode(): this is this & { | ||
parent: ThisType<SubstituteNodeBase>; | ||
}; | ||
protected getAllSiblings(): RecordsSet<T>; | ||
protected getAllSiblings(): RecordsSet<this>; | ||
protected hasChild(): this is this & { | ||
child: ThisType<SubstituteNodeBase>; | ||
}; | ||
abstract read(): void; | ||
abstract write(value: any): void; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.SubstituteNodeBase = void 0; | ||
const SubstituteBase_1 = require("./SubstituteBase"); | ||
const Substitute_1 = require("./Substitute"); | ||
class SubstituteNodeBase extends SubstituteBase_1.SubstituteBase { | ||
constructor(_key, caller) { | ||
const Recorder_1 = require("./Recorder"); | ||
class SubstituteNodeBase extends Function { | ||
constructor(_key, parent) { | ||
super(); | ||
this._key = _key; | ||
if (caller instanceof Substitute_1.Substitute) { | ||
caller.recorder.addIndexedRecord(this); | ||
this._root = caller; | ||
const shouldBeRoot = !this.isNode(parent); | ||
if (shouldBeRoot) { | ||
this._recorder = Recorder_1.Recorder.withIdentityProperty('key'); | ||
this._root = this; | ||
this._depth = 0; | ||
return; | ||
} | ||
if (!(caller instanceof SubstituteNodeBase)) | ||
return; | ||
this._parent = caller; | ||
this._head = caller.head; | ||
caller.child = this; | ||
parent.assignChild(this); | ||
this._parent = parent; | ||
this._recorder = parent.recorder; | ||
this._root = parent.root; | ||
this._depth = parent.depth + 1; | ||
if (this.parent === this.root) | ||
this.recorder.addIndexedRecord(this); | ||
} | ||
isNode(node) { | ||
return typeof node !== 'undefined'; | ||
} | ||
get key() { | ||
return this._key; | ||
} | ||
set parent(parent) { | ||
this._parent = parent; | ||
get recorder() { | ||
return this._recorder; | ||
} | ||
@@ -29,24 +36,27 @@ get parent() { | ||
} | ||
set child(child) { | ||
this._child = child; | ||
} | ||
get child() { | ||
return this._child; | ||
} | ||
get head() { | ||
return this.isHead() ? this : this._head; | ||
} | ||
get root() { | ||
return this.head._root; | ||
return this._root; | ||
} | ||
isHead() { | ||
get depth() { | ||
return this._depth; | ||
} | ||
assignChild(child) { | ||
this._child = child; | ||
} | ||
isRoot() { | ||
return typeof this._parent === 'undefined'; | ||
} | ||
isIntermediateNode() { | ||
return !this.isHead(); | ||
return !this.isRoot(); | ||
} | ||
getAllSiblings() { | ||
return this.root.recorder.getSiblingsOf(this); | ||
return this.recorder.getSiblingsOf(this); | ||
} | ||
hasChild() { | ||
return this.child instanceof SubstituteNodeBase; | ||
} | ||
} | ||
exports.SubstituteNodeBase = SubstituteNodeBase; |
@@ -1,2 +0,3 @@ | ||
import { AllArguments } from './Arguments'; | ||
import type { AllArguments } from './Arguments'; | ||
import type { ClearType, FirstLevelMethod } from './Types'; | ||
declare type FunctionSubstituteWithOverloads<TFunc, Terminating = false> = TFunc extends { | ||
@@ -27,3 +28,3 @@ (...args: infer A1): infer R1; | ||
export declare type NoArgumentFunctionSubstitute<TReturnType> = (() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>)); | ||
export declare type PropertySubstitute<TReturnType> = (TReturnType & Partial<NoArgumentMockObjectMixin<TReturnType>>); | ||
export declare type PropertySubstitute<TReturnType> = (TReturnType & NoArgumentMockObjectMixin<TReturnType>); | ||
declare type OneArgumentRequiredFunction<TArgs, TReturnType> = (requiredInput: TArgs, ...restInputs: TArgs[]) => TReturnType; | ||
@@ -44,8 +45,2 @@ declare type MockObjectPromise<TReturnType> = TReturnType extends Promise<infer U> ? { | ||
}; | ||
export declare type ObjectSubstitute<T extends Object, K extends Object = T> = ObjectSubstituteTransformation<T> & { | ||
received(amount?: number): TerminatingObject<K>; | ||
didNotReceive(): TerminatingObject<K>; | ||
mimick(instance: T): void; | ||
clearSubstitute(clearType?: ClearType): void; | ||
}; | ||
declare type TerminatingFunction<TArguments extends any[]> = ((...args: TArguments) => void) & ((arg: AllArguments<TArguments>) => void); | ||
@@ -55,9 +50,13 @@ declare type TerminatingObject<T> = { | ||
}; | ||
declare type ObjectSubstituteTransformation<T extends Object> = { | ||
declare type ObjectSubstituteTransformation<K, T = OmitProxyMethods<K>> = { | ||
[P in keyof T]: T[P] extends (...args: infer F) => infer R ? F extends [] ? NoArgumentFunctionSubstitute<R> : FunctionSubstituteWithOverloads<T[P]> : PropertySubstitute<T[P]>; | ||
}; | ||
declare type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; | ||
export declare type ClearType = 'all' | 'receivedCalls' | 'substituteValues'; | ||
export declare type OmitProxyMethods<T extends any> = Omit<T, 'mimick' | 'received' | 'didNotReceive' | 'clearSubstitute'>; | ||
export declare type DisabledSubstituteObject<T> = T extends ObjectSubstitute<OmitProxyMethods<infer K>, infer K> ? K : never; | ||
export declare type OmitProxyMethods<T> = Omit<T, FirstLevelMethod>; | ||
export declare type ObjectSubstitute<T> = ObjectSubstituteTransformation<T> & { | ||
received(amount?: number): TerminatingObject<T>; | ||
didNotReceive(): TerminatingObject<T>; | ||
mimick(instance: OmitProxyMethods<T>): void; | ||
clearSubstitute(clearType?: ClearType): void; | ||
}; | ||
export declare type DisabledSubstituteObject<T> = T extends ObjectSubstitute<infer K> ? K : never; | ||
export {}; |
import { RecordedArguments } from './RecordedArguments'; | ||
export declare enum PropertyType { | ||
method = "method", | ||
property = "property" | ||
} | ||
export declare type AssertionMethod = 'received' | 'didNotReceive'; | ||
import type { AssertionMethod, ConfigurationMethod, SubstituteMethod, SubstitutionMethod } from './Types'; | ||
export declare const PropertyType: { | ||
readonly Method: "method"; | ||
readonly Property: "property"; | ||
}; | ||
export declare const isAssertionMethod: (property: PropertyKey) => property is AssertionMethod; | ||
export declare type ConfigurationMethod = 'clearSubstitute'; | ||
export declare const isConfigurationMethod: (property: PropertyKey) => property is "clearSubstitute"; | ||
export declare type SubstitutionMethod = 'mimicks' | 'throws' | 'returns' | 'resolves' | 'rejects'; | ||
export declare const isConfigurationMethod: (property: PropertyKey) => property is ConfigurationMethod; | ||
export declare const isSubstitutionMethod: (property: PropertyKey) => property is SubstitutionMethod; | ||
export declare const isSubstituteMethod: (property: PropertyKey) => property is AssertionMethod | "clearSubstitute" | SubstitutionMethod; | ||
export declare const isSubstituteMethod: (property: PropertyKey) => property is SubstituteMethod; | ||
export declare const ClearType: { | ||
readonly All: "all"; | ||
readonly ReceivedCalls: "receivedCalls"; | ||
readonly SubstituteValues: "substituteValues"; | ||
}; | ||
export declare const stringifyArguments: (args: RecordedArguments) => string; | ||
@@ -20,2 +23,2 @@ export declare const stringifyCalls: (calls: RecordedArguments[]) => string; | ||
}; | ||
export declare const plurify: (str: string, count: number) => string; | ||
export declare const plurify: (str: string, count?: number | undefined) => string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.plurify = exports.textModifier = exports.stringifyCalls = exports.stringifyArguments = exports.isSubstituteMethod = exports.isSubstitutionMethod = exports.isConfigurationMethod = exports.isAssertionMethod = exports.PropertyType = void 0; | ||
exports.plurify = exports.textModifier = exports.stringifyCalls = exports.stringifyArguments = exports.ClearType = exports.isSubstituteMethod = exports.isSubstitutionMethod = exports.isConfigurationMethod = exports.isAssertionMethod = exports.PropertyType = void 0; | ||
const util_1 = require("util"); | ||
var PropertyType; | ||
(function (PropertyType) { | ||
PropertyType["method"] = "method"; | ||
PropertyType["property"] = "property"; | ||
})(PropertyType = exports.PropertyType || (exports.PropertyType = {})); | ||
exports.PropertyType = { | ||
Method: 'method', | ||
Property: 'property' | ||
}; | ||
const isAssertionMethod = (property) => property === 'received' || property === 'didNotReceive'; | ||
@@ -18,5 +17,10 @@ exports.isAssertionMethod = isAssertionMethod; | ||
exports.isSubstituteMethod = isSubstituteMethod; | ||
const stringifyArguments = (args) => exports.textModifier.faint(args.hasNoArguments | ||
? 'no arguments' | ||
: `arguments [${args.value.map(x => (0, util_1.inspect)(x, { colors: true })).join(', ')}]`); | ||
exports.ClearType = { | ||
All: 'all', | ||
ReceivedCalls: 'receivedCalls', | ||
SubstituteValues: 'substituteValues' | ||
}; | ||
const stringifyArguments = (args) => exports.textModifier.faint(args.hasArguments() ? | ||
`arguments [${args.value.map(x => (0, util_1.inspect)(x, { colors: true })).join(', ')}]` : | ||
'no arguments'); | ||
exports.stringifyArguments = stringifyArguments; | ||
@@ -23,0 +27,0 @@ const stringifyCalls = (calls) => { |
{ | ||
"name": "@fluffy-spoon/substitute", | ||
"version": "2.0.0-beta.1", | ||
"version": "2.0.0-beta.2", | ||
"description": "TypeScript port of NSubstitute, which aims to provide a much more fluent mocking opportunity for strong-typed languages", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -229,16 +229,2 @@ <a href="https://opencollective.com/substitute-js#section-contribute"><a href="https://opencollective.com/substitute-js" alt="Financial Contributors on Open Collective"><img src="https://opencollective.com/substitute-js/all/badge.svg?label=financial+contributors" /></a> | ||
## Strict mode | ||
If you have `strict` set to `true` in your `tsconfig.json`, you may need to toggle off strict null checks. The framework does not currently support this. | ||
However, it is only needed for your test projects anyway. | ||
```json | ||
{ | ||
"compilerOptions": { | ||
"strict": true, | ||
"strictNullChecks": false | ||
} | ||
} | ||
``` | ||
## Contributors | ||
@@ -245,0 +231,0 @@ |
1033
57206
27
258