@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.
Files changed (90) hide show
  1. package/README.md +7 -0
  2. package/dist/cli.d.mts +2 -0
  3. package/dist/cli.mjs +96 -0
  4. package/dist/cli.mjs.map +6 -0
  5. package/dist/execution/executable.cjs +299 -0
  6. package/dist/execution/executable.cjs.map +6 -0
  7. package/dist/execution/executable.d.ts +87 -0
  8. package/dist/execution/executable.mjs +260 -0
  9. package/dist/execution/executable.mjs.map +6 -0
  10. package/dist/execution/executor.cjs +125 -0
  11. package/dist/execution/executor.cjs.map +6 -0
  12. package/dist/execution/executor.d.ts +35 -0
  13. package/dist/execution/executor.mjs +90 -0
  14. package/dist/execution/executor.mjs.map +6 -0
  15. package/dist/execution/setup.cjs +127 -0
  16. package/dist/execution/setup.cjs.map +6 -0
  17. package/dist/execution/setup.d.ts +31 -0
  18. package/dist/execution/setup.mjs +87 -0
  19. package/dist/execution/setup.mjs.map +6 -0
  20. package/dist/expectation/basic.cjs +216 -0
  21. package/dist/expectation/basic.cjs.map +6 -0
  22. package/dist/expectation/basic.d.ts +47 -0
  23. package/dist/expectation/basic.mjs +177 -0
  24. package/dist/expectation/basic.mjs.map +6 -0
  25. package/dist/expectation/diff.cjs +253 -0
  26. package/dist/expectation/diff.cjs.map +6 -0
  27. package/dist/expectation/diff.d.ts +27 -0
  28. package/dist/expectation/diff.mjs +228 -0
  29. package/dist/expectation/diff.mjs.map +6 -0
  30. package/dist/expectation/expect.cjs +211 -0
  31. package/dist/expectation/expect.cjs.map +6 -0
  32. package/dist/expectation/expect.d.ts +140 -0
  33. package/dist/expectation/expect.mjs +219 -0
  34. package/dist/expectation/expect.mjs.map +6 -0
  35. package/dist/expectation/include.cjs +187 -0
  36. package/dist/expectation/include.cjs.map +6 -0
  37. package/dist/expectation/include.d.ts +10 -0
  38. package/dist/expectation/include.mjs +158 -0
  39. package/dist/expectation/include.mjs.map +6 -0
  40. package/dist/expectation/print.cjs +281 -0
  41. package/dist/expectation/print.cjs.map +6 -0
  42. package/dist/expectation/print.d.ts +4 -0
  43. package/dist/expectation/print.mjs +256 -0
  44. package/dist/expectation/print.mjs.map +6 -0
  45. package/dist/expectation/throwing.cjs +58 -0
  46. package/dist/expectation/throwing.cjs.map +6 -0
  47. package/dist/expectation/throwing.d.ts +8 -0
  48. package/dist/expectation/throwing.mjs +32 -0
  49. package/dist/expectation/throwing.mjs.map +6 -0
  50. package/dist/expectation/types.cjs +212 -0
  51. package/dist/expectation/types.cjs.map +6 -0
  52. package/dist/expectation/types.d.ts +57 -0
  53. package/dist/expectation/types.mjs +178 -0
  54. package/dist/expectation/types.mjs.map +6 -0
  55. package/dist/expectation/void.cjs +111 -0
  56. package/dist/expectation/void.cjs.map +6 -0
  57. package/dist/expectation/void.d.ts +39 -0
  58. package/dist/expectation/void.mjs +77 -0
  59. package/dist/expectation/void.mjs.map +6 -0
  60. package/dist/globals.cjs +2 -0
  61. package/dist/globals.cjs.map +6 -0
  62. package/dist/globals.d.ts +23 -0
  63. package/dist/globals.mjs +1 -0
  64. package/dist/globals.mjs.map +6 -0
  65. package/dist/index.cjs +66 -0
  66. package/dist/index.cjs.map +6 -0
  67. package/dist/index.d.ts +29 -0
  68. package/dist/index.mjs +41 -0
  69. package/dist/index.mjs.map +6 -0
  70. package/dist/test.cjs +229 -0
  71. package/dist/test.cjs.map +6 -0
  72. package/dist/test.d.ts +9 -0
  73. package/dist/test.mjs +194 -0
  74. package/dist/test.mjs.map +6 -0
  75. package/package.json +57 -0
  76. package/src/cli.mts +122 -0
  77. package/src/execution/executable.ts +364 -0
  78. package/src/execution/executor.ts +146 -0
  79. package/src/execution/setup.ts +108 -0
  80. package/src/expectation/basic.ts +209 -0
  81. package/src/expectation/diff.ts +445 -0
  82. package/src/expectation/expect.ts +401 -0
  83. package/src/expectation/include.ts +184 -0
  84. package/src/expectation/print.ts +386 -0
  85. package/src/expectation/throwing.ts +45 -0
  86. package/src/expectation/types.ts +263 -0
  87. package/src/expectation/void.ts +80 -0
  88. package/src/globals.ts +30 -0
  89. package/src/index.ts +54 -0
  90. 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
+ }