@corez/mock
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -26,2 +26,54 @@ /** | ||
/** | ||
* Log level for controlling debug output verbosity | ||
*/ | ||
declare enum LogLevel { | ||
NONE = 0, | ||
ERROR = 1, | ||
WARN = 2, | ||
INFO = 3, | ||
DEBUG = 4, | ||
TRACE = 5 | ||
} | ||
/** | ||
* Logging configuration options | ||
*/ | ||
interface LoggingOptions { | ||
/** | ||
* Whether to enable detailed logging | ||
* @default false | ||
*/ | ||
debug?: boolean; | ||
/** | ||
* Log level for controlling output verbosity | ||
* If debug is true, defaults to LogLevel.DEBUG | ||
* If debug is false, defaults to LogLevel.INFO | ||
* @default LogLevel.INFO | ||
*/ | ||
level?: LogLevel; | ||
/** | ||
* Custom prefix for log messages | ||
* If not provided, uses the mock name if available | ||
*/ | ||
prefix?: string; | ||
/** | ||
* Whether to output logs to console | ||
* Controls if logs should be written to console or suppressed | ||
* @default true if debug is true, false otherwise | ||
*/ | ||
consoleOutput?: boolean; | ||
/** | ||
* Custom log formatter | ||
* @param message The log message | ||
* @param level Current log level | ||
* @param context Additional context data | ||
*/ | ||
formatter?: (message: string, level: LogLevel, context?: Record<string, any>) => string; | ||
/** | ||
* Custom logger implementation | ||
* @param message Formatted log message | ||
* @param level Current log level | ||
*/ | ||
logger?: (message: string, level: LogLevel) => void; | ||
} | ||
/** | ||
* Base configuration options for all mock types | ||
@@ -33,3 +85,3 @@ * These options control general mock behavior | ||
* Enables detailed logging of mock operations | ||
* Useful for debugging and understanding mock behavior | ||
* Shorthand for logging.debug = true | ||
* @default false | ||
@@ -39,2 +91,7 @@ */ | ||
/** | ||
* Logging configuration | ||
* If debug is true, this will be merged with {debug: true, level: LogLevel.DEBUG} | ||
*/ | ||
logging?: LoggingOptions; | ||
/** | ||
* Maximum time to wait for async operations in milliseconds | ||
@@ -45,2 +102,7 @@ * Used when mocking async methods and properties | ||
asyncTimeout?: number; | ||
/** | ||
* Name for the mock instance | ||
* Used in debug logs to identify the mock | ||
*/ | ||
name?: string; | ||
} | ||
@@ -47,0 +109,0 @@ /** |
@@ -292,2 +292,82 @@ 'use strict'; | ||
// src/utils/debug.ts | ||
var DebugContext = class { | ||
constructor(options = {}) { | ||
this.options = { | ||
debug: false, | ||
level: 3 /* INFO */, | ||
logToConsole: true, | ||
...options | ||
}; | ||
this.mockName = options.name || "anonymous-mock"; | ||
} | ||
/** | ||
* Log a debug message | ||
*/ | ||
log(message, level = 3 /* INFO */) { | ||
if (!this.options.debug || level > (this.options.level || 3 /* INFO */)) { | ||
return; | ||
} | ||
const prefix = this.options.logPrefix || `[${this.mockName}]`; | ||
const formattedMessage = `${prefix} ${message}`; | ||
if (this.options.customLogger) { | ||
this.options.customLogger(formattedMessage, level); | ||
return; | ||
} | ||
if (this.options.logToConsole) { | ||
switch (level) { | ||
case 1 /* ERROR */: | ||
console.error(formattedMessage); | ||
break; | ||
case 2 /* WARN */: | ||
console.warn(formattedMessage); | ||
break; | ||
case 3 /* INFO */: | ||
console.info(formattedMessage); | ||
break; | ||
case 4 /* DEBUG */: | ||
case 5 /* TRACE */: | ||
console.debug(formattedMessage); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Log method call | ||
*/ | ||
logMethodCall(methodName, args) { | ||
this.log(`Method ${methodName} called with args: ${JSON.stringify(args)}`, 4 /* DEBUG */); | ||
} | ||
/** | ||
* Log method return | ||
*/ | ||
logMethodReturn(methodName, result) { | ||
this.log(`Method ${methodName} returned: ${JSON.stringify(result)}`, 4 /* DEBUG */); | ||
} | ||
/** | ||
* Log mock creation | ||
*/ | ||
logMockCreation(type) { | ||
this.log(`Created new ${type} mock`, 3 /* INFO */); | ||
} | ||
/** | ||
* Log mock configuration | ||
*/ | ||
logMockConfig(config) { | ||
this.log(`Configured with options: ${JSON.stringify(config)}`, 4 /* DEBUG */); | ||
} | ||
/** | ||
* Log error | ||
*/ | ||
logError(error) { | ||
this.log(`Error: ${error.message}`, 1 /* ERROR */); | ||
if (error.stack) { | ||
this.log(`Stack: ${error.stack}`, 4 /* DEBUG */); | ||
} | ||
} | ||
}; | ||
function createDebugContext(options) { | ||
return new DebugContext(options); | ||
} | ||
// src/utils/performance.ts | ||
@@ -325,5 +405,12 @@ function calculateStats(durations) { | ||
var MethodTracker = class { | ||
constructor() { | ||
constructor(target, options = {}) { | ||
this.calls = []; | ||
this.expectations = []; | ||
this.target = target || {}; | ||
if (options.debug) { | ||
this.debugContext = new DebugContext({ | ||
name: options.name || (target && target.constructor ? target.constructor.name : "anonymous"), | ||
...options | ||
}); | ||
} | ||
} | ||
@@ -467,2 +554,11 @@ /** | ||
} | ||
trackMethodCall(methodName, args) { | ||
this.debugContext?.logMethodCall(methodName, args); | ||
} | ||
trackMethodReturn(methodName, result) { | ||
this.debugContext?.logMethodReturn(methodName, result); | ||
} | ||
trackError(methodName, error) { | ||
this.debugContext?.logError(error); | ||
} | ||
}; | ||
@@ -629,14 +725,30 @@ | ||
// src/utils/method-spy.ts | ||
function createMethodSpy(method, context, options = {}) { | ||
({ | ||
...DEFAULT_FUNCTION_OPTIONS, | ||
...options | ||
function createMethodSpy(target, methodName, options = {}, originalMethod) { | ||
const tracker = new MethodTracker(target, options); | ||
const method = originalMethod || target[methodName]; | ||
const spy = createSpy(); | ||
if (!options.preservePrototype) { | ||
return spy; | ||
} | ||
spy.mockImplementation(function(...args) { | ||
tracker.trackMethodCall(methodName, args); | ||
try { | ||
const result = method.apply(this, args); | ||
if (result instanceof Promise) { | ||
return result.then((asyncResult) => { | ||
tracker.trackMethodReturn(methodName, asyncResult); | ||
return asyncResult; | ||
}).catch((error) => { | ||
tracker.trackError(methodName, error); | ||
throw error; | ||
}); | ||
} | ||
tracker.trackMethodReturn(methodName, result); | ||
return result; | ||
} catch (error) { | ||
tracker.trackError(methodName, error); | ||
throw error; | ||
} | ||
}); | ||
const spy = new UniversalSpy(); | ||
const spyFn = spy.getSpy(); | ||
spyFn.mockImplementation(function(...args) { | ||
const result = method.apply(context || this, args); | ||
return result; | ||
}); | ||
return spyFn; | ||
return spy; | ||
} | ||
@@ -651,3 +763,3 @@ | ||
const method = currentProto[methodName].bind(mockClass); | ||
mockClass[methodName] = createMethodSpy(method, mockClass, options); | ||
mockClass[methodName] = createMethodSpy(mockClass, methodName, options, method); | ||
} | ||
@@ -658,2 +770,13 @@ }); | ||
} | ||
function createPropertyDescriptor(target, name, descriptor, config) { | ||
const getterSpy = descriptor.get ? createMethodSpy(target, name, config, descriptor.get) : void 0; | ||
const setterSpy = descriptor.set ? createMethodSpy(target, name, config, descriptor.set) : void 0; | ||
return { | ||
...descriptor, | ||
get: getterSpy, | ||
set: setterSpy, | ||
configurable: true, | ||
enumerable: descriptor.enumerable | ||
}; | ||
} | ||
function cls(target, options = {}) { | ||
@@ -682,3 +805,3 @@ const config = { | ||
...descriptor, | ||
value: createMethodSpy(override, context, config) | ||
value: createMethodSpy(context, name, config, override) | ||
}; | ||
@@ -688,3 +811,3 @@ } | ||
...descriptor, | ||
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy() | ||
value: config.preservePrototype ? createMethodSpy(context, name, config, value) : createSpy() | ||
}; | ||
@@ -695,4 +818,4 @@ } | ||
const setterOverride = typeof name === "string" && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`]; | ||
const getterSpy = get && (getterOverride ? createMethodSpy(getterOverride, context, config) : config.preservePrototype ? createMethodSpy(get, context, config) : createSpy()); | ||
const setterSpy = set && (setterOverride ? createMethodSpy(setterOverride, context, config) : config.preservePrototype ? createMethodSpy(set, context, config) : createSpy()); | ||
const getterSpy = get && (getterOverride ? createMethodSpy(context, name, config, getterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, get) : createSpy()); | ||
const setterSpy = set && (setterOverride ? createMethodSpy(context, name, config, setterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, set) : createSpy()); | ||
return { | ||
@@ -710,11 +833,7 @@ ...descriptor, | ||
const descriptor = Object.getOwnPropertyDescriptor(source, name); | ||
const newDescriptor = handleDescriptor(name, descriptor, target2); | ||
if (!config.mockInPlace || !target2.hasOwnProperty(name)) { | ||
Object.defineProperty(target2, name, newDescriptor); | ||
} else { | ||
if (typeof source[name] === "function") { | ||
target2[name] = createMethodSpy(source[name], target2, config); | ||
} else { | ||
Object.defineProperty(target2, name, newDescriptor); | ||
} | ||
if (typeof source[name] === "function") { | ||
const override = config.overrides?.[name]; | ||
target2[name] = createMethodSpy(target2, name, config, override || source[name]); | ||
} else if (descriptor) { | ||
Object.defineProperty(target2, name, createPropertyDescriptor(target2, name, descriptor, config)); | ||
} | ||
@@ -729,5 +848,5 @@ }); | ||
if (typeof proto[name] === "function") { | ||
proto[name] = createMethodSpy(proto[name], proto, config); | ||
proto[name] = createMethodSpy(proto, name, config, proto[name]); | ||
} else if (descriptor) { | ||
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto)); | ||
Object.defineProperty(proto, name, createPropertyDescriptor(proto, name, descriptor, config)); | ||
} | ||
@@ -738,7 +857,7 @@ }); | ||
if (typeof target[name] === "function") { | ||
target[name] = createMethodSpy(target[name], target, config); | ||
target[name] = createMethodSpy(target, name, config, target[name]); | ||
} else { | ||
const descriptor = Object.getOwnPropertyDescriptor(target, name); | ||
if (descriptor) { | ||
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target)); | ||
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config)); | ||
} | ||
@@ -970,4 +1089,5 @@ } | ||
const result = obj(target, config); | ||
if (options.replace) { | ||
Object.entries(options.replace).forEach(([key, impl]) => { | ||
const objOptions = options; | ||
if (objOptions.replace) { | ||
Object.entries(objOptions.replace).forEach(([key, impl]) => { | ||
if (impl && typeof impl === "function") { | ||
@@ -1105,2 +1225,3 @@ replace(result, key, impl); | ||
this.currentOptions = { ...DEFAULT_BASE_OPTIONS, ...options }; | ||
this.debugContext = createDebugContext(options); | ||
} | ||
@@ -1111,2 +1232,4 @@ /** | ||
fn(_options) { | ||
this.debugContext.logMockCreation("function"); | ||
this.debugContext.logMockConfig(_options); | ||
const mockFn = fn(); | ||
@@ -1120,2 +1243,4 @@ this.registerMock(mockFn, "function"); | ||
obj(target, options = {}) { | ||
this.debugContext.logMockCreation("object"); | ||
this.debugContext.logMockConfig(options); | ||
const config = createObjectOptions({ ...this.currentOptions, ...options }); | ||
@@ -1130,2 +1255,4 @@ const mockObj = obj(target, config); | ||
cls(target, options) { | ||
this.debugContext.logMockCreation("class"); | ||
this.debugContext.logMockConfig(options); | ||
const config = createClassOptions({ ...this.currentOptions, ...options }); | ||
@@ -1132,0 +1259,0 @@ const mockCls = cls(target, config); |
@@ -286,2 +286,82 @@ import rfdc from 'rfdc'; | ||
// src/utils/debug.ts | ||
var DebugContext = class { | ||
constructor(options = {}) { | ||
this.options = { | ||
debug: false, | ||
level: 3 /* INFO */, | ||
logToConsole: true, | ||
...options | ||
}; | ||
this.mockName = options.name || "anonymous-mock"; | ||
} | ||
/** | ||
* Log a debug message | ||
*/ | ||
log(message, level = 3 /* INFO */) { | ||
if (!this.options.debug || level > (this.options.level || 3 /* INFO */)) { | ||
return; | ||
} | ||
const prefix = this.options.logPrefix || `[${this.mockName}]`; | ||
const formattedMessage = `${prefix} ${message}`; | ||
if (this.options.customLogger) { | ||
this.options.customLogger(formattedMessage, level); | ||
return; | ||
} | ||
if (this.options.logToConsole) { | ||
switch (level) { | ||
case 1 /* ERROR */: | ||
console.error(formattedMessage); | ||
break; | ||
case 2 /* WARN */: | ||
console.warn(formattedMessage); | ||
break; | ||
case 3 /* INFO */: | ||
console.info(formattedMessage); | ||
break; | ||
case 4 /* DEBUG */: | ||
case 5 /* TRACE */: | ||
console.debug(formattedMessage); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Log method call | ||
*/ | ||
logMethodCall(methodName, args) { | ||
this.log(`Method ${methodName} called with args: ${JSON.stringify(args)}`, 4 /* DEBUG */); | ||
} | ||
/** | ||
* Log method return | ||
*/ | ||
logMethodReturn(methodName, result) { | ||
this.log(`Method ${methodName} returned: ${JSON.stringify(result)}`, 4 /* DEBUG */); | ||
} | ||
/** | ||
* Log mock creation | ||
*/ | ||
logMockCreation(type) { | ||
this.log(`Created new ${type} mock`, 3 /* INFO */); | ||
} | ||
/** | ||
* Log mock configuration | ||
*/ | ||
logMockConfig(config) { | ||
this.log(`Configured with options: ${JSON.stringify(config)}`, 4 /* DEBUG */); | ||
} | ||
/** | ||
* Log error | ||
*/ | ||
logError(error) { | ||
this.log(`Error: ${error.message}`, 1 /* ERROR */); | ||
if (error.stack) { | ||
this.log(`Stack: ${error.stack}`, 4 /* DEBUG */); | ||
} | ||
} | ||
}; | ||
function createDebugContext(options) { | ||
return new DebugContext(options); | ||
} | ||
// src/utils/performance.ts | ||
@@ -319,5 +399,12 @@ function calculateStats(durations) { | ||
var MethodTracker = class { | ||
constructor() { | ||
constructor(target, options = {}) { | ||
this.calls = []; | ||
this.expectations = []; | ||
this.target = target || {}; | ||
if (options.debug) { | ||
this.debugContext = new DebugContext({ | ||
name: options.name || (target && target.constructor ? target.constructor.name : "anonymous"), | ||
...options | ||
}); | ||
} | ||
} | ||
@@ -461,2 +548,11 @@ /** | ||
} | ||
trackMethodCall(methodName, args) { | ||
this.debugContext?.logMethodCall(methodName, args); | ||
} | ||
trackMethodReturn(methodName, result) { | ||
this.debugContext?.logMethodReturn(methodName, result); | ||
} | ||
trackError(methodName, error) { | ||
this.debugContext?.logError(error); | ||
} | ||
}; | ||
@@ -623,14 +719,30 @@ | ||
// src/utils/method-spy.ts | ||
function createMethodSpy(method, context, options = {}) { | ||
({ | ||
...DEFAULT_FUNCTION_OPTIONS, | ||
...options | ||
function createMethodSpy(target, methodName, options = {}, originalMethod) { | ||
const tracker = new MethodTracker(target, options); | ||
const method = originalMethod || target[methodName]; | ||
const spy = createSpy(); | ||
if (!options.preservePrototype) { | ||
return spy; | ||
} | ||
spy.mockImplementation(function(...args) { | ||
tracker.trackMethodCall(methodName, args); | ||
try { | ||
const result = method.apply(this, args); | ||
if (result instanceof Promise) { | ||
return result.then((asyncResult) => { | ||
tracker.trackMethodReturn(methodName, asyncResult); | ||
return asyncResult; | ||
}).catch((error) => { | ||
tracker.trackError(methodName, error); | ||
throw error; | ||
}); | ||
} | ||
tracker.trackMethodReturn(methodName, result); | ||
return result; | ||
} catch (error) { | ||
tracker.trackError(methodName, error); | ||
throw error; | ||
} | ||
}); | ||
const spy = new UniversalSpy(); | ||
const spyFn = spy.getSpy(); | ||
spyFn.mockImplementation(function(...args) { | ||
const result = method.apply(context || this, args); | ||
return result; | ||
}); | ||
return spyFn; | ||
return spy; | ||
} | ||
@@ -645,3 +757,3 @@ | ||
const method = currentProto[methodName].bind(mockClass); | ||
mockClass[methodName] = createMethodSpy(method, mockClass, options); | ||
mockClass[methodName] = createMethodSpy(mockClass, methodName, options, method); | ||
} | ||
@@ -652,2 +764,13 @@ }); | ||
} | ||
function createPropertyDescriptor(target, name, descriptor, config) { | ||
const getterSpy = descriptor.get ? createMethodSpy(target, name, config, descriptor.get) : void 0; | ||
const setterSpy = descriptor.set ? createMethodSpy(target, name, config, descriptor.set) : void 0; | ||
return { | ||
...descriptor, | ||
get: getterSpy, | ||
set: setterSpy, | ||
configurable: true, | ||
enumerable: descriptor.enumerable | ||
}; | ||
} | ||
function cls(target, options = {}) { | ||
@@ -676,3 +799,3 @@ const config = { | ||
...descriptor, | ||
value: createMethodSpy(override, context, config) | ||
value: createMethodSpy(context, name, config, override) | ||
}; | ||
@@ -682,3 +805,3 @@ } | ||
...descriptor, | ||
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy() | ||
value: config.preservePrototype ? createMethodSpy(context, name, config, value) : createSpy() | ||
}; | ||
@@ -689,4 +812,4 @@ } | ||
const setterOverride = typeof name === "string" && config.overrides?.[`set${name.charAt(0).toUpperCase()}${name.slice(1)}`]; | ||
const getterSpy = get && (getterOverride ? createMethodSpy(getterOverride, context, config) : config.preservePrototype ? createMethodSpy(get, context, config) : createSpy()); | ||
const setterSpy = set && (setterOverride ? createMethodSpy(setterOverride, context, config) : config.preservePrototype ? createMethodSpy(set, context, config) : createSpy()); | ||
const getterSpy = get && (getterOverride ? createMethodSpy(context, name, config, getterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, get) : createSpy()); | ||
const setterSpy = set && (setterOverride ? createMethodSpy(context, name, config, setterOverride) : config.preservePrototype ? createMethodSpy(context, name, config, set) : createSpy()); | ||
return { | ||
@@ -704,11 +827,7 @@ ...descriptor, | ||
const descriptor = Object.getOwnPropertyDescriptor(source, name); | ||
const newDescriptor = handleDescriptor(name, descriptor, target2); | ||
if (!config.mockInPlace || !target2.hasOwnProperty(name)) { | ||
Object.defineProperty(target2, name, newDescriptor); | ||
} else { | ||
if (typeof source[name] === "function") { | ||
target2[name] = createMethodSpy(source[name], target2, config); | ||
} else { | ||
Object.defineProperty(target2, name, newDescriptor); | ||
} | ||
if (typeof source[name] === "function") { | ||
const override = config.overrides?.[name]; | ||
target2[name] = createMethodSpy(target2, name, config, override || source[name]); | ||
} else if (descriptor) { | ||
Object.defineProperty(target2, name, createPropertyDescriptor(target2, name, descriptor, config)); | ||
} | ||
@@ -723,5 +842,5 @@ }); | ||
if (typeof proto[name] === "function") { | ||
proto[name] = createMethodSpy(proto[name], proto, config); | ||
proto[name] = createMethodSpy(proto, name, config, proto[name]); | ||
} else if (descriptor) { | ||
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto)); | ||
Object.defineProperty(proto, name, createPropertyDescriptor(proto, name, descriptor, config)); | ||
} | ||
@@ -732,7 +851,7 @@ }); | ||
if (typeof target[name] === "function") { | ||
target[name] = createMethodSpy(target[name], target, config); | ||
target[name] = createMethodSpy(target, name, config, target[name]); | ||
} else { | ||
const descriptor = Object.getOwnPropertyDescriptor(target, name); | ||
if (descriptor) { | ||
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target)); | ||
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config)); | ||
} | ||
@@ -964,4 +1083,5 @@ } | ||
const result = obj(target, config); | ||
if (options.replace) { | ||
Object.entries(options.replace).forEach(([key, impl]) => { | ||
const objOptions = options; | ||
if (objOptions.replace) { | ||
Object.entries(objOptions.replace).forEach(([key, impl]) => { | ||
if (impl && typeof impl === "function") { | ||
@@ -1099,2 +1219,3 @@ replace(result, key, impl); | ||
this.currentOptions = { ...DEFAULT_BASE_OPTIONS, ...options }; | ||
this.debugContext = createDebugContext(options); | ||
} | ||
@@ -1105,2 +1226,4 @@ /** | ||
fn(_options) { | ||
this.debugContext.logMockCreation("function"); | ||
this.debugContext.logMockConfig(_options); | ||
const mockFn = fn(); | ||
@@ -1114,2 +1237,4 @@ this.registerMock(mockFn, "function"); | ||
obj(target, options = {}) { | ||
this.debugContext.logMockCreation("object"); | ||
this.debugContext.logMockConfig(options); | ||
const config = createObjectOptions({ ...this.currentOptions, ...options }); | ||
@@ -1124,2 +1249,4 @@ const mockObj = obj(target, config); | ||
cls(target, options) { | ||
this.debugContext.logMockCreation("class"); | ||
this.debugContext.logMockConfig(options); | ||
const config = createClassOptions({ ...this.currentOptions, ...options }); | ||
@@ -1126,0 +1253,0 @@ const mockCls = cls(target, config); |
{ | ||
"name": "@corez/mock", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "A powerful and flexible TypeScript mocking library for testing", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
275
README.md
@@ -20,2 +20,3 @@ # @corez/mock | ||
- [License](#license) | ||
- [Debugging Features](#debugging-features) | ||
@@ -669,1 +670,275 @@ ## Features | ||
``` | ||
## Mock Composition | ||
### Compose Function Options | ||
The `compose` function provides a unified interface for creating mocks of different types (classes, objects, and | ||
functions). It uses a sophisticated type system to provide appropriate options based on the target type. | ||
#### Options Structure | ||
```typescript | ||
type ComposeOptions<T> = BaseMockOptions & | ||
(T extends new (...args: any[]) => any | ||
? ClassMockOptions<InstanceType<T>> | ||
: ObjectMockOptions<T> & { | ||
replace?: { | ||
[K in keyof T]?: T[K] extends Fn ? Fn : never; | ||
}; | ||
}); | ||
``` | ||
#### Design Decisions | ||
1. **Replace vs Override** | ||
- `replace` is specifically designed for object mocking, allowing direct method replacement | ||
- `override` is used in class mocking for overriding prototype methods | ||
- While they may seem similar, they serve different purposes and contexts | ||
2. **Type Safety** | ||
- The options type automatically adapts based on the target type | ||
- Class mocks receive `ClassMockOptions` | ||
- Object mocks receive `ObjectMockOptions` with additional `replace` capability | ||
- This ensures type safety while maintaining flexibility | ||
3. **Usage Examples** | ||
```typescript | ||
// Class mocking | ||
class Service { | ||
getData(): string { | ||
return 'data'; | ||
} | ||
} | ||
const mockService = compose(Service, { | ||
override: { | ||
getData: () => 'mocked', | ||
}, | ||
}); | ||
// Object mocking with replace | ||
const obj = { | ||
method: () => 'original', | ||
}; | ||
const mockObj = compose(obj, { | ||
replace: { | ||
method: () => 'replaced', | ||
}, | ||
}); | ||
``` | ||
4. **Best Practices** | ||
- Use `override` for class method mocking | ||
- Use `replace` for direct object method replacement | ||
- Consider the scope and context when choosing between them | ||
- Avoid mixing `override` and `replace` in the same mock | ||
## Debugging Features | ||
### Debug Options | ||
The library provides comprehensive debugging capabilities through the `debug` option and `name` property: | ||
```typescript | ||
interface DebugOptions { | ||
debug?: boolean; // Enable/disable debugging | ||
name?: string; // Name for the mock instance | ||
level?: DebugLevel; // Debug level (ERROR, WARN, INFO, DEBUG, TRACE) | ||
logPrefix?: string; // Custom prefix for log messages | ||
logToConsole?: boolean; // Whether to log to console | ||
customLogger?: (message: string, level: DebugLevel) => void; // Custom logging function | ||
} | ||
// Usage example | ||
const userService = mock.compose<UserService>( | ||
{}, | ||
{ | ||
debug: true, | ||
name: 'UserService', | ||
level: DebugLevel.DEBUG, | ||
logPrefix: '[TEST]', | ||
}, | ||
); | ||
// Will output logs like: | ||
// [TEST][UserService] Method getUser called with args: [1] | ||
// [TEST][UserService] Method getUser returned: {"id":1,"name":"Test"} | ||
``` | ||
### Debug Levels | ||
The library supports different debug levels for fine-grained control: | ||
- `ERROR`: Only critical errors | ||
- `WARN`: Warnings and errors | ||
- `INFO`: General information (default) | ||
- `DEBUG`: Detailed debugging information | ||
- `TRACE`: Most verbose level | ||
### What Gets Logged | ||
When debugging is enabled, the following information is logged: | ||
1. **Method Calls**: | ||
- Method name | ||
- Arguments passed | ||
- Return values | ||
- Execution time (for async methods) | ||
2. **Mock Creation**: | ||
- Type of mock (function/object/class) | ||
- Configuration options used | ||
- Initial state | ||
3. **Errors and Warnings**: | ||
- Error messages | ||
- Stack traces (in DEBUG level) | ||
- Warning conditions | ||
### Custom Logging | ||
You can provide your own logging implementation: | ||
```typescript | ||
const customLogger = (message: string, level: DebugLevel) => { | ||
// Send to your logging service | ||
myLoggingService.log({ | ||
message, | ||
level, | ||
timestamp: new Date(), | ||
}); | ||
}; | ||
const mock = mock.compose<UserService>( | ||
{}, | ||
{ | ||
debug: true, | ||
name: 'UserService', | ||
customLogger, | ||
}, | ||
); | ||
``` | ||
### Best Practices for Debugging | ||
1. **Use Meaningful Names**: | ||
```typescript | ||
// Good | ||
const userServiceMock = mock.compose<UserService>({}, {name: 'UserService'}); | ||
// Not as helpful | ||
const mock1 = mock.compose<UserService>({}); | ||
``` | ||
2. **Set Appropriate Debug Levels**: | ||
```typescript | ||
// Development/Testing | ||
const devMock = mock.compose<UserService>( | ||
{}, | ||
{ | ||
debug: true, | ||
level: DebugLevel.DEBUG, | ||
}, | ||
); | ||
// Production Tests | ||
const prodMock = mock.compose<UserService>( | ||
{}, | ||
{ | ||
debug: true, | ||
level: DebugLevel.ERROR, | ||
}, | ||
); | ||
``` | ||
3. **Use Custom Logging for CI/CD**: | ||
```typescript | ||
const ciLogger = (message: string, level: DebugLevel) => { | ||
if (process.env.CI) { | ||
// Format for CI system | ||
process.stdout.write(`::${level}::${message}\n`); | ||
} | ||
}; | ||
``` | ||
## Logging and Debugging | ||
### Logging Configuration | ||
The library provides comprehensive logging capabilities to help with debugging and troubleshooting. You can configure | ||
logging at different levels: | ||
```typescript | ||
import {mock, LogLevel} from '@corez/mock'; | ||
// Simple debug mode | ||
const mockFn = mock.fn({ | ||
debug: true, // Enable detailed logging | ||
}); | ||
// Advanced logging configuration | ||
const userService = mock.obj<UserService>({ | ||
logging: { | ||
level: LogLevel.DEBUG, // Set log level | ||
prefix: '[UserService]', // Custom prefix for log messages | ||
consoleOutput: true, // Enable console output | ||
formatter: (msg, level, ctx) => { | ||
return `[${new Date().toISOString()}] ${level}: ${msg}`; | ||
}, | ||
logger: (msg, level) => { | ||
// Custom logging implementation | ||
customLogger.log(msg, level); | ||
}, | ||
}, | ||
}); | ||
``` | ||
### Log Levels | ||
The library supports the following log levels: | ||
- `LogLevel.NONE` (0) - Disable all logging | ||
- `LogLevel.ERROR` (1) - Only log errors | ||
- `LogLevel.WARN` (2) - Log warnings and errors | ||
- `LogLevel.INFO` (3) - Log general information (default) | ||
- `LogLevel.DEBUG` (4) - Log detailed debug information | ||
- `LogLevel.TRACE` (5) - Log everything including internal details | ||
### Logging Options | ||
| Option | Type | Default | Description | | ||
| --------------- | -------- | ------------------ | ------------------------------------ | | ||
| `debug` | boolean | false | Quick way to enable detailed logging | | ||
| `level` | LogLevel | LogLevel.INFO | Controls log verbosity | | ||
| `prefix` | string | mock name | Custom prefix for log messages | | ||
| `consoleOutput` | boolean | true if debug=true | Whether to write logs to console | | ||
| `formatter` | Function | undefined | Custom log message formatter | | ||
| `logger` | Function | console.log | Custom logging implementation | | ||
### Debug Information | ||
When logging is enabled, the library will output information about: | ||
- Mock creation and configuration | ||
- Function calls and arguments | ||
- Return values and errors | ||
- Mock state changes | ||
- Spy tracking information | ||
- Method overrides and restorations | ||
Example debug output: | ||
``` | ||
[UserService] DEBUG: Creating mock object | ||
[UserService] INFO: Method 'getUser' called with args: [1] | ||
[UserService] DEBUG: Return value: { id: 1, name: 'Mock User' } | ||
[UserService] WARN: Method 'updateUser' not implemented | ||
``` |
@@ -24,2 +24,3 @@ import { | ||
} from './types'; | ||
import {createDebugContext, DebugContext} from './utils/debug'; | ||
@@ -32,2 +33,3 @@ /** | ||
private currentOptions: BaseMockOptions; | ||
private debugContext: DebugContext; | ||
@@ -37,2 +39,3 @@ constructor(options: Partial<BaseMockOptions> = {}) { | ||
this.currentOptions = {...DEFAULT_BASE_OPTIONS, ...options}; | ||
this.debugContext = createDebugContext(options); | ||
} | ||
@@ -44,2 +47,4 @@ | ||
fn<T extends Fn>(_options?: Partial<FunctionMockOptions>): MockFunction<T> { | ||
this.debugContext.logMockCreation('function'); | ||
this.debugContext.logMockConfig(_options); | ||
const mockFn = fn<T>(); | ||
@@ -54,2 +59,4 @@ this.registerMock(mockFn, 'function'); | ||
obj<T extends object>(target: T | undefined, options: ObjectMockOptions<T> = {}): MockObject<T> { | ||
this.debugContext.logMockCreation('object'); | ||
this.debugContext.logMockConfig(options); | ||
const config = createObjectOptions({...this.currentOptions, ...options}); | ||
@@ -65,2 +72,4 @@ const mockObj = obj(target, config); | ||
cls<T extends Constructor<any>>(target: T, options?: ClassMockOptions<InstanceType<T>>): ClsMock<T> { | ||
this.debugContext.logMockCreation('class'); | ||
this.debugContext.logMockConfig(options); | ||
const config = createClassOptions({...this.currentOptions, ...options}); | ||
@@ -67,0 +76,0 @@ const mockCls = cls(target, config); |
import {DEFAULT_CLASS_OPTIONS} from '../defaults'; | ||
import {createSpy} from '../spy'; | ||
import {ClassMockOptions, ClsMock} from '../types'; | ||
import {ClassMockOptions, ClsMock, Constructor} from '../types'; | ||
import {createMethodSpy} from '../utils/method-spy'; | ||
@@ -19,3 +19,3 @@ | ||
const method = currentProto[methodName].bind(mockClass); | ||
mockClass[methodName] = createMethodSpy(method, mockClass, options); | ||
mockClass[methodName] = createMethodSpy(mockClass, methodName, options, method); | ||
} | ||
@@ -27,2 +27,20 @@ }); | ||
function createPropertyDescriptor<T>( | ||
target: any, | ||
name: string | symbol, | ||
descriptor: PropertyDescriptor, | ||
config: ClassMockOptions<T>, | ||
): PropertyDescriptor { | ||
const getterSpy = descriptor.get ? createMethodSpy(target, name as string, config, descriptor.get) : undefined; | ||
const setterSpy = descriptor.set ? createMethodSpy(target, name as string, config, descriptor.set) : undefined; | ||
return { | ||
...descriptor, | ||
get: getterSpy as (() => any) | undefined, | ||
set: setterSpy as ((v: any) => void) | undefined, | ||
configurable: true, | ||
enumerable: descriptor.enumerable, | ||
}; | ||
} | ||
/** | ||
@@ -49,3 +67,3 @@ * Creates a mock class with all methods and properties mocked while preserving the original class structure. | ||
*/ | ||
export function cls<T extends new (...args: any[]) => any>( | ||
export function cls<T extends Constructor<any>>( | ||
target: T, | ||
@@ -86,3 +104,3 @@ options: ClassMockOptions<InstanceType<T>> = {}, | ||
...descriptor, | ||
value: createMethodSpy(override, context, config), | ||
value: createMethodSpy(context, name as string, config, override), | ||
}; | ||
@@ -92,3 +110,3 @@ } | ||
...descriptor, | ||
value: config.preservePrototype ? createMethodSpy(value, context, config) : createSpy(), | ||
value: config.preservePrototype ? createMethodSpy(context, name as string, config, value) : createSpy(), | ||
}; | ||
@@ -107,5 +125,5 @@ } | ||
(getterOverride | ||
? createMethodSpy(getterOverride, context, config) | ||
? createMethodSpy(context, name as string, config, getterOverride) | ||
: config.preservePrototype | ||
? createMethodSpy(get, context, config) | ||
? createMethodSpy(context, name as string, config, get) | ||
: createSpy()); | ||
@@ -115,5 +133,5 @@ const setterSpy = | ||
(setterOverride | ||
? createMethodSpy(setterOverride, context, config) | ||
? createMethodSpy(context, name as string, config, setterOverride) | ||
: config.preservePrototype | ||
? createMethodSpy(set, context, config) | ||
? createMethodSpy(context, name as string, config, set) | ||
: createSpy()); | ||
@@ -137,13 +155,7 @@ | ||
const descriptor = Object.getOwnPropertyDescriptor(source, name); | ||
const newDescriptor = handleDescriptor(name, descriptor, target); | ||
if (!config.mockInPlace || !target.hasOwnProperty(name)) { | ||
Object.defineProperty(target, name, newDescriptor); | ||
} else { | ||
// When modifying original class, directly replace the method | ||
if (typeof source[name] === 'function') { | ||
target[name] = createMethodSpy(source[name], target, config); | ||
} else { | ||
Object.defineProperty(target, name, newDescriptor); | ||
} | ||
if (typeof source[name] === 'function') { | ||
const override = config.overrides?.[name]; | ||
target[name] = createMethodSpy(target, name, config, override || source[name]); | ||
} else if (descriptor) { | ||
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config)); | ||
} | ||
@@ -161,5 +173,5 @@ }); | ||
if (typeof proto[name] === 'function') { | ||
(proto as any)[name] = createMethodSpy(proto[name], proto, config); | ||
(proto as any)[name] = createMethodSpy(proto, name, config, proto[name]); | ||
} else if (descriptor) { | ||
Object.defineProperty(proto, name, handleDescriptor(name, descriptor, proto)); | ||
Object.defineProperty(proto, name, createPropertyDescriptor(proto, name, descriptor, config)); | ||
} | ||
@@ -173,7 +185,7 @@ }); | ||
if (typeof (target as any)[name] === 'function') { | ||
(target as any)[name] = createMethodSpy((target as any)[name], target, config); | ||
(target as any)[name] = createMethodSpy(target, name, config, (target as any)[name]); | ||
} else { | ||
const descriptor = Object.getOwnPropertyDescriptor(target, name); | ||
if (descriptor) { | ||
Object.defineProperty(target, name, handleDescriptor(name, descriptor, target)); | ||
Object.defineProperty(target, name, createPropertyDescriptor(target, name, descriptor, config)); | ||
} | ||
@@ -180,0 +192,0 @@ } |
import {DEFAULT_CLASS_OPTIONS, DEFAULT_OBJECT_OPTIONS} from '../defaults'; | ||
import {ClassMockOptions, ClsMock, Fn, MockFunction, ObjectMockOptions} from '../types'; | ||
import {ClassMockOptions, ClsMock, ComposeOptions, Fn, MockFunction, ObjectMockOptions} from '../types'; | ||
import * as clsModule from './class'; | ||
@@ -58,16 +58,6 @@ import * as fnModule from './function'; | ||
// Class mock overload | ||
export function compose<T extends new (...args: any[]) => any>( | ||
target: T, | ||
options?: ClassMockOptions<InstanceType<T>>, | ||
): ClsMock<T>; | ||
export function compose<T extends new (...args: any[]) => any>(target: T, options?: ComposeOptions<T>): ClsMock<T>; | ||
// Object mock overload | ||
export function compose<T extends object>( | ||
target: T, | ||
options?: ObjectMockOptions<T> & { | ||
replace?: { | ||
[K in keyof T]?: T[K] extends Fn ? Fn : never; | ||
}; | ||
}, | ||
): T; | ||
export function compose<T extends object>(target: T, options?: ComposeOptions<T>): T; | ||
@@ -77,7 +67,3 @@ // Implementation | ||
target?: T | (new (...args: any[]) => any), | ||
options: ObjectMockOptions<T> & { | ||
replace?: { | ||
[K in keyof T]?: T[K] extends Fn ? Fn : never; | ||
}; | ||
} = {}, | ||
options: ComposeOptions<T> = {} as ComposeOptions<T>, | ||
): T | ClsMock<any> | MockFunction<any> { | ||
@@ -94,3 +80,3 @@ if (!target) { | ||
...DEFAULT_CLASS_OPTIONS.overrides, | ||
...options.overrides, | ||
...(options as ClassMockOptions<any>).overrides, | ||
}, | ||
@@ -108,4 +94,10 @@ }; | ||
if (options.replace) { | ||
Object.entries(options.replace).forEach(([key, impl]) => { | ||
const objOptions = options as ObjectMockOptions<T> & { | ||
replace?: { | ||
[K in keyof T]?: T[K] extends Fn ? Fn : never; | ||
}; | ||
}; | ||
if (objOptions.replace) { | ||
Object.entries(objOptions.replace).forEach(([key, impl]) => { | ||
if (impl && typeof impl === 'function') { | ||
@@ -112,0 +104,0 @@ replaceModule.replace(result, key as keyof T, impl as unknown as Fn); |
@@ -14,2 +14,61 @@ import type {Constructor, DeepPartial, Fn, PropertyDescriptor, RecursivePartial} from './common'; | ||
/** | ||
* Log level for controlling debug output verbosity | ||
*/ | ||
export enum LogLevel { | ||
NONE = 0, | ||
ERROR = 1, | ||
WARN = 2, | ||
INFO = 3, | ||
DEBUG = 4, | ||
TRACE = 5, | ||
} | ||
/** | ||
* Logging configuration options | ||
*/ | ||
export interface LoggingOptions { | ||
/** | ||
* Whether to enable detailed logging | ||
* @default false | ||
*/ | ||
debug?: boolean; | ||
/** | ||
* Log level for controlling output verbosity | ||
* If debug is true, defaults to LogLevel.DEBUG | ||
* If debug is false, defaults to LogLevel.INFO | ||
* @default LogLevel.INFO | ||
*/ | ||
level?: LogLevel; | ||
/** | ||
* Custom prefix for log messages | ||
* If not provided, uses the mock name if available | ||
*/ | ||
prefix?: string; | ||
/** | ||
* Whether to output logs to console | ||
* Controls if logs should be written to console or suppressed | ||
* @default true if debug is true, false otherwise | ||
*/ | ||
consoleOutput?: boolean; | ||
/** | ||
* Custom log formatter | ||
* @param message The log message | ||
* @param level Current log level | ||
* @param context Additional context data | ||
*/ | ||
formatter?: (message: string, level: LogLevel, context?: Record<string, any>) => string; | ||
/** | ||
* Custom logger implementation | ||
* @param message Formatted log message | ||
* @param level Current log level | ||
*/ | ||
logger?: (message: string, level: LogLevel) => void; | ||
} | ||
/** | ||
* Base configuration options for all mock types | ||
@@ -21,3 +80,3 @@ * These options control general mock behavior | ||
* Enables detailed logging of mock operations | ||
* Useful for debugging and understanding mock behavior | ||
* Shorthand for logging.debug = true | ||
* @default false | ||
@@ -28,2 +87,8 @@ */ | ||
/** | ||
* Logging configuration | ||
* If debug is true, this will be merged with {debug: true, level: LogLevel.DEBUG} | ||
*/ | ||
logging?: LoggingOptions; | ||
/** | ||
* Maximum time to wait for async operations in milliseconds | ||
@@ -34,2 +99,8 @@ * Used when mocking async methods and properties | ||
asyncTimeout?: number; | ||
/** | ||
* Name for the mock instance | ||
* Used in debug logs to identify the mock | ||
*/ | ||
name?: string; | ||
} | ||
@@ -361,1 +432,13 @@ | ||
} | ||
/** | ||
* Unified options type for compose function | ||
*/ | ||
export type ComposeOptions<T> = BaseMockOptions & | ||
(T extends new (...args: any[]) => any | ||
? ClassMockOptions<InstanceType<T>> | ||
: ObjectMockOptions<T> & { | ||
replace?: { | ||
[K in keyof T]?: T[K] extends Fn ? Fn : never; | ||
}; | ||
}); |
@@ -246,2 +246,63 @@ import {MethodTracker} from '../method-tracker'; | ||
}); | ||
describe('debug functionality', () => { | ||
it('should initialize debug context when debug option is true', () => { | ||
const trackerWithDebug = new MethodTracker( | ||
{}, | ||
{ | ||
debug: true, | ||
name: 'test-tracker', | ||
}, | ||
); | ||
const finish = trackerWithDebug.record('testMethod', [1, 2, 3]); | ||
finish(undefined, 'result'); | ||
// Debug context is private, so we verify through behavior | ||
const calls = trackerWithDebug.getCalls(); | ||
expect(calls[0]).toMatchObject({ | ||
method: 'testMethod', | ||
args: [1, 2, 3], | ||
returnValue: 'result', | ||
}); | ||
}); | ||
it('should track method calls with debug context', () => { | ||
const trackerWithDebug = new MethodTracker( | ||
{}, | ||
{ | ||
debug: true, | ||
name: 'test-tracker', | ||
}, | ||
); | ||
trackerWithDebug.trackMethodCall('testMethod', [1, 2, 3]); | ||
trackerWithDebug.trackMethodReturn('testMethod', 'result'); | ||
// Since debug output goes to console, we don't test the actual output | ||
// but verify the methods don't throw | ||
expect(() => { | ||
trackerWithDebug.trackMethodCall('anotherMethod', [4, 5, 6]); | ||
trackerWithDebug.trackMethodReturn('anotherMethod', 'another result'); | ||
}).not.toThrow(); | ||
}); | ||
it('should track errors with debug context', () => { | ||
const trackerWithDebug = new MethodTracker( | ||
{}, | ||
{ | ||
debug: true, | ||
name: 'test-tracker', | ||
}, | ||
); | ||
const error = new Error('test error'); | ||
trackerWithDebug.trackError('testMethod', error); | ||
// Verify error tracking doesn't throw | ||
expect(() => { | ||
trackerWithDebug.trackError('anotherMethod', new Error('another error')); | ||
}).not.toThrow(); | ||
}); | ||
}); | ||
}); |
@@ -1,59 +0,47 @@ | ||
import {DEFAULT_FUNCTION_OPTIONS} from '../defaults'; | ||
import {UniversalSpy} from '../spy'; | ||
import {Fn, FunctionMockOptions, MockFunction} from '../types'; | ||
import {createSpy} from '../spy'; | ||
import {Fn, MockFunction} from '../types'; | ||
import {MethodTracker} from './method-tracker'; | ||
/** | ||
* Creates a spy for a method with proper context binding and tracking capabilities. | ||
* This utility function is used internally by the mocking system to create method spies | ||
* that maintain their execution context and track calls. | ||
* | ||
* @template T - The type of the method being spied on | ||
* @param method - The original method to spy on | ||
* @param context - The context (this) to bind the method to | ||
* @param options - Optional configuration options | ||
* @returns A spy function that wraps the original method | ||
*/ | ||
export function createMethodSpy<T extends Fn>( | ||
method: T, | ||
context: any, | ||
options: Partial<FunctionMockOptions> = {}, | ||
target: any, | ||
methodName: string, | ||
options: any = {}, | ||
originalMethod?: T, | ||
): MockFunction<T> { | ||
const _config = { | ||
...DEFAULT_FUNCTION_OPTIONS, | ||
...options, | ||
}; | ||
const tracker = new MethodTracker(target, options); | ||
const method = originalMethod || target[methodName]; | ||
const spy = createSpy<T>(); | ||
const spy = new UniversalSpy<T>(); | ||
const spyFn = spy.getSpy(); | ||
if (!options.preservePrototype) { | ||
return spy; | ||
} | ||
spyFn.mockImplementation(function (this: any, ...args: Parameters<T>): ReturnType<T> { | ||
const result = method.apply(context || this, args); | ||
return result; | ||
spy.mockImplementation(function (this: any, ...args: Parameters<T>): ReturnType<T> { | ||
tracker.trackMethodCall(methodName, args); | ||
try { | ||
const result = method.apply(this, args); | ||
// Handle async results | ||
if (result instanceof Promise) { | ||
return result | ||
.then(asyncResult => { | ||
tracker.trackMethodReturn(methodName, asyncResult); | ||
return asyncResult; | ||
}) | ||
.catch(error => { | ||
tracker.trackError(methodName, error); | ||
throw error; | ||
}) as ReturnType<T>; | ||
} | ||
tracker.trackMethodReturn(methodName, result); | ||
return result; | ||
} catch (error) { | ||
tracker.trackError(methodName, error as Error); | ||
throw error; | ||
} | ||
} as T); | ||
return spyFn; | ||
return spy; | ||
} | ||
/** | ||
* Creates a spy for a property accessor (getter/setter) with proper context binding. | ||
* This utility function is used internally by the mocking system to create property spies | ||
* that maintain their execution context and track access/modifications. | ||
* | ||
* @template T - The type of the property value | ||
* @param getter - The original getter function | ||
* @param setter - The original setter function | ||
* @param context - The context (this) to bind the accessors to | ||
* @param options - Optional configuration options | ||
* @returns An object containing spy functions for get and set operations | ||
*/ | ||
export function createPropertyMethodSpy<T>( | ||
getter?: (() => T) | undefined, | ||
setter?: ((value: T) => void) | undefined, | ||
context?: any, | ||
options: Partial<FunctionMockOptions> = {}, | ||
): {get?: MockFunction<() => T>; set?: MockFunction<(value: T) => void>} { | ||
return { | ||
get: getter ? createMethodSpy(getter, context, options) : undefined, | ||
set: setter ? createMethodSpy(setter, context, options) : undefined, | ||
}; | ||
} |
import {MockError} from '../errors'; | ||
import {compareArrays} from './compare'; | ||
import {DebugContext} from './debug'; | ||
import {calculateStats, checkPerformanceConstraints, type PerformanceStats} from './performance'; | ||
@@ -57,3 +58,15 @@ | ||
private startTime?: number; | ||
private debugContext?: DebugContext; | ||
private target: any; | ||
constructor(target?: any, options: any = {}) { | ||
this.target = target || {}; | ||
if (options.debug) { | ||
this.debugContext = new DebugContext({ | ||
name: options.name || (target && target.constructor ? target.constructor.name : 'anonymous'), | ||
...options, | ||
}); | ||
} | ||
} | ||
/** | ||
@@ -221,2 +234,14 @@ * Starts tracking method calls | ||
} | ||
trackMethodCall(methodName: string, args: any[]): void { | ||
this.debugContext?.logMethodCall(methodName, args); | ||
} | ||
trackMethodReturn(methodName: string, result: any): void { | ||
this.debugContext?.logMethodReturn(methodName, result); | ||
} | ||
trackError(methodName: string, error: Error): void { | ||
this.debugContext?.logError(error); | ||
} | ||
} |
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
509879
51
7726
943