Skip to content

Commit cdd3d04

Browse files
committed
fix: progress on symbols
1 parent 2e93eef commit cdd3d04

File tree

8 files changed

+89
-61
lines changed

8 files changed

+89
-61
lines changed
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import test from 'ava'
2-
import { Substitute, Arg } from '../../src'
2+
import { Substitute, Arg, didNotReceive, received } from '../../src'
33
import { SubstituteException } from '../../src/SubstituteException'
44

55
interface Calculator {
@@ -12,41 +12,41 @@ interface Calculator {
1212
test('not calling a method correctly asserts the call count', t => {
1313
const calculator = Substitute.for<Calculator>()
1414

15-
calculator.didNotReceive().add(1, 1)
16-
t.throws(() => calculator.received(1).add(1, 1), { instanceOf: SubstituteException })
17-
t.throws(() => calculator.received().add(Arg.all()), { instanceOf: SubstituteException })
15+
calculator[didNotReceive]().add(1, 1)
16+
t.throws(() => calculator[received]().add(1, 1), { instanceOf: SubstituteException })
17+
t.throws(() => calculator[received]().add(Arg.all()), { instanceOf: SubstituteException })
1818
})
1919

2020
test('not getting a property correctly asserts the call count', t => {
2121
const calculator = Substitute.for<Calculator>()
2222

23-
calculator.didNotReceive().isEnabled
24-
t.throws(() => calculator.received(1).isEnabled, { instanceOf: SubstituteException })
25-
t.throws(() => calculator.received().isEnabled, { instanceOf: SubstituteException })
23+
calculator[didNotReceive]().isEnabled
24+
t.throws(() => calculator[received](1).isEnabled, { instanceOf: SubstituteException })
25+
t.throws(() => calculator[received]().isEnabled, { instanceOf: SubstituteException })
2626
})
2727

2828
test('not setting a property correctly asserts the call count', t => {
2929
const calculator = Substitute.for<Calculator>()
3030

31-
calculator.didNotReceive().isEnabled = true
32-
t.throws(() => calculator.received(1).isEnabled = true, { instanceOf: SubstituteException })
33-
t.throws(() => calculator.received().isEnabled = true, { instanceOf: SubstituteException })
31+
calculator[didNotReceive]().isEnabled = true
32+
t.throws(() => calculator[received](1).isEnabled = true, { instanceOf: SubstituteException })
33+
t.throws(() => calculator[received]().isEnabled = true, { instanceOf: SubstituteException })
3434
})
3535

3636
test('not calling a method with mock correctly asserts the call count', t => {
3737
const calculator = Substitute.for<Calculator>()
3838
calculator.add(1, 1).returns(2)
3939

40-
calculator.didNotReceive().add(1, 1)
41-
t.throws(() => calculator.received(1).add(1, 1), { instanceOf: SubstituteException })
42-
t.throws(() => calculator.received().add(Arg.all()), { instanceOf: SubstituteException })
40+
calculator[didNotReceive]().add(1, 1)
41+
t.throws(() => calculator[received](1).add(1, 1), { instanceOf: SubstituteException })
42+
t.throws(() => calculator[received]().add(Arg.all()), { instanceOf: SubstituteException })
4343
})
4444

4545
test('not getting a property with mock correctly asserts the call count', t => {
4646
const calculator = Substitute.for<Calculator>()
4747
calculator.isEnabled.returns(true)
4848

49-
calculator.didNotReceive().isEnabled
50-
t.throws(() => calculator.received(1).isEnabled, { instanceOf: SubstituteException })
51-
t.throws(() => calculator.received().isEnabled, { instanceOf: SubstituteException })
49+
calculator[didNotReceive]().isEnabled
50+
t.throws(() => calculator[received](1).isEnabled, { instanceOf: SubstituteException })
51+
t.throws(() => calculator[received]().isEnabled, { instanceOf: SubstituteException })
5252
})

spec/regression/returns.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ test('returns a primitive value for method with specific arguments', t => {
3232

3333
calculator.add(1, 1).returns(2)
3434

35-
calculator.clearSubstitute
36-
3735
t.is(2, calculator.add(1, 1))
3836
t.is(2, calculator.add(1, 1))
3937
t.true(types.isProxy(noResult))

src/Substitute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DisabledSubstituteObject, ObjectSubstitute } from './Transformations'
22
import { SubstituteNode } from './SubstituteNode'
33

44
export type SubstituteOf<T> = ObjectSubstitute<T> & T
5+
56
type InstantiableSubstitute<T extends SubstituteOf<unknown>> = T & { [SubstituteNode.instance]: SubstituteNode }
67

78
export class Substitute {

src/SubstituteNode.ts

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,11 @@ import { SubstituteNodeBase } from './SubstituteNodeBase'
44
import { RecordedArguments } from './RecordedArguments'
55
import { ClearType as ClearTypeMap, PropertyType as PropertyTypeMap, isAssertionMethod, isSubstituteMethod, isSubstitutionMethod, textModifier, isConfigurationMethod } from './Utilities'
66
import { SubstituteException } from './SubstituteException'
7-
import type { FilterFunction, SubstituteContext, SubstitutionMethod, ClearType, PropertyType } from './Types'
7+
import type { FilterFunction, SubstituteContext, SubstitutionMethod, PropertyType } from './Types'
88
import type { ObjectSubstitute } from './Transformations'
9-
import { didNotReceive, mimick, mimicks, received, rejects, resolves, returns, throws } from './Symbols'
9+
import { didNotReceive, mimick, mimicks, received, rejects, resolves, returns, throws, clearReceivedCalls } from './Transformations'
1010

1111
const instance = Symbol('Substitute:Instance')
12-
const clearTypeToFilterMap: Record<ClearType, FilterFunction<SubstituteNode>> = {
13-
all: () => true,
14-
receivedCalls: node => !node.hasContext,
15-
substituteValues: node => node.isSubstitution
16-
}
1712

1813
type SpecialProperty = typeof instance | typeof inspect.custom | 'then'
1914
type RootContext = { substituteMethodsEnabled: boolean }
@@ -31,35 +26,54 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
3126

3227
private constructor(key: PropertyKey, parent?: SubstituteNode) {
3328
super(key, parent)
34-
if (this.isRoot()) this._rootContext = { substituteMethodsEnabled: true }
35-
else this._rootContext = this.root.rootContext
29+
if (this.isRoot()) {
30+
this._rootContext = { substituteMethodsEnabled: true }
31+
}
32+
else {
33+
this._rootContext = this.root.rootContext
34+
}
35+
3636
this._proxy = new Proxy(
3737
this,
3838
{
3939
get: function (target, property) {
40-
if (target.isSpecialProperty(property)) return target.evaluateSpecialProperty(property)
41-
if (target._retrySubstitutionExecutionAttempt) return target.reattemptSubstitutionExecution()[property]
40+
if (target.isSpecialProperty(property))
41+
return target.evaluateSpecialProperty(property)
42+
43+
if (target._retrySubstitutionExecutionAttempt)
44+
return target.reattemptSubstitutionExecution()[property]
45+
4246
const newNode = SubstituteNode.createChild(property, target)
43-
if (target.isAssertion) newNode.executeAssertion()
47+
if (target.isAssertion)
48+
newNode.executeAssertion()
49+
4450
if (target.isRoot() && target.rootContext.substituteMethodsEnabled && (isAssertionMethod(property) || isConfigurationMethod(property))) {
4551
newNode.assignContext(property)
4652
return newNode[property].bind(newNode)
4753
}
54+
4855
return newNode.attemptSubstitutionExecution()
4956
},
5057
set: function (target, property, value) {
5158
const newNode = SubstituteNode.createChild(property, target)
5259
newNode.handleSetter(value)
53-
if (target.isAssertion) newNode.executeAssertion()
60+
if (target.isAssertion)
61+
newNode.executeAssertion()
62+
5463
return true
5564
},
5665
apply: function (target, _thisArg, rawArguments) {
5766
target.handleMethod(rawArguments)
5867
if (target.hasDepthOfAtLeast(2)) {
59-
if (isSubstitutionMethod(target.property)) return target.parent.assignContext(target.property)
60-
if (target.parent.isAssertion) return target.executeAssertion()
68+
if (isSubstitutionMethod(target.property))
69+
return target.parent.assignContext(target.property)
70+
71+
if (target.parent.isAssertion)
72+
return target.executeAssertion()
6173
}
62-
return target.isAssertion ? target.proxy : target.attemptSubstitutionExecution()
74+
return target.isAssertion ?
75+
target.proxy :
76+
target.attemptSubstitutionExecution()
6377
}
6478
}
6579
)
@@ -129,9 +143,10 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
129143
throw new Error('Mimick is not implemented yet')
130144
}
131145

132-
public clearSubstitute(clearType: ClearType = ClearTypeMap.All): void {
133-
this.handleMethod([clearType])
134-
const filter = clearTypeToFilterMap[clearType]
146+
public [clearReceivedCalls](): void {
147+
this.handleMethod([])
148+
149+
const filter = (node: SubstituteNode) => !node.hasContext
135150
this.recorder.clearRecords(filter)
136151
}
137152

@@ -140,7 +155,9 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
140155
}
141156

142157
private assignContext(context: SubstituteContext): void {
143-
if (!isSubstituteMethod(context)) throw new Error(`Cannot assign context for property ${context.toString()}`)
158+
if (!isSubstituteMethod(context))
159+
throw new Error(`Cannot assign context for property ${context.toString()}`)
160+
144161
this._context = context
145162
}
146163

@@ -158,8 +175,11 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
158175
}
159176

160177
private executeSubstitution(contextArguments: RecordedArguments) {
161-
if (!this.hasChild()) throw new TypeError('Substitue node has no child')
162-
if (!this.child.recordedArguments.hasArguments()) throw new TypeError('Child args')
178+
if (!this.hasChild())
179+
throw new TypeError('Substitue node has no child')
180+
181+
if (!this.child.recordedArguments.hasArguments())
182+
throw new TypeError('Child args')
163183

164184
const substitutionMethod = this.context as SubstitutionMethod
165185
const substitutionValue = this.child.recordedArguments.value.length > 1
@@ -168,10 +188,16 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
168188
switch (substitutionMethod) {
169189
case throws:
170190
throw substitutionValue
191+
171192
case mimicks:
172-
if (this.propertyType === PropertyTypeMap.Property) return substitutionValue()
173-
if (!contextArguments.hasArguments()) throw new TypeError('Context arguments cannot be undefined')
193+
if (this.propertyType === PropertyTypeMap.Property)
194+
return substitutionValue()
195+
196+
if (!contextArguments.hasArguments())
197+
throw new TypeError('Context arguments cannot be undefined')
198+
174199
return substitutionValue(...contextArguments.value)
200+
175201
case resolves:
176202
return Promise.resolve(substitutionValue)
177203
case rejects:
@@ -184,8 +210,12 @@ export class SubstituteNode extends SubstituteNodeBase implements ObjectSubstitu
184210
}
185211

186212
private executeAssertion(): void | never {
187-
if (!this.hasDepthOfAtLeast(2)) throw new Error('Not possible')
188-
if (!this.parent.recordedArguments.hasArguments()) throw new TypeError('Parent args')
213+
if (!this.hasDepthOfAtLeast(2))
214+
throw new Error('Not possible')
215+
216+
if (!this.parent.recordedArguments.hasArguments())
217+
throw new TypeError('Parent args')
218+
189219
const expectedCount = this.parent.recordedArguments.value[0] ?? undefined
190220
const finiteExpectation = expectedCount !== undefined
191221
if (finiteExpectation && (!Number.isInteger(expectedCount) || expectedCount < 0)) throw new Error('Expected count has to be a positive integer')

src/Symbols.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/Transformations.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { AllArguments } from './Arguments';
2-
import { clearReceivedCalls, didNotReceive, mimick, received } from './Symbols';
3-
import type { ClearType, FirstLevelMethod } from './Types';
2+
import type { FirstLevelMethod } from './Types';
43

54
type FunctionSubstituteWithOverloads<TFunc, Terminating = false> =
65
TFunc extends {
@@ -91,11 +90,22 @@ type ObjectSubstituteTransformation<K, T = OmitProxyMethods<K>> = {
9190
[P in keyof T]: TryToExpandNonArgumentedFunctionSubstitute<T, P> & TryToExpandArgumentedFunctionSubstitute<T, P> & TryToExpandPropertySubstitute<T, P>;
9291
}
9392

93+
export const received = Symbol('received');
94+
export const didNotReceive = Symbol('didNotReceive');
95+
export const mimick = Symbol('mimick');
96+
export const clearReceivedCalls = Symbol('clearReceivedCalls');
97+
98+
export const mimicks = Symbol('mimicks');
99+
export const throws = Symbol('throws');
100+
export const returns = Symbol('returns');
101+
export const resolves = Symbol('resolves');
102+
export const rejects = Symbol('rejects');
103+
94104
export type OmitProxyMethods<T> = Omit<T, FirstLevelMethod>;
95105
export type ObjectSubstitute<T> = ObjectSubstituteTransformation<T> & {
96106
[received](amount?: number): TerminatingObject<T>;
97107
[didNotReceive](): TerminatingObject<T>;
98108
[mimick](instance: OmitProxyMethods<T>): void;
99-
[clearReceivedCalls](clearType?: ClearType): void;
109+
[clearReceivedCalls](): void;
100110
}
101-
export type DisabledSubstituteObject<T> = T extends ObjectSubstitute<infer K> ? K : never;
111+
export type DisabledSubstituteObject<T> = T extends ObjectSubstitute<infer K> ? K : never;

src/Types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { clearReceivedCalls, didNotReceive, mimick, mimicks, received, rejects, resolves, returns, throws } from "./Symbols"
1+
import type { clearReceivedCalls, didNotReceive, mimick, mimicks, received, rejects, resolves, returns, throws } from "./Transformations"
22

33
export type PropertyType = 'method' | 'property'
44
export type AssertionMethod = typeof received | typeof didNotReceive

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import { Substitute, SubstituteOf } from './Substitute'
33
export { Arg } from './Arguments'
44
export { Substitute, SubstituteOf }
55
export { ClearType } from './Utilities'
6-
export { clearReceivedCalls, didNotReceive, mimick, received } from './Symbols'
6+
export { clearReceivedCalls, didNotReceive, mimick, received } from './Transformations'
77

88
export default Substitute

0 commit comments

Comments
 (0)