Skip to content

Commit

Permalink
refactor utilities
Browse files Browse the repository at this point in the history
  • Loading branch information
notanengineercom committed Mar 19, 2024
1 parent 1edfd9c commit 07165bd
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 168 deletions.
168 changes: 0 additions & 168 deletions src/Utilities.ts

This file was deleted.

17 changes: 17 additions & 0 deletions src/utilities/Constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ClearType, PropertyType } from '../Types'

type ValueToMap<T extends string> = { [key in T]: key }
const propertyTypes: ValueToMap<PropertyType> = {
method: 'method',
property: 'property'
}
const clearTypes: ValueToMap<ClearType> = {
all: 'all',
receivedCalls: 'receivedCalls',
substituteValues: 'substituteValues'
}

export const constants = {
PROPERTY: propertyTypes,
CLEAR: clearTypes
}
35 changes: 35 additions & 0 deletions src/utilities/Guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { AssertionMethod, ClearType, ConfigurationMethod, PropertyType, SubstituteMethod, SubstitutionMethod } from '../Types'
import { constants } from './Constants'

const isAssertionMethod = (property: PropertyKey): property is AssertionMethod =>
property === 'received' || property === 'didNotReceive'
const isConfigurationMethod = (property: PropertyKey): property is ConfigurationMethod => property === 'clearSubstitute' || property === 'mimick'
const isSubstitutionMethod = (property: PropertyKey): property is SubstitutionMethod =>
property === 'mimicks' || property === 'returns' || property === 'throws' || property === 'resolves' || property === 'rejects'
const isSubstituteMethod = (property: PropertyKey): property is SubstituteMethod =>
isSubstitutionMethod(property) || isConfigurationMethod(property) || isAssertionMethod(property)

const isPropertyProperty = (value: PropertyType): value is (typeof constants['PROPERTY']['property']) => value === constants.PROPERTY.property
const isPropertyMethod = (value: PropertyType): value is (typeof constants['PROPERTY']['method']) => value === constants.PROPERTY.method

const isClearAll = (value: ClearType): value is (typeof constants['CLEAR']['all']) => value === constants.CLEAR.all
const isClearReceivedCalls = (value: ClearType): value is (typeof constants['CLEAR']['receivedCalls']) => value === constants.CLEAR.receivedCalls
const isClearSubstituteValues = (value: ClearType): value is (typeof constants['CLEAR']['substituteValues']) => value === constants.CLEAR.substituteValues

export const method = {
assertion: isAssertionMethod,
configuration: isConfigurationMethod,
substitution: isSubstitutionMethod,
substitute: isSubstituteMethod,
}

export const PROPERTY = {
property: isPropertyProperty,
method: isPropertyMethod
}

export const CLEAR = {
all: isClearAll,
receivedCalls: isClearReceivedCalls,
substituteValues: isClearSubstituteValues
}
68 changes: 68 additions & 0 deletions src/utilities/Stringify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { inspect } from 'node:util'
import { RecordedArguments } from '../RecordedArguments'
import { SubstituteNodeModel } from '../Types'
import { TextBuilder, TextPart } from './TextBuilder'

const stringifyArguments = (args: RecordedArguments) => args.hasArguments()
? `(${args.value.map(x => inspect(x, { colors: true })).join(', ')})`
: ''

const matchBasedPrefix = (isMatch?: boolean) => {
switch (isMatch) {
case true: return '✔ '
case false: return '✘ '
default: return ''
}
}

const matchBasedTextPartModifier = (isMatch?: boolean) => (part: TextPart) => isMatch === undefined
? part
: isMatch
? part.faint()
: part.bold()

const stringifyCall = (context: { callPath: string, expectedArguments?: RecordedArguments }) => {
return (call: SubstituteNodeModel): TextPart[] => {
const isMatch = context.expectedArguments?.match(call.recordedArguments)
const textBuilder = new TextBuilder()
.add(matchBasedPrefix(isMatch))
.add(context.callPath)
.add(stringifyArguments(call.recordedArguments))
if (call.stack !== undefined && isMatch !== undefined) textBuilder.newLine().add(call.stack.split('\n')[1].replace('at ', 'called at '), t => t.faint())
return textBuilder.parts.map(matchBasedTextPartModifier(isMatch))
}
}

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

const stringifyExpectation = (expected: { count: number | undefined, call: SubstituteNodeModel }) => {
const textBuilder = new TextBuilder()
textBuilder.add(expected.count === undefined ? '1 or more' : expected.count.toString(), t => t.bold())
.add(' ')
.add(expected.call.propertyType, t => t.bold())
.add(plurify(' call', expected.count), t => t.bold())
.add(' matching ')
.addParts(...stringifyCall({ callPath: expected.call.key.toString() })(expected.call).map(t => t.bold()))
return textBuilder.parts
}

const createKey = () => {
const textBuilder = new TextBuilder()
textBuilder.newLine().add('› ')
return textBuilder
}

const stringifyReceivedCalls = (callPath: string, expected: SubstituteNodeModel, received: SubstituteNodeModel[]) => {
const textBuilder = new TextBuilder()
const stringify = stringifyCall({ callPath, expectedArguments: expected.recordedArguments })
received.forEach(receivedCall => textBuilder.addParts(...createKey().parts, ...stringify(receivedCall)))
return textBuilder.newLine().toString()
}

// const stringifyNode

export const stringify = {
call: stringifyCall,
expectation: stringifyExpectation,
receivedCalls: stringifyReceivedCalls
}
79 changes: 79 additions & 0 deletions src/utilities/TextBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export class TextPart {
private _modifiers: string[] = []
private readonly _value: string
constructor(valueOrInstance: string | TextPart) {
if (valueOrInstance instanceof TextPart) {
this._modifiers = [...valueOrInstance._modifiers]
this._value = valueOrInstance._value
} else this._value = valueOrInstance
}

private baseTextModifier(modifier: number) {
return `\x1b[${modifier}m`
}

public bold() {
this._modifiers.push(this.baseTextModifier(1))
return this
}

public faint() {
this._modifiers.push(this.baseTextModifier(2))
return this
}

public italic() {
this._modifiers.push(this.baseTextModifier(3))
return this
}

public underline() {
this._modifiers.push(this.baseTextModifier(4))
return this
}

public resetFormat(): void {
this._modifiers = []
}

public clone(): TextPart {
return new TextPart(this)
}

toString() {
return this._modifiers.length > 0 ? `${this._modifiers.join('')}${this._value}\x1b[0m` : this._value
}
}

export class TextBuilder {
private readonly _parts: TextPart[] = []

public add(text: string, texPartCb: (textPart: TextPart) => void = () => { }): this {
const textPart = new TextPart(text)
this._parts.push(textPart)
texPartCb(textPart)
return this
}

public addParts(...textParts: TextPart[]): this {
this._parts.push(...textParts)
return this
}

public newLine() {
this._parts.push(new TextPart('\n'))
return this
}

public toString() {
return this._parts.join('')
}

public clone() {
return new TextBuilder().addParts(...this._parts.map(x => x.clone()))
}

public get parts() {
return this._parts
}
}
4 changes: 4 additions & 0 deletions src/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './TextBuilder'
export * from './Stringify'
export * from './Constants'
export * as is from './Guards'

0 comments on commit 07165bd

Please sign in to comment.