Skip to content

Commit

Permalink
capture stacktraces on each call
Browse files Browse the repository at this point in the history
  • Loading branch information
notanengineercom committed Mar 9, 2024
1 parent 78dc1a9 commit d40a66b
Showing 1 changed file with 13 additions and 11 deletions.
24 changes: 13 additions & 11 deletions src/SubstituteNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SubstituteNodeBase } from './SubstituteNodeBase'
import { RecordedArguments } from './RecordedArguments'
import { ClearType as ClearTypeMap, PropertyType as PropertyTypeMap, isAssertionMethod, isSubstituteMethod, isSubstitutionMethod, textModifier, isConfigurationMethod } from './Utilities'
import { SubstituteException } from './SubstituteException'
import type { FilterFunction, SubstituteContext, SubstitutionMethod, ClearType, PropertyType } from './Types'
import type { FilterFunction, SubstituteContext, SubstitutionMethod, ClearType, PropertyType, SubstituteNodeModel } from './Types'
import type { ObjectSubstitute } from './Transformations'

const instance = Symbol('Substitute:Instance')
Expand All @@ -17,7 +17,7 @@ const clearTypeToFilterMap: Record<ClearType, FilterFunction<SubstituteNode>> =
type SpecialProperty = typeof instance | typeof inspect.custom | 'then'
type RootContext = { substituteMethodsEnabled: boolean }

export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitute<unknown> {
export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitute<unknown>, SubstituteNodeModel {
private _proxy: SubstituteNode
private _rootContext: RootContext

Expand All @@ -28,6 +28,8 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
private _context: SubstituteContext = 'none'
private _retrySubstitutionExecutionAttempt: boolean = false

public stack?: string

private constructor(key: PropertyKey, parent?: SubstituteNode) {
super(key, parent)
if (this.isRoot()) this._rootContext = { substituteMethodsEnabled: true }
Expand All @@ -44,6 +46,7 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
newNode.assignContext(property)
return newNode[property].bind(newNode)
}
Error.captureStackTrace(newNode, this.get)
return newNode.attemptSubstitutionExecution()
},
set: function (target, property, value) {
Expand All @@ -58,6 +61,7 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
if (isSubstitutionMethod(target.property)) return target.parent.assignContext(target.property)
if (target.parent.isAssertion) return target.executeAssertion()
}
Error.captureStackTrace(target, this.apply)
return target.isAssertion ? target.proxy : target.attemptSubstitutionExecution()
}
}
Expand Down Expand Up @@ -185,7 +189,7 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
private executeAssertion(): void | never {
if (!this.hasDepthOfAtLeast(2)) throw new Error('Not possible')
if (!this.parent.recordedArguments.hasArguments()) throw new TypeError('Parent args')
const expectedCount = this.parent.recordedArguments.value[0] ?? undefined
const expectedCount: number | undefined = this.parent.recordedArguments.value[0] ?? undefined
const finiteExpectation = expectedCount !== undefined
if (finiteExpectation && (!Number.isInteger(expectedCount) || expectedCount < 0)) throw new Error('Expected count has to be a positive integer')

Expand All @@ -197,21 +201,19 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
if (
!hasBeenCalled &&
(!finiteExpectation || expectedCount > 0)
) throw SubstituteException.forCallCountMissMatch( // Here we don't know here if it's a property or method, so we should throw something more generic
{ expected: expectedCount, received: 0 },
{ type: this.propertyType, value: this.property },
{ expected: this.recordedArguments, received: allRecordedArguments }
) throw SubstituteException.forCallCountMismatch( // Here we don't know here if it's a property or method, so we should throw something more generic
{ count: expectedCount, call: this },
{ matchCount: 0, calls: siblings }
)

if (!hasBeenCalled || hasSiblingOfSamePropertyType) {
this._scheduledAssertionException = undefined
const actualCount = allRecordedArguments.filter(r => r.match(this.recordedArguments)).length
const matchedExpectation = (!finiteExpectation && actualCount > 0) || expectedCount === actualCount
if (matchedExpectation) return
const exception = SubstituteException.forCallCountMissMatch(
{ expected: expectedCount, received: actualCount },
{ type: this.propertyType, value: this.property },
{ expected: this.recordedArguments, received: allRecordedArguments }
const exception = SubstituteException.forCallCountMismatch(
{ count: expectedCount, call: this },
{ matchCount: actualCount, calls: siblings }
)
const potentialMethodAssertion = this.propertyType === PropertyTypeMap.Property && siblings.some(sibling => sibling.propertyType === PropertyTypeMap.Method)
if (potentialMethodAssertion) this.schedulePropertyAssertionException(exception)
Expand Down

0 comments on commit d40a66b

Please sign in to comment.