Skip to content

Commit 90b8093

Browse files
make types more verbose and explicit
1 parent c926969 commit 90b8093

File tree

8 files changed

+57
-38
lines changed

8 files changed

+57
-38
lines changed

src/Recorder.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,10 @@ export class Recorder<TRecord> {
4343
return siblings.filter(sibling => sibling !== record)
4444
}
4545

46-
public clearRecords(filterFunction: FilterFunction<TRecord>) {
46+
public clearRecords(filterFunction: FilterFunction<TRecord>): void {
4747
const recordsToRemove = this.records.filter(filterFunction)
4848
for (const record of recordsToRemove) {
49-
const id = this.getIdentity(record)
50-
const indexedRecord = this.indexedRecords.get(id)
51-
indexedRecord.delete(record)
52-
if (indexedRecord.size === 0) this.indexedRecords.delete(id)
49+
this.clearIndexedRecord(record)
5350
this.records.delete(record)
5451
}
5552
}
@@ -65,4 +62,12 @@ export class Recorder<TRecord> {
6562
// for typescript < 4.6, we need to intersect PropertyKey with the default type
6663
return record[this._identity as keyof TRecord] as TRecord[keyof TRecord] & PropertyKey
6764
}
65+
66+
private clearIndexedRecord(record: TRecord): void {
67+
const id = this.getIdentity(record)
68+
const indexedRecord = this.indexedRecords.get(id)
69+
if (typeof indexedRecord === 'undefined') return
70+
indexedRecord.delete(record)
71+
if (indexedRecord.size === 0) this.indexedRecords.delete(id)
72+
}
6873
}

src/RecordsSet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type MapperFunction<T, R> = (item: T) => R
44
type Transformer<T, R> = { type: 'filter', predicate: FilterFunction<T> } | { type: 'mapper', predicate: MapperFunction<T, R> }
55

66
export class RecordsSet<T> extends Set<T> {
7-
private _transformer: Transformer<any, any>
7+
private _transformer?: Transformer<any, any>
88
private readonly _prevIter?: RecordsSet<T>
99

1010
constructor(value?: Iterable<T> | readonly T[]) {

src/Substitute.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import { DisabledSubstituteObject, ObjectSubstitute } from './Transformations'
22
import { SubstituteNode } from './SubstituteNode'
33

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

77
export class Substitute {
8-
static for<T>(): SubstituteOf<T> {
8+
public static for<T>(): SubstituteOf<T> {
99
const substitute = SubstituteNode.createRoot()
1010
return substitute.proxy as unknown as SubstituteOf<T>
1111
}
1212

13-
static disableFor<T extends InstantiableSubstitute>(substituteProxy: T): DisabledSubstituteObject<T> {
14-
const substitute = substituteProxy[SubstituteNode.instance]
13+
public static disableFor<T extends SubstituteOf<unknown>>(substituteProxy: T): DisabledSubstituteObject<T> {
14+
const substitute = this.extractSubstituteNodeFromSubstitute(substituteProxy as InstantiableSubstitute<T>)
1515

1616
const disableProxy = <
1717
TParameters extends unknown[],
@@ -35,4 +35,8 @@ export class Substitute {
3535
}
3636
}) as DisabledSubstituteObject<T>
3737
}
38+
39+
private static extractSubstituteNodeFromSubstitute(substitute: InstantiableSubstitute<SubstituteOf<unknown>>): SubstituteNode {
40+
return substitute[SubstituteNode.instance]
41+
}
3842
}

src/SubstituteException.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ enum SubstituteExceptionTypes {
88
}
99

1010
export class SubstituteException extends Error {
11-
type: SubstituteExceptionTypes
11+
type?: SubstituteExceptionTypes
1212

1313
constructor(msg: string, exceptionType?: SubstituteExceptionTypes) {
1414
super(msg)
@@ -18,7 +18,7 @@ export class SubstituteException extends Error {
1818
}
1919

2020
static forCallCountMissMatch(
21-
count: { expected: number | null, received: number },
21+
count: { expected: number | undefined, received: number },
2222
property: { type: PropertyType, value: PropertyKey },
2323
calls: { expected: RecordedArguments, received: RecordedArguments[] }
2424
) {

src/SubstituteNode.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import { SubstituteException } from './SubstituteException'
77
import type { FilterFunction, SubstituteContext, SubstitutionMethod, ClearType, PropertyType } from './Types'
88

99
const instance = Symbol('Substitute:Instance')
10-
type SpecialProperty = typeof instance | typeof inspect.custom | 'then'
11-
type RootContext = { substituteMethodsEnabled: boolean }
12-
1310
const clearTypeToFilterMap: Record<ClearType, FilterFunction<SubstituteNode>> = {
1411
all: () => true,
1512
receivedCalls: node => !node.hasContext,
1613
substituteValues: node => node.isSubstitution
1714
}
1815

16+
type SpecialProperty = typeof instance | typeof inspect.custom | 'then'
17+
type RootContext = { substituteMethodsEnabled: boolean }
18+
1919
export class SubstituteNode extends SubstituteNodeBase {
2020
private _proxy: SubstituteNode
2121
private _rootContext: RootContext
@@ -30,7 +30,7 @@ export class SubstituteNode extends SubstituteNodeBase {
3030
private constructor(key: PropertyKey, parent?: SubstituteNode) {
3131
super(key, parent)
3232
if (this.isRoot()) this._rootContext = { substituteMethodsEnabled: true }
33-
if (this.isIntermediateNode()) this._rootContext = this.root.rootContext
33+
else this._rootContext = this.root.rootContext
3434
this._proxy = new Proxy(
3535
this,
3636
{
@@ -66,11 +66,11 @@ export class SubstituteNode extends SubstituteNodeBase {
6666
return new this(key, parent)
6767
}
6868

69-
public get proxy() {
69+
public get proxy(): SubstituteNode {
7070
return this._proxy
7171
}
7272

73-
public get rootContext() {
73+
public get rootContext(): RootContext {
7474
return this._rootContext
7575
}
7676

@@ -90,23 +90,23 @@ export class SubstituteNode extends SubstituteNodeBase {
9090
return isAssertionMethod(this.context)
9191
}
9292

93-
get property() {
93+
get property(): PropertyKey {
9494
return this.key
9595
}
9696

97-
get propertyType() {
97+
get propertyType(): PropertyType {
9898
return this._propertyType
9999
}
100100

101101
get accessorType() {
102102
return this._accessorType
103103
}
104104

105-
get recordedArguments() {
105+
get recordedArguments(): RecordedArguments {
106106
return this._recordedArguments
107107
}
108108

109-
public get disabledSubstituteMethods() {
109+
public get disabledSubstituteMethods(): boolean {
110110
return this._disabledSubstituteMethods
111111
}
112112

@@ -134,22 +134,27 @@ export class SubstituteNode extends SubstituteNodeBase {
134134
}
135135

136136
public clear() {
137+
if (!this.recordedArguments.hasArguments()) throw new TypeError('No args')
137138
const clearType: ClearType = this.recordedArguments.value[0] ?? ClearTypeMap.All
138139
const filter = clearTypeToFilterMap[clearType]
139140
this.recorder.clearRecords(filter)
140141
}
141142

142143
public executeSubstitution(contextArguments: RecordedArguments) {
144+
if (!this.hasChild()) throw new TypeError('Substitue node has no child')
145+
if (!this.child.recordedArguments.hasArguments()) throw new TypeError('Child args')
146+
143147
const substitutionMethod = this.context as SubstitutionMethod
144148
const substitutionValue = this.child.recordedArguments.value.length > 1
145-
? this.child.recordedArguments.value.shift()
149+
? this.child.recordedArguments.value?.shift()
146150
: this.child.recordedArguments.value[0]
147151
switch (substitutionMethod) {
148152
case 'throws':
149153
throw substitutionValue
150154
case 'mimicks':
151-
const argumentsToApply = this.propertyType === PropertyTypeMap.Property ? [] : contextArguments.value
152-
return substitutionValue(...argumentsToApply)
155+
if (this.propertyType === PropertyTypeMap.Property) return substitutionValue()
156+
if (!contextArguments.hasArguments()) throw new TypeError('Context arguments cannot be undefined')
157+
return substitutionValue(...contextArguments.value)
153158
case 'resolves':
154159
return Promise.resolve(substitutionValue)
155160
case 'rejects':
@@ -163,6 +168,7 @@ export class SubstituteNode extends SubstituteNodeBase {
163168

164169
public executeAssertion(): void | never {
165170
if (!this.isIntermediateNode()) throw new Error('Not possible')
171+
if (!this.parent.recordedArguments.hasArguments()) throw new TypeError('Parent args')
166172
const siblings = [...this.getAllSiblings().filter(n => !n.hasContext && n.accessorType === this.accessorType)]
167173

168174
const expectedCount = this.parent.recordedArguments.value[0] ?? undefined
@@ -257,7 +263,7 @@ export class SubstituteNode extends SubstituteNodeBase {
257263
const label = this.isSubstitution
258264
? '=> '
259265
: this.isAssertion
260-
? `${this.child.property.toString()}`
266+
? `${this.child?.property.toString()}`
261267
: ''
262268
const s = hasContext
263269
? ` ${label}${inspect(this.child?.recordedArguments, options)}`

src/SubstituteNodeBase.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export abstract class SubstituteNodeBase extends Function {
1919
return
2020
}
2121

22-
parent.child = this
22+
parent.assignChild(this)
2323
this._parent = parent
2424
this._recorder = parent.recorder
2525
this._root = parent.root
@@ -40,14 +40,10 @@ export abstract class SubstituteNodeBase extends Function {
4040
}
4141

4242
protected get parent(): this | undefined {
43-
return this._parent as this
43+
return this._parent
4444
}
4545

46-
protected set child(child: this) {
47-
this._child = child
48-
}
49-
50-
protected get child(): this {
46+
protected get child(): this | undefined {
5147
return this._child
5248
}
5349

@@ -59,6 +55,10 @@ export abstract class SubstituteNodeBase extends Function {
5955
return this._depth
6056
}
6157

58+
private assignChild(child: this): void {
59+
this._child = child
60+
}
61+
6262
protected isRoot(): this is this & { parent: undefined } {
6363
return typeof this._parent === 'undefined'
6464
}
@@ -71,6 +71,10 @@ export abstract class SubstituteNodeBase extends Function {
7171
return this.recorder.getSiblingsOf(this)
7272
}
7373

74+
protected hasChild(): this is this & { child: ThisType<SubstituteNodeBase> } {
75+
return this.child instanceof SubstituteNodeBase
76+
}
77+
7478
public abstract read(): void
7579
public abstract write(value: any): void
7680
}

src/Transformations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export type FunctionSubstitute<TArguments extends any[], TReturnType> =
4545
((allArguments: AllArguments<TArguments>) => (TReturnType & MockObjectMixin<TArguments, TReturnType>))
4646

4747
export type NoArgumentFunctionSubstitute<TReturnType> = (() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>))
48-
export type PropertySubstitute<TReturnType> = (TReturnType & Partial<NoArgumentMockObjectMixin<TReturnType>>);
48+
export type PropertySubstitute<TReturnType> = (TReturnType & NoArgumentMockObjectMixin<TReturnType>);
4949

5050
type OneArgumentRequiredFunction<TArgs, TReturnType> = (requiredInput: TArgs, ...restInputs: TArgs[]) => TReturnType;
5151

src/Utilities.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export const ClearType = {
2525
} as const
2626

2727
export const stringifyArguments = (args: RecordedArguments) => textModifier.faint(
28-
args.hasNoArguments
29-
? 'no arguments'
30-
: `arguments [${args.value.map(x => inspect(x, { colors: true })).join(', ')}]`
28+
args.hasArguments() ?
29+
`arguments [${args.value.map(x => inspect(x, { colors: true })).join(', ')}]` :
30+
'no arguments'
3131
)
3232

3333
export const stringifyCalls = (calls: RecordedArguments[]) => {
@@ -45,4 +45,4 @@ export const textModifier = {
4545
italic: (str: string) => baseTextModifier(str, 3, 23)
4646
}
4747

48-
export const plurify = (str: string, count: number) => `${str}${count === 1 ? '' : 's'}`
48+
export const plurify = (str: string, count?: number) => `${str}${count === 1 ? '' : 's'}`

0 commit comments

Comments
 (0)