@plugjs/expect5 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.mjs +96 -0
- package/dist/cli.mjs.map +6 -0
- package/dist/execution/executable.cjs +299 -0
- package/dist/execution/executable.cjs.map +6 -0
- package/dist/execution/executable.d.ts +87 -0
- package/dist/execution/executable.mjs +260 -0
- package/dist/execution/executable.mjs.map +6 -0
- package/dist/execution/executor.cjs +125 -0
- package/dist/execution/executor.cjs.map +6 -0
- package/dist/execution/executor.d.ts +35 -0
- package/dist/execution/executor.mjs +90 -0
- package/dist/execution/executor.mjs.map +6 -0
- package/dist/execution/setup.cjs +127 -0
- package/dist/execution/setup.cjs.map +6 -0
- package/dist/execution/setup.d.ts +31 -0
- package/dist/execution/setup.mjs +87 -0
- package/dist/execution/setup.mjs.map +6 -0
- package/dist/expectation/basic.cjs +216 -0
- package/dist/expectation/basic.cjs.map +6 -0
- package/dist/expectation/basic.d.ts +47 -0
- package/dist/expectation/basic.mjs +177 -0
- package/dist/expectation/basic.mjs.map +6 -0
- package/dist/expectation/diff.cjs +253 -0
- package/dist/expectation/diff.cjs.map +6 -0
- package/dist/expectation/diff.d.ts +27 -0
- package/dist/expectation/diff.mjs +228 -0
- package/dist/expectation/diff.mjs.map +6 -0
- package/dist/expectation/expect.cjs +211 -0
- package/dist/expectation/expect.cjs.map +6 -0
- package/dist/expectation/expect.d.ts +140 -0
- package/dist/expectation/expect.mjs +219 -0
- package/dist/expectation/expect.mjs.map +6 -0
- package/dist/expectation/include.cjs +187 -0
- package/dist/expectation/include.cjs.map +6 -0
- package/dist/expectation/include.d.ts +10 -0
- package/dist/expectation/include.mjs +158 -0
- package/dist/expectation/include.mjs.map +6 -0
- package/dist/expectation/print.cjs +281 -0
- package/dist/expectation/print.cjs.map +6 -0
- package/dist/expectation/print.d.ts +4 -0
- package/dist/expectation/print.mjs +256 -0
- package/dist/expectation/print.mjs.map +6 -0
- package/dist/expectation/throwing.cjs +58 -0
- package/dist/expectation/throwing.cjs.map +6 -0
- package/dist/expectation/throwing.d.ts +8 -0
- package/dist/expectation/throwing.mjs +32 -0
- package/dist/expectation/throwing.mjs.map +6 -0
- package/dist/expectation/types.cjs +212 -0
- package/dist/expectation/types.cjs.map +6 -0
- package/dist/expectation/types.d.ts +57 -0
- package/dist/expectation/types.mjs +178 -0
- package/dist/expectation/types.mjs.map +6 -0
- package/dist/expectation/void.cjs +111 -0
- package/dist/expectation/void.cjs.map +6 -0
- package/dist/expectation/void.d.ts +39 -0
- package/dist/expectation/void.mjs +77 -0
- package/dist/expectation/void.mjs.map +6 -0
- package/dist/globals.cjs +2 -0
- package/dist/globals.cjs.map +6 -0
- package/dist/globals.d.ts +23 -0
- package/dist/globals.mjs +1 -0
- package/dist/globals.mjs.map +6 -0
- package/dist/index.cjs +66 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.mjs +41 -0
- package/dist/index.mjs.map +6 -0
- package/dist/test.cjs +229 -0
- package/dist/test.cjs.map +6 -0
- package/dist/test.d.ts +9 -0
- package/dist/test.mjs +194 -0
- package/dist/test.mjs.map +6 -0
- package/package.json +57 -0
- package/src/cli.mts +122 -0
- package/src/execution/executable.ts +364 -0
- package/src/execution/executor.ts +146 -0
- package/src/execution/setup.ts +108 -0
- package/src/expectation/basic.ts +209 -0
- package/src/expectation/diff.ts +445 -0
- package/src/expectation/expect.ts +401 -0
- package/src/expectation/include.ts +184 -0
- package/src/expectation/print.ts +386 -0
- package/src/expectation/throwing.ts +45 -0
- package/src/expectation/types.ts +263 -0
- package/src/expectation/void.ts +80 -0
- package/src/globals.ts +30 -0
- package/src/index.ts +54 -0
- package/src/test.ts +239 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { ExpectationError, matcherMarker } from './types'
|
|
2
|
+
import {
|
|
3
|
+
ToBeA,
|
|
4
|
+
ToBeCloseTo,
|
|
5
|
+
ToBeError,
|
|
6
|
+
ToBeGreaterThan,
|
|
7
|
+
ToBeGreaterThanOrEqual,
|
|
8
|
+
ToBeInstanceOf,
|
|
9
|
+
ToBeLessThan,
|
|
10
|
+
ToBeLessThanOrEqual,
|
|
11
|
+
ToBeWithinRange,
|
|
12
|
+
ToEqual,
|
|
13
|
+
ToHaveLength,
|
|
14
|
+
ToHaveProperty,
|
|
15
|
+
ToHaveSize,
|
|
16
|
+
ToMatch,
|
|
17
|
+
ToStrictlyEqual,
|
|
18
|
+
} from './basic'
|
|
19
|
+
import {
|
|
20
|
+
ToInclude, ToMatchContents,
|
|
21
|
+
} from './include'
|
|
22
|
+
import {
|
|
23
|
+
ToThrow,
|
|
24
|
+
ToThrowError,
|
|
25
|
+
} from './throwing'
|
|
26
|
+
import {
|
|
27
|
+
ToBeDefined,
|
|
28
|
+
ToBeFalse,
|
|
29
|
+
ToBeFalsy,
|
|
30
|
+
ToBeNaN,
|
|
31
|
+
ToBeNegativeInfinity,
|
|
32
|
+
ToBeNull,
|
|
33
|
+
ToBePositiveInfinity,
|
|
34
|
+
ToBeTrue,
|
|
35
|
+
ToBeTruthy,
|
|
36
|
+
ToBeUndefined,
|
|
37
|
+
} from './void'
|
|
38
|
+
|
|
39
|
+
import type { Constructor, StringMatcher } from './types'
|
|
40
|
+
|
|
41
|
+
/* ========================================================================== *
|
|
42
|
+
* IMPORT AND PREPARE EXTERNAL EXPECTATIONS *
|
|
43
|
+
* ========================================================================== */
|
|
44
|
+
|
|
45
|
+
/** Singleton with all imported (known) expectations */
|
|
46
|
+
const expectations = {
|
|
47
|
+
// basic expectations
|
|
48
|
+
toBeA: new ToBeA(),
|
|
49
|
+
toBeCloseTo: new ToBeCloseTo(),
|
|
50
|
+
toBeError: new ToBeError(),
|
|
51
|
+
toBeGreaterThan: new ToBeGreaterThan(),
|
|
52
|
+
toBeGreaterThanOrEqual: new ToBeGreaterThanOrEqual(),
|
|
53
|
+
toBeInstanceOf: new ToBeInstanceOf(),
|
|
54
|
+
toBeLessThan: new ToBeLessThan(),
|
|
55
|
+
toBeLessThanOrEqual: new ToBeLessThanOrEqual(),
|
|
56
|
+
toBeWithinRange: new ToBeWithinRange(),
|
|
57
|
+
toEqual: new ToEqual(),
|
|
58
|
+
toHaveLength: new ToHaveLength(),
|
|
59
|
+
toHaveProperty: new ToHaveProperty(),
|
|
60
|
+
toHaveSize: new ToHaveSize(),
|
|
61
|
+
toMatch: new ToMatch(),
|
|
62
|
+
toStrictlyEqual: new ToStrictlyEqual(),
|
|
63
|
+
|
|
64
|
+
// include
|
|
65
|
+
toInclude: new ToInclude(),
|
|
66
|
+
toMatchContents: new ToMatchContents(),
|
|
67
|
+
|
|
68
|
+
// throwing
|
|
69
|
+
toThrow: new ToThrow(),
|
|
70
|
+
toThrowError: new ToThrowError(),
|
|
71
|
+
|
|
72
|
+
// void expectations
|
|
73
|
+
toBeDefined: new ToBeDefined(),
|
|
74
|
+
toBeFalse: new ToBeFalse(),
|
|
75
|
+
toBeFalsy: new ToBeFalsy(),
|
|
76
|
+
toBeNaN: new ToBeNaN(),
|
|
77
|
+
toBeNegativeInfinity: new ToBeNegativeInfinity(),
|
|
78
|
+
toBeNull: new ToBeNull(),
|
|
79
|
+
toBePositiveInfinity: new ToBePositiveInfinity(),
|
|
80
|
+
toBeTrue: new ToBeTrue(),
|
|
81
|
+
toBeTruthy: new ToBeTruthy(),
|
|
82
|
+
toBeUndefined: new ToBeUndefined(),
|
|
83
|
+
} as const
|
|
84
|
+
|
|
85
|
+
/** The type of our imported expectations */
|
|
86
|
+
type ExpectationsByName = typeof expectations
|
|
87
|
+
|
|
88
|
+
/** Infer expectations parameter from {@link Expectation} type */
|
|
89
|
+
type ExpectationParameters<E> =
|
|
90
|
+
E extends Expectation ?
|
|
91
|
+
Parameters<E['expect']> extends [ any, any, ...infer P ] ? P : never :
|
|
92
|
+
never
|
|
93
|
+
|
|
94
|
+
/** Infer return parameter from {@link Expectation} type */
|
|
95
|
+
type ExpectationReturn<E, T> = E extends Expectation ? Expectations<T> : never
|
|
96
|
+
|
|
97
|
+
/** Infer expectation functions from imported {@link Expectation} instances */
|
|
98
|
+
type ImportedExpectations<T = unknown> = {
|
|
99
|
+
[ k in keyof ExpectationsByName ]: (
|
|
100
|
+
...args: ExpectationParameters<ExpectationsByName[k]>
|
|
101
|
+
) => ExpectationReturn<ExpectationsByName[k], T>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ========================================================================== *
|
|
105
|
+
* EXPECTATIONS DEFINITION *
|
|
106
|
+
* ========================================================================== */
|
|
107
|
+
|
|
108
|
+
/** An interface describing all expectations returned by `expect(...)` */
|
|
109
|
+
export interface Expectations<T = unknown> extends ImportedExpectations<T> {
|
|
110
|
+
/**
|
|
111
|
+
* The parent of this instance, if and only if this is a child derived from
|
|
112
|
+
* a property of the parent instance's value.
|
|
113
|
+
*/
|
|
114
|
+
parent?: ExpectationsParent
|
|
115
|
+
|
|
116
|
+
/** The value this {@link Expectations} instance operates on */
|
|
117
|
+
value: T
|
|
118
|
+
|
|
119
|
+
/** The _negated_ expectations of _this_ {@link Expectations} instance. */
|
|
120
|
+
not: Expectations<T>
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Programmatically return _positive_ or _negative_ {@link Expectations}
|
|
124
|
+
* for the value wrapped by this instance.
|
|
125
|
+
*/
|
|
126
|
+
negated(negative: boolean): Expectations<T>
|
|
127
|
+
|
|
128
|
+
/** Create an {@link Expectations} associated with a property of this value */
|
|
129
|
+
forProperty(prop: string | number | symbol): Expectations
|
|
130
|
+
|
|
131
|
+
/** Create a new {@link Expectations} instance for the specified value */
|
|
132
|
+
forValue<T = unknown>(value: T): Expectations<T>
|
|
133
|
+
|
|
134
|
+
/* == ASYNCHRONOUS EXPECTATIONS =========================================== */
|
|
135
|
+
|
|
136
|
+
/** Expect the value to be a _resolved_ {@link Promise} */
|
|
137
|
+
toBeResolved(): Promise<ExpectationsImpl<T>>
|
|
138
|
+
/**
|
|
139
|
+
* Expect the value to be a _resolved_ {@link Promise}, and assert the
|
|
140
|
+
* resolved result with the specified callback
|
|
141
|
+
*/
|
|
142
|
+
toBeResolved(assert: (resultExpectations: Expectations) => void): Promise<ExpectationsImpl<T>>
|
|
143
|
+
|
|
144
|
+
/** Expect the value to be a _rejected_ {@link Promise} */
|
|
145
|
+
toBeRejected(): Promise<ExpectationsImpl<T>>
|
|
146
|
+
/**
|
|
147
|
+
* Expect the value to be a _rejected_ {@link Promise}, and assert the
|
|
148
|
+
* rejected reason with the specified callback
|
|
149
|
+
*/
|
|
150
|
+
toBeRejected(assert?: (rejectionExpectations: Expectations) => void): Promise<ExpectationsImpl<T>>
|
|
151
|
+
|
|
152
|
+
/** Expect the value to be a {@link Promise} _rejected_ by an {@link Error} */
|
|
153
|
+
toBeRejectedWithError(): Promise<Expectations<T>>
|
|
154
|
+
/**
|
|
155
|
+
* Expect the value to be a {@link Promise} _rejected_ by an {@link Error}
|
|
156
|
+
* with the specified _message_
|
|
157
|
+
*/
|
|
158
|
+
toBeRejectedWithError(message: StringMatcher): Promise<Expectations<T>>
|
|
159
|
+
/**
|
|
160
|
+
* Expect the value to be a {@link Promise} _rejected_ by an {@link Error}
|
|
161
|
+
* of the specified _type_
|
|
162
|
+
*/
|
|
163
|
+
toBeRejectedWithError(constructor: Constructor<Error>): Promise<Expectations<T>>
|
|
164
|
+
/**
|
|
165
|
+
* Expect the value to be a {@link Promise} _rejected_ by an {@link Error}
|
|
166
|
+
* of the specified _type_ and with the specified _message_
|
|
167
|
+
*/
|
|
168
|
+
toBeRejectedWithError(constructor: Constructor<Error>, message: StringMatcher): Promise<Expectations<T>>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Parent expectations */
|
|
172
|
+
export interface ExpectationsParent {
|
|
173
|
+
context: Expectations,
|
|
174
|
+
prop: string | number | symbol,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Basic definition of an {@link Expectation} as an object */
|
|
178
|
+
export interface Expectation {
|
|
179
|
+
expect(context: Expectations, negative: boolean, ...args: any[]): void
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ========================================================================== *
|
|
183
|
+
* EXPECTATIONS IMPLEMENTATION *
|
|
184
|
+
* ========================================================================== */
|
|
185
|
+
|
|
186
|
+
/** Empty interface: the `class` below won't complain about missing stuff */
|
|
187
|
+
interface ExpectationsImpl<T = unknown> extends Expectations<T> {}
|
|
188
|
+
|
|
189
|
+
/** Implementation of our {@link Expectations} interface */
|
|
190
|
+
class ExpectationsImpl<T = unknown> implements Expectations<T> {
|
|
191
|
+
private readonly _positiveExpectations: ExpectationsImpl<T>
|
|
192
|
+
private readonly _negativeExpectations: ExpectationsImpl<T>
|
|
193
|
+
private readonly _negative: boolean
|
|
194
|
+
|
|
195
|
+
constructor(
|
|
196
|
+
public readonly value: T,
|
|
197
|
+
_positiveExpectations?: ExpectationsImpl<T>,
|
|
198
|
+
) {
|
|
199
|
+
if (_positiveExpectations) {
|
|
200
|
+
this._negative = true
|
|
201
|
+
this._positiveExpectations = _positiveExpectations
|
|
202
|
+
this._negativeExpectations = this
|
|
203
|
+
} else {
|
|
204
|
+
this._negative = false
|
|
205
|
+
this._positiveExpectations = this
|
|
206
|
+
this._negativeExpectations = new ExpectationsImpl(value, this)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* == NEW EXPECTATIONS ==================================================== */
|
|
211
|
+
|
|
212
|
+
forProperty(prop: string | number | symbol): ExpectationsImpl {
|
|
213
|
+
this.toBeDefined()
|
|
214
|
+
|
|
215
|
+
const child = new ExpectationsImpl((this.value as any)[prop])
|
|
216
|
+
child.parent = { context: this, prop }
|
|
217
|
+
return child
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
forValue<T = unknown>(value: T): ExpectationsImpl<T> {
|
|
221
|
+
return new ExpectationsImpl(value)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* == NEGATION ============================================================ */
|
|
225
|
+
|
|
226
|
+
negated(negative: boolean): ExpectationsImpl<T> {
|
|
227
|
+
return negative ? this._negativeExpectations : this._positiveExpectations
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get not(): ExpectationsImpl<T> {
|
|
231
|
+
return this._negative ? this._positiveExpectations : this._negativeExpectations
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* == ASYNCHRONOUS EXPECTATIONS =========================================== */
|
|
235
|
+
|
|
236
|
+
toBeResolved(assert?: (resultExpectations: Expectations) => void): Promise<ExpectationsImpl<T>> {
|
|
237
|
+
return Promise.resolve()
|
|
238
|
+
.then(() => {
|
|
239
|
+
this._positiveExpectations.toHaveProperty('then', (a) => a.toBeA('function'))
|
|
240
|
+
return Promise.allSettled([ Promise.resolve(this.value) ])
|
|
241
|
+
})
|
|
242
|
+
.then(([ settlement ]) => {
|
|
243
|
+
if (settlement.status === 'fulfilled') {
|
|
244
|
+
if (this._negative) throw new ExpectationError(this, true, 'to be resolved')
|
|
245
|
+
if (assert) assert(new ExpectationsImpl(settlement.value))
|
|
246
|
+
} else if (! this._negative) {
|
|
247
|
+
throw new ExpectationError(this, false, 'to be resolved')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return this._positiveExpectations
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
toBeRejected(assert?: (reasonExpectations: Expectations) => void): Promise<ExpectationsImpl<T>> {
|
|
255
|
+
return Promise.resolve()
|
|
256
|
+
.then(() => {
|
|
257
|
+
this._positiveExpectations.toHaveProperty('then', (a) => a.toBeA('function'))
|
|
258
|
+
return Promise.allSettled([ Promise.resolve(this.value) ])
|
|
259
|
+
})
|
|
260
|
+
.then(([ settlement ]) => {
|
|
261
|
+
if (settlement.status === 'rejected') {
|
|
262
|
+
if (this._negative) throw new ExpectationError(this, true, 'to be rejected')
|
|
263
|
+
if (assert) assert(new ExpectationsImpl(settlement.reason))
|
|
264
|
+
} else if (! this._negative) {
|
|
265
|
+
throw new ExpectationError(this, false, 'to be rejected')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return this._positiveExpectations
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
toBeRejectedWithError(
|
|
273
|
+
...args:
|
|
274
|
+
| []
|
|
275
|
+
| [ message: StringMatcher ]
|
|
276
|
+
| [ constructor: Constructor<Error> ]
|
|
277
|
+
| [ constructor: Constructor<Error>, message: StringMatcher ]
|
|
278
|
+
): Promise<ExpectationsImpl<T>> {
|
|
279
|
+
return this.toBeRejected((assert) => assert.toBeError(...args))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* == STATIC INITALIZER =================================================== */
|
|
283
|
+
|
|
284
|
+
static {
|
|
285
|
+
for (const [ key, value ] of Object.entries(expectations)) {
|
|
286
|
+
const expectation = value as Expectation
|
|
287
|
+
|
|
288
|
+
const fn = function(this: ExpectationsImpl, ...args: any[]): any {
|
|
289
|
+
try {
|
|
290
|
+
expectation.expect(this._positiveExpectations, this._negative, ...args)
|
|
291
|
+
return this._positiveExpectations
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (error instanceof ExpectationError) Error.captureStackTrace(error, fn)
|
|
294
|
+
throw error
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Object.defineProperty(fn, 'name', { value: key })
|
|
299
|
+
Object.defineProperty(this.prototype, key, { value: fn })
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* ========================================================================== *
|
|
305
|
+
* EXPECTATIONS MATCHERS *
|
|
306
|
+
* ========================================================================== */
|
|
307
|
+
|
|
308
|
+
/** Infer return parameter from {@link Expectation} type */
|
|
309
|
+
type MatcherReturn<E> = E extends Expectation ? ExpectationsMatcher : never
|
|
310
|
+
|
|
311
|
+
/** Infer expectation functions from imported {@link Expectation} instances */
|
|
312
|
+
type ImportedMatchers = {
|
|
313
|
+
[ k in keyof ExpectationsByName ]: (
|
|
314
|
+
...args: ExpectationParameters<ExpectationsByName[k]>
|
|
315
|
+
) => MatcherReturn<ExpectationsByName[k]>
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** An interface describing all expectations returned by `expect(...)` */
|
|
319
|
+
export interface ExpectationsMatcher extends ImportedMatchers {
|
|
320
|
+
not: ExpectationsMatcher
|
|
321
|
+
expect(value: unknown): void
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
interface ExpectationsMatcherImpl extends ExpectationsMatcher {}
|
|
326
|
+
|
|
327
|
+
class ExpectationsMatcherImpl {
|
|
328
|
+
private readonly _matchers: readonly [ string, boolean, any[] ][]
|
|
329
|
+
private readonly _positiveBuilder: ExpectationsMatcherImpl
|
|
330
|
+
private readonly _negativeBuilder: ExpectationsMatcherImpl
|
|
331
|
+
private readonly _negative: boolean
|
|
332
|
+
|
|
333
|
+
constructor(
|
|
334
|
+
_matchers: readonly [ string, boolean, any[] ][],
|
|
335
|
+
_positiveBuilder?: ExpectationsMatcherImpl,
|
|
336
|
+
) {
|
|
337
|
+
this._matchers = _matchers
|
|
338
|
+
if (_positiveBuilder) {
|
|
339
|
+
this._negative = true
|
|
340
|
+
this._positiveBuilder = _positiveBuilder
|
|
341
|
+
this._negativeBuilder = this
|
|
342
|
+
} else {
|
|
343
|
+
this._negative = false
|
|
344
|
+
this._positiveBuilder = this
|
|
345
|
+
this._negativeBuilder = new ExpectationsMatcherImpl(this._matchers, this)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
get not(): ExpectationsMatcherImpl {
|
|
350
|
+
return this._negative ? this._positiveBuilder : this._negativeBuilder
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
expect(value: unknown): void {
|
|
354
|
+
const expectations = new ExpectationsImpl(value)
|
|
355
|
+
for (const [ expectation, negative, args ] of this._matchers) {
|
|
356
|
+
(expectations.negated(negative) as any)[expectation](...args)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* == STATIC INITALIZER =================================================== */
|
|
361
|
+
|
|
362
|
+
static {
|
|
363
|
+
// for "isMatcher(...)" used by "diff(...)"
|
|
364
|
+
Object.defineProperty(this.prototype, matcherMarker, { value: matcherMarker })
|
|
365
|
+
|
|
366
|
+
// all our matchers
|
|
367
|
+
for (const key in expectations) {
|
|
368
|
+
Object.defineProperty(this.prototype, key, {
|
|
369
|
+
value: function(this: ExpectationsMatcherImpl, ...args: any[]): any {
|
|
370
|
+
return new ExpectationsMatcherImpl([
|
|
371
|
+
...this._matchers, [ key, this._negative, args ],
|
|
372
|
+
])
|
|
373
|
+
},
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* ========================================================================== *
|
|
380
|
+
* EXPECT FUNCTION *
|
|
381
|
+
* ========================================================================== */
|
|
382
|
+
|
|
383
|
+
/** The `expect` function exposing expectations and matchers */
|
|
384
|
+
export const expect = (<T = unknown>(value: T): Expectations<T> => {
|
|
385
|
+
return new ExpectationsImpl(value)
|
|
386
|
+
}) as ExpectationsMatcher & (<T = unknown>(value: T) => Expectations<T>)
|
|
387
|
+
|
|
388
|
+
// Instrument a getter for negative matchers
|
|
389
|
+
Object.defineProperty(expect, 'not', {
|
|
390
|
+
get: () => new ExpectationsMatcherImpl([]).not,
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
// Create a matcher for each expectation
|
|
394
|
+
for (const name in expectations) {
|
|
395
|
+
Object.defineProperty(expect, name, {
|
|
396
|
+
value: function(...args: any[]): ExpectationsMatcher {
|
|
397
|
+
const builder = new ExpectationsMatcherImpl([])
|
|
398
|
+
return (builder as any)[name](...args)
|
|
399
|
+
},
|
|
400
|
+
})
|
|
401
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { diff } from './diff'
|
|
2
|
+
import { ExpectationError, stringifyObjectType, stringifyValue } from './types'
|
|
3
|
+
|
|
4
|
+
import type { Diff } from './diff'
|
|
5
|
+
import type { Expectation, Expectations } from './expect'
|
|
6
|
+
|
|
7
|
+
export class ToInclude implements Expectation {
|
|
8
|
+
expect(
|
|
9
|
+
context: Expectations,
|
|
10
|
+
negative: boolean,
|
|
11
|
+
expected:
|
|
12
|
+
| Record<string, any>
|
|
13
|
+
| Map<any, any>
|
|
14
|
+
| Set<any>
|
|
15
|
+
| any [],
|
|
16
|
+
): void {
|
|
17
|
+
// get diff depending on type of "expected"
|
|
18
|
+
if (expected instanceof Map) return includesMappings(context, negative, expected)
|
|
19
|
+
if (expected instanceof Set) return includesValues(context, negative, expected)
|
|
20
|
+
if (Array.isArray(expected)) return includesValues(context, negative, new Set(expected))
|
|
21
|
+
if (expected instanceof Object) return includesProps(context, negative, expected)
|
|
22
|
+
throw new TypeError(`Invalid type for "toInclude(...)": ${stringifyValue(expected)}`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ToMatchContents implements Expectation {
|
|
27
|
+
expect(
|
|
28
|
+
context: Expectations,
|
|
29
|
+
negative: boolean,
|
|
30
|
+
contents: any[] | Set<any>,
|
|
31
|
+
): void {
|
|
32
|
+
let actual: Set<any>
|
|
33
|
+
let expected: Set<any>
|
|
34
|
+
try {
|
|
35
|
+
actual = new Set(context.value as any)
|
|
36
|
+
expected = new Set(contents)
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new ExpectationError(context, false, 'to be an iterable object')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = diff(actual, expected)
|
|
42
|
+
delete result.error // remove extra error message about size differences...
|
|
43
|
+
if (result.diff === negative) return
|
|
44
|
+
throw new ExpectationError(
|
|
45
|
+
context,
|
|
46
|
+
negative,
|
|
47
|
+
`to match contents of ${stringifyObjectType(contents)}`,
|
|
48
|
+
{ ...result, value: context.value })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function includesProps(context: Expectations, negative: boolean, expected: Record<string, any>): void {
|
|
53
|
+
// simple include for maps with objects...
|
|
54
|
+
if (context.value instanceof Map) {
|
|
55
|
+
return includesMappings(context, negative, new Map(Object.entries(expected)))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// we really need an object as actual
|
|
59
|
+
context.toBeInstanceOf(Object)
|
|
60
|
+
const actual: Record<string, any> = context.value as any
|
|
61
|
+
|
|
62
|
+
// get expected key set and process...
|
|
63
|
+
const keys = new Set(Object.keys(expected))
|
|
64
|
+
const props: Record<string, Diff> = {}
|
|
65
|
+
|
|
66
|
+
if (negative) {
|
|
67
|
+
// only consider keys... if they exist, fail!
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
if ((actual[key] !== undefined) || (key in actual)) {
|
|
70
|
+
props[key] = { diff: true, extra: actual[key] }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
for (const key of keys) {
|
|
75
|
+
const act = actual[key]
|
|
76
|
+
const exp = expected[key]
|
|
77
|
+
|
|
78
|
+
const result = diff(act, exp)
|
|
79
|
+
if (! result.diff) continue
|
|
80
|
+
|
|
81
|
+
// if there is a difference, we _might_ have a missing/extra property
|
|
82
|
+
if ((act === undefined) && (! (key in actual))) {
|
|
83
|
+
props[key] = { diff: true, missing: exp }
|
|
84
|
+
} else {
|
|
85
|
+
props[key] = result
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const count = Object.keys(props).length
|
|
91
|
+
if (count === 0) return // no props? no errors!
|
|
92
|
+
|
|
93
|
+
const type = count === 1 ? 'property' : 'properties'
|
|
94
|
+
throw new ExpectationError(context, negative, `to include ${count} ${type}`, {
|
|
95
|
+
diff: true,
|
|
96
|
+
value: actual,
|
|
97
|
+
props,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function includesValues(context: Expectations, negative: boolean, expected: Set<any>): void {
|
|
102
|
+
// we really need an _iterable_ object as actual
|
|
103
|
+
context.toBeInstanceOf(Object)
|
|
104
|
+
if (typeof (context.value as any)[Symbol.iterator] !== 'function') {
|
|
105
|
+
throw new ExpectationError(context, false, 'to be an iterable object')
|
|
106
|
+
}
|
|
107
|
+
const actual = new Set(context.value as Iterable<any>)
|
|
108
|
+
|
|
109
|
+
// iterate through all the values and see what we can find
|
|
110
|
+
const values: Diff[] = []
|
|
111
|
+
if (negative) {
|
|
112
|
+
for (const exp of expected) {
|
|
113
|
+
for (const act of actual) {
|
|
114
|
+
const result = diff(act, exp)
|
|
115
|
+
if (result.diff) continue
|
|
116
|
+
|
|
117
|
+
values.push({ diff: true, extra: act })
|
|
118
|
+
break
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
for (const exp of expected) {
|
|
123
|
+
let found = false
|
|
124
|
+
|
|
125
|
+
for (const act of actual) {
|
|
126
|
+
const result = diff(act, exp)
|
|
127
|
+
if (result.diff) continue
|
|
128
|
+
found = true
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (! found) {
|
|
133
|
+
values.push({ diff: true, missing: exp })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const count = values.length
|
|
139
|
+
if (count === 0) return // no values? no errors!
|
|
140
|
+
|
|
141
|
+
const type = count === 1 ? 'value' : 'values'
|
|
142
|
+
throw new ExpectationError(context, negative, `to include ${count} ${type}`, {
|
|
143
|
+
diff: true,
|
|
144
|
+
value: context.value,
|
|
145
|
+
values,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function includesMappings(context: Expectations, negative: boolean, expected: Map<any, any>): void {
|
|
150
|
+
context.toBeInstanceOf(Map)
|
|
151
|
+
const actual: Map<any, any> = context.value as any
|
|
152
|
+
|
|
153
|
+
// Get expected key set and process...
|
|
154
|
+
const keys = new Set(expected.keys())
|
|
155
|
+
const mappings: [ string, Diff ][] = []
|
|
156
|
+
|
|
157
|
+
if (negative) {
|
|
158
|
+
// only consider keys... if they exist, fail!
|
|
159
|
+
for (const key of keys) {
|
|
160
|
+
if (actual.has(key)) {
|
|
161
|
+
mappings.push([ key, { diff: true, extra: actual.get(key) } ])
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
for (const key of keys) {
|
|
166
|
+
if (! actual.has(key)) {
|
|
167
|
+
mappings.push([ key, { diff: true, missing: expected.get(key) } ])
|
|
168
|
+
} else {
|
|
169
|
+
const result = diff(actual.get(key), expected.get(key))
|
|
170
|
+
if (result.diff) mappings.push([ key, result ])
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const count = mappings.length
|
|
176
|
+
if (count === 0) return // no mappings? no errors!
|
|
177
|
+
|
|
178
|
+
const type = count === 1 ? 'mapping' : 'mappings'
|
|
179
|
+
throw new ExpectationError(context, negative, `to include ${count} ${type}`, {
|
|
180
|
+
diff: true,
|
|
181
|
+
value: context.value,
|
|
182
|
+
mappings,
|
|
183
|
+
})
|
|
184
|
+
}
|