@plugjs/expect5 0.4.2 → 0.4.3

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.
Files changed (59) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/expectation/async.cjs +73 -0
  3. package/dist/expectation/async.cjs.map +6 -0
  4. package/dist/expectation/async.d.ts +54 -0
  5. package/dist/expectation/async.mjs +46 -0
  6. package/dist/expectation/async.mjs.map +6 -0
  7. package/dist/expectation/basic.cjs +155 -183
  8. package/dist/expectation/basic.cjs.map +1 -1
  9. package/dist/expectation/basic.d.ts +90 -47
  10. package/dist/expectation/basic.mjs +142 -163
  11. package/dist/expectation/basic.mjs.map +1 -1
  12. package/dist/expectation/diff.cjs +7 -7
  13. package/dist/expectation/diff.cjs.map +1 -1
  14. package/dist/expectation/diff.mjs +7 -7
  15. package/dist/expectation/diff.mjs.map +1 -1
  16. package/dist/expectation/expect.cjs +94 -108
  17. package/dist/expectation/expect.cjs.map +2 -2
  18. package/dist/expectation/expect.d.ts +103 -130
  19. package/dist/expectation/expect.mjs +131 -137
  20. package/dist/expectation/expect.mjs.map +2 -2
  21. package/dist/expectation/include.cjs +50 -61
  22. package/dist/expectation/include.cjs.map +1 -1
  23. package/dist/expectation/include.d.ts +19 -10
  24. package/dist/expectation/include.mjs +53 -57
  25. package/dist/expectation/include.mjs.map +1 -1
  26. package/dist/expectation/throwing.cjs +27 -27
  27. package/dist/expectation/throwing.cjs.map +1 -1
  28. package/dist/expectation/throwing.d.ts +36 -8
  29. package/dist/expectation/throwing.mjs +26 -26
  30. package/dist/expectation/throwing.mjs.map +1 -1
  31. package/dist/expectation/trivial.cjs +96 -0
  32. package/dist/expectation/trivial.cjs.map +6 -0
  33. package/dist/expectation/trivial.d.ts +13 -0
  34. package/dist/expectation/trivial.mjs +61 -0
  35. package/dist/expectation/trivial.mjs.map +6 -0
  36. package/dist/expectation/types.cjs +9 -12
  37. package/dist/expectation/types.cjs.map +1 -1
  38. package/dist/expectation/types.d.ts +52 -10
  39. package/dist/expectation/types.mjs +8 -10
  40. package/dist/expectation/types.mjs.map +1 -1
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.mjs.map +1 -1
  44. package/package.json +2 -2
  45. package/src/expectation/async.ts +151 -0
  46. package/src/expectation/basic.ts +356 -156
  47. package/src/expectation/diff.ts +8 -9
  48. package/src/expectation/expect.ts +239 -268
  49. package/src/expectation/include.ts +93 -59
  50. package/src/expectation/throwing.ts +102 -41
  51. package/src/expectation/trivial.ts +107 -0
  52. package/src/expectation/types.ts +82 -25
  53. package/src/index.ts +2 -0
  54. package/dist/expectation/void.cjs +0 -111
  55. package/dist/expectation/void.cjs.map +0 -6
  56. package/dist/expectation/void.d.ts +0 -39
  57. package/dist/expectation/void.mjs +0 -77
  58. package/dist/expectation/void.mjs.map +0 -6
  59. package/src/expectation/void.ts +0 -80
@@ -1,294 +1,274 @@
1
- import { ExpectationError, matcherMarker } from './types'
2
1
  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,
2
+ toBeRejected,
3
+ toBeRejectedWithError,
4
+ toBeResolved,
5
+ } from './async'
6
+ import {
7
+ toBeA,
8
+ toBeCloseTo,
9
+ toBeError,
10
+ toBeGreaterThan,
11
+ toBeGreaterThanOrEqual,
12
+ toBeInstanceOf,
13
+ toBeLessThan,
14
+ toBeLessThanOrEqual,
15
+ toBeWithinRange,
16
+ toEqual,
17
+ toHaveLength,
18
+ toHaveProperty,
19
+ toHaveSize,
20
+ toMatch,
21
+ toStrictlyEqual,
18
22
  } from './basic'
19
23
  import {
20
- ToInclude, ToMatchContents,
24
+ toInclude,
25
+ toMatchContents,
21
26
  } from './include'
22
27
  import {
23
- ToThrow,
24
- ToThrowError,
28
+ toThrow,
29
+ toThrowError,
25
30
  } from './throwing'
26
31
  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'
32
+ toBeDefined,
33
+ toBeFalse,
34
+ toBeFalsy,
35
+ toBeNaN,
36
+ toBeNegativeInfinity,
37
+ toBeNull,
38
+ toBeNullable,
39
+ toBePositiveInfinity,
40
+ toBeTrue,
41
+ toBeTruthy,
42
+ toBeUndefined,
43
+ } from './trivial'
44
+ import {
45
+ ExpectationError,
46
+ matcherMarker,
47
+ } from './types'
48
+
49
+ import type {
50
+ ExpectationsContext,
51
+ ExpectationsParent,
52
+ } from './types'
40
53
 
41
54
  /* ========================================================================== *
42
55
  * IMPORT AND PREPARE EXTERNAL EXPECTATIONS *
43
56
  * ========================================================================== */
44
57
 
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(),
58
+ const asyncExpectations = {
59
+ toBeResolved,
60
+ toBeRejected,
61
+ toBeRejectedWithError,
62
+ } as const
63
+
64
+ type AsyncExpectations = typeof asyncExpectations
65
+
66
+ const syncExpectations = {
67
+ // basic
68
+ toBeA,
69
+ toBeCloseTo,
70
+ toBeError,
71
+ toBeGreaterThan,
72
+ toBeGreaterThanOrEqual,
73
+ toBeInstanceOf,
74
+ toBeLessThan,
75
+ toBeLessThanOrEqual,
76
+ toBeWithinRange,
77
+ toEqual,
78
+ toHaveLength,
79
+ toHaveProperty,
80
+ toHaveSize,
81
+ toMatch,
82
+ toStrictlyEqual,
63
83
 
64
84
  // include
65
- toInclude: new ToInclude(),
66
- toMatchContents: new ToMatchContents(),
85
+ toInclude,
86
+ toMatchContents,
67
87
 
68
88
  // 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(),
89
+ toThrow,
90
+ toThrowError,
91
+
92
+ // trivial
93
+ toBeDefined,
94
+ toBeFalse,
95
+ toBeFalsy,
96
+ toBeNaN,
97
+ toBeNegativeInfinity,
98
+ toBeNull,
99
+ toBeNullable,
100
+ toBePositiveInfinity,
101
+ toBeTrue,
102
+ toBeTruthy,
103
+ toBeUndefined,
83
104
  } as const
84
105
 
85
- /** The type of our imported expectations */
86
- type ExpectationsByName = typeof expectations
106
+ type SyncExpectations = typeof syncExpectations
87
107
 
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
108
+ const allExpectations = {
109
+ ...asyncExpectations,
110
+ ...syncExpectations,
111
+ }
93
112
 
94
- /** Infer return parameter from {@link Expectation} type */
95
- type ExpectationReturn<E, T> = E extends Expectation ? Expectations<T> : never
113
+ type AllExpectations = SyncExpectations & AsyncExpectations
96
114
 
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>
115
+ /* ========================================================================== *
116
+ * OVERLOADED FUNCTIONS TYPES *
117
+ * ========================================================================== */
118
+
119
+ /** Combine the arguments of a number of overloads (tuples) */
120
+ type OverloadArguments<T> =
121
+ T extends readonly [ infer T, ...infer Rest ] ?
122
+ [ T, ...OverloadArguments<Rest> ] :
123
+ T extends readonly [] ? [] :
124
+ T extends readonly (infer T)[] ?
125
+ unknown extends T ? never :
126
+ T extends undefined ? [] :
127
+ [ T ] :
128
+ never
129
+
130
+ /**
131
+ * Remap `Functions` (a record of functions) inferring arguments and forcing
132
+ * return type to `Result`
133
+ */
134
+ type OverloadFunctions<Functions, Result> = {
135
+ [ k in keyof Functions ]:
136
+ Functions[k] extends {
137
+ (...args: infer A0): any
138
+ (...args: infer A1): any
139
+ (...args: infer A2): any
140
+ (...args: infer A3): any
141
+ (...args: infer A4): any
142
+ (...args: infer A5): any
143
+ } ? (...args: OverloadArguments<A0 | A1 | A2 | A3 | A4 | A5>) => Result :
144
+ Functions[k] extends {
145
+ (...args: infer A0): any
146
+ (...args: infer A1): any
147
+ (...args: infer A2): any
148
+ (...args: infer A3): any
149
+ (...args: infer A4): any
150
+ } ? (...args: OverloadArguments<A0 | A1 | A2 | A3 | A4>) => Result :
151
+ Functions[k] extends {
152
+ (...args: infer A0): any
153
+ (...args: infer A1): any
154
+ (...args: infer A2): any
155
+ (...args: infer A3): any
156
+ } ? (...args: OverloadArguments<A0 | A1 | A2 | A3>) => Result :
157
+ Functions[k] extends {
158
+ (...args: infer A0): any
159
+ (...args: infer A1): any
160
+ (...args: infer A2): any
161
+ } ? (...args: OverloadArguments<A0 | A1 | A2>) => Result :
162
+ Functions[k] extends {
163
+ (...args: infer A0): any
164
+ (...args: infer A1): any
165
+ } ? (...args: OverloadArguments<A0 | A1>) => Result :
166
+ Functions[k] extends {
167
+ (...args: infer A0): any
168
+ } ? (...args: OverloadArguments<A0>) => Result :
169
+ never
102
170
  }
103
171
 
104
172
  /* ========================================================================== *
105
173
  * EXPECTATIONS DEFINITION *
106
174
  * ========================================================================== */
107
175
 
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
176
+ /**
177
+ * Expectation functions simply check a _value_, but do not alter the type
178
+ * returned by each expectation.
179
+ */
180
+ export interface ExpectationFunctions<T> extends
181
+ OverloadFunctions<AsyncExpectations, Promise<Expectations<PromiseLike<T>>>>,
182
+ OverloadFunctions<SyncExpectations, Expectations<T>> {
183
+ // empty interface, specifically without `value` or `not` so that
184
+ // in no way this can be confused with the full `Expectations<T>`.
185
+ }
115
186
 
187
+ /**
188
+ * An interface describing all expectations returned by `expect(...)`.
189
+ *
190
+ * Each function, upon checking, might return an expectation bound to a
191
+ * different _type_ (for example `.toBeNull()` returns always
192
+ * `Expectations<null>`, inferring that `value` is indeed `null`).
193
+ */
194
+ export interface Expectations<T = unknown> extends AllExpectations {
116
195
  /** The value this {@link Expectations} instance operates on */
117
- value: T
118
-
196
+ readonly value: T
119
197
  /** 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
198
+ readonly not: ExpectationFunctions<T>
180
199
  }
181
200
 
182
201
  /* ========================================================================== *
183
202
  * EXPECTATIONS IMPLEMENTATION *
184
203
  * ========================================================================== */
185
204
 
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
-
205
+ class ExpectationsContextImpl<T = unknown> implements ExpectationsContext<T> {
195
206
  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> {
207
+ readonly value: T,
208
+ readonly negative: boolean,
209
+ readonly expects: Expectations<T>,
210
+ readonly negated: ExpectationFunctions<T>,
211
+ readonly parent?: ExpectationsParent,
212
+ ) {}
213
+
214
+ forValue<V>(value: V): Expectations<V> {
221
215
  return new ExpectationsImpl(value)
222
216
  }
223
217
 
224
- /* == NEGATION ============================================================ */
218
+ forProperty(prop: string | number | symbol): Expectations<unknown> {
219
+ this.expects.toBeDefined()
225
220
 
226
- negated(negative: boolean): ExpectationsImpl<T> {
227
- return negative ? this._negativeExpectations : this._positiveExpectations
221
+ const value = (this.value as any)[prop]
222
+ const parent = { context: this, prop }
223
+ return new ExpectationsImpl(value, parent)
228
224
  }
225
+ }
229
226
 
230
- get not(): ExpectationsImpl<T> {
231
- return this._negative ? this._positiveExpectations : this._negativeExpectations
232
- }
227
+ /** Empty interface: the `class` below won't complain about missing stuff */
228
+ interface ExpectationsImpl<T = unknown> extends Expectations<T> {}
233
229
 
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
- }
230
+ /** Implementation of our {@link Expectations} interface */
231
+ class ExpectationsImpl<T = unknown> implements Expectations<T> {
232
+ private readonly _context: ExpectationsContext<T>
253
233
 
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
- }
234
+ readonly value: T
235
+ readonly not: ExpectationFunctions<T>
271
236
 
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))
237
+ constructor(
238
+ value: T,
239
+ parent?: ExpectationsParent,
240
+ positives?: Expectations<T>,
241
+ ) {
242
+ this.value = value
243
+
244
+ if (positives) {
245
+ this.not = positives as ExpectationFunctions<any>
246
+ this._context = new ExpectationsContextImpl(
247
+ value,
248
+ true,
249
+ positives,
250
+ this as ExpectationFunctions<any>,
251
+ parent)
252
+ } else {
253
+ this._context = new ExpectationsContextImpl(
254
+ value,
255
+ false,
256
+ this,
257
+ this as ExpectationFunctions<any>,
258
+ parent)
259
+ this.not = new ExpectationsImpl(value, parent, this) as ExpectationFunctions<any>
260
+ }
280
261
  }
281
262
 
282
263
  /* == STATIC INITALIZER =================================================== */
283
264
 
284
265
  static {
285
- for (const [ key, value ] of Object.entries(expectations)) {
286
- const expectation = value as Expectation
266
+ for (const [ key, value ] of Object.entries(allExpectations)) {
267
+ const expectation = value as (this: ExpectationsContext, ...args: any[]) => any
287
268
 
288
269
  const fn = function(this: ExpectationsImpl, ...args: any[]): any {
289
270
  try {
290
- expectation.expect(this._positiveExpectations, this._negative, ...args)
291
- return this._positiveExpectations
271
+ return expectation.call(this._context, ...args)
292
272
  } catch (error) {
293
273
  if (error instanceof ExpectationError) Error.captureStackTrace(error, fn)
294
274
  throw error
@@ -305,34 +285,24 @@ class ExpectationsImpl<T = unknown> implements Expectations<T> {
305
285
  * EXPECTATIONS MATCHERS *
306
286
  * ========================================================================== */
307
287
 
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
288
  /** An interface describing all expectations returned by `expect(...)` */
319
- export interface ExpectationsMatcher extends ImportedMatchers {
320
- not: ExpectationsMatcher
289
+ export interface Matchers extends OverloadFunctions<SyncExpectations, Matchers> {
290
+ not: Matchers
291
+ /* The assertion here will trigger */
321
292
  expect(value: unknown): void
322
293
  }
323
294
 
295
+ interface MatcherImpl extends Matchers {}
324
296
 
325
- interface ExpectationsMatcherImpl extends ExpectationsMatcher {}
326
-
327
- class ExpectationsMatcherImpl {
297
+ class MatcherImpl {
328
298
  private readonly _matchers: readonly [ string, boolean, any[] ][]
329
- private readonly _positiveBuilder: ExpectationsMatcherImpl
330
- private readonly _negativeBuilder: ExpectationsMatcherImpl
299
+ private readonly _positiveBuilder: MatcherImpl
300
+ private readonly _negativeBuilder: MatcherImpl
331
301
  private readonly _negative: boolean
332
302
 
333
303
  constructor(
334
304
  _matchers: readonly [ string, boolean, any[] ][],
335
- _positiveBuilder?: ExpectationsMatcherImpl,
305
+ _positiveBuilder?: MatcherImpl,
336
306
  ) {
337
307
  this._matchers = _matchers
338
308
  if (_positiveBuilder) {
@@ -342,18 +312,19 @@ class ExpectationsMatcherImpl {
342
312
  } else {
343
313
  this._negative = false
344
314
  this._positiveBuilder = this
345
- this._negativeBuilder = new ExpectationsMatcherImpl(this._matchers, this)
315
+ this._negativeBuilder = new MatcherImpl(this._matchers, this)
346
316
  }
347
317
  }
348
318
 
349
- get not(): ExpectationsMatcherImpl {
319
+ get not(): MatcherImpl {
350
320
  return this._negative ? this._positiveBuilder : this._negativeBuilder
351
321
  }
352
322
 
353
323
  expect(value: unknown): void {
354
- const expectations = new ExpectationsImpl(value)
324
+ const expectations = expect(value)
355
325
  for (const [ expectation, negative, args ] of this._matchers) {
356
- (expectations.negated(negative) as any)[expectation](...args)
326
+ const expect = negative ? expectations.not as any : expectations as any
327
+ expect[expectation](...args)
357
328
  }
358
329
  }
359
330
 
@@ -364,10 +335,10 @@ class ExpectationsMatcherImpl {
364
335
  Object.defineProperty(this.prototype, matcherMarker, { value: matcherMarker })
365
336
 
366
337
  // all our matchers
367
- for (const key in expectations) {
338
+ for (const key in syncExpectations) {
368
339
  Object.defineProperty(this.prototype, key, {
369
- value: function(this: ExpectationsMatcherImpl, ...args: any[]): any {
370
- return new ExpectationsMatcherImpl([
340
+ value: function(this: MatcherImpl, ...args: any[]): any {
341
+ return new MatcherImpl([
371
342
  ...this._matchers, [ key, this._negative, args ],
372
343
  ])
373
344
  },
@@ -383,18 +354,18 @@ class ExpectationsMatcherImpl {
383
354
  /** The `expect` function exposing expectations and matchers */
384
355
  export const expect = (<T = unknown>(value: T): Expectations<T> => {
385
356
  return new ExpectationsImpl(value)
386
- }) as ExpectationsMatcher & (<T = unknown>(value: T) => Expectations<T>)
357
+ }) as Matchers & (<T = unknown>(value: T) => Expectations<T>)
387
358
 
388
359
  // Instrument a getter for negative matchers
389
360
  Object.defineProperty(expect, 'not', {
390
- get: () => new ExpectationsMatcherImpl([]).not,
361
+ get: () => new MatcherImpl([]).not,
391
362
  })
392
363
 
393
- // Create a matcher for each expectation
394
- for (const name in expectations) {
364
+ // Create a matcher for each expectation function
365
+ for (const name in syncExpectations) {
395
366
  Object.defineProperty(expect, name, {
396
- value: function(...args: any[]): ExpectationsMatcher {
397
- const builder = new ExpectationsMatcherImpl([])
367
+ value: function(...args: any[]): Matchers {
368
+ const builder = new MatcherImpl([])
398
369
  return (builder as any)[name](...args)
399
370
  },
400
371
  })