Skip to content

Commit

Permalink
make types more verbose and explicit
Browse files Browse the repository at this point in the history
  • Loading branch information
notanengineercom committed Oct 9, 2022
1 parent c926969 commit 90b8093
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 38 deletions.
15 changes: 10 additions & 5 deletions src/Recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,10 @@ export class Recorder<TRecord> {
return siblings.filter(sibling => sibling !== record)
}

public clearRecords(filterFunction: FilterFunction<TRecord>) {
public clearRecords(filterFunction: FilterFunction<TRecord>): void {
const recordsToRemove = this.records.filter(filterFunction)
for (const record of recordsToRemove) {
const id = this.getIdentity(record)
const indexedRecord = this.indexedRecords.get(id)
indexedRecord.delete(record)
if (indexedRecord.size === 0) this.indexedRecords.delete(id)
this.clearIndexedRecord(record)
this.records.delete(record)
}
}
Expand All @@ -65,4 +62,12 @@ export class Recorder<TRecord> {
// for typescript < 4.6, we need to intersect PropertyKey with the default type
return record[this._identity as keyof TRecord] as TRecord[keyof TRecord] & PropertyKey
}

private clearIndexedRecord(record: TRecord): void {
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)
}
}
2 changes: 1 addition & 1 deletion src/RecordsSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type MapperFunction<T, R> = (item: T) => R
type Transformer<T, R> = { type: 'filter', predicate: FilterFunction<T> } | { type: 'mapper', predicate: MapperFunction<T, R> }

export class RecordsSet<T> extends Set<T> {
private _transformer: Transformer<any, any>
private _transformer?: Transformer<any, any>
private readonly _prevIter?: RecordsSet<T>

constructor(value?: Iterable<T> | readonly T[]) {
Expand Down
12 changes: 8 additions & 4 deletions src/Substitute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { DisabledSubstituteObject, ObjectSubstitute } from './Transformations'
import { SubstituteNode } from './SubstituteNode'

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

export class Substitute {
static for<T>(): SubstituteOf<T> {
public static for<T>(): SubstituteOf<T> {
const substitute = SubstituteNode.createRoot()
return substitute.proxy as unknown as SubstituteOf<T>
}

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

const disableProxy = <
TParameters extends unknown[],
Expand All @@ -35,4 +35,8 @@ export class Substitute {
}
}) as DisabledSubstituteObject<T>
}

private static extractSubstituteNodeFromSubstitute(substitute: InstantiableSubstitute<SubstituteOf<unknown>>): SubstituteNode {
return substitute[SubstituteNode.instance]
}
}
4 changes: 2 additions & 2 deletions src/SubstituteException.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ enum SubstituteExceptionTypes {
}

export class SubstituteException extends Error {
type: SubstituteExceptionTypes
type?: SubstituteExceptionTypes

constructor(msg: string, exceptionType?: SubstituteExceptionTypes) {
super(msg)
Expand All @@ -18,7 +18,7 @@ export class SubstituteException extends Error {
}

static forCallCountMissMatch(
count: { expected: number | null, received: number },
count: { expected: number | undefined, received: number },
property: { type: PropertyType, value: PropertyKey },
calls: { expected: RecordedArguments, received: RecordedArguments[] }
) {
Expand Down
34 changes: 20 additions & 14 deletions src/SubstituteNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { SubstituteException } from './SubstituteException'
import type { FilterFunction, SubstituteContext, SubstitutionMethod, ClearType, PropertyType } from './Types'

const instance = Symbol('Substitute:Instance')
type SpecialProperty = typeof instance | typeof inspect.custom | 'then'
type RootContext = { substituteMethodsEnabled: boolean }

const clearTypeToFilterMap: Record<ClearType, FilterFunction<SubstituteNode>> = {
all: () => true,
receivedCalls: node => !node.hasContext,
substituteValues: node => node.isSubstitution
}

type SpecialProperty = typeof instance | typeof inspect.custom | 'then'
type RootContext = { substituteMethodsEnabled: boolean }

export class SubstituteNode extends SubstituteNodeBase {
private _proxy: SubstituteNode
private _rootContext: RootContext
Expand All @@ -30,7 +30,7 @@ export class SubstituteNode extends SubstituteNodeBase {
private constructor(key: PropertyKey, parent?: SubstituteNode) {
super(key, parent)
if (this.isRoot()) this._rootContext = { substituteMethodsEnabled: true }
if (this.isIntermediateNode()) this._rootContext = this.root.rootContext
else this._rootContext = this.root.rootContext
this._proxy = new Proxy(
this,
{
Expand Down Expand Up @@ -66,11 +66,11 @@ export class SubstituteNode extends SubstituteNodeBase {
return new this(key, parent)
}

public get proxy() {
public get proxy(): SubstituteNode {
return this._proxy
}

public get rootContext() {
public get rootContext(): RootContext {
return this._rootContext
}

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

get property() {
get property(): PropertyKey {
return this.key
}

get propertyType() {
get propertyType(): PropertyType {
return this._propertyType
}

get accessorType() {
return this._accessorType
}

get recordedArguments() {
get recordedArguments(): RecordedArguments {
return this._recordedArguments
}

public get disabledSubstituteMethods() {
public get disabledSubstituteMethods(): boolean {
return this._disabledSubstituteMethods
}

Expand Down Expand Up @@ -134,22 +134,27 @@ export class SubstituteNode extends SubstituteNodeBase {
}

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

public executeSubstitution(contextArguments: RecordedArguments) {
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 as SubstitutionMethod
const substitutionValue = this.child.recordedArguments.value.length > 1
? this.child.recordedArguments.value.shift()
? this.child.recordedArguments.value?.shift()
: this.child.recordedArguments.value[0]
switch (substitutionMethod) {
case 'throws':
throw substitutionValue
case 'mimicks':
const argumentsToApply = this.propertyType === PropertyTypeMap.Property ? [] : contextArguments.value
return substitutionValue(...argumentsToApply)
if (this.propertyType === PropertyTypeMap.Property) return substitutionValue()
if (!contextArguments.hasArguments()) throw new TypeError('Context arguments cannot be undefined')
return substitutionValue(...contextArguments.value)
case 'resolves':
return Promise.resolve(substitutionValue)
case 'rejects':
Expand All @@ -163,6 +168,7 @@ export class SubstituteNode extends SubstituteNodeBase {

public executeAssertion(): void | never {
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 = this.parent.recordedArguments.value[0] ?? undefined
Expand Down Expand Up @@ -257,7 +263,7 @@ export class SubstituteNode extends SubstituteNodeBase {
const label = this.isSubstitution
? '=> '
: this.isAssertion
? `${this.child.property.toString()}`
? `${this.child?.property.toString()}`
: ''
const s = hasContext
? ` ${label}${inspect(this.child?.recordedArguments, options)}`
Expand Down
18 changes: 11 additions & 7 deletions src/SubstituteNodeBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export abstract class SubstituteNodeBase extends Function {
return
}

parent.child = this
parent.assignChild(this)
this._parent = parent
this._recorder = parent.recorder
this._root = parent.root
Expand All @@ -40,14 +40,10 @@ export abstract class SubstituteNodeBase extends Function {
}

protected get parent(): this | undefined {
return this._parent as this
return this._parent
}

protected set child(child: this) {
this._child = child
}

protected get child(): this {
protected get child(): this | undefined {
return this._child
}

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

private assignChild(child: this): void {
this._child = child
}

protected isRoot(): this is this & { parent: undefined } {
return typeof this._parent === 'undefined'
}
Expand All @@ -71,6 +71,10 @@ export abstract class SubstituteNodeBase extends Function {
return this.recorder.getSiblingsOf(this)
}

protected hasChild(): this is this & { child: ThisType<SubstituteNodeBase> } {
return this.child instanceof SubstituteNodeBase
}

public abstract read(): void
public abstract write(value: any): void
}
2 changes: 1 addition & 1 deletion src/Transformations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export type FunctionSubstitute<TArguments extends any[], TReturnType> =
((allArguments: AllArguments<TArguments>) => (TReturnType & MockObjectMixin<TArguments, TReturnType>))

export type NoArgumentFunctionSubstitute<TReturnType> = (() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>))
export type PropertySubstitute<TReturnType> = (TReturnType & Partial<NoArgumentMockObjectMixin<TReturnType>>);
export type PropertySubstitute<TReturnType> = (TReturnType & NoArgumentMockObjectMixin<TReturnType>);

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

Expand Down
8 changes: 4 additions & 4 deletions src/Utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export const ClearType = {
} as const

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

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

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

0 comments on commit 90b8093

Please sign in to comment.