@serenity-js/assertions 3.0.0-rc.4 → 3.0.0-rc.40

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 (120) hide show
  1. package/CHANGELOG.md +63 -1850
  2. package/README.md +10 -8
  3. package/lib/Ensure.d.ts +56 -77
  4. package/lib/Ensure.d.ts.map +1 -0
  5. package/lib/Ensure.js +72 -95
  6. package/lib/Ensure.js.map +1 -1
  7. package/lib/expectations/and.d.ts +22 -1
  8. package/lib/expectations/and.d.ts.map +1 -0
  9. package/lib/expectations/and.js +26 -6
  10. package/lib/expectations/and.js.map +1 -1
  11. package/lib/expectations/contain.d.ts +26 -1
  12. package/lib/expectations/contain.d.ts.map +1 -0
  13. package/lib/expectations/contain.js +25 -1
  14. package/lib/expectations/contain.js.map +1 -1
  15. package/lib/expectations/containAtLeastOneItemThat.d.ts +23 -1
  16. package/lib/expectations/containAtLeastOneItemThat.d.ts.map +1 -0
  17. package/lib/expectations/containAtLeastOneItemThat.js +38 -10
  18. package/lib/expectations/containAtLeastOneItemThat.js.map +1 -1
  19. package/lib/expectations/containItemsWhereEachItem.d.ts +23 -1
  20. package/lib/expectations/containItemsWhereEachItem.d.ts.map +1 -0
  21. package/lib/expectations/containItemsWhereEachItem.js +38 -10
  22. package/lib/expectations/containItemsWhereEachItem.js.map +1 -1
  23. package/lib/expectations/endsWith.d.ts +20 -0
  24. package/lib/expectations/endsWith.d.ts.map +1 -0
  25. package/lib/expectations/endsWith.js +19 -0
  26. package/lib/expectations/endsWith.js.map +1 -1
  27. package/lib/expectations/equals.d.ts +26 -0
  28. package/lib/expectations/equals.d.ts.map +1 -0
  29. package/lib/expectations/equals.js +25 -0
  30. package/lib/expectations/equals.js.map +1 -1
  31. package/lib/expectations/includes.d.ts +20 -0
  32. package/lib/expectations/includes.d.ts.map +1 -0
  33. package/lib/expectations/includes.js +19 -0
  34. package/lib/expectations/includes.js.map +1 -1
  35. package/lib/expectations/index.d.ts +3 -0
  36. package/lib/expectations/index.d.ts.map +1 -0
  37. package/lib/expectations/index.js +7 -1
  38. package/lib/expectations/index.js.map +1 -1
  39. package/lib/expectations/isAfter.d.ts +40 -0
  40. package/lib/expectations/isAfter.d.ts.map +1 -0
  41. package/lib/expectations/isAfter.js +39 -0
  42. package/lib/expectations/isAfter.js.map +1 -1
  43. package/lib/expectations/isBefore.d.ts +40 -0
  44. package/lib/expectations/isBefore.d.ts.map +1 -0
  45. package/lib/expectations/isBefore.js +39 -0
  46. package/lib/expectations/isBefore.js.map +1 -1
  47. package/lib/expectations/isCloseTo.d.ts +24 -0
  48. package/lib/expectations/isCloseTo.d.ts.map +1 -0
  49. package/lib/expectations/isCloseTo.js +57 -0
  50. package/lib/expectations/isCloseTo.js.map +1 -0
  51. package/lib/expectations/isFalse.d.ts +19 -0
  52. package/lib/expectations/isFalse.d.ts.map +1 -0
  53. package/lib/expectations/isFalse.js +18 -0
  54. package/lib/expectations/isFalse.js.map +1 -1
  55. package/lib/expectations/isGreaterThan.d.ts +43 -0
  56. package/lib/expectations/isGreaterThan.d.ts.map +1 -0
  57. package/lib/expectations/isGreaterThan.js +42 -0
  58. package/lib/expectations/isGreaterThan.js.map +1 -1
  59. package/lib/expectations/isLessThan.d.ts +43 -0
  60. package/lib/expectations/isLessThan.d.ts.map +1 -0
  61. package/lib/expectations/isLessThan.js +42 -0
  62. package/lib/expectations/isLessThan.js.map +1 -1
  63. package/lib/expectations/isPresent.d.ts +64 -0
  64. package/lib/expectations/isPresent.d.ts.map +1 -0
  65. package/lib/expectations/isPresent.js +99 -0
  66. package/lib/expectations/isPresent.js.map +1 -0
  67. package/lib/expectations/isTrue.d.ts +19 -0
  68. package/lib/expectations/isTrue.d.ts.map +1 -0
  69. package/lib/expectations/isTrue.js +18 -0
  70. package/lib/expectations/isTrue.js.map +1 -1
  71. package/lib/expectations/matches.d.ts +21 -1
  72. package/lib/expectations/matches.d.ts.map +1 -0
  73. package/lib/expectations/matches.js +19 -0
  74. package/lib/expectations/matches.js.map +1 -1
  75. package/lib/expectations/not.d.ts +23 -1
  76. package/lib/expectations/not.d.ts.map +1 -0
  77. package/lib/expectations/not.js +32 -12
  78. package/lib/expectations/not.js.map +1 -1
  79. package/lib/expectations/or.d.ts +22 -1
  80. package/lib/expectations/or.d.ts.map +1 -0
  81. package/lib/expectations/or.js +40 -15
  82. package/lib/expectations/or.js.map +1 -1
  83. package/lib/expectations/property.d.ts +61 -1
  84. package/lib/expectations/property.d.ts.map +1 -0
  85. package/lib/expectations/property.js +66 -10
  86. package/lib/expectations/property.js.map +1 -1
  87. package/lib/expectations/startsWith.d.ts +20 -0
  88. package/lib/expectations/startsWith.d.ts.map +1 -0
  89. package/lib/expectations/startsWith.js +19 -0
  90. package/lib/expectations/startsWith.js.map +1 -1
  91. package/lib/index.d.ts +1 -1
  92. package/lib/index.d.ts.map +1 -0
  93. package/lib/index.js +5 -5
  94. package/lib/index.js.map +1 -1
  95. package/package.json +17 -41
  96. package/src/Ensure.ts +81 -101
  97. package/src/expectations/and.ts +39 -18
  98. package/src/expectations/contain.ts +26 -2
  99. package/src/expectations/containAtLeastOneItemThat.ts +59 -14
  100. package/src/expectations/containItemsWhereEachItem.ts +59 -14
  101. package/src/expectations/endsWith.ts +19 -0
  102. package/src/expectations/equals.ts +25 -0
  103. package/src/expectations/includes.ts +19 -0
  104. package/src/expectations/index.ts +2 -0
  105. package/src/expectations/isAfter.ts +39 -0
  106. package/src/expectations/isBefore.ts +39 -0
  107. package/src/expectations/isCloseTo.ts +66 -0
  108. package/src/expectations/isFalse.ts +19 -1
  109. package/src/expectations/isGreaterThan.ts +42 -0
  110. package/src/expectations/isLessThan.ts +42 -0
  111. package/src/expectations/isPresent.ts +107 -0
  112. package/src/expectations/isTrue.ts +19 -1
  113. package/src/expectations/matches.ts +20 -1
  114. package/src/expectations/not.ts +36 -15
  115. package/src/expectations/or.ts +49 -26
  116. package/src/expectations/property.ts +78 -21
  117. package/src/expectations/startsWith.ts +19 -0
  118. package/src/index.ts +0 -1
  119. package/tsconfig.build.json +10 -0
  120. package/tsconfig.eslint.json +0 -10
package/src/Ensure.ts CHANGED
@@ -1,173 +1,153 @@
1
1
  import {
2
+ Activity,
2
3
  Answerable,
3
4
  AnswersQuestions,
4
5
  AssertionError,
5
6
  CollectsArtifacts,
7
+ d,
6
8
  Expectation,
7
9
  ExpectationMet,
8
10
  ExpectationNotMet,
9
11
  ExpectationOutcome,
12
+ f,
10
13
  Interaction,
11
14
  LogicError,
12
15
  RuntimeError,
13
16
  UsesAbilities,
14
17
  } from '@serenity-js/core';
15
- import { formatted } from '@serenity-js/core/lib/io';
18
+ import { FileSystemLocation } from '@serenity-js/core/lib/io';
16
19
  import { inspected } from '@serenity-js/core/lib/io/inspected';
17
20
  import { Artifact, AssertionReport, Name } from '@serenity-js/core/lib/model';
18
21
  import { match } from 'tiny-types';
19
22
 
20
23
  /**
21
- * @desc
22
- * Used to perform verification of the system under test.
24
+ * The {@apilink Interaction|interaction} to `Ensure`
25
+ * verifies if the resolved value of the provided {@apilink Answerable}
26
+ * meets the specified {@apilink Expectation}.
27
+ * If not, it throws an {@apilink AssertionError}.
23
28
  *
24
- * Resolves any `Answerable` describing the actual
25
- * state and ensures that its value meets the {@link @serenity-js/core/lib/screenplay/questions~Expectation}s provided.
29
+ * Use `Ensure` to verify the state of the system under test.
26
30
  *
27
- * @example <caption>Usage with static values</caption>
28
- * import { actorCalled } from '@serenity-js/core';
29
- * import { Ensure, equals } from '@serenity-js/assertions';
31
+ * ## Basic usage with static values
32
+ * ```ts
33
+ * import { actorCalled } from '@serenity-js/core'
34
+ * import { Ensure, equals } from '@serenity-js/assertions'
30
35
  *
31
- * const actor = actorCalled('Erica');
36
+ * await actorCalled('Erica').attemptsTo(
37
+ * Ensure.that('Hello world!', equals('Hello world!'))
38
+ * )
39
+ * ```
32
40
  *
33
- * actor.attemptsTo(
34
- * Ensure.that('Hello world!', equals('Hello world!'))
35
- * );
41
+ * ## Composing expectations with `and`
36
42
  *
37
- * @example <caption>Composing expectations with `and`</caption>
38
- * import { actorCalled } from '@serenity-js/core';
39
- * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions';
43
+ * ```ts
44
+ * import { actorCalled } from '@serenity-js/core'
45
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
40
46
  *
41
- * const actor = actorCalled('Erica');
47
+ * await actorCalled('Erica').attemptsTo(
48
+ * Ensure.that('Hello world!', and(startsWith('Hello'), endsWith('!'))
49
+ * )
50
+ * ```
42
51
  *
43
- * actor.attemptsTo(
44
- * Ensure.that('Hello world!', and(startsWith('Hello'), endsWith('!'))
45
- * );
52
+ * ## Overriding the type of Error thrown upon assertion failure
46
53
  *
47
- * @example <caption>Overriding the type of Error thrown upon assertion failure</caption>
48
- * import { actorCalled, TestCompromisedError } from '@serenity-js/core';
49
- * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions';
50
- * import { CallAnApi, GetRequest, LastResponse, Send } from '@serenity-js/rest';
54
+ * ```ts
55
+ * import { actorCalled, TestCompromisedError } from '@serenity-js/core'
56
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
57
+ * import { CallAnApi, GetRequest, LastResponse, Send } from '@serenity-js/rest'
51
58
  *
52
- * const actor = actorCalled('Erica')
53
- * .whoCan(CallAnApi.at('https://example.com'));
59
+ * await actorCalled('Erica')
60
+ * .whoCan(CallAnApi.at('https://example.com'))
61
+ * .attemptsTo(
62
+ * Send.a(GetRequest.to('/api/health')),
63
+ * Ensure.that(LastResponse.status(), equals(200))
64
+ * .otherwiseFailWith(TestCompromisedError, 'The server is down, please cheer it up!')
65
+ * )
66
+ * ```
54
67
  *
55
- * actor.attemptsTo(
56
- * Send.a(GetRequest.to('/api/health')),
57
- * Ensure.that(LastResponse.status(), equals(200))
58
- * .otherwiseFailWith(TestCompromisedError, 'The server is down, please cheer it up!')
59
- * );
60
- *
61
- * @extends {@serenity-js/core/lib/screenplay~Interaction}
68
+ * @group Interactions
62
69
  */
63
70
  export class Ensure<Actual> extends Interaction {
71
+
64
72
  /**
73
+ * @param {Answerable<Actual_Type>} actual
74
+ * An {@apilink Answerable} describing the actual state of the system.
65
75
  *
66
- * @param {@serenity-js/core/lib/screenplay~Answerable<T>} actual
67
- * @param {@serenity-js/core/lib/screenplay/questions~Expectation<any, A>} expectation
76
+ * @param {Expectation<Actual_Type>} expectation
77
+ * An {@apilink Expectation} you expect the `actual` value to meet
68
78
  *
69
- * @returns {Ensure<A>}
79
+ * @returns {Ensure<Actual_Type>}
70
80
  */
71
- static that<A>(actual: Answerable<A>, expectation: Expectation<any, A>): Ensure<A> {
72
- return new Ensure(actual, expectation);
81
+ static that<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): Ensure<Actual_Type> {
82
+ return new Ensure(actual, expectation, Activity.callerLocation(5));
73
83
  }
74
84
 
75
85
  /**
76
- * @param {@serenity-js/core/lib/screenplay~Answerable<T>} actual
77
- * @param {@serenity-js/core/lib/screenplay/questions~Expectation<T>} expectation
86
+ * @param actual
87
+ * @param expectation
88
+ * @param location
78
89
  */
79
- constructor(
90
+ protected constructor(
80
91
  protected readonly actual: Answerable<Actual>,
81
92
  protected readonly expectation: Expectation<Actual>,
93
+ location: FileSystemLocation,
82
94
  ) {
83
- super();
95
+ super(d`#actor ensures that ${ actual } does ${ expectation }`, location);
84
96
  }
85
97
 
86
98
  /**
87
- * @desc
88
- * Makes the provided {@link @serenity-js/core/lib/screenplay/actor~Actor}
89
- * perform this {@link @serenity-js/core/lib/screenplay~Interaction}.
90
- *
91
- * @param {UsesAbilities & CollectsArtifacts & AnswersQuestions} actor
92
- * @returns {Promise<void>}
93
- *
94
- * @see {@link @serenity-js/core/lib/screenplay/actor~Actor}
95
- * @see {@link @serenity-js/core/lib/screenplay/actor~UsesAbilities}
96
- * @see {@link @serenity-js/core/lib/screenplay/actor~CollectsArtifacts}
97
- * @see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions}
99
+ * @inheritDoc
98
100
  */
99
- performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void> {
100
- return Promise.all([
101
- actor.answer(this.actual),
102
- actor.answer(this.expectation),
103
- ]).then(([ actual, expectation ]) =>
104
- expectation(actual).then(outcome =>
105
- match<ExpectationOutcome<unknown, Actual>, void>(outcome)
106
- .when(ExpectationNotMet, o => {
107
- actor.collect(this.artifactFrom(o.expected, o.actual), new Name(`Assertion Report`));
101
+ async performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void> {
102
+ const outcome = await actor.answer(this.expectation.isMetFor(this.actual));
108
103
 
109
- throw this.errorForOutcome(o);
110
- })
111
- .when(ExpectationMet, _ => void 0)
112
- .else(o => {
113
- throw new LogicError(formatted `An Expectation should return an instance of an ExpectationOutcome, not ${ o }`);
114
- }),
115
- ),
116
- );
117
- }
104
+ return match<ExpectationOutcome<unknown, Actual>, void>(outcome)
105
+ .when(ExpectationNotMet, o => {
106
+ actor.collect(this.artifactFrom(o.expected, o.actual), new Name(`Assertion Report`));
118
107
 
119
- /**
120
- * @desc
121
- * Generates a description to be used when reporting this {@link @serenity-js/core/lib/screenplay~Activity}.
122
- *
123
- * @returns {string}
124
- */
125
- toString(): string {
126
- return formatted `#actor ensures that ${ this.actual } does ${ this.expectation }`;
108
+ throw this.errorForOutcome(o);
109
+ })
110
+ .when(ExpectationMet, _ => void 0)
111
+ .else(o => {
112
+ throw new LogicError(f`Expectation#isMetFor(actual) should return an instance of an ExpectationOutcome, not ${ o }`);
113
+ });
127
114
  }
128
115
 
129
116
  /**
130
- * @desc
131
- * Overrides the default {@link @serenity-js/core/lib/errors~AssertionError} thrown when
132
- * the actual value does not meet the expectations set.
117
+ * Overrides the default {@apilink AssertionError} thrown when
118
+ * the actual value does not meet the expectation.
133
119
  *
134
- * @param {Function} typeOfRuntimeError
135
- * The type of RuntimeError to throw, i.e. TestCompromisedError
120
+ * @param typeOfRuntimeError
121
+ * A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
136
122
  *
137
- * @param {string} message
123
+ * @param message
138
124
  * The message explaining the failure
139
- *
140
- * @returns {@serenity-js/core/lib/screenplay~Interaction}
141
125
  */
142
126
  otherwiseFailWith(typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, message?: string): Interaction {
143
127
  return new EnsureOrFailWithCustomError(this.actual, this.expectation, typeOfRuntimeError, message);
144
128
  }
145
129
 
146
130
  /**
147
- * @desc
148
- * Maps an {@link @serenity-js/core/lib/screenplay/questions/expectations~ExpectationOutcome} to appropriate {@link @serenity-js/core/lib/errors~RuntimeError}.
149
- *
150
- * @param {@serenity-js/core/lib/screenplay/questions/expectations~ExpectationOutcome} outcome
151
- * @returns {@serenity-js/core/lib/errors~RuntimeError}
152
- *
153
- * @protected
131
+ * Maps an {@apilink ExpectationOutcome} to appropriate {@apilink RuntimeError}.
154
132
  */
155
133
  protected errorForOutcome(outcome: ExpectationOutcome<any, Actual>): RuntimeError {
156
134
  return this.asAssertionError(outcome);
157
135
  }
158
136
 
159
137
  /**
160
- * @desc
161
- * Maps an {@link Outcome} to {@link @serenity-js/core/lib/errors~AssertionError}.
162
- *
163
- * @param {Outcome} outcome
164
- * @returns {@serenity-js/core/lib/errors~AssertionError}
138
+ * Maps an {@apilink Outcome} to {@apilink AssertionError}.
165
139
  *
166
- * @protected
140
+ * @param outcome
167
141
  */
168
142
  protected asAssertionError(outcome: ExpectationOutcome<any, Actual>): AssertionError {
143
+ const actualDescription = d`${ this.actual }`;
144
+ const inspectedActual = inspected(outcome.actual, { inline: true, markQuestions: false });
145
+ const message = actualDescription === inspectedActual
146
+ ? `Expected ${ actualDescription } to ${ outcome.message }`
147
+ : `Expected ${ actualDescription } to ${ outcome.message } but got ${ inspectedActual }`;
148
+
169
149
  return new AssertionError(
170
- `Expected ${ formatted`${ this.actual }` } to ${ outcome.message }`,
150
+ message,
171
151
  outcome.expected,
172
152
  outcome.actual,
173
153
  );
@@ -191,7 +171,7 @@ class EnsureOrFailWithCustomError<Actual> extends Ensure<Actual> {
191
171
  private readonly typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError,
192
172
  private readonly message?: string,
193
173
  ) {
194
- super(actual, expectation);
174
+ super(actual, expectation, Activity.callerLocation(6));
195
175
  }
196
176
 
197
177
  protected errorForOutcome(outcome: ExpectationOutcome<any, Actual>): RuntimeError {
@@ -1,29 +1,50 @@
1
- import { AnswersQuestions, Expectation, ExpectationNotMet, ExpectationOutcome } from '@serenity-js/core';
1
+ import { Answerable, AnswersQuestions, Expectation, ExpectationNotMet } from '@serenity-js/core';
2
2
  import { match } from 'tiny-types';
3
3
 
4
- export function and<Actual>(...expectations: Array<Expectation<any, Actual>>): Expectation<any, Actual> {
4
+ /**
5
+ * Creates an {@apilink Expectation|expectation} that is met when all the `expectations` are met for the given actual value.
6
+ *
7
+ * Use `and` to combine several expectations using logical "and",
8
+ *
9
+ * ## Combining several expectations
10
+ *
11
+ * ```ts
12
+ * import { actorCalled } from '@serenity-js/core'
13
+ * import { Ensure, and, startsWith, endsWith } from '@serenity-js/assertions'
14
+ *
15
+ * await actorCalled('Ester').attemptsTo(
16
+ * Ensure.that('Hello World!', and(startsWith('Hello'), endsWith('!'))),
17
+ * )
18
+ * ```
19
+ *
20
+ * @param expectations
21
+ *
22
+ * @group Expectations
23
+ */
24
+ export function and<Actual_Type>(...expectations: Array<Expectation<Actual_Type>>): Expectation<Actual_Type> {
5
25
  return new And(expectations);
6
26
  }
7
27
 
8
28
  /**
9
29
  * @package
10
30
  */
11
- class And<Actual> extends Expectation<any, Actual> {
12
- constructor(private readonly expectations: Array<Expectation<any, Actual>>) {
13
- super(expectations.map(assertion => assertion.toString()).join(' and '));
14
- }
15
-
16
- answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<ExpectationOutcome<any, Actual>> {
31
+ class And<Actual> extends Expectation<Actual> {
32
+ private static readonly Separator = ' and ';
17
33
 
18
- return (actual: any) =>
19
- this.expectations.reduce(
20
- (previous, current) =>
21
- previous.then(outcome =>
22
- match(outcome)
23
- .when(ExpectationNotMet, o => o)
24
- .else(_ => current.answeredBy(actor)(actual)),
25
- ),
26
- Promise.resolve(void 0),
27
- );
34
+ constructor(private readonly expectations: Array<Expectation<Actual>>) {
35
+ super(
36
+ expectations.map(expectation => expectation.toString()).join(And.Separator),
37
+ (actor: AnswersQuestions, actual: Answerable<Actual>) => {
38
+ return expectations.reduce(
39
+ (previous, current) =>
40
+ previous.then(outcome =>
41
+ match(outcome)
42
+ .when(ExpectationNotMet, o => o)
43
+ .else(_ => actor.answer(current.isMetFor(actual))),
44
+ ),
45
+ Promise.resolve(void 0),
46
+ );
47
+ }
48
+ );
28
49
  }
29
50
  }
@@ -1,7 +1,31 @@
1
1
  import { Answerable, Expectation } from '@serenity-js/core';
2
2
  import { equal } from 'tiny-types/lib/objects';
3
3
 
4
- export function contain<Item>(expected: Answerable<Item>): Expectation<Item, Item[]> {
4
+ /**
5
+ * Produces an {@apilink Expectation|expectation} that is met when the actual array of `Item[]` contains
6
+ * at least one `Item` that is equal to the resolved value of `expected`.
7
+ *
8
+ * Note that the equality check performs comparison **by value**
9
+ * using [TinyTypes `equal`](https://github.com/jan-molak/tiny-types/blob/master/src/objects/equal.ts).
10
+ *
11
+ * ## Ensuring that the array contains the given item
12
+ *
13
+ * ```ts
14
+ * import { actorCalled } from '@serenity-js/core'
15
+ * import { Ensure, and, startsWith, endsWith } from '@serenity-js/assertions'
16
+ *
17
+ * const items = [ { name: 'apples' }, { name: 'bananas' } ]
18
+ *
19
+ * await actorCalled('Ester').attemptsTo(
20
+ * Ensure.that(items, contain({ name: 'bananas' })),
21
+ * )
22
+ * ```
23
+ *
24
+ * @param expected
25
+ *
26
+ * @group Expectations
27
+ */
28
+ export function contain<Item>(expected: Answerable<Item>): Expectation<Item[]> {
5
29
  return Expectation.thatActualShould<Item, Item[]>('contain', expected)
6
- .soThat((actualValue, expectedValue) => !! ~ actualValue.findIndex(av => equal(av, expectedValue)));
30
+ .soThat((actualValue, expectedValue) => actualValue.some(item => equal(item, expectedValue)));
7
31
  }
@@ -1,26 +1,71 @@
1
- import { AnswersQuestions, Expectation, ExpectationMet, ExpectationNotMet, ExpectationOutcome } from '@serenity-js/core';
2
- import { formatted } from '@serenity-js/core/lib/io';
1
+ import { Answerable, AnswersQuestions, d, Expectation, ExpectationMet, ExpectationNotMet, ExpectationOutcome } from '@serenity-js/core';
3
2
 
4
- export function containAtLeastOneItemThat<Actual>(expectation: Expectation<any, Actual>): Expectation<any, Actual[]> {
3
+ /**
4
+ * Produces an {@apilink Expectation|expectation} that is met when the actual array of `Item[]` contains
5
+ * at least one `Item` for which the `expectation` is met.
6
+ *
7
+ * ## Ensuring that at least one item in an array meets the expectation
8
+ *
9
+ * ```ts
10
+ * import { actorCalled } from '@serenity-js/core'
11
+ * import { Ensure, containAtLeastOneItemThat, isGreaterThan } from '@serenity-js/assertions'
12
+ *
13
+ * const items = [ 10, 15, 20 ]
14
+ *
15
+ * await actorCalled('Ester').attemptsTo(
16
+ * Ensure.that(items, containAtLeastOneItemThat(isGreaterThan(18))),
17
+ * )
18
+ * ```
19
+ *
20
+ * @param expectation
21
+ *
22
+ * @group Expectations
23
+ */
24
+ export function containAtLeastOneItemThat<Item>(expectation: Expectation<Item>): Expectation<Item[]> {
5
25
  return new ContainAtLeastOneItemThatMeetsExpectation(expectation);
6
26
  }
7
27
 
8
28
  /**
9
29
  * @package
10
30
  */
11
- class ContainAtLeastOneItemThatMeetsExpectation<Expected, Actual> extends Expectation<Expected, Actual[]> {
12
- constructor(private readonly expectation: Expectation<Expected, Actual>) {
13
- super(formatted `contain at least one item that does ${ expectation }`);
31
+ class ContainAtLeastOneItemThatMeetsExpectation<Item> extends Expectation<Item[]> {
32
+
33
+ private static descriptionFor(expectation: Expectation<any>) {
34
+ return d`contain at least one item that does ${ expectation }`;
14
35
  }
15
36
 
16
- answeredBy(actor: AnswersQuestions): (actual: Actual[]) => Promise<ExpectationOutcome<Expected, Actual[]>> {
17
- return (actual: Actual[]) =>
18
- actual.length === 0
19
- ? Promise.resolve(new ExpectationNotMet(this.toString(), undefined, actual))
20
- : Promise.all(actual.map(item => this.expectation.answeredBy(actor)(item)))
21
- .then(results => results.some(result => result instanceof ExpectationMet)
22
- ? new ExpectationMet(this.toString(), results[0].expected, actual)
23
- : new ExpectationNotMet(this.toString(), results[0].expected, actual),
37
+ constructor(private readonly expectation: Expectation<Item>) {
38
+ super(
39
+ ContainAtLeastOneItemThatMeetsExpectation.descriptionFor(expectation),
40
+ async (actor: AnswersQuestions, actual: Answerable<Item[]>) => {
41
+
42
+ const items: Item[] = await actor.answer(actual);
43
+
44
+ if (! items || items.length === 0) {
45
+ return new ExpectationNotMet(
46
+ ContainAtLeastOneItemThatMeetsExpectation.descriptionFor(expectation),
47
+ undefined,
48
+ items,
24
49
  );
50
+ }
51
+
52
+ let outcome: ExpectationOutcome<unknown, Item>;
53
+
54
+ for (const item of items) {
55
+
56
+ outcome = await actor.answer(expectation.isMetFor(item))
57
+
58
+ if (outcome instanceof ExpectationMet) {
59
+ return new ExpectationMet(
60
+ ContainAtLeastOneItemThatMeetsExpectation.descriptionFor(expectation),
61
+ outcome.expected,
62
+ items
63
+ );
64
+ }
65
+ }
66
+
67
+ return new ExpectationNotMet(ContainAtLeastOneItemThatMeetsExpectation.descriptionFor(expectation), outcome.expected, items);
68
+ }
69
+ );
25
70
  }
26
71
  }
@@ -1,26 +1,71 @@
1
- import { AnswersQuestions, Expectation, ExpectationMet, ExpectationNotMet, ExpectationOutcome } from '@serenity-js/core';
2
- import { formatted } from '@serenity-js/core/lib/io';
1
+ import { Answerable, AnswersQuestions, d, Expectation, ExpectationMet, ExpectationNotMet, ExpectationOutcome } from '@serenity-js/core';
3
2
 
4
- export function containItemsWhereEachItem<Actual>(expectation: Expectation<any, Actual>): Expectation<any, Actual[]> {
3
+ /**
4
+ * Produces an {@apilink Expectation|expectation} that is met when all the items of the actual array of `Item[]`
5
+ * meet the `expectation`.
6
+ *
7
+ * ## Ensuring that all the items in an array meet the expectation
8
+ *
9
+ * ```ts
10
+ * import { actorCalled } from '@serenity-js/core'
11
+ * import { Ensure, containItemsWhereEachItem, endsWith } from '@serenity-js/assertions'
12
+ *
13
+ * const items = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday' ]
14
+ *
15
+ * await actorCalled('Ester').attemptsTo(
16
+ * Ensure.that(items, containItemsWhereEachItem(endsWith('day'))),
17
+ * )
18
+ * ```
19
+ *
20
+ * @param expectation
21
+ *
22
+ * @group Expectations
23
+ */
24
+ export function containItemsWhereEachItem<Actual>(expectation: Expectation<Actual>): Expectation<Actual[]> {
5
25
  return new ContainItemsWhereEachItemMeetsExpectation(expectation);
6
26
  }
7
27
 
8
28
  /**
9
29
  * @package
10
30
  */
11
- class ContainItemsWhereEachItemMeetsExpectation<Expected, Actual> extends Expectation<Expected, Actual[]> {
12
- constructor(private readonly expectation: Expectation<Expected, Actual>) {
13
- super(formatted `contain items where each item does ${ expectation }`);
31
+ class ContainItemsWhereEachItemMeetsExpectation<Actual> extends Expectation<Actual[]> {
32
+
33
+ private static descriptionFor(expectation: Expectation<any>) {
34
+ return d`contain items where each item does ${ expectation }`;
14
35
  }
15
36
 
16
- answeredBy(actor: AnswersQuestions): (actual: Actual[]) => Promise<ExpectationOutcome<Expected, Actual[]>> {
17
- return (actual: Actual[]) =>
18
- actual.length === 0
19
- ? Promise.resolve(new ExpectationNotMet(this.toString(), undefined, actual))
20
- : Promise.all(actual.map(item => this.expectation.answeredBy(actor)(item)))
21
- .then(results => results.every(result => result instanceof ExpectationMet)
22
- ? new ExpectationMet(this.toString(), results[0].expected, actual)
23
- : new ExpectationNotMet(this.toString(), results[0].expected, actual),
37
+ constructor(private readonly expectation: Expectation<Actual>) {
38
+ super(
39
+ ContainItemsWhereEachItemMeetsExpectation.descriptionFor(expectation),
40
+ async (actor: AnswersQuestions, actual: Answerable<Actual[]>) => {
41
+
42
+ const items: Actual[] = await actor.answer(actual);
43
+
44
+ if (! items || items.length === 0) {
45
+ return new ExpectationNotMet(
46
+ ContainItemsWhereEachItemMeetsExpectation.descriptionFor(expectation),
47
+ undefined,
48
+ items,
24
49
  );
50
+ }
51
+
52
+ let outcome: ExpectationOutcome<unknown, Actual>;
53
+
54
+ for (const item of items) {
55
+
56
+ outcome = await actor.answer(expectation.isMetFor(item))
57
+
58
+ if (outcome instanceof ExpectationNotMet) {
59
+ return new ExpectationNotMet(
60
+ ContainItemsWhereEachItemMeetsExpectation.descriptionFor(expectation),
61
+ outcome.expected,
62
+ items
63
+ );
64
+ }
65
+ }
66
+
67
+ return new ExpectationMet(ContainItemsWhereEachItemMeetsExpectation.descriptionFor(expectation), outcome.expected, items);
68
+ }
69
+ );
25
70
  }
26
71
  }
@@ -1,5 +1,24 @@
1
1
  import { Answerable, Expectation } from '@serenity-js/core';
2
2
 
3
+ /**
4
+ * Creates an {@apilink Expectation|expectation} that is met when the actual `string` value
5
+ * ends with the resolved value of `expected`.
6
+ *
7
+ * ## Ensuring that a given string ends with an expected substring
8
+ *
9
+ * ```ts
10
+ * import { actorCalled } from '@serenity-js/core'
11
+ * import { Ensure, endsWith } from '@serenity-js/assertions'
12
+ *
13
+ * await actorCalled('Ester').attemptsTo(
14
+ * Ensure.that('Hello World!', endsWith('!')),
15
+ * )
16
+ * ```
17
+ *
18
+ * @param expected
19
+ *
20
+ * @group Expectations
21
+ */
3
22
  export function endsWith(expected: Answerable<string>): Expectation<string> {
4
23
  return Expectation.thatActualShould<string, string>('end with', expected)
5
24
  .soThat((actualValue, expectedValue) => actualValue.endsWith(expectedValue));
@@ -1,6 +1,31 @@
1
1
  import { Answerable, Expectation } from '@serenity-js/core';
2
2
  import { equal } from 'tiny-types/lib/objects';
3
3
 
4
+ /**
5
+ * Produces an {@apilink Expectation|expectation} that is met when the actual value
6
+ * is equal to the resolved value of `expectedValue`.
7
+ *
8
+ * Note that the equality check performs comparison **by value**
9
+ * using [TinyTypes `equal`](https://github.com/jan-molak/tiny-types/blob/master/src/objects/equal.ts).
10
+ *
11
+ * ## Ensuring that the actual value equals expected value
12
+ *
13
+ * ```ts
14
+ * import { actorCalled } from '@serenity-js/core'
15
+ * import { Ensure, equals } from '@serenity-js/assertions'
16
+ *
17
+ * const actual = { name: 'apples' }
18
+ * const expected = { name: 'apples' }
19
+ *
20
+ * await actorCalled('Ester').attemptsTo(
21
+ * Ensure.that(actual, equals(expected)),
22
+ * )
23
+ * ```
24
+ *
25
+ * @param expectedValue
26
+ *
27
+ * @group Expectations
28
+ */
4
29
  export function equals<Expected>(expectedValue: Answerable<Expected>): Expectation<Expected> {
5
30
  return Expectation.thatActualShould<Expected, Expected>('equal', expectedValue)
6
31
  .soThat((actual, expected) => equal(actual, expected));
@@ -1,5 +1,24 @@
1
1
  import { Answerable, Expectation } from '@serenity-js/core';
2
2
 
3
+ /**
4
+ * Creates an {@apilink Expectation|expectation} that is met when the actual `string` value
5
+ * includes a substring of `expected`.
6
+ *
7
+ * ## Ensuring that a given string includes the expected substring
8
+ *
9
+ * ```ts
10
+ * import { actorCalled } from '@serenity-js/core'
11
+ * import { Ensure, includes } from '@serenity-js/assertions'
12
+ *
13
+ * await actorCalled('Ester').attemptsTo(
14
+ * Ensure.that('Hello World!', includes('World')),
15
+ * )
16
+ * ```
17
+ *
18
+ * @param expected
19
+ *
20
+ * @group Expectations
21
+ */
3
22
  export function includes(expected: Answerable<string>): Expectation<string> {
4
23
  return Expectation.thatActualShould<string, string>('include', expected)
5
24
  .soThat((actualValue, expectedValue) => actualValue.includes(expectedValue));
@@ -7,9 +7,11 @@ export * from './equals';
7
7
  export * from './includes';
8
8
  export * from './isAfter';
9
9
  export * from './isBefore';
10
+ export * from './isCloseTo';
10
11
  export * from './isFalse';
11
12
  export * from './isGreaterThan';
12
13
  export * from './isLessThan';
14
+ export * from './isPresent';
13
15
  export * from './isTrue';
14
16
  export * from './matches';
15
17
  export * from './not';