1
1
import type { DefaultRecord } from '../typing' ;
2
+ import { deepEquals } from '../utils' ;
2
3
4
+ export class AssertionError extends Error {
5
+ constructor ( message : string ) {
6
+ super ( message ) ;
7
+ }
8
+ }
3
9
export const isSubset = ( superObj : unknown , subObj : unknown ) : boolean => {
4
10
const sup = superObj as DefaultRecord ;
5
11
const sub = subObj as DefaultRecord ;
@@ -12,9 +18,215 @@ export const isSubset = (superObj: unknown, subObj: unknown): boolean => {
12
18
} ) ;
13
19
} ;
14
20
15
- export const assertMatches = ( actual : unknown , expected : unknown ) => {
21
+ export const assertMatches = (
22
+ actual : unknown ,
23
+ expected : unknown ,
24
+ message ?: string ,
25
+ ) => {
16
26
if ( ! isSubset ( actual , expected ) )
17
27
throw Error (
18
- `subObj:\n${ JSON . stringify ( expected ) } \nis not subset of\n${ JSON . stringify ( actual ) } ` ,
28
+ message ??
29
+ `subObj:\n${ JSON . stringify ( expected ) } \nis not subset of\n${ JSON . stringify ( actual ) } ` ,
19
30
) ;
20
31
} ;
32
+
33
+ export const assertDeepEquals = (
34
+ actual : unknown ,
35
+ expected : unknown ,
36
+ message ?: string ,
37
+ ) => {
38
+ if ( ! deepEquals ( actual , expected ) )
39
+ throw Error (
40
+ message ??
41
+ `subObj:\n${ JSON . stringify ( expected ) } \nis equals to\n${ JSON . stringify ( actual ) } ` ,
42
+ ) ;
43
+ } ;
44
+
45
+ export const assertThat = < T > ( item : T ) => {
46
+ return {
47
+ isEqualTo : ( other : T ) => assertTrue ( deepEquals ( item , other ) ) ,
48
+ } ;
49
+ } ;
50
+
51
+ export function assertFalse (
52
+ condition : boolean ,
53
+ message ?: string ,
54
+ ) : asserts condition is false {
55
+ if ( condition ) throw Error ( message ?? `Condition is false` ) ;
56
+ }
57
+
58
+ export function assertTrue (
59
+ condition : boolean ,
60
+ message ?: string ,
61
+ ) : asserts condition is true {
62
+ if ( ! condition ) throw Error ( message ?? `Condition is false` ) ;
63
+ }
64
+
65
+ export function assertOk < T extends object > (
66
+ obj : T | null | undefined ,
67
+ message ?: string ,
68
+ ) : asserts obj is T {
69
+ if ( ! obj ) throw Error ( message ?? `Condition is not truthy` ) ;
70
+ }
71
+
72
+ export function assertEqual < T > (
73
+ obj : T | null | undefined ,
74
+ other : T | null | undefined ,
75
+ message ?: string ,
76
+ ) : void {
77
+ if ( ! obj || ! other || obj != other )
78
+ throw Error ( message ?? `Objects are not equal` ) ;
79
+ }
80
+
81
+ export function assertNotEqual < T > (
82
+ obj : T | null | undefined ,
83
+ other : T | null | undefined ,
84
+ message ?: string ,
85
+ ) : void {
86
+ if ( obj === other ) throw Error ( message ?? `Objects are equal` ) ;
87
+ }
88
+
89
+ export function assertIsNotNull < T extends object > (
90
+ result : T | null ,
91
+ ) : asserts result is T {
92
+ assertNotEqual ( result , null ) ;
93
+ assertOk ( result ) ;
94
+ }
95
+
96
+ export function assertIsNull < T extends object > (
97
+ result : T | null ,
98
+ ) : asserts result is null {
99
+ assertEqual ( result , null ) ;
100
+ }
101
+
102
+ type Call = {
103
+ arguments : unknown [ ] ;
104
+ result : unknown ;
105
+ target : unknown ;
106
+ this : unknown ;
107
+ } ;
108
+
109
+ export type ArgumentMatcher = ( arg : unknown ) => boolean ;
110
+
111
+ export const argValue =
112
+ < T > ( value : T ) : ArgumentMatcher =>
113
+ ( arg ) =>
114
+ deepEquals ( arg , value ) ;
115
+
116
+ export const argMatches =
117
+ < T > ( matches : ( arg : T ) => boolean ) : ArgumentMatcher =>
118
+ ( arg ) =>
119
+ matches ( arg as T ) ;
120
+
121
+ // eslint-disable-next-line @typescript-eslint/ban-types
122
+ export type MockedFunction = Function & { mock ?: { calls : Call [ ] } } ;
123
+
124
+ export function verifyThat ( fn : MockedFunction ) {
125
+ return {
126
+ calledTimes : ( times : number ) => {
127
+ assertEqual ( fn . mock ?. calls ?. length , times ) ;
128
+ } ,
129
+ notCalled : ( ) => {
130
+ assertEqual ( fn ?. mock ?. calls ?. length , 0 ) ;
131
+ } ,
132
+ called : ( ) => {
133
+ assertTrue (
134
+ fn . mock ?. calls . length !== undefined && fn . mock . calls . length > 0 ,
135
+ ) ;
136
+ } ,
137
+ calledWith : ( ...args : unknown [ ] ) => {
138
+ assertTrue (
139
+ fn . mock ?. calls . length !== undefined &&
140
+ fn . mock . calls . length >= 1 &&
141
+ fn . mock . calls . some ( ( call ) => deepEquals ( call . arguments , args ) ) ,
142
+ ) ;
143
+ } ,
144
+ calledOnceWith : ( ...args : unknown [ ] ) => {
145
+ assertTrue (
146
+ fn . mock ?. calls . length !== undefined &&
147
+ fn . mock . calls . length === 1 &&
148
+ fn . mock . calls . some ( ( call ) => deepEquals ( call . arguments , args ) ) ,
149
+ ) ;
150
+ } ,
151
+ calledWithArgumentMatching : ( ...matches : ArgumentMatcher [ ] ) => {
152
+ assertTrue (
153
+ fn . mock ?. calls . length !== undefined && fn . mock . calls . length >= 1 ,
154
+ ) ;
155
+ assertTrue (
156
+ fn . mock ?. calls . length !== undefined &&
157
+ fn . mock . calls . length >= 1 &&
158
+ fn . mock . calls . some (
159
+ ( call ) =>
160
+ call . arguments &&
161
+ call . arguments . length >= matches . length &&
162
+ matches . every ( ( match , index ) => match ( call . arguments [ index ] ) ) ,
163
+ ) ,
164
+ ) ;
165
+ } ,
166
+ notCalledWithArgumentMatching : ( ...matches : ArgumentMatcher [ ] ) => {
167
+ assertFalse (
168
+ fn . mock ?. calls . length !== undefined &&
169
+ fn . mock . calls . length >= 1 &&
170
+ fn . mock . calls [ 0 ] ! . arguments &&
171
+ fn . mock . calls [ 0 ] ! . arguments . length >= matches . length &&
172
+ matches . every ( ( match , index ) =>
173
+ match ( fn . mock ! . calls [ 0 ] ! . arguments [ index ] ) ,
174
+ ) ,
175
+ ) ;
176
+ } ,
177
+ } ;
178
+ }
179
+
180
+ export const assertThatArray = < T > ( array : T [ ] ) => {
181
+ return {
182
+ isEmpty : ( ) => assertEqual ( array . length , 0 ) ,
183
+ hasSize : ( length : number ) => assertEqual ( array . length , length ) ,
184
+ containsElements : ( ...other : T [ ] ) => {
185
+ assertTrue ( other . every ( ( ts ) => other . some ( ( o ) => deepEquals ( ts , o ) ) ) ) ;
186
+ } ,
187
+ containsExactlyInAnyOrder : ( ...other : T [ ] ) => {
188
+ assertEqual ( array . length , other . length ) ;
189
+ assertTrue ( array . every ( ( ts ) => other . some ( ( o ) => deepEquals ( ts , o ) ) ) ) ;
190
+ } ,
191
+ containsExactlyInAnyOrderElementsOf : ( other : T [ ] ) => {
192
+ assertEqual ( array . length , other . length ) ;
193
+ assertTrue ( array . every ( ( ts ) => other . some ( ( o ) => deepEquals ( ts , o ) ) ) ) ;
194
+ } ,
195
+ containsExactlyElementsOf : ( other : T [ ] ) => {
196
+ assertEqual ( array . length , other . length ) ;
197
+ for ( let i = 0 ; i < array . length ; i ++ ) {
198
+ assertTrue ( deepEquals ( array [ i ] , other [ i ] ) ) ;
199
+ }
200
+ } ,
201
+ containsExactly : ( elem : T ) => {
202
+ assertEqual ( array . length , 1 ) ;
203
+ assertTrue ( deepEquals ( array [ 0 ] , elem ) ) ;
204
+ } ,
205
+ contains : ( elem : T ) => {
206
+ assertTrue ( array . some ( ( a ) => deepEquals ( a , elem ) ) ) ;
207
+ } ,
208
+ containsOnlyOnceElementsOf : ( other : T [ ] ) => {
209
+ assertTrue (
210
+ other
211
+ . map ( ( o ) => array . filter ( ( a ) => deepEquals ( a , o ) ) . length )
212
+ . filter ( ( a ) => a === 1 ) . length === other . length ,
213
+ ) ;
214
+ } ,
215
+ containsAnyOf : ( ...other : T [ ] ) => {
216
+ assertTrue ( array . some ( ( a ) => other . some ( ( o ) => deepEquals ( a , o ) ) ) ) ;
217
+ } ,
218
+ allMatch : ( matches : ( item : T ) => boolean ) => {
219
+ assertTrue ( array . every ( matches ) ) ;
220
+ } ,
221
+ anyMatches : ( matches : ( item : T ) => boolean ) => {
222
+ assertTrue ( array . some ( matches ) ) ;
223
+ } ,
224
+ allMatchAsync : async (
225
+ matches : ( item : T ) => Promise < boolean > ,
226
+ ) : Promise < void > => {
227
+ for ( const item of array ) {
228
+ assertTrue ( await matches ( item ) ) ;
229
+ }
230
+ } ,
231
+ } ;
232
+ } ;
0 commit comments