@serenity-js/core 2.32.2 → 3.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/lib/index.d.ts +2 -1
  3. package/lib/index.js +4 -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/collections/index.d.ts +0 -1
  8. package/lib/io/collections/index.js +0 -1
  9. package/lib/io/collections/index.js.map +1 -1
  10. package/lib/io/format.d.ts +37 -0
  11. package/lib/io/format.js +49 -0
  12. package/lib/io/format.js.map +1 -0
  13. package/lib/io/formatted.d.ts +5 -1
  14. package/lib/io/formatted.js +6 -13
  15. package/lib/io/formatted.js.map +1 -1
  16. package/lib/io/index.d.ts +1 -0
  17. package/lib/io/index.js +1 -0
  18. package/lib/io/index.js.map +1 -1
  19. package/lib/io/inspected.d.ts +9 -1
  20. package/lib/io/inspected.js +50 -14
  21. package/lib/io/inspected.js.map +1 -1
  22. package/lib/model/Timestamp.d.ts +4 -2
  23. package/lib/model/Timestamp.js +8 -2
  24. package/lib/model/Timestamp.js.map +1 -1
  25. package/lib/screenplay/Question.d.ts +6 -83
  26. package/lib/screenplay/Question.js +14 -98
  27. package/lib/screenplay/Question.js.map +1 -1
  28. package/lib/screenplay/actor/Actor.js +2 -2
  29. package/lib/screenplay/actor/Actor.js.map +1 -1
  30. package/lib/screenplay/index.d.ts +1 -1
  31. package/lib/screenplay/index.js +1 -1
  32. package/lib/screenplay/index.js.map +1 -1
  33. package/lib/screenplay/model/Adapter.d.ts +8 -0
  34. package/lib/screenplay/{questions/proxies/PropertyPathKey.js → model/Adapter.js} +1 -1
  35. package/lib/screenplay/model/Adapter.js.map +1 -0
  36. package/lib/screenplay/model/createAdapter.d.ts +3 -0
  37. package/lib/screenplay/model/createAdapter.js +112 -0
  38. package/lib/screenplay/model/createAdapter.js.map +1 -0
  39. package/lib/screenplay/model/index.d.ts +2 -0
  40. package/lib/screenplay/{tasks → model}/index.js +2 -1
  41. package/lib/screenplay/model/index.js.map +1 -0
  42. package/lib/screenplay/questions/Expectation.d.ts +11 -0
  43. package/lib/screenplay/questions/Expectation.js +18 -1
  44. package/lib/screenplay/questions/Expectation.js.map +1 -1
  45. package/lib/screenplay/questions/List.d.ts +10 -0
  46. package/lib/screenplay/questions/List.js +16 -1
  47. package/lib/screenplay/questions/List.js.map +1 -1
  48. package/lib/screenplay/questions/Note.d.ts +10 -0
  49. package/lib/screenplay/questions/Note.js +17 -1
  50. package/lib/screenplay/questions/Note.js.map +1 -1
  51. package/lib/screenplay/questions/index.d.ts +0 -3
  52. package/lib/screenplay/questions/index.js +0 -5
  53. package/lib/screenplay/questions/index.js.map +1 -1
  54. package/lib/screenplay/questions/lists/ArrayListAdapter.js +20 -5
  55. package/lib/screenplay/questions/lists/ArrayListAdapter.js.map +1 -1
  56. package/package.json +10 -10
  57. package/src/index.ts +2 -1
  58. package/src/io/ErrorSerialiser.ts +5 -1
  59. package/src/io/collections/index.ts +0 -1
  60. package/src/io/format.ts +46 -0
  61. package/src/io/formatted.ts +7 -15
  62. package/src/io/index.ts +1 -0
  63. package/src/io/inspected.ts +66 -14
  64. package/src/model/Timestamp.ts +10 -2
  65. package/src/screenplay/Question.ts +21 -119
  66. package/src/screenplay/actor/Actor.ts +2 -2
  67. package/src/screenplay/index.ts +1 -1
  68. package/src/screenplay/model/Adapter.ts +14 -0
  69. package/src/screenplay/model/createAdapter.ts +142 -0
  70. package/src/screenplay/model/index.ts +2 -0
  71. package/src/screenplay/questions/Expectation.ts +20 -1
  72. package/src/screenplay/questions/List.ts +19 -1
  73. package/src/screenplay/questions/Note.ts +21 -1
  74. package/src/screenplay/questions/index.ts +0 -3
  75. package/src/screenplay/questions/lists/ArrayListAdapter.ts +21 -2
  76. package/lib/decorators/pending.d.ts +0 -0
  77. package/lib/decorators/pending.js +0 -2
  78. package/lib/decorators/pending.js.map +0 -1
  79. package/lib/decorators/step.d.ts +0 -0
  80. package/lib/decorators/step.js +0 -2
  81. package/lib/decorators/step.js.map +0 -1
  82. package/lib/io/collections/mappable.d.ts +0 -52
  83. package/lib/io/collections/mappable.js +0 -28
  84. package/lib/io/collections/mappable.js.map +0 -1
  85. package/lib/screenplay/questions/Property.d.ts +0 -91
  86. package/lib/screenplay/questions/Property.js +0 -99
  87. package/lib/screenplay/questions/Property.js.map +0 -1
  88. package/lib/screenplay/questions/Transform.d.ts +0 -31
  89. package/lib/screenplay/questions/Transform.js +0 -46
  90. package/lib/screenplay/questions/Transform.js.map +0 -1
  91. package/lib/screenplay/questions/mappings/AnswerMappingFunction.d.ts +0 -11
  92. package/lib/screenplay/questions/mappings/AnswerMappingFunction.js +0 -3
  93. package/lib/screenplay/questions/mappings/AnswerMappingFunction.js.map +0 -1
  94. package/lib/screenplay/questions/mappings/index.d.ts +0 -2
  95. package/lib/screenplay/questions/mappings/index.js +0 -15
  96. package/lib/screenplay/questions/mappings/index.js.map +0 -1
  97. package/lib/screenplay/questions/mappings/string/append.d.ts +0 -14
  98. package/lib/screenplay/questions/mappings/string/append.js +0 -25
  99. package/lib/screenplay/questions/mappings/string/append.js.map +0 -1
  100. package/lib/screenplay/questions/mappings/string/index.d.ts +0 -11
  101. package/lib/screenplay/questions/mappings/string/index.js +0 -24
  102. package/lib/screenplay/questions/mappings/string/index.js.map +0 -1
  103. package/lib/screenplay/questions/mappings/string/normalize.d.ts +0 -20
  104. package/lib/screenplay/questions/mappings/string/normalize.js +0 -30
  105. package/lib/screenplay/questions/mappings/string/normalize.js.map +0 -1
  106. package/lib/screenplay/questions/mappings/string/replace.d.ts +0 -17
  107. package/lib/screenplay/questions/mappings/string/replace.js +0 -30
  108. package/lib/screenplay/questions/mappings/string/replace.js.map +0 -1
  109. package/lib/screenplay/questions/mappings/string/slice.d.ts +0 -28
  110. package/lib/screenplay/questions/mappings/string/slice.js +0 -47
  111. package/lib/screenplay/questions/mappings/string/slice.js.map +0 -1
  112. package/lib/screenplay/questions/mappings/string/split.d.ts +0 -19
  113. package/lib/screenplay/questions/mappings/string/split.js +0 -36
  114. package/lib/screenplay/questions/mappings/string/split.js.map +0 -1
  115. package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.d.ts +0 -17
  116. package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.js +0 -28
  117. package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.js.map +0 -1
  118. package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.d.ts +0 -17
  119. package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.js +0 -29
  120. package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.js.map +0 -1
  121. package/lib/screenplay/questions/mappings/string/toLowerCase.d.ts +0 -10
  122. package/lib/screenplay/questions/mappings/string/toLowerCase.js +0 -19
  123. package/lib/screenplay/questions/mappings/string/toLowerCase.js.map +0 -1
  124. package/lib/screenplay/questions/mappings/string/toNumber.d.ts +0 -10
  125. package/lib/screenplay/questions/mappings/string/toNumber.js +0 -18
  126. package/lib/screenplay/questions/mappings/string/toNumber.js.map +0 -1
  127. package/lib/screenplay/questions/mappings/string/toUpperCase.d.ts +0 -10
  128. package/lib/screenplay/questions/mappings/string/toUpperCase.js +0 -19
  129. package/lib/screenplay/questions/mappings/string/toUpperCase.js.map +0 -1
  130. package/lib/screenplay/questions/mappings/string/trim.d.ts +0 -12
  131. package/lib/screenplay/questions/mappings/string/trim.js +0 -21
  132. package/lib/screenplay/questions/mappings/string/trim.js.map +0 -1
  133. package/lib/screenplay/questions/proxies/PropertyPathKey.d.ts +0 -4
  134. package/lib/screenplay/questions/proxies/PropertyPathKey.js.map +0 -1
  135. package/lib/screenplay/questions/proxies/createMetaQuestionProxy.d.ts +0 -14
  136. package/lib/screenplay/questions/proxies/createMetaQuestionProxy.js +0 -35
  137. package/lib/screenplay/questions/proxies/createMetaQuestionProxy.js.map +0 -1
  138. package/lib/screenplay/questions/proxies/createQuestionProxy.d.ts +0 -13
  139. package/lib/screenplay/questions/proxies/createQuestionProxy.js +0 -34
  140. package/lib/screenplay/questions/proxies/createQuestionProxy.js.map +0 -1
  141. package/lib/screenplay/questions/proxies/describePath.d.ts +0 -5
  142. package/lib/screenplay/questions/proxies/describePath.js +0 -19
  143. package/lib/screenplay/questions/proxies/describePath.js.map +0 -1
  144. package/lib/screenplay/questions/proxies/index.d.ts +0 -2
  145. package/lib/screenplay/questions/proxies/index.js +0 -15
  146. package/lib/screenplay/questions/proxies/index.js.map +0 -1
  147. package/lib/screenplay/questions/proxies/key.d.ts +0 -8
  148. package/lib/screenplay/questions/proxies/key.js +0 -16
  149. package/lib/screenplay/questions/proxies/key.js.map +0 -1
  150. package/lib/screenplay/tasks/Loop.d.ts +0 -198
  151. package/lib/screenplay/tasks/Loop.js +0 -222
  152. package/lib/screenplay/tasks/Loop.js.map +0 -1
  153. package/lib/screenplay/tasks/index.d.ts +0 -1
  154. package/lib/screenplay/tasks/index.js.map +0 -1
  155. package/src/decorators/pending.ts +0 -1
  156. package/src/decorators/step.ts +0 -1
  157. package/src/io/collections/mappable.ts +0 -60
  158. package/src/screenplay/questions/Property.ts +0 -98
  159. package/src/screenplay/questions/Transform.ts +0 -50
  160. package/src/screenplay/questions/mappings/AnswerMappingFunction.ts +0 -13
  161. package/src/screenplay/questions/mappings/index.ts +0 -2
  162. package/src/screenplay/questions/mappings/string/append.ts +0 -28
  163. package/src/screenplay/questions/mappings/string/index.ts +0 -11
  164. package/src/screenplay/questions/mappings/string/normalize.ts +0 -33
  165. package/src/screenplay/questions/mappings/string/replace.ts +0 -34
  166. package/src/screenplay/questions/mappings/string/slice.ts +0 -53
  167. package/src/screenplay/questions/mappings/string/split.ts +0 -38
  168. package/src/screenplay/questions/mappings/string/toLocaleLowerCase.ts +0 -31
  169. package/src/screenplay/questions/mappings/string/toLocaleUpperCase.ts +0 -30
  170. package/src/screenplay/questions/mappings/string/toLowerCase.ts +0 -20
  171. package/src/screenplay/questions/mappings/string/toNumber.ts +0 -19
  172. package/src/screenplay/questions/mappings/string/toUpperCase.ts +0 -20
  173. package/src/screenplay/questions/mappings/string/trim.ts +0 -22
  174. package/src/screenplay/questions/proxies/PropertyPathKey.ts +0 -4
  175. package/src/screenplay/questions/proxies/createMetaQuestionProxy.ts +0 -51
  176. package/src/screenplay/questions/proxies/createQuestionProxy.ts +0 -49
  177. package/src/screenplay/questions/proxies/describePath.ts +0 -23
  178. package/src/screenplay/questions/proxies/index.ts +0 -2
  179. package/src/screenplay/questions/proxies/key.ts +0 -14
  180. package/src/screenplay/tasks/Loop.ts +0 -240
  181. package/src/screenplay/tasks/index.ts +0 -1
@@ -1,6 +1,6 @@
1
- import { isMappable, Mappable } from '../io/collections';
1
+ import { inspected } from '../io/inspected';
2
2
  import { AnswersQuestions, UsesAbilities } from './actor';
3
- import { AnswerMappingFunction } from './questions/mappings';
3
+ import { Adapter, createAdapter } from './model';
4
4
 
5
5
  /**
6
6
  * @desc
@@ -53,15 +53,6 @@ import { AnswerMappingFunction } from './questions/mappings';
53
53
  */
54
54
  export abstract class Question<T> {
55
55
 
56
- /**
57
- * @param {string} subject
58
- * The subject of this question
59
- *
60
- * @protected
61
- */
62
- protected constructor(protected subject: string) {
63
- }
64
-
65
56
  /**
66
57
  * @desc
67
58
  * Factory method that simplifies the process of defining custom questions.
@@ -77,8 +68,8 @@ export abstract class Question<T> {
77
68
  *
78
69
  * @returns {Question<R>}
79
70
  */
80
- static about<R>(description: string, body: (actor: AnswersQuestions & UsesAbilities) => R): Question<R> {
81
- return new AnonymousQuestion<R>(description, body);
71
+ static about<R>(description: string, body: (actor: AnswersQuestions & UsesAbilities) => R): Question<R> & Adapter<Awaited<R>> {
72
+ return createAdapter<R>(new AnonymousQuestion<R>(description, body));
82
73
  }
83
74
 
84
75
  /**
@@ -102,9 +93,7 @@ export abstract class Question<T> {
102
93
  *
103
94
  * @returns {string}
104
95
  */
105
- toString(): string {
106
- return this.subject;
107
- }
96
+ abstract toString(): string;
108
97
 
109
98
  /**
110
99
  * @desc
@@ -113,122 +102,31 @@ export abstract class Question<T> {
113
102
  * @param {string} subject
114
103
  * @returns {Question<T>}
115
104
  */
116
- describedAs(subject: string): this {
117
- this.subject = subject;
118
-
119
- return this;
120
- }
121
-
122
- /**
123
- * @desc
124
- * Creates a new {@link Question}, which value is a result of applying the `mapping`
125
- * function to the value of this {@link Question}.
126
- *
127
- * @example <caption>Mapping a Question<Promise<string>> to Question<Promise<number>></caption>
128
- * import { Question, replace, toNumber } from '@serenity-js/core';
129
- *
130
- * Question.about('the price of some item', actor => '$3.99')
131
- * .map(replace('$', ''))
132
- * .map(toNumber)
133
- *
134
- * // => Question<Promise<number>>
135
- * // 3.99
136
- *
137
- * @example <caption>Mapping all items of Question<string[]> to Question<Promise<number>></caption>
138
- * import { Question, trim } from '@serenity-js/core';
139
- *
140
- * Question.about('things to do', actor => [ ' walk the dog ', ' read a book ' ])
141
- * .map(trim())
142
- *
143
- * // => Question<Promise<string[]>>
144
- * // [ 'walk the dog', 'read a book' ]
145
- *
146
- * @example <caption>Using a custom mapping function</caption>
147
- * import { Question } from '@serenity-js/core';
148
- *
149
- * Question.about('normalised percentages', actor => [ 0.1, 0.3, 0.6 ])
150
- * .map((actor: AnswersQuestions) => (value: number) => value * 100)
151
- *
152
- * // => Question<Promise<number[]>>
153
- * // [ 10, 30, 60 ]
154
- *
155
- * @example <caption>Extracting values from LastResponse.body()</caption>
156
- * import { Question } from '@serenity-js/core';
157
- * import { LastResponse } from '@serenity-js/rest';
158
- *
159
- * interface UserDetails {
160
- * id: number;
161
- * name: string;
162
- * }
163
- *
164
- * LastResponse.body<UserDetails>().map(actor => details => details.id)
165
- *
166
- * // => Question<number>
167
- *
168
- * @param {function(value: A, index?: number): Promise<O> | O} mapping
169
- * A mapping function that receives a value of type `<A>`, which is either:
170
- * - an answer to the original question, if the question is defined as `Question<Promise<A>>` or `Question<A>`
171
- * - or, if the question is defined as `Question<Promise<Mappable<A>>`, `Question<Mappable<A>>` - each item of the {@link Mappable} collection,
172
- *
173
- * @returns {Question<Promise<Mapped>>}
174
- * A new Question which value is a result of applying the `mapping` function
175
- * to the value of the current question, so that:
176
- * - if the answer to the current question is a `Mappable<A>`, the result becomes `Question<Promise<O[]>>`
177
- * - if the answer is a value `<A>` or `Promise<A>`, the result becomes `Question<Promise<O>>`
178
- *
179
- * @see {@link AnswerMappingFunction}
180
- * @see {@link Mappable}
181
- */
182
- map<O>(mapping: AnswerMappingFunction<AnswerOrItemOfMappableCollection<T>, O>): Question<Promise<Mapped<T, O>>> {
183
- return Question.about(this.subject, actor =>
184
- actor.answer(this).then(value =>
185
- (isMappable(value)
186
- ? Promise.all(((value).map(mapping(actor)) as Array<PromiseLike<O> | O>))
187
- : mapping(actor)(value as AnswerOrItemOfMappableCollection<T>)
188
- ) as Promise<Mapped<T, O>>
189
- )
190
- ) as Question<Promise<Mapped<T, O>>>;
191
- }
105
+ abstract describedAs(subject: string): this;
192
106
 
193
107
  /**
194
108
  * @abstract
109
+ * // todo check why api docs are not getting generated for this methods
195
110
  */
196
111
  abstract answeredBy(actor: AnswersQuestions & UsesAbilities): T;
197
- }
198
-
199
- /**
200
- * @package
201
- */
202
- type AnswerOrItemOfMappableCollection<V> =
203
- V extends PromiseLike<infer PromisedValue>
204
- ? PromisedValue extends Mappable<infer Item>
205
- ? Item
206
- : PromisedValue
207
- : V extends Mappable<infer Item>
208
- ? Item
209
- : V;
210
112
 
211
- /**
212
- * @package
213
- */
214
- type Mapped<T, O> =
215
- T extends PromiseLike<infer PromisedValue>
216
- ? PromisedValue extends Mappable<infer Item> // eslint-disable-line @typescript-eslint/no-unused-vars
217
- ? O[]
218
- : O
219
- : T extends Mappable<infer Item> // eslint-disable-line @typescript-eslint/no-unused-vars
220
- ? O[]
221
- : O
113
+ public as<O>(mapping: (answer: Awaited<T>) => Promise<O> | O): Question<Promise<O>> {
114
+ return Question.about<Promise<O>>(`${ this.toString() } as ${ inspected(mapping, { inline: true }) }`, async actor => {
115
+ const answer = (await actor.answer(this)) as Awaited<T>;
116
+ return mapping(answer);
117
+ });
118
+ }
119
+ }
222
120
 
223
121
  /**
224
122
  * @package
225
123
  */
226
124
  class AnonymousQuestion<T> extends Question<T> {
227
- constructor(private description: string, private body: (actor: AnswersQuestions & UsesAbilities) => T) {
228
- super(description);
125
+ constructor(private subject: string, private body: (actor: AnswersQuestions & UsesAbilities) => T) {
126
+ super();
229
127
  }
230
128
 
231
- answeredBy(actor: AnswersQuestions & UsesAbilities) {
129
+ answeredBy(actor: AnswersQuestions & UsesAbilities): T {
232
130
  return this.body(actor);
233
131
  }
234
132
 
@@ -244,4 +142,8 @@ class AnonymousQuestion<T> extends Question<T> {
244
142
 
245
143
  return this;
246
144
  }
145
+
146
+ toString(): string {
147
+ return this.subject;
148
+ }
247
149
  }
@@ -114,11 +114,11 @@ export class Actor implements
114
114
  */
115
115
  answer<T>(answerable: Answerable<T>): Promise<T> {
116
116
  function isAPromise<V>(v: Answerable<V>): v is Promise<V> {
117
- return !!(v as any).then;
117
+ return Object.prototype.hasOwnProperty.call(v, 'then');
118
118
  }
119
119
 
120
120
  function isDefined<V>(v: Answerable<V>) {
121
- return ! (answerable === undefined || answerable === null);
121
+ return ! (v === undefined || v === null);
122
122
  }
123
123
 
124
124
  if (isDefined(answerable) && isAPromise(answerable)) {
@@ -6,7 +6,7 @@ export * from './actor';
6
6
  export * from './Answerable';
7
7
  export * from './Interaction';
8
8
  export * from './interactions';
9
+ export * from './model';
9
10
  export * from './Question';
10
11
  export * from './questions';
11
12
  export * from './Task';
12
- export * from './tasks';
@@ -0,0 +1,14 @@
1
+ import { Answerable } from '../Answerable';
2
+ import { Interaction } from '../Interaction';
3
+ import { Question } from '../Question';
4
+
5
+ export type Adapter<OriginalType> = {
6
+ [Field in keyof Omit<OriginalType, keyof Question<OriginalType>>]:
7
+ // is it a method?
8
+ OriginalType[Field] extends (...args: infer OriginalParameters) => infer OriginalMethodResult
9
+ // make the method signature asynchronous, accepting Answerables and returning a Promise
10
+ ? (...args: { [P in keyof OriginalParameters]: Answerable<OriginalParameters[P]> }) => Question<Promise<Awaited<OriginalMethodResult>>> & Interaction & Adapter<Awaited<OriginalMethodResult>>
11
+ // is it an object? wrap each field
12
+ : Question<Promise<Awaited<OriginalType[Field]>>> & Interaction & Adapter<Awaited<OriginalType[Field]>>
13
+ }
14
+
@@ -0,0 +1,142 @@
1
+ import { LogicError } from '../../errors';
2
+ import { inspected } from '../../io/inspected';
3
+ import { Actor, AnswersQuestions, UsesAbilities } from '../actor';
4
+ import { Interaction } from '../Interaction';
5
+ import { Question } from '../Question';
6
+ import { Adapter } from './Adapter';
7
+
8
+ export function createAdapter<A, Q extends Question<A> = Question<A>>(question: Q): Q & Adapter<Awaited<A>> {
9
+
10
+ return new Proxy<Q>(question, {
11
+
12
+ get(target: Q, key: string | symbol | number, receiver: any) {
13
+ if (key === Symbol.toPrimitive) {
14
+ return (_hint: 'number' | 'string' | 'default') => {
15
+ return target.toString();
16
+ }
17
+ }
18
+
19
+ if (key in target) {
20
+ return Reflect.get(target, key, receiver);
21
+ }
22
+
23
+ if (doNotProxy(key)) {
24
+ return;
25
+ }
26
+
27
+ const fieldDescription = (typeof key === 'number' || /^\d+$/.test(String(key)))
28
+ ? `[${ String(key) }]` // array index
29
+ : `.${ String(key) }`; // field/method name
30
+
31
+ // when property is invoked as a method
32
+ function proxy(...originalParameters) {
33
+
34
+ const parameterDescriptions = [
35
+ '(',
36
+ originalParameters.map(p => inspected(p, { inline: true, markQuestions: true })).join(', '),
37
+ ')'
38
+ ].join('');
39
+
40
+ const originalSubject = inspected(question, { inline: true, markQuestions: true });
41
+
42
+ return createAdapter(new DynamicProp(originalSubject + fieldDescription + parameterDescriptions, async actor => {
43
+ // resolve the original answer
44
+ const answer = await actor.answer(target);
45
+
46
+ if (answer === undefined) {
47
+ throw new LogicError(`${ inspected(target, { inline: true, markQuestions: true }) } is undefined, can't read property '${ String(key) }'`);
48
+ }
49
+
50
+ if (typeof answer[key] !== 'function') {
51
+ throw new LogicError(`${ inspected(target, { inline: true, markQuestions: true }) } does not have a method called '${ String(key) }'`);
52
+ }
53
+
54
+ // convert parameters from Answerable<T> => T
55
+ const parameters = await (Promise.all(
56
+ originalParameters.map(parameter => {
57
+ return actor.answer(parameter)
58
+ })
59
+ ));
60
+
61
+ // invoke the method call on the original answer
62
+ return answer[key](...parameters)
63
+ }));
64
+ }
65
+
66
+ const originalSubject = inspected(question, { inline: true, markQuestions: true });
67
+
68
+ proxy.subject = originalSubject + fieldDescription
69
+
70
+ // when property is accessed as a field
71
+ const body = async (actor: Actor) => {
72
+ const answer = await actor.answer(target);
73
+
74
+ if (answer === undefined) {
75
+ throw new LogicError(`${ target } is undefined, can't read property "${ String(key) }"`);
76
+ }
77
+
78
+ return answer[key];
79
+ };
80
+
81
+ proxy.answeredBy = body;
82
+ proxy.performAs = body;
83
+
84
+ proxy.toString = () => {
85
+ return proxy.subject;
86
+ };
87
+
88
+ proxy.describedAs = (newSubject: string) => {
89
+ proxy.subject = newSubject;
90
+
91
+ return proxy;
92
+ }
93
+
94
+ proxy.as = <O>(mapping: (answer: Awaited<A>) => O): Question<Promise<O>> => {
95
+ return Question.about<Promise<O>>(`${ proxy.subject } as ${ inspected(mapping, { inline: true }) }`, async actor => {
96
+ const answer = await actor.answer(proxy)
97
+ return mapping(answer);
98
+ });
99
+ }
100
+
101
+ return createAdapter(proxy as any);
102
+ }
103
+ }) as Q & Adapter<Awaited<A>>
104
+ }
105
+
106
+ function doNotProxy(key: string | symbol | number) {
107
+ const prohibited: Array<string | symbol | number> = [
108
+ 'then', // don't proxy Promises
109
+ ];
110
+
111
+ return prohibited.includes(key)
112
+ }
113
+
114
+ class DynamicProp<T> extends Interaction implements Question<T> {
115
+ constructor(protected subject: string, private body: (actor: AnswersQuestions & UsesAbilities) => T) {
116
+ super();
117
+ }
118
+
119
+ answeredBy(actor: AnswersQuestions & UsesAbilities): T {
120
+ return this.body(actor);
121
+ }
122
+
123
+ async performAs(actor: UsesAbilities & AnswersQuestions): Promise<void> {
124
+ await this.body(actor);
125
+ }
126
+
127
+ as<O>(mapping: (answer: Awaited<T>) => Promise<O> | O): Question<Promise<O>> {
128
+ return createAdapter(new DynamicProp(`${ this.subject } as ${ inspected(mapping, { inline: true }) }`, async actor => {
129
+ const answer = (await actor.answer(this)) as Awaited<T>;
130
+ return mapping(answer);
131
+ })) as Question<Promise<O>>;
132
+ }
133
+
134
+ describedAs(subject: string): this {
135
+ this.subject = subject;
136
+ return this;
137
+ }
138
+
139
+ toString(): string {
140
+ return this.subject;
141
+ }
142
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Adapter';
2
+ export * from './createAdapter';
@@ -19,6 +19,9 @@ export type Predicate<A, E> = (actual: A, expected: E) => boolean;
19
19
  export abstract class Expectation<Expected, Actual = Expected>
20
20
  extends Question<(actual: Actual) => Promise<ExpectationOutcome<Expected, Actual>>>
21
21
  {
22
+ constructor(protected subject: string) {
23
+ super();
24
+ }
22
25
 
23
26
  /**
24
27
  * @desc
@@ -88,6 +91,22 @@ export abstract class Expectation<Expected, Actual = Expected>
88
91
  }
89
92
 
90
93
  abstract answeredBy(actor: AnswersQuestions): (actual: Actual) => Promise<ExpectationOutcome<Expected, Actual>>;
94
+
95
+ /**
96
+ * @desc
97
+ * Changes the description of this question's subject.
98
+ *
99
+ * @param {string} subject
100
+ * @returns {Question<T>}
101
+ */
102
+ describedAs(subject: string): this {
103
+ this.subject = subject;
104
+ return this;
105
+ }
106
+
107
+ toString(): string {
108
+ return this.subject;
109
+ }
91
110
  }
92
111
 
93
112
  /**
@@ -96,7 +115,7 @@ export abstract class Expectation<Expected, Actual = Expected>
96
115
  class DynamicallyGeneratedExpectation<Expected, Actual> extends Expectation<Expected, Actual> {
97
116
 
98
117
  constructor(
99
- private readonly description: string,
118
+ description: string,
100
119
  private readonly predicate: Predicate<Actual, Expected>,
101
120
  private readonly expectedValue: Answerable<Expected>,
102
121
  ) {
@@ -64,6 +64,7 @@ export class List<
64
64
  >
65
65
  extends Question<Collection_Return_Type>
66
66
  {
67
+ private subject: string;
67
68
 
68
69
  /**
69
70
  * @desc
@@ -88,7 +89,8 @@ export class List<
88
89
  * @param {List_Adapter_Type} collection
89
90
  */
90
91
  constructor(private readonly collection: List_Adapter_Type) {
91
- super(formatted`${ collection }`);
92
+ super();
93
+ this.subject = formatted`${ collection }`;
92
94
  }
93
95
 
94
96
  /**
@@ -241,6 +243,22 @@ export class List<
241
243
  return this.collection.items(actor);
242
244
  }
243
245
 
246
+ /**
247
+ * @desc
248
+ * Changes the description of this question's subject.
249
+ *
250
+ * @param {string} subject
251
+ * @returns {Question<T>}
252
+ */
253
+ describedAs(subject: string): this {
254
+ this.subject = subject;
255
+ return this;
256
+ }
257
+
258
+ toString(): string {
259
+ return this.subject;
260
+ }
261
+
244
262
  private static ordinalSuffixOf(index: number): string {
245
263
  const
246
264
  lastDigit = index % 10,
@@ -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';
@@ -154,16 +154,19 @@ export class ArrayListAdapter<Item_Type> implements ListAdapter<Item_Type, Item_
154
154
  class ArrayListFilter<Item_Type, Answer_Type>
155
155
  extends Question<Promise<Item_Type[]>>
156
156
  {
157
+ private subject: string;
158
+
157
159
  constructor(
158
160
  private readonly collection: Answerable<Item_Type[]>,
159
161
  private readonly question: MetaQuestion<Item_Type, Promise<Answer_Type> | Answer_Type>,
160
162
  private readonly expectation: Expectation<any, Answer_Type>
161
163
  ) {
162
- super([
164
+ super();
165
+ this.subject = [
163
166
  formatted `${ collection }`,
164
167
  collection instanceof ArrayListFilter ? 'and' : 'where',
165
168
  formatted `${ question } does ${ expectation }`
166
- ].join(' '));
169
+ ].join(' ');
167
170
  }
168
171
 
169
172
  answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Item_Type[]> {
@@ -182,5 +185,21 @@ class ArrayListFilter<Item_Type, Answer_Type>
182
185
  .map(result => result.item)
183
186
  );
184
187
  }
188
+
189
+ /**
190
+ * @desc
191
+ * Changes the description of this question's subject.
192
+ *
193
+ * @param {string} subject
194
+ * @returns {Question<T>}
195
+ */
196
+ describedAs(subject: string): this {
197
+ this.subject = subject;
198
+ return this;
199
+ }
200
+
201
+ toString(): string {
202
+ return this.subject;
203
+ }
185
204
  }
186
205
 
File without changes
@@ -1,2 +0,0 @@
1
- // class-level
2
- //# sourceMappingURL=pending.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pending.js","sourceRoot":"","sources":["../../src/decorators/pending.ts"],"names":[],"mappings":"AAAA,cAAc"}
File without changes
@@ -1,2 +0,0 @@
1
- // performAs level; sets a toString; deprecated
2
- //# sourceMappingURL=step.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"step.js","sourceRoot":"","sources":["../../src/decorators/step.ts"],"names":[],"mappings":"AAAA,+CAA+C"}
@@ -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"}