Skip to content

Commit cc5f11b

Browse files
add recorded arguments class
1 parent de52c77 commit cc5f11b

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

src/RecordedArguments.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { Argument, AllArguments } from './Arguments'
2+
3+
type ArgumentsClass = 'primitives-only' | 'with-single-argument' | 'all-arguments'
4+
export class RecordedArguments {
5+
private _argumentsClass: ArgumentsClass
6+
private _value?: any[]
7+
private readonly _hasNoArguments: boolean = false
8+
9+
private constructor(rawArguments?: any[]) {
10+
if (typeof rawArguments === 'undefined') {
11+
this._hasNoArguments = true
12+
return this
13+
}
14+
15+
this._argumentsClass = this.classifyArguments(rawArguments)
16+
this._value = rawArguments
17+
}
18+
19+
static from(rawArguments: any[]): RecordedArguments {
20+
return new this(rawArguments)
21+
}
22+
23+
static noArguments(): RecordedArguments {
24+
return new this()
25+
}
26+
27+
static sort<T extends { arguments: RecordedArguments }>(objectWithArguments: T[]): T[] {
28+
return objectWithArguments.sort((a, b) => {
29+
const aClass = a.arguments.argumentsClass
30+
const bClass = b.arguments.argumentsClass
31+
32+
if (aClass === bClass) return 0
33+
if (aClass === 'primitives-only') return -1
34+
if (bClass === 'primitives-only') return 1
35+
36+
if (aClass === 'with-single-argument') return -1
37+
if (bClass === 'with-single-argument') return 1
38+
39+
if (aClass === 'all-arguments') return -1
40+
return 1
41+
})
42+
}
43+
44+
get argumentsClass(): ArgumentsClass {
45+
return this._argumentsClass
46+
}
47+
48+
get value(): any[] | undefined {
49+
return this._value
50+
}
51+
52+
get hasNoArguments(): boolean {
53+
return this._hasNoArguments
54+
}
55+
56+
public match(other: RecordedArguments) {
57+
if (this.hasNoArguments && other.hasNoArguments) return true
58+
if (this.argumentsClass === 'all-arguments' || other.argumentsClass === 'all-arguments') return true
59+
if (this.value.length !== this.value.length) return false
60+
61+
return this.value.every((argument, index) => this.areArgumentsEqual(argument, other.value[index]))
62+
}
63+
64+
private classifyArguments(rawArguments: any[]): ArgumentsClass {
65+
const allPrimitives = rawArguments.every(arg => !(arg instanceof Argument || arg instanceof AllArguments))
66+
if (allPrimitives) return 'primitives-only'
67+
68+
const hasSingleArgument = rawArguments.some(arg => arg instanceof Argument)
69+
if (hasSingleArgument) return 'with-single-argument'
70+
71+
return 'all-arguments'
72+
}
73+
74+
private areArgumentsEqual(a: any, b: any): boolean {
75+
if (a instanceof Argument && b instanceof Argument) return false
76+
if (a instanceof Argument) return a.matches(b)
77+
if (b instanceof Argument) return b.matches(a)
78+
return this.areArgumentsDeepEqual(a, b)
79+
}
80+
81+
private areArgumentsDeepEqual(a: any, b: any, previousSeenObjects?: WeakSet<object>): boolean {
82+
const seenObjects = previousSeenObjects ?? new WeakSet()
83+
84+
const seenA = seenObjects.has(a)
85+
const seenB = seenObjects.has(b)
86+
const aIsNonNullObject = typeof a === 'object' && a !== null
87+
const bIsNonNullObject = typeof b === 'object' && b !== null
88+
89+
if (!seenA && aIsNonNullObject) seenObjects.add(a)
90+
if (!seenB && bIsNonNullObject) seenObjects.add(b)
91+
92+
const aEqualsB = a === b
93+
if ((seenA && seenB) || aEqualsB) return aEqualsB
94+
95+
if (aIsNonNullObject && bIsNonNullObject) {
96+
if (a.constructor !== b.constructor) return false
97+
const objectAKeys = Object.keys(a)
98+
if (objectAKeys.length !== Object.keys(b).length) return false
99+
return objectAKeys.every(key => this.areArgumentsDeepEqual(a[key], b[key], seenObjects))
100+
}
101+
102+
return aEqualsB
103+
}
104+
}

0 commit comments

Comments
 (0)