@serenity-js/core 2.33.1 → 3.0.0-rc.11

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 (210) hide show
  1. package/CHANGELOG.md +476 -0
  2. package/lib/index.d.ts +2 -1
  3. package/lib/index.js +6 -1
  4. package/lib/index.js.map +1 -1
  5. package/lib/io/ErrorSerialiser.js +4 -1
  6. package/lib/io/ErrorSerialiser.js.map +1 -1
  7. package/lib/io/ErrorStackParser.d.ts +2 -2
  8. package/lib/io/ErrorStackParser.js.map +1 -1
  9. package/lib/io/asyncMap.d.ts +8 -0
  10. package/lib/io/asyncMap.js +18 -0
  11. package/lib/io/asyncMap.js.map +1 -0
  12. package/lib/io/format.d.ts +39 -0
  13. package/lib/io/format.js +51 -0
  14. package/lib/io/format.js.map +1 -0
  15. package/lib/io/formatted.d.ts +5 -1
  16. package/lib/io/formatted.js +6 -13
  17. package/lib/io/formatted.js.map +1 -1
  18. package/lib/io/index.d.ts +2 -1
  19. package/lib/io/index.js +2 -1
  20. package/lib/io/index.js.map +1 -1
  21. package/lib/io/inspected.d.ts +9 -1
  22. package/lib/io/inspected.js +52 -15
  23. package/lib/io/inspected.js.map +1 -1
  24. package/lib/model/Timestamp.d.ts +4 -2
  25. package/lib/model/Timestamp.js +8 -2
  26. package/lib/model/Timestamp.js.map +1 -1
  27. package/lib/screenplay/Optional.d.ts +29 -0
  28. package/lib/{io/collections/reducible.js → screenplay/Optional.js} +1 -1
  29. package/lib/screenplay/Optional.js.map +1 -0
  30. package/lib/screenplay/Question.d.ts +41 -82
  31. package/lib/screenplay/Question.js +132 -100
  32. package/lib/screenplay/Question.js.map +1 -1
  33. package/lib/screenplay/actor/Actor.js +2 -2
  34. package/lib/screenplay/actor/Actor.js.map +1 -1
  35. package/lib/screenplay/index.d.ts +1 -1
  36. package/lib/screenplay/index.js +1 -1
  37. package/lib/screenplay/index.js.map +1 -1
  38. package/lib/screenplay/interactions/index.d.ts +0 -1
  39. package/lib/screenplay/interactions/index.js +0 -1
  40. package/lib/screenplay/interactions/index.js.map +1 -1
  41. package/lib/screenplay/questions/Check.d.ts +3 -3
  42. package/lib/screenplay/questions/Check.js +5 -7
  43. package/lib/screenplay/questions/Check.js.map +1 -1
  44. package/lib/screenplay/questions/Expectation.d.ts +15 -10
  45. package/lib/screenplay/questions/Expectation.js +28 -37
  46. package/lib/screenplay/questions/Expectation.js.map +1 -1
  47. package/lib/screenplay/questions/List.d.ts +22 -192
  48. package/lib/screenplay/questions/List.js +160 -208
  49. package/lib/screenplay/questions/List.js.map +1 -1
  50. package/lib/screenplay/questions/Note.d.ts +10 -0
  51. package/lib/screenplay/questions/Note.js +17 -1
  52. package/lib/screenplay/questions/Note.js.map +1 -1
  53. package/lib/screenplay/questions/index.d.ts +0 -3
  54. package/lib/screenplay/questions/index.js +0 -5
  55. package/lib/screenplay/questions/index.js.map +1 -1
  56. package/lib/stage/index.d.ts +0 -2
  57. package/lib/stage/index.js +0 -2
  58. package/lib/stage/index.js.map +1 -1
  59. package/package.json +7 -6
  60. package/src/index.ts +2 -1
  61. package/src/io/ErrorSerialiser.ts +5 -1
  62. package/src/io/ErrorStackParser.ts +2 -1
  63. package/src/io/asyncMap.ts +18 -0
  64. package/src/io/format.ts +49 -0
  65. package/src/io/formatted.ts +7 -15
  66. package/src/io/index.ts +2 -1
  67. package/src/io/inspected.ts +68 -15
  68. package/src/model/Timestamp.ts +10 -2
  69. package/src/screenplay/Optional.ts +30 -0
  70. package/src/screenplay/Question.ts +206 -124
  71. package/src/screenplay/actor/Actor.ts +2 -2
  72. package/src/screenplay/index.ts +1 -1
  73. package/src/screenplay/interactions/index.ts +0 -1
  74. package/src/screenplay/questions/Check.ts +10 -15
  75. package/src/screenplay/questions/Expectation.ts +47 -55
  76. package/src/screenplay/questions/List.ts +224 -233
  77. package/src/screenplay/questions/Note.ts +21 -1
  78. package/src/screenplay/questions/index.ts +0 -3
  79. package/src/stage/index.ts +0 -2
  80. package/lib/io/collections/index.d.ts +0 -2
  81. package/lib/io/collections/index.js +0 -15
  82. package/lib/io/collections/index.js.map +0 -1
  83. package/lib/io/collections/mappable.d.ts +0 -52
  84. package/lib/io/collections/mappable.js +0 -28
  85. package/lib/io/collections/mappable.js.map +0 -1
  86. package/lib/io/collections/reducible.d.ts +0 -16
  87. package/lib/io/collections/reducible.js.map +0 -1
  88. package/lib/screenplay/interactions/See.d.ts +0 -31
  89. package/lib/screenplay/interactions/See.js +0 -43
  90. package/lib/screenplay/interactions/See.js.map +0 -1
  91. package/lib/screenplay/questions/Property.d.ts +0 -91
  92. package/lib/screenplay/questions/Property.js +0 -99
  93. package/lib/screenplay/questions/Property.js.map +0 -1
  94. package/lib/screenplay/questions/Transform.d.ts +0 -31
  95. package/lib/screenplay/questions/Transform.js +0 -46
  96. package/lib/screenplay/questions/Transform.js.map +0 -1
  97. package/lib/screenplay/questions/lists/ArrayListAdapter.d.ts +0 -88
  98. package/lib/screenplay/questions/lists/ArrayListAdapter.js +0 -152
  99. package/lib/screenplay/questions/lists/ArrayListAdapter.js.map +0 -1
  100. package/lib/screenplay/questions/lists/ListAdapter.d.ts +0 -20
  101. package/lib/screenplay/questions/lists/ListAdapter.js +0 -3
  102. package/lib/screenplay/questions/lists/ListAdapter.js.map +0 -1
  103. package/lib/screenplay/questions/lists/index.d.ts +0 -2
  104. package/lib/screenplay/questions/lists/index.js +0 -15
  105. package/lib/screenplay/questions/lists/index.js.map +0 -1
  106. package/lib/screenplay/questions/mappings/AnswerMappingFunction.d.ts +0 -11
  107. package/lib/screenplay/questions/mappings/AnswerMappingFunction.js +0 -3
  108. package/lib/screenplay/questions/mappings/AnswerMappingFunction.js.map +0 -1
  109. package/lib/screenplay/questions/mappings/index.d.ts +0 -2
  110. package/lib/screenplay/questions/mappings/index.js +0 -15
  111. package/lib/screenplay/questions/mappings/index.js.map +0 -1
  112. package/lib/screenplay/questions/mappings/string/append.d.ts +0 -14
  113. package/lib/screenplay/questions/mappings/string/append.js +0 -25
  114. package/lib/screenplay/questions/mappings/string/append.js.map +0 -1
  115. package/lib/screenplay/questions/mappings/string/index.d.ts +0 -11
  116. package/lib/screenplay/questions/mappings/string/index.js +0 -24
  117. package/lib/screenplay/questions/mappings/string/index.js.map +0 -1
  118. package/lib/screenplay/questions/mappings/string/normalize.d.ts +0 -20
  119. package/lib/screenplay/questions/mappings/string/normalize.js +0 -30
  120. package/lib/screenplay/questions/mappings/string/normalize.js.map +0 -1
  121. package/lib/screenplay/questions/mappings/string/replace.d.ts +0 -17
  122. package/lib/screenplay/questions/mappings/string/replace.js +0 -30
  123. package/lib/screenplay/questions/mappings/string/replace.js.map +0 -1
  124. package/lib/screenplay/questions/mappings/string/slice.d.ts +0 -28
  125. package/lib/screenplay/questions/mappings/string/slice.js +0 -47
  126. package/lib/screenplay/questions/mappings/string/slice.js.map +0 -1
  127. package/lib/screenplay/questions/mappings/string/split.d.ts +0 -19
  128. package/lib/screenplay/questions/mappings/string/split.js +0 -36
  129. package/lib/screenplay/questions/mappings/string/split.js.map +0 -1
  130. package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.d.ts +0 -17
  131. package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.js +0 -28
  132. package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.js.map +0 -1
  133. package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.d.ts +0 -17
  134. package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.js +0 -29
  135. package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.js.map +0 -1
  136. package/lib/screenplay/questions/mappings/string/toLowerCase.d.ts +0 -10
  137. package/lib/screenplay/questions/mappings/string/toLowerCase.js +0 -19
  138. package/lib/screenplay/questions/mappings/string/toLowerCase.js.map +0 -1
  139. package/lib/screenplay/questions/mappings/string/toNumber.d.ts +0 -10
  140. package/lib/screenplay/questions/mappings/string/toNumber.js +0 -18
  141. package/lib/screenplay/questions/mappings/string/toNumber.js.map +0 -1
  142. package/lib/screenplay/questions/mappings/string/toUpperCase.d.ts +0 -10
  143. package/lib/screenplay/questions/mappings/string/toUpperCase.js +0 -19
  144. package/lib/screenplay/questions/mappings/string/toUpperCase.js.map +0 -1
  145. package/lib/screenplay/questions/mappings/string/trim.d.ts +0 -12
  146. package/lib/screenplay/questions/mappings/string/trim.js +0 -21
  147. package/lib/screenplay/questions/mappings/string/trim.js.map +0 -1
  148. package/lib/screenplay/questions/proxies/PropertyPathKey.d.ts +0 -4
  149. package/lib/screenplay/questions/proxies/PropertyPathKey.js +0 -3
  150. package/lib/screenplay/questions/proxies/PropertyPathKey.js.map +0 -1
  151. package/lib/screenplay/questions/proxies/createMetaQuestionProxy.d.ts +0 -14
  152. package/lib/screenplay/questions/proxies/createMetaQuestionProxy.js +0 -35
  153. package/lib/screenplay/questions/proxies/createMetaQuestionProxy.js.map +0 -1
  154. package/lib/screenplay/questions/proxies/createQuestionProxy.d.ts +0 -13
  155. package/lib/screenplay/questions/proxies/createQuestionProxy.js +0 -34
  156. package/lib/screenplay/questions/proxies/createQuestionProxy.js.map +0 -1
  157. package/lib/screenplay/questions/proxies/describePath.d.ts +0 -5
  158. package/lib/screenplay/questions/proxies/describePath.js +0 -19
  159. package/lib/screenplay/questions/proxies/describePath.js.map +0 -1
  160. package/lib/screenplay/questions/proxies/index.d.ts +0 -2
  161. package/lib/screenplay/questions/proxies/index.js +0 -15
  162. package/lib/screenplay/questions/proxies/index.js.map +0 -1
  163. package/lib/screenplay/questions/proxies/key.d.ts +0 -8
  164. package/lib/screenplay/questions/proxies/key.js +0 -16
  165. package/lib/screenplay/questions/proxies/key.js.map +0 -1
  166. package/lib/screenplay/tasks/Loop.d.ts +0 -198
  167. package/lib/screenplay/tasks/Loop.js +0 -222
  168. package/lib/screenplay/tasks/Loop.js.map +0 -1
  169. package/lib/screenplay/tasks/index.d.ts +0 -1
  170. package/lib/screenplay/tasks/index.js +0 -14
  171. package/lib/screenplay/tasks/index.js.map +0 -1
  172. package/lib/stage/DressingRoom.d.ts +0 -37
  173. package/lib/stage/DressingRoom.js +0 -53
  174. package/lib/stage/DressingRoom.js.map +0 -1
  175. package/lib/stage/WithStage.d.ts +0 -51
  176. package/lib/stage/WithStage.js +0 -3
  177. package/lib/stage/WithStage.js.map +0 -1
  178. package/src/io/collections/index.ts +0 -2
  179. package/src/io/collections/mappable.ts +0 -60
  180. package/src/io/collections/reducible.ts +0 -16
  181. package/src/screenplay/interactions/See.ts +0 -45
  182. package/src/screenplay/questions/Property.ts +0 -98
  183. package/src/screenplay/questions/Transform.ts +0 -51
  184. package/src/screenplay/questions/lists/ArrayListAdapter.ts +0 -186
  185. package/src/screenplay/questions/lists/ListAdapter.ts +0 -33
  186. package/src/screenplay/questions/lists/index.ts +0 -2
  187. package/src/screenplay/questions/mappings/AnswerMappingFunction.ts +0 -13
  188. package/src/screenplay/questions/mappings/index.ts +0 -2
  189. package/src/screenplay/questions/mappings/string/append.ts +0 -28
  190. package/src/screenplay/questions/mappings/string/index.ts +0 -11
  191. package/src/screenplay/questions/mappings/string/normalize.ts +0 -33
  192. package/src/screenplay/questions/mappings/string/replace.ts +0 -34
  193. package/src/screenplay/questions/mappings/string/slice.ts +0 -53
  194. package/src/screenplay/questions/mappings/string/split.ts +0 -38
  195. package/src/screenplay/questions/mappings/string/toLocaleLowerCase.ts +0 -31
  196. package/src/screenplay/questions/mappings/string/toLocaleUpperCase.ts +0 -30
  197. package/src/screenplay/questions/mappings/string/toLowerCase.ts +0 -20
  198. package/src/screenplay/questions/mappings/string/toNumber.ts +0 -19
  199. package/src/screenplay/questions/mappings/string/toUpperCase.ts +0 -20
  200. package/src/screenplay/questions/mappings/string/trim.ts +0 -22
  201. package/src/screenplay/questions/proxies/PropertyPathKey.ts +0 -4
  202. package/src/screenplay/questions/proxies/createMetaQuestionProxy.ts +0 -51
  203. package/src/screenplay/questions/proxies/createQuestionProxy.ts +0 -49
  204. package/src/screenplay/questions/proxies/describePath.ts +0 -23
  205. package/src/screenplay/questions/proxies/index.ts +0 -2
  206. package/src/screenplay/questions/proxies/key.ts +0 -14
  207. package/src/screenplay/tasks/Loop.ts +0 -240
  208. package/src/screenplay/tasks/index.ts +0 -1
  209. package/src/stage/DressingRoom.ts +0 -53
  210. package/src/stage/WithStage.ts +0 -52
@@ -1,260 +1,251 @@
1
- import { formatted } from '../../io';
2
- import { AnswersQuestions, UsesAbilities } from '../actor';
1
+ import { LogicError } from '../../errors';
2
+ import { d } from '../../io';
3
+ import { Actor, AnswersQuestions, UsesAbilities } from '../actor';
3
4
  import { Answerable } from '../Answerable';
4
- import { Question } from '../Question';
5
+ import { Question, QuestionAdapter } from '../Question';
6
+ import { Task } from '../Task';
5
7
  import { Expectation } from './Expectation';
6
- import { ArrayListAdapter, ListAdapter } from './lists';
8
+ import { ExpectationMet } from './expectations';
7
9
  import { MetaQuestion } from './MetaQuestion';
8
10
 
11
+ export class List<Item_Type> extends Question<Promise<Item_Type[]>> {
12
+ private subject: string;
13
+
14
+ static of<IT>(collection: Answerable<Array<IT>>): List<IT> {
15
+ return new List<IT>(collection);
16
+ }
17
+
18
+ constructor(
19
+ protected readonly collection: Answerable<Array<Item_Type>>,
20
+ ) {
21
+ super();
22
+ this.subject = d`${ collection }`;
23
+ }
24
+
25
+ eachMappedTo<Mapped_Item_Type>(
26
+ question: MetaQuestion<Item_Type, Promise<Mapped_Item_Type> | Mapped_Item_Type>,
27
+ ): List<Mapped_Item_Type> {
28
+ return new List(
29
+ new EachMappedTo(this.collection, question, this.subject)
30
+ );
31
+ }
32
+
33
+ forEach(callback: (current: CurrentItem<Item_Type>, index: number, items: Array<Item_Type>) => Promise<void> | void): Task {
34
+ return new ForEachLoop(this.collection, this.subject, callback);
35
+ }
36
+
37
+ where<Answer_Type>(
38
+ question: MetaQuestion<Item_Type, Promise<Answer_Type> | Answer_Type>,
39
+ expectation: Expectation<Answer_Type>
40
+ ): this {
41
+ return new List<Item_Type>(
42
+ new Where(this.collection, question, expectation, this.subject)
43
+ ) as this;
44
+ }
45
+
46
+ count(): QuestionAdapter<number> {
47
+ return Question.about(`the number of ${ this.subject }`, async actor => {
48
+ const items = await this.answeredBy(actor);
49
+ return items.length;
50
+ });
51
+ }
52
+
53
+ first(): QuestionAdapter<Item_Type> {
54
+ return Question.about(`the first of ${ this.subject }`, async actor => {
55
+ const items = await this.answeredBy(actor);
56
+
57
+ if (items.length === 0) {
58
+ throw new LogicError(d`Can't retrieve the first item from a list with 0 items: ${ items }`)
59
+ }
60
+
61
+ return items[0];
62
+ });
63
+ }
64
+
65
+ last(): QuestionAdapter<Item_Type> {
66
+ return Question.about(`the last of ${ this.subject }`, async actor => {
67
+ const items = await this.answeredBy(actor);
68
+
69
+ if (items.length === 0) {
70
+ throw new LogicError(d`Can't retrieve the last item from a list with 0 items: ${ items }`)
71
+ }
72
+
73
+ return items[items.length - 1];
74
+ });
75
+ }
76
+
77
+ get(index: number): QuestionAdapter<Item_Type> {
78
+ return Question.about(`the ${ ordinal(index + 1) } of ${ this.subject }`, async actor => {
79
+ const items = await this.answeredBy(actor);
80
+
81
+ if (index < 0 || items.length <= index) {
82
+ throw new LogicError(`Can't retrieve the ${ ordinal(index) } item from a list with ${ items.length } items: ` + d`${ items }`)
83
+ }
84
+
85
+ return items[index];
86
+ });
87
+ }
88
+
89
+ async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Array<Item_Type>> {
90
+ const collection = await actor.answer(this.collection);
91
+
92
+ if (! Array.isArray(collection)) {
93
+ throw new LogicError(d`A List has to wrap an Array-compatible object. ${ collection } given.`);
94
+ }
95
+
96
+ return collection;
97
+ }
98
+
99
+ describedAs(subject: string): this {
100
+ this.subject = subject;
101
+ return this;
102
+ }
103
+
104
+ toString(): string {
105
+ return this.subject;
106
+ }
107
+ }
108
+
9
109
  /**
10
- * @desc
11
- * Filters an {@link Answerable} list of items based on the criteria provided.
12
- * Instantiate via {@link List.of}
13
- *
14
- * @example <caption>Example data structure</caption>
15
- * interface TestAccount {
16
- * username: string;
17
- * role: string;
18
- * environments: string[];
19
- * }
20
- *
21
- * const testAccounts: TestAccount[] = [
22
- * {
23
- * "username": "tester.one@example.com",
24
- * "role": "test-automation"
25
- * "environments": [ "dev", "sit" ],
26
- * },
27
- * {
28
- * "username": "tester.two@example.com",
29
- * "role": "test-automation"
30
- * "environments": [ "dev", "sit", "prod" ],
31
- * },
32
- * {
33
- * "username": "release.bot@example.com",
34
- * "role": "release-automation"
35
- * "environments": [ "dev", "sit", "prod" ],
36
- * }
37
- * ]
38
- *
39
- * @example <caption>Using with Property</caption>
40
- * import { actorCalled, List, Property } from '@serenity-js/core';
41
- * import { contain, Ensure, equals } from '@serenity-js/assertions';
42
- *
43
- * actorCalled('Lisa').attemptsTo(
44
- * Ensure.that(
45
- * Property.of(
46
- * List.of(testAccounts)
47
- * .where(Property.at<TestAccount>().environments, contain('prod'))
48
- * .where(Property.at<TestAccount>().role, equals('test-automation'))
49
- * .first()
50
- * ).username,
51
- * equals('tester.two@example.com')
52
- * )
53
- * )
54
- *
55
- * @extends {Question}
56
- * @see {@link MetaQuestion}
110
+ * @package
111
+ * @param {number} index
57
112
  */
58
- export class List<
59
- List_Adapter_Type extends ListAdapter<Item_Type, Collection_Type, Item_Return_Type, Collection_Return_Type>,
60
- Item_Type,
61
- Collection_Type,
62
- Item_Return_Type = Item_Type,
63
- Collection_Return_Type = Collection_Type,
64
- >
65
- extends Question<Collection_Return_Type>
113
+ function ordinal(index: number): string {
114
+ const
115
+ lastDigit = Math.abs(index) % 10,
116
+ lastTwoDigits = Math.abs(index) % 100;
117
+
118
+ switch (true) {
119
+ case (lastDigit === 1 && lastTwoDigits !== 11):
120
+ return index + 'st';
121
+ case (lastDigit === 2 && lastTwoDigits !== 12):
122
+ return index + 'nd';
123
+ case (lastDigit === 3 && lastTwoDigits !== 13):
124
+ return index + 'rd';
125
+ default:
126
+ return index + 'th';
127
+ }
128
+ }
129
+
130
+ /**
131
+ * @package
132
+ */
133
+ class Where<Item_Type, Answer_Type>
134
+ extends Question<Promise<Array<Item_Type>>>
66
135
  {
136
+ private subject: string;
67
137
 
68
- /**
69
- * @desc
70
- * Instantiates a new {@link List} configured to support standard {@link Array}.
71
- *
72
- * **Please note:** Don't use `List.of` to wrap `Question<ElementArrayFinder>` returned by
73
- * [`Target.all`](/modules/protractor/class/src/screenplay/questions/targets/Target.ts~Target.html#static-method-all).
74
- * Instead, use [`Target.all(...).located(...).where(...)`](/modules/protractor/class/src/screenplay/questions/targets/Target.ts~Target.html),
75
- * which uses a Protractor-specific {@link ListAdapter}.
76
- *
77
- * @param {Answerable<Item_Type[]>} items
78
- *
79
- * @returns {List<ArrayListAdapter<Item_Type>, Item_Type, Item_Type[], Promise<Item_Type>, Promise<Item_Type[]>>}
80
- */
81
- static of<Item_Type>(items: Answerable<Item_Type[]>): List<ArrayListAdapter<Item_Type>, Item_Type, Item_Type[], Promise<Item_Type>, Promise<Item_Type[]>> {
82
- return new List<ArrayListAdapter<Item_Type>, Item_Type, Item_Type[], Promise<Item_Type>, Promise<Item_Type[]>>(
83
- new ArrayListAdapter(items)
84
- );
138
+ constructor(
139
+ private readonly collection: Answerable<Array<Item_Type>>,
140
+ private readonly question: MetaQuestion<Item_Type, Promise<Answer_Type> | Answer_Type>,
141
+ private readonly expectation: Expectation<Answer_Type>,
142
+ originalSubject: string,
143
+ ) {
144
+ super();
145
+
146
+ const prefix = this.collection instanceof Where
147
+ ? ' and'
148
+ : ' where';
149
+
150
+ this.subject = originalSubject + prefix + d` ${ question } does ${ expectation }`;
85
151
  }
86
152
 
87
- /**
88
- * @param {List_Adapter_Type} collection
89
- */
90
- constructor(private readonly collection: List_Adapter_Type) {
91
- super(formatted`${ collection }`);
153
+ async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Array<Item_Type>> {
154
+ try {
155
+ const collection = await actor.answer(this.collection);
156
+ const results: Item_Type[] = [];
157
+
158
+ for (const item of collection) {
159
+ const actual = this.question.of(item) as Answerable<Answer_Type>;
160
+ const expectationOutcome = await actor.answer(this.expectation.isMetFor(actual));
161
+
162
+ if (expectationOutcome instanceof ExpectationMet) {
163
+ results.push(item);
164
+ }
165
+ }
166
+
167
+ return results;
168
+ } catch (error) {
169
+ throw new LogicError(d`Couldn't check if ${ this.question } of an item of ${ this.collection } does ${ this.expectation }`, error);
170
+ }
92
171
  }
93
172
 
94
- /**
95
- * @desc
96
- * Returns the number of items left after applying any filters,
97
- *
98
- * @example <caption>Counting items</caption>
99
- * import { actorCalled, List } from '@serenity-js/core';
100
- * import { Ensure, equals, property } from '@serenity-js/assertions';
101
- *
102
- * actorCalled('Lisa').attemptsTo(
103
- * Ensure.that(
104
- * List.of(testAccounts).count(),
105
- * equals(3)
106
- * )
107
- * )
108
- *
109
- * @returns {Question<Promise<number>>}
110
- *
111
- * @see {@link List#where}
112
- */
113
- count(): Question<Promise<number>> {
114
- return Question.about(`the number of ${ this.collection.toString() }`, actor =>
115
- this.collection.count(actor)
116
- );
173
+ describedAs(subject: string): this {
174
+ this.subject = subject;
175
+ return this;
117
176
  }
118
177
 
119
- /**
120
- * @desc
121
- * Returns the first of items left after applying any filters,
122
- *
123
- * @example <caption>Retrieving the first item</caption>
124
- * import { actorCalled, List } from '@serenity-js/core';
125
- * import { Ensure, equals, property } from '@serenity-js/assertions';
126
- *
127
- * actorCalled('Lisa').attemptsTo(
128
- * Ensure.that(
129
- * List.of(testAccounts).first(),
130
- * property('username', equals('tester.one@example.com'))
131
- * )
132
- * )
133
- *
134
- * @returns {Question<Item_Return_Type>}
135
- *
136
- * @see {@link List#where}
137
- */
138
- first(): Question<Item_Return_Type> {
139
- return Question.about(`the first of ${ this.collection.toString() }`, actor =>
140
- this.collection.first(actor)
141
- );
178
+ toString(): string {
179
+ return this.subject;
142
180
  }
181
+ }
143
182
 
144
- /**
145
- * @desc
146
- * Returns the last of items left after applying any filters,
147
- *
148
- * @example <caption>Retrieving the last item</caption>
149
- * import { actorCalled, List } from '@serenity-js/core';
150
- * import { Ensure, equals, property } from '@serenity-js/assertions';
151
- *
152
- * actorCalled('Lisa').attemptsTo(
153
- * Ensure.that(
154
- * List.of(testAccounts).last(),
155
- * property('username', equals('release.bot@example.com'))
156
- * )
157
- * )
158
- *
159
- * @returns {Question<Item_Return_Type>}
160
- *
161
- * @see {@link List#where}
162
- */
163
- last(): Question<Item_Return_Type> {
164
- return Question.about(`the last of ${ this.collection.toString() }`, actor =>
165
- this.collection.last(actor)
166
- );
183
+ /**
184
+ * @package
185
+ */
186
+ class EachMappedTo<Item_Type, Mapped_Item_Type> extends Question<Promise<Array<Mapped_Item_Type>>> {
187
+
188
+ private subject: string;
189
+
190
+ constructor(
191
+ private readonly collection: Answerable<Array<Item_Type>>,
192
+ private readonly mapping: MetaQuestion<Item_Type, Promise<Mapped_Item_Type> | Mapped_Item_Type>,
193
+ originalSubject: string,
194
+ ) {
195
+ super();
196
+
197
+ this.subject = originalSubject + d` mapped to ${ this.mapping }`;
167
198
  }
168
199
 
169
- /**
170
- * @desc
171
- * Returns the nth of the items left after applying any filters,
172
- *
173
- * @example <caption>Retrieving the nth item</caption>
174
- * import { actorCalled, List } from '@serenity-js/core';
175
- * import { Ensure, equals, property } from '@serenity-js/assertions';
176
- *
177
- * actorCalled('Lisa').attemptsTo(
178
- * Ensure.that(
179
- * List.of(testAccounts).get(1),
180
- * property('username', equals('tester.two@example.com'))
181
- * )
182
- * )
183
- *
184
- * @param {number} index
185
- * Zero-based index of the item to return
186
- *
187
- * @returns {Question<Item_Return_Type>}
188
- *
189
- * @see {@link List#where}
190
- */
191
- get(index: number): Question<Item_Return_Type> {
192
- return Question.about(`the ${ List.ordinalSuffixOf(index + 1) } of ${ this.collection }`, actor =>
193
- this.collection.get(actor, index)
194
- );
200
+ async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Array<Mapped_Item_Type>> {
201
+ const collection: Array<Item_Type> = await actor.answer(this.collection);
202
+
203
+ const mapped: Mapped_Item_Type[] = [];
204
+
205
+ for (const item of collection) {
206
+ mapped.push(await actor.answer(this.mapping.of(item)))
207
+ }
208
+
209
+ return mapped;
195
210
  }
196
211
 
197
- /**
198
- * @desc
199
- * Filters the underlying collection so that the result contains only those elements that meet the {@link Expectation}
200
- *
201
- * @example <caption>Filtering a list</caption>
202
- * import { actorCalled, List, Property } from '@serenity-js/core';
203
- * import { contain, Ensure, equals, property } from '@serenity-js/assertions';
204
- *
205
- * actorCalled('Lisa').attemptsTo(
206
- * Ensure.that(
207
- * List.of(testAccounts)
208
- * .where(Property.at<TestAccount>().environments, contain('prod'))
209
- * .where(Property.at<TestAccount>().role, equals('test-automation'))
210
- * .first(),
211
- * property('username', equals('tester.two@example.com'))
212
- * )
213
- * )
214
- *
215
- * @param {MetaQuestion<Item_Type, Promise<Answer_Type> | Answer_Type>} question
216
- * @param {Expectation<any, Answer_Type>} expectation
217
- *
218
- * @returns {List<List_Adapter_Type, Item_Type, Collection_Type, Item_Return_Type, Collection_Return_Type>}
219
- */
220
- where<Answer_Type>(
221
- question: MetaQuestion<Item_Type, Promise<Answer_Type> | Answer_Type>,
222
- expectation: Expectation<any, Answer_Type>,
223
- ): List<List_Adapter_Type, Item_Type, Collection_Type, Item_Return_Type, Collection_Return_Type> {
224
- return new List<List_Adapter_Type, Item_Type, Collection_Type, Item_Return_Type, Collection_Return_Type>(
225
- this.collection.withFilter(question, expectation) as List_Adapter_Type,
226
- );
212
+ describedAs(subject: string): this {
213
+ this.subject = subject;
214
+ return this;
227
215
  }
228
216
 
229
- /**
230
- * @desc
231
- * Makes the provided {@link Actor} answer this {@link Question} and return the underlying collection.
232
- *
233
- * @param {AnswersQuestions & UsesAbilities} actor
234
- * @returns {Collection_Return_Type}
235
- *
236
- * @see {@link Actor}
237
- * @see {@link AnswersQuestions}
238
- * @see {@link UsesAbilities}
239
- */
240
- answeredBy(actor: AnswersQuestions & UsesAbilities): Collection_Return_Type {
241
- return this.collection.items(actor);
217
+ toString(): string {
218
+ return this.subject;
242
219
  }
220
+ }
243
221
 
244
- private static ordinalSuffixOf(index: number): string {
245
- const
246
- lastDigit = index % 10,
247
- lastTwoDigits = index % 100;
248
-
249
- switch (true) {
250
- case (lastDigit === 1 && lastTwoDigits !== 11):
251
- return index + 'st';
252
- case (lastDigit === 2 && lastTwoDigits !== 12):
253
- return index + 'nd';
254
- case (lastDigit === 3 && lastTwoDigits !== 13):
255
- return index + 'rd';
256
- default:
257
- return index + 'th';
222
+ /**
223
+ * @package
224
+ */
225
+ class ForEachLoop<Item_Type> extends Task {
226
+
227
+ constructor(
228
+ private readonly collection: Answerable<Array<Item_Type>>,
229
+ private readonly subject: string,
230
+ private readonly fn: (current: CurrentItem<Item_Type>, index: number, items: Array<Item_Type>) => Promise<void> | void,
231
+ ) {
232
+ super();
233
+ }
234
+
235
+ async performAs(actor: Actor): Promise<void> {
236
+ const collection: Array<Item_Type> = await actor.answer(this.collection);
237
+
238
+ for (const [index, item] of collection.entries()) {
239
+ await this.fn({ actor, item }, index, collection);
258
240
  }
259
241
  }
242
+
243
+ toString(): string {
244
+ return `#actor iterates over ${ this.subject }`;
245
+ }
246
+ }
247
+
248
+ export interface CurrentItem<Item_Type> {
249
+ item: Item_Type;
250
+ actor: Actor;
260
251
  }
@@ -1,3 +1,4 @@
1
+ import { formatted } from '../../io';
1
2
  import { TakeNotes } from '../abilities';
2
3
  import { AnswersQuestions, UsesAbilities } from '../actor';
3
4
  import { Question } from '../Question';
@@ -42,6 +43,8 @@ import { Question } from '../Question';
42
43
  */
43
44
  export class Note<Answer> extends Question<Promise<Answer>> {
44
45
 
46
+ private subject: string;
47
+
45
48
  /**
46
49
  * @desc
47
50
  * Retrieves the previously recorded answer to a given {@link Question}
@@ -58,7 +61,8 @@ export class Note<Answer> extends Question<Promise<Answer>> {
58
61
  * @param {Question<Promise<Answer>> | Question<Answer> | string} topic
59
62
  */
60
63
  constructor(private readonly topic: Question<Promise<Answer>> | Question<Answer> | string) {
61
- super(`a note of ${ topic }`);
64
+ super();
65
+ this.subject = formatted `a note of ${ topic }`;
62
66
  }
63
67
 
64
68
  /**
@@ -76,4 +80,20 @@ export class Note<Answer> extends Question<Promise<Answer>> {
76
80
  answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Answer> {
77
81
  return TakeNotes.as(actor).answerTo(this.topic);
78
82
  }
83
+
84
+ /**
85
+ * @desc
86
+ * Changes the description of this question's subject.
87
+ *
88
+ * @param {string} subject
89
+ * @returns {Question<T>}
90
+ */
91
+ describedAs(subject: string): this {
92
+ this.subject = subject;
93
+ return this;
94
+ }
95
+
96
+ toString(): string {
97
+ return this.subject;
98
+ }
79
99
  }
@@ -2,9 +2,6 @@ export * from './Check';
2
2
  export * from './Expectation';
3
3
  export * from './expectations';
4
4
  export * from './List';
5
- export * from './mappings';
6
5
  export * from './MetaQuestion';
7
6
  export * from './Note';
8
- export { Property } from './Property';
9
7
  export * from './q';
10
- export * from './Transform';
@@ -1,11 +1,9 @@
1
1
  export * from './Cast';
2
2
  export * from './Clock';
3
3
  export * from './crew';
4
- export * from './DressingRoom';
5
4
  export * from './ListensToDomainEvents';
6
5
  export * from './Stage';
7
6
  export * from './StageCrewMember';
8
7
  export * from './StageCrewMemberBuilder';
9
8
  export * from './StageCrewMemberBuilderDependencies';
10
9
  export * from './StageManager';
11
- export * from './WithStage';
@@ -1,2 +0,0 @@
1
- export * from './mappable';
2
- export * from './reducible';
@@ -1,15 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
- };
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- __exportStar(require("./mappable"), exports);
14
- __exportStar(require("./reducible"), exports);
15
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/io/collections/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6CAA2B;AAC3B,8CAA4B"}
@@ -1,52 +0,0 @@
1
- /**
2
- * @desc
3
- * Describes a collection providing
4
- * a [`map`-like interface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
5
- *
6
- * @interface
7
- *
8
- * @see {@link Question#map}
9
- * @see {@link ElementArrayFinder}
10
- */
11
- export interface Mappable<Item> {
12
- /**
13
- * @desc
14
- * Applies a {@link MappingFunction} function to each element of a {@link Mappable} collection.
15
- *
16
- * The callback receives an item from the collection as the first argument
17
- * and its index as the second argument.
18
- *
19
- * @abstract
20
- *
21
- * @type {function<U>(callback: (item?: Item, index?: number) => U): PromiseLike<U[]> | U[]}
22
- */
23
- map: <U>(callback: (item?: Item, index?: number) => U) => PromiseLike<U[]> | U[];
24
- }
25
- /**
26
- * @desc
27
- * A mapping function converting one type into another.
28
- *
29
- * @public
30
- *
31
- * @typedef {function(item?: V, index?: number) => Promise<O> | O} Mapping<V,O>
32
- */
33
- export declare type MappingFunction<V, O> = (item?: V, index?: number) => O;
34
- /**
35
- * @desc
36
- * Checks if the value is a {@link Mappable} collection of items.
37
- *
38
- * @example <caption>An Array</caption>
39
- * import { Mappable } from '@serenity-js/core/lib/io';
40
- *
41
- * Mappable.isMappable([ 1, 2, 3 ]) === true
42
- *
43
- * @example <caption>Protractor's ElementArrayFinder</caption>
44
- * import { Mappable } from '@serenity-js/core/lib/io';
45
- * import { element } from 'protractor';
46
- *
47
- * Mappable.isMappable(element.all(by.tagName('li')) === true
48
- *
49
- * @param {Mappable<Item> | any} maybeCollection
50
- * @returns {boolean}
51
- */
52
- export declare function isMappable<Item>(maybeCollection: Mappable<Item> | any): maybeCollection is Mappable<Item>;
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isMappable = void 0;
4
- /**
5
- * @desc
6
- * Checks if the value is a {@link Mappable} collection of items.
7
- *
8
- * @example <caption>An Array</caption>
9
- * import { Mappable } from '@serenity-js/core/lib/io';
10
- *
11
- * Mappable.isMappable([ 1, 2, 3 ]) === true
12
- *
13
- * @example <caption>Protractor's ElementArrayFinder</caption>
14
- * import { Mappable } from '@serenity-js/core/lib/io';
15
- * import { element } from 'protractor';
16
- *
17
- * Mappable.isMappable(element.all(by.tagName('li')) === true
18
- *
19
- * @param {Mappable<Item> | any} maybeCollection
20
- * @returns {boolean}
21
- */
22
- function isMappable(maybeCollection) {
23
- return !!maybeCollection
24
- && !!maybeCollection.map
25
- && typeof maybeCollection.map === 'function';
26
- }
27
- exports.isMappable = isMappable;
28
- //# sourceMappingURL=mappable.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mappable.js","sourceRoot":"","sources":["../../../src/io/collections/mappable.ts"],"names":[],"mappings":";;;AAqCA;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,UAAU,CAAO,eAAqC;IAClE,OAAO,CAAC,CAAE,eAAe;WAClB,CAAC,CAAE,eAAe,CAAC,GAAG;WACtB,OAAO,eAAe,CAAC,GAAG,KAAK,UAAU,CAAC;AACrD,CAAC;AAJD,gCAIC"}