@plugjs/expect5 0.4.1 → 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,69 +1,103 @@
1
1
  import { diff } from './diff'
2
- import { ExpectationError, stringifyObjectType, stringifyValue } from './types'
2
+ import {
3
+ ExpectationError,
4
+ stringifyObjectType,
5
+ stringifyValue,
6
+ } from './types'
3
7
 
4
8
  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)}`)
9
+ import type { Expectations } from './expect'
10
+ import type { ExpectationsContext } from './types'
11
+
12
+ /* === TO INCLUDE =========================================================== */
13
+
14
+ /** Expect the value to include _all_ properties from the specified _object_. */
15
+ function toInclude<T, P extends Record<string, any>>(this: T, properties: P): T
16
+ /** Expect the value to include _all_ mappings from the specified {@link Map}. */
17
+ function toInclude<T>(this: T, mappings: Map<any, any>): T
18
+ /** Expect the value to include _all_ values from the specified {@link Set}. */
19
+ function toInclude<T>(this: T, entries: Set<any>): T
20
+ /** Expect the value to include _all_ values in any order from the specified _array_. */
21
+ function toInclude<T>(this: T, values: any[]): T
22
+
23
+ /* Overloaded function implementation */
24
+ function toInclude(
25
+ this: ExpectationsContext,
26
+ expected:
27
+ | Record<string, any>
28
+ | Map<any, any>
29
+ | Set<any>
30
+ | any [],
31
+ ): Expectations {
32
+ // get diff depending on type of "expected"
33
+ if (expected instanceof Map) return includesMappings(this, expected)
34
+ if (expected instanceof Set) return includesValues(this, expected)
35
+ if (Array.isArray(expected)) return includesValues(this, new Set(expected))
36
+ if (expected instanceof Object) return includesProps(this, expected)
37
+ throw new TypeError(`Invalid type for "toInclude(...)": ${stringifyValue(expected)}`)
38
+ }
39
+
40
+ /* === TO MATCH CONTENTS ==================================================== */
41
+
42
+ /**
43
+ * Expect the value to include _all_ values (and only those values, in any
44
+ * order) from the specified _array_.
45
+ */
46
+ function toMatchContents<T>(this: T, contents: any[]): T
47
+ /**
48
+ * Expect the value to include _all_ values (and only those values, in any
49
+ * order) from the specified {@link Set}.
50
+ */
51
+ function toMatchContents<T>(this: T, contents: Set<any>): T
52
+
53
+ /* Overloaded function implementation */
54
+ function toMatchContents(
55
+ this: ExpectationsContext,
56
+ contents: any[] | Set<any>,
57
+ ): Expectations {
58
+ let actual: Set<any>
59
+ let expected: Set<any>
60
+ try {
61
+ actual = new Set(this.value as any)
62
+ expected = new Set(contents)
63
+ } catch (error) {
64
+ throw new ExpectationError(this, 'to be an iterable object', false)
23
65
  }
66
+
67
+ const result = diff(actual, expected)
68
+ delete result.error // remove extra error message about size differences...
69
+ if (result.diff === this.negative) return this.expects
70
+ throw new ExpectationError(this,
71
+ `to match contents of ${stringifyObjectType(contents)}`,
72
+ { ...result, value: this.value })
24
73
  }
25
74
 
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
- }
75
+ /* === EXPORTS ============================================================== */
40
76
 
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
- }
77
+ export {
78
+ toInclude,
79
+ toMatchContents,
50
80
  }
51
81
 
52
- export function includesProps(context: Expectations, negative: boolean, expected: Record<string, any>): void {
82
+ /* ========================================================================== *
83
+ * INTERNALS *
84
+ * ========================================================================== */
85
+
86
+ function includesProps(context: ExpectationsContext, expected: Record<string, any>): Expectations {
53
87
  // simple include for maps with objects...
54
88
  if (context.value instanceof Map) {
55
- return includesMappings(context, negative, new Map(Object.entries(expected)))
89
+ return includesMappings(context, new Map(Object.entries(expected)))
56
90
  }
57
91
 
58
92
  // we really need an object as actual
59
- context.toBeInstanceOf(Object)
93
+ context.expects.toBeInstanceOf(Object)
60
94
  const actual: Record<string, any> = context.value as any
61
95
 
62
96
  // get expected key set and process...
63
97
  const keys = new Set(Object.keys(expected))
64
98
  const props: Record<string, Diff> = {}
65
99
 
66
- if (negative) {
100
+ if (context.negative) {
67
101
  // only consider keys... if they exist, fail!
68
102
  for (const key of keys) {
69
103
  if ((actual[key] !== undefined) || (key in actual)) {
@@ -88,27 +122,27 @@ export function includesProps(context: Expectations, negative: boolean, expected
88
122
  }
89
123
 
90
124
  const count = Object.keys(props).length
91
- if (count === 0) return // no props? no errors!
125
+ if (count === 0) return context.expects // no props? no errors!
92
126
 
93
127
  const type = count === 1 ? 'property' : 'properties'
94
- throw new ExpectationError(context, negative, `to include ${count} ${type}`, {
128
+ throw new ExpectationError(context, `to include ${count} ${type}`, {
95
129
  diff: true,
96
130
  value: actual,
97
131
  props,
98
132
  })
99
133
  }
100
134
 
101
- export function includesValues(context: Expectations, negative: boolean, expected: Set<any>): void {
135
+ function includesValues(context: ExpectationsContext, expected: Set<any>): Expectations {
102
136
  // we really need an _iterable_ object as actual
103
- context.toBeInstanceOf(Object)
137
+ context.expects.toBeInstanceOf(Object)
104
138
  if (typeof (context.value as any)[Symbol.iterator] !== 'function') {
105
- throw new ExpectationError(context, false, 'to be an iterable object')
139
+ throw new ExpectationError(context, 'to be an iterable object', false)
106
140
  }
107
141
  const actual = new Set(context.value as Iterable<any>)
108
142
 
109
143
  // iterate through all the values and see what we can find
110
144
  const values: Diff[] = []
111
- if (negative) {
145
+ if (context.negative) {
112
146
  for (const exp of expected) {
113
147
  for (const act of actual) {
114
148
  const result = diff(act, exp)
@@ -136,25 +170,25 @@ export function includesValues(context: Expectations, negative: boolean, expecte
136
170
  }
137
171
 
138
172
  const count = values.length
139
- if (count === 0) return // no values? no errors!
173
+ if (count === 0) return context.expects // no values? no errors!
140
174
 
141
175
  const type = count === 1 ? 'value' : 'values'
142
- throw new ExpectationError(context, negative, `to include ${count} ${type}`, {
176
+ throw new ExpectationError(context, `to include ${count} ${type}`, {
143
177
  diff: true,
144
178
  value: context.value,
145
179
  values,
146
180
  })
147
181
  }
148
182
 
149
- export function includesMappings(context: Expectations, negative: boolean, expected: Map<any, any>): void {
150
- context.toBeInstanceOf(Map)
183
+ function includesMappings(context: ExpectationsContext, expected: Map<any, any>): Expectations {
184
+ context.expects.toBeInstanceOf(Map)
151
185
  const actual: Map<any, any> = context.value as any
152
186
 
153
187
  // Get expected key set and process...
154
188
  const keys = new Set(expected.keys())
155
189
  const mappings: [ string, Diff ][] = []
156
190
 
157
- if (negative) {
191
+ if (context.negative) {
158
192
  // only consider keys... if they exist, fail!
159
193
  for (const key of keys) {
160
194
  if (actual.has(key)) {
@@ -173,10 +207,10 @@ export function includesMappings(context: Expectations, negative: boolean, expec
173
207
  }
174
208
 
175
209
  const count = mappings.length
176
- if (count === 0) return // no mappings? no errors!
210
+ if (count === 0) return context.expects // no mappings? no errors!
177
211
 
178
212
  const type = count === 1 ? 'mapping' : 'mappings'
179
- throw new ExpectationError(context, negative, `to include ${count} ${type}`, {
213
+ throw new ExpectationError(context, `to include ${count} ${type}`, {
180
214
  diff: true,
181
215
  value: context.value,
182
216
  mappings,
@@ -1,45 +1,106 @@
1
- import { ExpectationError, assertType } from './types'
2
-
3
- import type { Expectation, Expectations } from './expect'
4
- import type { Constructor, StringMatcher } from './types'
5
-
6
- export class ToThrow implements Expectation {
7
- expect(
8
- context: Expectations,
9
- negative: boolean,
10
- assert?: (errorExpectations: Expectations) => void,
11
- ): void {
12
- assertType(context, 'function')
13
-
14
- let thrown: boolean
15
- let error: unknown
16
- try {
17
- context.value()
18
- thrown = false
19
- error = undefined
20
- } catch (caught) {
21
- thrown = true
22
- error = caught
23
- }
24
-
25
- if (thrown === negative) {
26
- throw new ExpectationError(context, negative, 'to throw')
27
- } else if (thrown && assert) {
28
- assert(context.forValue(error))
29
- }
1
+ import { ExpectationError, assertContextType } from './types'
2
+
3
+ import type { Expectations } from './expect'
4
+ import type {
5
+ AssertionFunction,
6
+ Constructor,
7
+ ExpectationsContext,
8
+ JoinExpectations,
9
+ } from './types'
10
+
11
+ /* === TO THROW ============================================================= */
12
+
13
+ /** Expects the value to be a `function` throwing _anything_. */
14
+ function toThrow<T>(this: T): JoinExpectations<T, Function>
15
+
16
+ /**
17
+ * Expects the value to be a `function` throwing, and asserts the
18
+ * thrown value with the specified callback.
19
+ */
20
+ function toThrow<T>(this: T, assert: AssertionFunction): JoinExpectations<T, Function>
21
+
22
+ /* Overloaded function implementation */
23
+ function toThrow(
24
+ this: ExpectationsContext,
25
+ assert?: AssertionFunction,
26
+ ): Expectations {
27
+ assertContextType(this, 'function')
28
+
29
+ let thrown: boolean
30
+ let error: unknown
31
+ try {
32
+ this.value()
33
+ thrown = false
34
+ error = undefined
35
+ } catch (caught) {
36
+ thrown = true
37
+ error = caught
30
38
  }
31
- }
32
39
 
33
- export class ToThrowError implements Expectation {
34
- expect(
35
- context: Expectations,
36
- negative: boolean,
37
- ...args:
38
- | []
39
- | [ message: StringMatcher ]
40
- | [ constructor: Constructor<Error> ]
41
- | [ constructor: Constructor<Error>, message: StringMatcher ]
42
- ): void {
43
- context.negated(negative).toThrow((assert) => assert.toBeError(...args))
40
+ if (thrown === this.negative) {
41
+ throw new ExpectationError(this, 'to throw')
42
+ } else if (thrown && assert) {
43
+ assert(this.forValue(error))
44
44
  }
45
+
46
+ return this.expects
47
+ }
48
+
49
+ /* === TO THROW ERROR ======================================================= */
50
+
51
+ /** Expects the value to be a `function` throwing an {@link Error}. */
52
+ function toThrowError<T>(this: T): JoinExpectations<T, Function>
53
+
54
+ /**
55
+ * Expects the value to be a `function` throwing an {@link Error} with the
56
+ * specified _message_.
57
+ */
58
+ function toThrowError<T>(this: T, message: string): JoinExpectations<T, Function>
59
+
60
+ /**
61
+ * Expects the value to be a `function` throwing an {@link Error} with its
62
+ * _message_ matching the specified {@link RegExp}.
63
+ */
64
+ function toThrowError<T>(this: T, expession: RegExp): JoinExpectations<T, Function>
65
+
66
+ /**
67
+ * Expects the value to be a `function` throwing an {@link Error} of the
68
+ * specified _type_.
69
+ */
70
+ function toThrowError<T>(this: T, constructor: Constructor<Error>): JoinExpectations<T, Function>
71
+
72
+ /**
73
+ * Expects the value to be a `function` throwing an {@link Error} of the
74
+ * specified _type_ with the specified _message_.
75
+ */
76
+ function toThrowError<T>(this: T, constructor: Constructor<Error>, message: string): JoinExpectations<T, Function>
77
+
78
+ /**
79
+ * Expects the value to be a `function` throwing an {@link Error} of the
80
+ * specified _type_ with its _message_ matching the specified {@link RegExp}.
81
+ */
82
+ function toThrowError<T>(this: T, constructor: Constructor<Error>, expression: RegExp): JoinExpectations<T, Function>
83
+
84
+ /* Overloaded function implementation */
85
+ function toThrowError(
86
+ this: ExpectationsContext,
87
+ ...args:
88
+ | []
89
+ | [ string ]
90
+ | [ RegExp ]
91
+ | [ Constructor<Error> ]
92
+ | [ Constructor<Error>, string ]
93
+ | [ Constructor<Error>, RegExp ]
94
+ ): Expectations {
95
+ return this.negated.toThrow((assert) =>
96
+ // @ts-ignore // can't reconcile the types with overloads...
97
+ assert.toBeError(...args))
98
+ }
99
+
100
+ /* === EXPORTS ============================================================== */
101
+
102
+ /* coverage ignore next */
103
+ export {
104
+ toThrow,
105
+ toThrowError,
45
106
  }
@@ -0,0 +1,107 @@
1
+ import { ExpectationError, stringifyValue } from './types'
2
+
3
+ import type { Expectations } from './expect'
4
+ import type { ExpectationsContext } from './types'
5
+
6
+ /* Expects the value to be _defined_ (that is not `null` nor `undefined`). */
7
+ function toBeDefined<T>(this: T): T
8
+ function toBeDefined(this: ExpectationsContext): Expectations {
9
+ return check(this, 'to be defined', (value) => (value !== null) && (value !== undefined))
10
+ }
11
+
12
+ /* Expects the value strictly equal to `false`. */
13
+ function toBeFalse(): Expectations<false>
14
+ function toBeFalse(this: ExpectationsContext): Expectations {
15
+ return check(this, `to be ${stringifyValue(false)}`, (value) => value === false)
16
+ }
17
+
18
+ /* Expects the value to be _falsy_ (zero, empty string, `false`, ...). */
19
+ function toBeFalsy<T>(this: T): T
20
+ function toBeFalsy(this: ExpectationsContext): Expectations {
21
+ return check(this, 'to be falsy', (value) => ! value)
22
+ }
23
+
24
+ /* Expects the value to be `NaN`. */
25
+ function toBeNaN(): Expectations<number>
26
+ function toBeNaN(this: ExpectationsContext): Expectations {
27
+ return check(this, `to be ${stringifyValue(NaN)}`, (value) => (typeof value === 'number') && isNaN(value))
28
+ }
29
+
30
+ /* Expects the value to strictly equal to `-Infinity` (negative infinity). */
31
+ function toBeNegativeInfinity(): Expectations<number>
32
+ function toBeNegativeInfinity(this: ExpectationsContext): Expectations {
33
+ return check(this, `to equal ${stringifyValue(Number.NEGATIVE_INFINITY)}`, (value) => value === Number.NEGATIVE_INFINITY)
34
+ }
35
+
36
+ /* Expects the value to strictly equal to `null`. */
37
+ function toBeNull(): Expectations<null>
38
+ function toBeNull(this: ExpectationsContext): Expectations {
39
+ return check(this, `to be ${stringifyValue(null)}`, (value) => value === null)
40
+ }
41
+
42
+ /* Expects the value to strictly equal to `null` or `undefined`. */
43
+ function toBeNullable(): Expectations<null | undefined>
44
+ function toBeNullable(this: ExpectationsContext): Expectations {
45
+ return check(
46
+ this,
47
+ `to be ${stringifyValue(null)} or ${stringifyValue(undefined)}`,
48
+ (value) => ((value === null) || (value === undefined)))
49
+ }
50
+
51
+ /* Expects the value to strictly equal to `+Infinity` (positive infinity). */
52
+ function toBePositiveInfinity(): Expectations<number>
53
+ function toBePositiveInfinity(this: ExpectationsContext): Expectations {
54
+ return check(this, `to equal ${stringifyValue(Number.POSITIVE_INFINITY)}`, (value) => value === Number.POSITIVE_INFINITY)
55
+ }
56
+
57
+ /* Expects the value to strictly equal to `true`. */
58
+ function toBeTrue(): Expectations<true>
59
+ function toBeTrue(this: ExpectationsContext): Expectations {
60
+ return check(this, `to be ${stringifyValue(true)}`, (value) => value === true)
61
+ }
62
+
63
+ /* Expects the value to be _falsy_ (non-zero, non-empty string, `true`, ...). */
64
+ function toBeTruthy<T>(this: T): T
65
+ function toBeTruthy(this:ExpectationsContext): Expectations {
66
+ return check(this, 'to be truthy', (value) => !! value)
67
+ }
68
+
69
+ /* Expects the value to strictly equal to `undefined`. */
70
+ function toBeUndefined(): Expectations<undefined>
71
+ function toBeUndefined(this: ExpectationsContext): Expectations {
72
+ return check(this, `to be ${stringifyValue(undefined)}`, (value) => value === undefined)
73
+ }
74
+
75
+ /* === EXPORTS ============================================================== */
76
+
77
+ /* coverage ignore next */
78
+ export {
79
+ toBeDefined,
80
+ toBeFalse,
81
+ toBeFalsy,
82
+ toBeNaN,
83
+ toBeNegativeInfinity,
84
+ toBeNull,
85
+ toBeNullable,
86
+ toBePositiveInfinity,
87
+ toBeTrue,
88
+ toBeTruthy,
89
+ toBeUndefined,
90
+ }
91
+
92
+ /* ========================================================================== *
93
+ * INTERNALS *
94
+ * ========================================================================== */
95
+
96
+ function check(
97
+ context: ExpectationsContext,
98
+ details: string,
99
+ cb: (value: unknown) => boolean,
100
+ ): Expectations {
101
+ const match = cb(context.value)
102
+ if (match === context.negative) {
103
+ throw new ExpectationError(context, details)
104
+ } else {
105
+ return context.expects
106
+ }
107
+ }
@@ -1,5 +1,9 @@
1
1
  import type { Diff } from './diff'
2
- import type { Expectations, ExpectationsMatcher } from './expect'
2
+ import type { Expectations, Matchers, ExpectationFunctions } from './expect'
3
+
4
+ /* ========================================================================== *
5
+ * INTERNAL TYPES FOR EXPECTATIONS *
6
+ * ========================================================================== */
3
7
 
4
8
  /** A type identifying any constructor */
5
9
  export type Constructor<T = any> = new (...args: any[]) => T
@@ -14,11 +18,6 @@ export type NonArrayObject<T = any> = {
14
18
  [c: number]: never
15
19
  }
16
20
 
17
- /** A type identifying the parameter of `string.match(...)` */
18
- export type StringMatcher = string | RegExp | {
19
- [Symbol.match](string: string): RegExpMatchArray | null
20
- }
21
-
22
21
  /** Mappings for our _expanded_ {@link typeOf} implementation */
23
22
  export type TypeMappings = {
24
23
  // standard types, from "typeof"
@@ -45,6 +44,45 @@ export type TypeMappings = {
45
44
  /** Values returned by our own _expanded_ `{@link typeOf}` */
46
45
  export type TypeName = keyof TypeMappings
47
46
 
47
+ /** Join the asserted type of an {@link Expectations} with another type */
48
+ export type JoinExpectations<E, T2> =
49
+ E extends Expectations<infer T1> ? Expectations<T1 & T2> : Expectations<T2>
50
+
51
+ /** An assertion function, for sub-expectations and value introspection */
52
+ export type AssertionFunction<T = unknown> = (assert: Expectations<T>) => void | Expectations
53
+
54
+ /** Get the type asserted by an {@link AssertionFunction} */
55
+ export type AssertedType<F extends AssertionFunction<any>, R = ReturnType<F>> =
56
+ R extends Expectations<infer T> ? T : unknown
57
+
58
+ /** Internal context used by expectations functions to operate */
59
+ export interface ExpectationsContext<T = unknown> {
60
+ /** The value being expected */
61
+ readonly value: T,
62
+ /** Whether this is a negative or positive expectation */
63
+ readonly negative: boolean,
64
+ /** The optional parent of this instance, when constructed for a property */
65
+ readonly parent?: ExpectationsParent
66
+ /** The current _positive_ {@link Expectations} for the value */
67
+ readonly expects: Expectations<T>
68
+ /**
69
+ * If _negative_, the _negative_ {@link ExpectationFunctions} for the value,
70
+ * otherwise the _positive_ ones (basically, follow the `not` of `expect`).
71
+ */
72
+ readonly negated: ExpectationFunctions<T>
73
+
74
+ /** Create an {@link Expectation} instance for the specified value */
75
+ forValue<V>(value: V): Expectations<V>,
76
+ /** Create an {@link Expectation} instance for a property of this value */
77
+ forProperty(prop: string | number | symbol): Expectations
78
+ }
79
+
80
+ /** Parent expectations context (for properties) */
81
+ export interface ExpectationsParent {
82
+ context: ExpectationsContext,
83
+ prop: string | number | symbol,
84
+ }
85
+
48
86
  /* ========================================================================== *
49
87
  * TYPE INSPECTION, GUARD, AND ASSERTION *
50
88
  * ========================================================================== */
@@ -81,24 +119,14 @@ export function typeOf(value: unknown): TypeName {
81
119
  return 'object'
82
120
  }
83
121
 
84
- /** Determines if the specified `value` is of the specified _expanded_ `type` */
85
- export function isType<T extends keyof TypeMappings>(
86
- context: Expectations,
87
- type: T,
88
- ): context is Expectations<TypeMappings[T]> {
89
- return typeOf(context.value) === type
90
- }
91
-
92
122
  /** Asserts that the specified `value` is of the specified _expanded_ `type` */
93
- export function assertType<T extends keyof TypeMappings>(
94
- context: Expectations,
123
+ export function assertContextType<T extends keyof TypeMappings>(
124
+ context: ExpectationsContext,
95
125
  type: T,
96
- ): asserts context is Expectations<TypeMappings[T]> {
97
- const { value } = context
98
-
99
- if (typeOf(value) === type) return
126
+ ): asserts context is ExpectationsContext<TypeMappings[T]> {
127
+ if (typeOf(context.value) === type) return
100
128
 
101
- throw new ExpectationError(context, false, `to be ${prefixType(type)}`)
129
+ throw new ExpectationError(context, `to be ${prefixType(type)}`, false)
102
130
  }
103
131
 
104
132
  /* ========================================================================== *
@@ -215,7 +243,7 @@ export function prefixType(type: TypeName): string {
215
243
 
216
244
  export const matcherMarker = Symbol.for('plugjs:expect5:types:ExpectationsMatcher')
217
245
 
218
- export function isMatcher(what: any): what is ExpectationsMatcher {
246
+ export function isMatcher(what: any): what is Matchers {
219
247
  return what && what[matcherMarker] === matcherMarker
220
248
  }
221
249
 
@@ -226,12 +254,41 @@ export function isMatcher(what: any): what is ExpectationsMatcher {
226
254
  export class ExpectationError extends Error {
227
255
  diff?: Diff | undefined
228
256
 
257
+ /** Create an {@link ExpectationError} from a context and details message */
258
+ constructor(context: ExpectationsContext, details: string)
259
+
260
+ /**
261
+ * Create an {@link ExpectationError} from a context and details message,
262
+ * including an optional {@link Diff}
263
+ */
264
+ constructor(context: ExpectationsContext, details: string, diff?: Diff)
265
+
266
+ /**
267
+ * Create an {@link ExpectationError} from a context and details message,
268
+ * optionally forcing _negation_ to be as specified.
269
+ */
270
+ constructor(context: ExpectationsContext, details: string, forcedNegative?: boolean)
271
+
272
+ /**
273
+ * Create an {@link ExpectationError} from a context and details message,
274
+ * including an optional {@link Diff} and forcing _negation_ to be as
275
+ * specified.
276
+ */
277
+ constructor(context: ExpectationsContext, details: string, diff?: Diff, forcedNegative?: boolean)
278
+
279
+ /* Overloaded constructor implementation */
229
280
  constructor(
230
- context: Expectations,
231
- negative: boolean,
281
+ context: ExpectationsContext,
232
282
  details: string,
233
- diff?: Diff,
283
+ diffOrForcedNegative?: Diff | boolean,
284
+ maybeForcedNegative?: boolean,
234
285
  ) {
286
+ const diff = typeof diffOrForcedNegative === 'object' ? diffOrForcedNegative : null
287
+ const negative =
288
+ typeof diffOrForcedNegative === 'boolean' ? diffOrForcedNegative :
289
+ typeof maybeForcedNegative === 'boolean' ? maybeForcedNegative :
290
+ context.negative
291
+
235
292
  const { value } = context
236
293
  const not = negative ? ' not' : ''
237
294
 
package/src/index.ts CHANGED
@@ -16,6 +16,8 @@ export {
16
16
  export { skip } from './execution/executable'
17
17
  export { expect } from './expectation/expect'
18
18
 
19
+ export type { Expectations } from './expectation/expect'
20
+
19
21
  /* ========================================================================== *
20
22
  * EXPORTED OPTIONS TYPE AND PLUG DEFINITION *
21
23
  * ========================================================================== */