@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.
- package/CHANGELOG.md +64 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +4 -1
- package/lib/index.js.map +1 -1
- package/lib/io/ErrorSerialiser.js +4 -1
- package/lib/io/ErrorSerialiser.js.map +1 -1
- package/lib/io/collections/index.d.ts +0 -1
- package/lib/io/collections/index.js +0 -1
- package/lib/io/collections/index.js.map +1 -1
- package/lib/io/format.d.ts +37 -0
- package/lib/io/format.js +49 -0
- package/lib/io/format.js.map +1 -0
- package/lib/io/formatted.d.ts +5 -1
- package/lib/io/formatted.js +6 -13
- package/lib/io/formatted.js.map +1 -1
- package/lib/io/index.d.ts +1 -0
- package/lib/io/index.js +1 -0
- package/lib/io/index.js.map +1 -1
- package/lib/io/inspected.d.ts +9 -1
- package/lib/io/inspected.js +50 -14
- package/lib/io/inspected.js.map +1 -1
- package/lib/model/Timestamp.d.ts +4 -2
- package/lib/model/Timestamp.js +8 -2
- package/lib/model/Timestamp.js.map +1 -1
- package/lib/screenplay/Question.d.ts +6 -83
- package/lib/screenplay/Question.js +14 -98
- package/lib/screenplay/Question.js.map +1 -1
- package/lib/screenplay/actor/Actor.js +2 -2
- package/lib/screenplay/actor/Actor.js.map +1 -1
- package/lib/screenplay/index.d.ts +1 -1
- package/lib/screenplay/index.js +1 -1
- package/lib/screenplay/index.js.map +1 -1
- package/lib/screenplay/model/Adapter.d.ts +8 -0
- package/lib/screenplay/{questions/proxies/PropertyPathKey.js → model/Adapter.js} +1 -1
- package/lib/screenplay/model/Adapter.js.map +1 -0
- package/lib/screenplay/model/createAdapter.d.ts +3 -0
- package/lib/screenplay/model/createAdapter.js +112 -0
- package/lib/screenplay/model/createAdapter.js.map +1 -0
- package/lib/screenplay/model/index.d.ts +2 -0
- package/lib/screenplay/{tasks → model}/index.js +2 -1
- package/lib/screenplay/model/index.js.map +1 -0
- package/lib/screenplay/questions/Expectation.d.ts +11 -0
- package/lib/screenplay/questions/Expectation.js +18 -1
- package/lib/screenplay/questions/Expectation.js.map +1 -1
- package/lib/screenplay/questions/List.d.ts +10 -0
- package/lib/screenplay/questions/List.js +16 -1
- package/lib/screenplay/questions/List.js.map +1 -1
- package/lib/screenplay/questions/Note.d.ts +10 -0
- package/lib/screenplay/questions/Note.js +17 -1
- package/lib/screenplay/questions/Note.js.map +1 -1
- package/lib/screenplay/questions/index.d.ts +0 -3
- package/lib/screenplay/questions/index.js +0 -5
- package/lib/screenplay/questions/index.js.map +1 -1
- package/lib/screenplay/questions/lists/ArrayListAdapter.js +20 -5
- package/lib/screenplay/questions/lists/ArrayListAdapter.js.map +1 -1
- package/package.json +10 -10
- package/src/index.ts +2 -1
- package/src/io/ErrorSerialiser.ts +5 -1
- package/src/io/collections/index.ts +0 -1
- package/src/io/format.ts +46 -0
- package/src/io/formatted.ts +7 -15
- package/src/io/index.ts +1 -0
- package/src/io/inspected.ts +66 -14
- package/src/model/Timestamp.ts +10 -2
- package/src/screenplay/Question.ts +21 -119
- package/src/screenplay/actor/Actor.ts +2 -2
- package/src/screenplay/index.ts +1 -1
- package/src/screenplay/model/Adapter.ts +14 -0
- package/src/screenplay/model/createAdapter.ts +142 -0
- package/src/screenplay/model/index.ts +2 -0
- package/src/screenplay/questions/Expectation.ts +20 -1
- package/src/screenplay/questions/List.ts +19 -1
- package/src/screenplay/questions/Note.ts +21 -1
- package/src/screenplay/questions/index.ts +0 -3
- package/src/screenplay/questions/lists/ArrayListAdapter.ts +21 -2
- package/lib/decorators/pending.d.ts +0 -0
- package/lib/decorators/pending.js +0 -2
- package/lib/decorators/pending.js.map +0 -1
- package/lib/decorators/step.d.ts +0 -0
- package/lib/decorators/step.js +0 -2
- package/lib/decorators/step.js.map +0 -1
- package/lib/io/collections/mappable.d.ts +0 -52
- package/lib/io/collections/mappable.js +0 -28
- package/lib/io/collections/mappable.js.map +0 -1
- package/lib/screenplay/questions/Property.d.ts +0 -91
- package/lib/screenplay/questions/Property.js +0 -99
- package/lib/screenplay/questions/Property.js.map +0 -1
- package/lib/screenplay/questions/Transform.d.ts +0 -31
- package/lib/screenplay/questions/Transform.js +0 -46
- package/lib/screenplay/questions/Transform.js.map +0 -1
- package/lib/screenplay/questions/mappings/AnswerMappingFunction.d.ts +0 -11
- package/lib/screenplay/questions/mappings/AnswerMappingFunction.js +0 -3
- package/lib/screenplay/questions/mappings/AnswerMappingFunction.js.map +0 -1
- package/lib/screenplay/questions/mappings/index.d.ts +0 -2
- package/lib/screenplay/questions/mappings/index.js +0 -15
- package/lib/screenplay/questions/mappings/index.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/append.d.ts +0 -14
- package/lib/screenplay/questions/mappings/string/append.js +0 -25
- package/lib/screenplay/questions/mappings/string/append.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/index.d.ts +0 -11
- package/lib/screenplay/questions/mappings/string/index.js +0 -24
- package/lib/screenplay/questions/mappings/string/index.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/normalize.d.ts +0 -20
- package/lib/screenplay/questions/mappings/string/normalize.js +0 -30
- package/lib/screenplay/questions/mappings/string/normalize.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/replace.d.ts +0 -17
- package/lib/screenplay/questions/mappings/string/replace.js +0 -30
- package/lib/screenplay/questions/mappings/string/replace.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/slice.d.ts +0 -28
- package/lib/screenplay/questions/mappings/string/slice.js +0 -47
- package/lib/screenplay/questions/mappings/string/slice.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/split.d.ts +0 -19
- package/lib/screenplay/questions/mappings/string/split.js +0 -36
- package/lib/screenplay/questions/mappings/string/split.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.d.ts +0 -17
- package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.js +0 -28
- package/lib/screenplay/questions/mappings/string/toLocaleLowerCase.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.d.ts +0 -17
- package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.js +0 -29
- package/lib/screenplay/questions/mappings/string/toLocaleUpperCase.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/toLowerCase.d.ts +0 -10
- package/lib/screenplay/questions/mappings/string/toLowerCase.js +0 -19
- package/lib/screenplay/questions/mappings/string/toLowerCase.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/toNumber.d.ts +0 -10
- package/lib/screenplay/questions/mappings/string/toNumber.js +0 -18
- package/lib/screenplay/questions/mappings/string/toNumber.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/toUpperCase.d.ts +0 -10
- package/lib/screenplay/questions/mappings/string/toUpperCase.js +0 -19
- package/lib/screenplay/questions/mappings/string/toUpperCase.js.map +0 -1
- package/lib/screenplay/questions/mappings/string/trim.d.ts +0 -12
- package/lib/screenplay/questions/mappings/string/trim.js +0 -21
- package/lib/screenplay/questions/mappings/string/trim.js.map +0 -1
- package/lib/screenplay/questions/proxies/PropertyPathKey.d.ts +0 -4
- package/lib/screenplay/questions/proxies/PropertyPathKey.js.map +0 -1
- package/lib/screenplay/questions/proxies/createMetaQuestionProxy.d.ts +0 -14
- package/lib/screenplay/questions/proxies/createMetaQuestionProxy.js +0 -35
- package/lib/screenplay/questions/proxies/createMetaQuestionProxy.js.map +0 -1
- package/lib/screenplay/questions/proxies/createQuestionProxy.d.ts +0 -13
- package/lib/screenplay/questions/proxies/createQuestionProxy.js +0 -34
- package/lib/screenplay/questions/proxies/createQuestionProxy.js.map +0 -1
- package/lib/screenplay/questions/proxies/describePath.d.ts +0 -5
- package/lib/screenplay/questions/proxies/describePath.js +0 -19
- package/lib/screenplay/questions/proxies/describePath.js.map +0 -1
- package/lib/screenplay/questions/proxies/index.d.ts +0 -2
- package/lib/screenplay/questions/proxies/index.js +0 -15
- package/lib/screenplay/questions/proxies/index.js.map +0 -1
- package/lib/screenplay/questions/proxies/key.d.ts +0 -8
- package/lib/screenplay/questions/proxies/key.js +0 -16
- package/lib/screenplay/questions/proxies/key.js.map +0 -1
- package/lib/screenplay/tasks/Loop.d.ts +0 -198
- package/lib/screenplay/tasks/Loop.js +0 -222
- package/lib/screenplay/tasks/Loop.js.map +0 -1
- package/lib/screenplay/tasks/index.d.ts +0 -1
- package/lib/screenplay/tasks/index.js.map +0 -1
- package/src/decorators/pending.ts +0 -1
- package/src/decorators/step.ts +0 -1
- package/src/io/collections/mappable.ts +0 -60
- package/src/screenplay/questions/Property.ts +0 -98
- package/src/screenplay/questions/Transform.ts +0 -50
- package/src/screenplay/questions/mappings/AnswerMappingFunction.ts +0 -13
- package/src/screenplay/questions/mappings/index.ts +0 -2
- package/src/screenplay/questions/mappings/string/append.ts +0 -28
- package/src/screenplay/questions/mappings/string/index.ts +0 -11
- package/src/screenplay/questions/mappings/string/normalize.ts +0 -33
- package/src/screenplay/questions/mappings/string/replace.ts +0 -34
- package/src/screenplay/questions/mappings/string/slice.ts +0 -53
- package/src/screenplay/questions/mappings/string/split.ts +0 -38
- package/src/screenplay/questions/mappings/string/toLocaleLowerCase.ts +0 -31
- package/src/screenplay/questions/mappings/string/toLocaleUpperCase.ts +0 -30
- package/src/screenplay/questions/mappings/string/toLowerCase.ts +0 -20
- package/src/screenplay/questions/mappings/string/toNumber.ts +0 -19
- package/src/screenplay/questions/mappings/string/toUpperCase.ts +0 -20
- package/src/screenplay/questions/mappings/string/trim.ts +0 -22
- package/src/screenplay/questions/proxies/PropertyPathKey.ts +0 -4
- package/src/screenplay/questions/proxies/createMetaQuestionProxy.ts +0 -51
- package/src/screenplay/questions/proxies/createQuestionProxy.ts +0 -49
- package/src/screenplay/questions/proxies/describePath.ts +0 -23
- package/src/screenplay/questions/proxies/index.ts +0 -2
- package/src/screenplay/questions/proxies/key.ts +0 -14
- package/src/screenplay/tasks/Loop.ts +0 -240
- package/src/screenplay/tasks/index.ts +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inspected } from '../io/inspected';
|
|
2
2
|
import { AnswersQuestions, UsesAbilities } from './actor';
|
|
3
|
-
import {
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
228
|
-
super(
|
|
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
|
|
117
|
+
return Object.prototype.hasOwnProperty.call(v, 'then');
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
function isDefined<V>(v: Answerable<V>) {
|
|
121
|
-
return ! (
|
|
121
|
+
return ! (v === undefined || v === null);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
if (isDefined(answerable) && isAPromise(answerable)) {
|
package/src/screenplay/index.ts
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pending.js","sourceRoot":"","sources":["../../src/decorators/pending.ts"],"names":[],"mappings":"AAAA,cAAc"}
|
package/lib/decorators/step.d.ts
DELETED
|
File without changes
|
package/lib/decorators/step.js
DELETED
|
@@ -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"}
|