@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.
- package/CHANGELOG.md +476 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +6 -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/ErrorStackParser.d.ts +2 -2
- package/lib/io/ErrorStackParser.js.map +1 -1
- package/lib/io/asyncMap.d.ts +8 -0
- package/lib/io/asyncMap.js +18 -0
- package/lib/io/asyncMap.js.map +1 -0
- package/lib/io/format.d.ts +39 -0
- package/lib/io/format.js +51 -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 +2 -1
- package/lib/io/index.js +2 -1
- package/lib/io/index.js.map +1 -1
- package/lib/io/inspected.d.ts +9 -1
- package/lib/io/inspected.js +52 -15
- 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/Optional.d.ts +29 -0
- package/lib/{io/collections/reducible.js → screenplay/Optional.js} +1 -1
- package/lib/screenplay/Optional.js.map +1 -0
- package/lib/screenplay/Question.d.ts +41 -82
- package/lib/screenplay/Question.js +132 -100
- 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/interactions/index.d.ts +0 -1
- package/lib/screenplay/interactions/index.js +0 -1
- package/lib/screenplay/interactions/index.js.map +1 -1
- package/lib/screenplay/questions/Check.d.ts +3 -3
- package/lib/screenplay/questions/Check.js +5 -7
- package/lib/screenplay/questions/Check.js.map +1 -1
- package/lib/screenplay/questions/Expectation.d.ts +15 -10
- package/lib/screenplay/questions/Expectation.js +28 -37
- package/lib/screenplay/questions/Expectation.js.map +1 -1
- package/lib/screenplay/questions/List.d.ts +22 -192
- package/lib/screenplay/questions/List.js +160 -208
- 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/stage/index.d.ts +0 -2
- package/lib/stage/index.js +0 -2
- package/lib/stage/index.js.map +1 -1
- package/package.json +7 -6
- package/src/index.ts +2 -1
- package/src/io/ErrorSerialiser.ts +5 -1
- package/src/io/ErrorStackParser.ts +2 -1
- package/src/io/asyncMap.ts +18 -0
- package/src/io/format.ts +49 -0
- package/src/io/formatted.ts +7 -15
- package/src/io/index.ts +2 -1
- package/src/io/inspected.ts +68 -15
- package/src/model/Timestamp.ts +10 -2
- package/src/screenplay/Optional.ts +30 -0
- package/src/screenplay/Question.ts +206 -124
- package/src/screenplay/actor/Actor.ts +2 -2
- package/src/screenplay/index.ts +1 -1
- package/src/screenplay/interactions/index.ts +0 -1
- package/src/screenplay/questions/Check.ts +10 -15
- package/src/screenplay/questions/Expectation.ts +47 -55
- package/src/screenplay/questions/List.ts +224 -233
- package/src/screenplay/questions/Note.ts +21 -1
- package/src/screenplay/questions/index.ts +0 -3
- package/src/stage/index.ts +0 -2
- package/lib/io/collections/index.d.ts +0 -2
- package/lib/io/collections/index.js +0 -15
- package/lib/io/collections/index.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/io/collections/reducible.d.ts +0 -16
- package/lib/io/collections/reducible.js.map +0 -1
- package/lib/screenplay/interactions/See.d.ts +0 -31
- package/lib/screenplay/interactions/See.js +0 -43
- package/lib/screenplay/interactions/See.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/lists/ArrayListAdapter.d.ts +0 -88
- package/lib/screenplay/questions/lists/ArrayListAdapter.js +0 -152
- package/lib/screenplay/questions/lists/ArrayListAdapter.js.map +0 -1
- package/lib/screenplay/questions/lists/ListAdapter.d.ts +0 -20
- package/lib/screenplay/questions/lists/ListAdapter.js +0 -3
- package/lib/screenplay/questions/lists/ListAdapter.js.map +0 -1
- package/lib/screenplay/questions/lists/index.d.ts +0 -2
- package/lib/screenplay/questions/lists/index.js +0 -15
- package/lib/screenplay/questions/lists/index.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 +0 -3
- 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 +0 -14
- package/lib/screenplay/tasks/index.js.map +0 -1
- package/lib/stage/DressingRoom.d.ts +0 -37
- package/lib/stage/DressingRoom.js +0 -53
- package/lib/stage/DressingRoom.js.map +0 -1
- package/lib/stage/WithStage.d.ts +0 -51
- package/lib/stage/WithStage.js +0 -3
- package/lib/stage/WithStage.js.map +0 -1
- package/src/io/collections/index.ts +0 -2
- package/src/io/collections/mappable.ts +0 -60
- package/src/io/collections/reducible.ts +0 -16
- package/src/screenplay/interactions/See.ts +0 -45
- package/src/screenplay/questions/Property.ts +0 -98
- package/src/screenplay/questions/Transform.ts +0 -51
- package/src/screenplay/questions/lists/ArrayListAdapter.ts +0 -186
- package/src/screenplay/questions/lists/ListAdapter.ts +0 -33
- package/src/screenplay/questions/lists/index.ts +0 -2
- 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
- package/src/stage/DressingRoom.ts +0 -53
- package/src/stage/WithStage.ts +0 -52
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f } from '../io';
|
|
2
2
|
import { AnswersQuestions, UsesAbilities } from './actor';
|
|
3
|
-
import {
|
|
3
|
+
import { Answerable } from './Answerable';
|
|
4
|
+
import { Interaction } from './Interaction';
|
|
5
|
+
import { Optional } from './Optional';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @desc
|
|
@@ -52,16 +54,6 @@ import { AnswerMappingFunction } from './questions/mappings';
|
|
|
52
54
|
* @abstract
|
|
53
55
|
*/
|
|
54
56
|
export abstract class Question<T> {
|
|
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
57
|
/**
|
|
66
58
|
* @desc
|
|
67
59
|
* Factory method that simplifies the process of defining custom questions.
|
|
@@ -77,8 +69,8 @@ export abstract class Question<T> {
|
|
|
77
69
|
*
|
|
78
70
|
* @returns {Question<R>}
|
|
79
71
|
*/
|
|
80
|
-
static about<R>(description: string, body: (actor: AnswersQuestions & UsesAbilities) => R):
|
|
81
|
-
return new
|
|
72
|
+
static about<R>(description: string, body: (actor: AnswersQuestions & UsesAbilities) => Promise<R> | R): QuestionAdapter<Awaited<R>> {
|
|
73
|
+
return Question.createAdapter(new QuestionStatement(description, body));
|
|
82
74
|
}
|
|
83
75
|
|
|
84
76
|
/**
|
|
@@ -96,15 +88,91 @@ export abstract class Question<T> {
|
|
|
96
88
|
return !! maybeQuestion && !! (maybeQuestion as any).answeredBy;
|
|
97
89
|
}
|
|
98
90
|
|
|
91
|
+
protected static createAdapter<AT>(statement: Question<AT>): QuestionAdapter<Awaited<AT>> {
|
|
92
|
+
return new Proxy<() => Question<AT>, QuestionStatement<AT>>(() => statement, {
|
|
93
|
+
|
|
94
|
+
get(currentStatement: () => Question<AT>, key: string | symbol, receiver: any) {
|
|
95
|
+
const target = currentStatement();
|
|
96
|
+
|
|
97
|
+
if (key === Symbol.toPrimitive) {
|
|
98
|
+
return (_hint: 'number' | 'string' | 'default') => {
|
|
99
|
+
return target.toString();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (key in target) {
|
|
104
|
+
return Reflect.get(target, key)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (key === 'then') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const originalSubject = f`${ target }`;
|
|
112
|
+
|
|
113
|
+
const fieldDescription = (typeof key === 'number' || /^\d+$/.test(String(key)))
|
|
114
|
+
? `[${ String(key) }]` // array index
|
|
115
|
+
: `.${ String(key) }`; // field/method name
|
|
116
|
+
|
|
117
|
+
return Question.about(`${ originalSubject }${ fieldDescription }`, async (actor: AnswersQuestions & UsesAbilities) => {
|
|
118
|
+
const answer = await actor.answer(target as Answerable<AT>);
|
|
119
|
+
|
|
120
|
+
if (! isDefined(answer)) {
|
|
121
|
+
return undefined; // eslint-disable-line unicorn/no-useless-undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const field = answer[key];
|
|
125
|
+
|
|
126
|
+
return typeof field === 'function'
|
|
127
|
+
? field.bind(answer)
|
|
128
|
+
: field;
|
|
129
|
+
})
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
set(currentStatement: () => Question<AT>, key: string | symbol, value: any, receiver: any): boolean {
|
|
133
|
+
const target = currentStatement();
|
|
134
|
+
|
|
135
|
+
return Reflect.set(target, key, value);
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
apply(currentStatement: () => Question<AT>, _thisArgument: any, parameters: unknown[]): QuestionAdapter<AT> {
|
|
139
|
+
|
|
140
|
+
const target = currentStatement();
|
|
141
|
+
|
|
142
|
+
const parameterDescriptions = [
|
|
143
|
+
'(',
|
|
144
|
+
parameters.map(p => f`${ p }`).join(', '),
|
|
145
|
+
')'
|
|
146
|
+
].join('');
|
|
147
|
+
|
|
148
|
+
return Question.about(target.toString() + parameterDescriptions, async actor => {
|
|
149
|
+
const params = [] as any;
|
|
150
|
+
for (const parameter of parameters) {
|
|
151
|
+
const answered = await actor.answer(parameter);
|
|
152
|
+
params.push(answered);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const field = await actor.answer(target);
|
|
156
|
+
|
|
157
|
+
return typeof field === 'function'
|
|
158
|
+
? field(...params)
|
|
159
|
+
: field;
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
getPrototypeOf(currentStatement: () => Question<AT>): object | null {
|
|
164
|
+
return Reflect.getPrototypeOf(currentStatement());
|
|
165
|
+
},
|
|
166
|
+
}) as any;
|
|
167
|
+
}
|
|
168
|
+
|
|
99
169
|
/**
|
|
100
170
|
* @desc
|
|
101
171
|
* Describes the subject of this {@link Question}.
|
|
102
172
|
*
|
|
103
173
|
* @returns {string}
|
|
104
174
|
*/
|
|
105
|
-
toString(): string
|
|
106
|
-
return this.subject;
|
|
107
|
-
}
|
|
175
|
+
abstract toString(): string;
|
|
108
176
|
|
|
109
177
|
/**
|
|
110
178
|
* @desc
|
|
@@ -113,135 +181,149 @@ export abstract class Question<T> {
|
|
|
113
181
|
* @param {string} subject
|
|
114
182
|
* @returns {Question<T>}
|
|
115
183
|
*/
|
|
116
|
-
describedAs(subject: string): this
|
|
117
|
-
this.subject = subject;
|
|
184
|
+
abstract describedAs(subject: string): this;
|
|
118
185
|
|
|
119
|
-
|
|
186
|
+
/**
|
|
187
|
+
* @abstract
|
|
188
|
+
*/
|
|
189
|
+
abstract answeredBy(actor: AnswersQuestions & UsesAbilities): T;
|
|
190
|
+
|
|
191
|
+
public as<O>(mapping: (answer: Awaited<T>) => Promise<O> | O): QuestionAdapter<O> {
|
|
192
|
+
return Question.about<O>(f`${ this }.as(${ mapping })`, async actor => {
|
|
193
|
+
const answer = (await actor.answer(this)) as Awaited<T>;
|
|
194
|
+
return mapping(answer);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
declare global {
|
|
200
|
+
interface ProxyConstructor {
|
|
201
|
+
new <Source_Type extends object, Target_Type extends object>(target: Source_Type, handler: ProxyHandler<Source_Type>): Target_Type;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
206
|
+
export type ProxiedAnswer<Original_Type> = {
|
|
207
|
+
[Field in keyof Omit<Original_Type, keyof QuestionStatement<Original_Type>>]:
|
|
208
|
+
// is it a method?
|
|
209
|
+
Original_Type[Field] extends (...args: infer OriginalParameters) => infer OriginalMethodResult
|
|
210
|
+
// make the method signature asynchronous, accepting Answerables and returning a Promise
|
|
211
|
+
? (...args: { [P in keyof OriginalParameters]: Answerable<OriginalParameters[P]> }) =>
|
|
212
|
+
{ isPresent(): Question<Promise<boolean>> } & QuestionAdapter<Awaited<OriginalMethodResult>>
|
|
213
|
+
// is it an object? wrap each field
|
|
214
|
+
: { isPresent(): Question<Promise<boolean>> } & QuestionAdapter<Awaited<Original_Type[Field]>>
|
|
215
|
+
};
|
|
216
|
+
/* eslint-enable @typescript-eslint/indent */
|
|
217
|
+
|
|
218
|
+
export type QuestionAdapter<Original_Type> =
|
|
219
|
+
Question<Promise<Original_Type>> &
|
|
220
|
+
Interaction &
|
|
221
|
+
Optional &
|
|
222
|
+
ProxiedAnswer<Original_Type>;
|
|
223
|
+
|
|
224
|
+
class QuestionStatement<Answer_Type> extends Interaction implements Question<Promise<Answer_Type>>, Optional {
|
|
225
|
+
|
|
226
|
+
constructor(
|
|
227
|
+
private subject: string,
|
|
228
|
+
private readonly body: (actor: AnswersQuestions & UsesAbilities, ...Parameters) => Promise<Answer_Type> | Answer_Type,
|
|
229
|
+
) {
|
|
230
|
+
super();
|
|
120
231
|
}
|
|
121
232
|
|
|
122
233
|
/**
|
|
123
234
|
* @desc
|
|
124
|
-
*
|
|
125
|
-
*
|
|
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';
|
|
235
|
+
* Returns a Question that resolves to `true` if resolving the {@link QuestionStatement}
|
|
236
|
+
* returns a value other than `null` or `undefined` and doesn't throw errors.
|
|
148
237
|
*
|
|
149
|
-
*
|
|
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}
|
|
238
|
+
* @returns {Question<Promise<boolean>>}
|
|
181
239
|
*/
|
|
182
|
-
|
|
183
|
-
return
|
|
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>>>;
|
|
240
|
+
isPresent(): Question<Promise<boolean>> {
|
|
241
|
+
return new IsPresent(f`${ this }.isPresent()`, this.body);
|
|
191
242
|
}
|
|
192
243
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
244
|
+
async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Answer_Type> {
|
|
245
|
+
const result = await this.body(actor);
|
|
246
|
+
|
|
247
|
+
return isDefined(result)
|
|
248
|
+
? result
|
|
249
|
+
: undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async performAs(actor: UsesAbilities & AnswersQuestions): Promise<void> {
|
|
253
|
+
await this.body(actor);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
describedAs(subject: string): this {
|
|
257
|
+
this.subject = subject;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
toString(): string {
|
|
262
|
+
return this.subject;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
as<O>(mapping: (answer: Awaited<Answer_Type>) => (Promise<O> | O)): QuestionAdapter<O>{
|
|
266
|
+
return Question.about<O>(f`${ this }.as(${ mapping })`, async actor => {
|
|
267
|
+
const answer = await actor.answer(this);
|
|
268
|
+
|
|
269
|
+
if (! isDefined(answer)) {
|
|
270
|
+
return undefined; // eslint-disable-line unicorn/no-useless-undefined
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return mapping(answer);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
197
276
|
}
|
|
198
277
|
|
|
199
278
|
/**
|
|
200
279
|
* @package
|
|
201
280
|
*/
|
|
202
|
-
|
|
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;
|
|
281
|
+
class IsPresent<T> extends Question<Promise<boolean>> {
|
|
210
282
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
? O[]
|
|
218
|
-
: O
|
|
219
|
-
: T extends Mappable<infer Item> // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
220
|
-
? O[]
|
|
221
|
-
: O
|
|
283
|
+
constructor(
|
|
284
|
+
private subject: string,
|
|
285
|
+
private readonly body: (actor: AnswersQuestions & UsesAbilities) => T,
|
|
286
|
+
) {
|
|
287
|
+
super();
|
|
288
|
+
}
|
|
222
289
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
290
|
+
async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<boolean> {
|
|
291
|
+
try {
|
|
292
|
+
const answer = await this.body(actor);
|
|
293
|
+
|
|
294
|
+
if (answer === undefined || answer === null) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (this.isOptional(answer)) {
|
|
299
|
+
return await actor.answer(answer.isPresent());
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return true;
|
|
303
|
+
} catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
229
306
|
}
|
|
230
307
|
|
|
231
|
-
|
|
232
|
-
return
|
|
308
|
+
private isOptional(maybeOptional: any): maybeOptional is Optional {
|
|
309
|
+
return typeof maybeOptional === 'object'
|
|
310
|
+
&& Reflect.has(maybeOptional, 'isPresent');
|
|
233
311
|
}
|
|
234
312
|
|
|
235
|
-
/**
|
|
236
|
-
* Changes the description of this question's subject
|
|
237
|
-
* and produces a new instance without mutating the original one.
|
|
238
|
-
*
|
|
239
|
-
* @param {string} subject
|
|
240
|
-
* @returns {Question<T>}
|
|
241
|
-
*/
|
|
242
313
|
describedAs(subject: string): this {
|
|
243
314
|
this.subject = subject;
|
|
244
|
-
|
|
245
315
|
return this;
|
|
246
316
|
}
|
|
317
|
+
|
|
318
|
+
toString(): string {
|
|
319
|
+
return this.subject;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @package
|
|
325
|
+
*/
|
|
326
|
+
function isDefined<T>(value: T): boolean {
|
|
327
|
+
return value !== undefined
|
|
328
|
+
&& value !== null;
|
|
247
329
|
}
|
|
@@ -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 './Optional';
|
|
9
10
|
export * from './Question';
|
|
10
11
|
export * from './questions';
|
|
11
12
|
export * from './Task';
|
|
12
|
-
export * from './tasks';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d } from '../../io';
|
|
2
2
|
import { Activity } from '../Activity';
|
|
3
3
|
import { AnswersQuestions, PerformsActivities } from '../actor';
|
|
4
4
|
import { Answerable } from '../Answerable';
|
|
@@ -44,7 +44,7 @@ import { ExpectationMet } from './expectations';
|
|
|
44
44
|
*/
|
|
45
45
|
export class Check<Actual> extends Task {
|
|
46
46
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
47
|
-
static whether<A>(actual: Answerable<A>, expectation: Expectation<
|
|
47
|
+
static whether<A>(actual: Answerable<A>, expectation: Expectation<A>) {
|
|
48
48
|
return {
|
|
49
49
|
andIfSo: (...activities: Activity[]) => new Check(actual, expectation, activities),
|
|
50
50
|
};
|
|
@@ -59,7 +59,7 @@ export class Check<Actual> extends Task {
|
|
|
59
59
|
*/
|
|
60
60
|
constructor(
|
|
61
61
|
private readonly actual: Answerable<Actual>,
|
|
62
|
-
private readonly expectation: Expectation<
|
|
62
|
+
private readonly expectation: Expectation<Actual>,
|
|
63
63
|
private readonly activities: Activity[],
|
|
64
64
|
private readonly alternativeActivities: Activity[] = [],
|
|
65
65
|
) {
|
|
@@ -86,17 +86,12 @@ export class Check<Actual> extends Task {
|
|
|
86
86
|
* @see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions}
|
|
87
87
|
* @see {@link @serenity-js/core/lib/screenplay/actor~PerformsActivities}
|
|
88
88
|
*/
|
|
89
|
-
performAs(actor: AnswersQuestions & PerformsActivities):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
outcome instanceof ExpectationMet
|
|
96
|
-
? actor.attemptsTo(...this.activities)
|
|
97
|
-
: actor.attemptsTo(...this.alternativeActivities),
|
|
98
|
-
),
|
|
99
|
-
);
|
|
89
|
+
async performAs(actor: AnswersQuestions & PerformsActivities): Promise<void> {
|
|
90
|
+
const outcome = await actor.answer(this.expectation.isMetFor(this.actual));
|
|
91
|
+
|
|
92
|
+
return outcome instanceof ExpectationMet
|
|
93
|
+
? actor.attemptsTo(...this.activities)
|
|
94
|
+
: actor.attemptsTo(...this.alternativeActivities);
|
|
100
95
|
}
|
|
101
96
|
|
|
102
97
|
/**
|
|
@@ -106,6 +101,6 @@ export class Check<Actual> extends Task {
|
|
|
106
101
|
* @returns {string}
|
|
107
102
|
*/
|
|
108
103
|
toString(): string {
|
|
109
|
-
return
|
|
104
|
+
return d`#actor checks whether ${ this.actual } does ${ this.expectation }`;
|
|
110
105
|
}
|
|
111
106
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Answerable, AnswersQuestions, Question } from '../';
|
|
3
|
-
import {
|
|
1
|
+
import { d } from '../../io';
|
|
2
|
+
import { Answerable, AnswersQuestions, ExpectationMet, ExpectationNotMet, Question, QuestionAdapter } from '../';
|
|
3
|
+
import { ExpectationOutcome } from './expectations';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @public
|
|
7
7
|
*
|
|
8
|
-
* @typedef {function(actual:
|
|
8
|
+
* @typedef {function(actual: Answerable<Actual>) => Promise<ExpectationOutcome<Expected, Actual>> | ExpectationOutcome<unknown, Actual>} Predicate<Actual>
|
|
9
9
|
*/
|
|
10
|
-
export type Predicate<
|
|
10
|
+
export type Predicate<Actual> = (actor: AnswersQuestions, actual: Answerable<Actual>) =>
|
|
11
|
+
Promise<ExpectationOutcome<unknown, Actual>> | ExpectationOutcome<unknown, Actual>; // eslint-disable-line @typescript-eslint/indent
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @desc
|
|
@@ -16,9 +17,7 @@ export type Predicate<A, E> = (actual: A, expected: E) => boolean;
|
|
|
16
17
|
*
|
|
17
18
|
* @extends {Question}
|
|
18
19
|
*/
|
|
19
|
-
export
|
|
20
|
-
extends Question<(actual: Actual) => Promise<ExpectationOutcome<Expected, Actual>>>
|
|
21
|
-
{
|
|
20
|
+
export class Expectation<Actual> {
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* @desc
|
|
@@ -40,14 +39,28 @@ export abstract class Expectation<Expected, Actual = Expected>
|
|
|
40
39
|
* @param {string} relationshipName
|
|
41
40
|
* @param {@serenity-js/core/lib/screenplay~Answerable<E>} expectedValue
|
|
42
41
|
*
|
|
43
|
-
* @returns {"soThat": function(predicate: Predicate<
|
|
42
|
+
* @returns {"soThat": function(predicate: Predicate<Expected, Actual>): Expectation<Expected, Actual>}
|
|
44
43
|
*/
|
|
45
|
-
static thatActualShould<E, A>(relationshipName: string, expectedValue
|
|
46
|
-
soThat: (
|
|
44
|
+
static thatActualShould<E, A>(relationshipName: string, expectedValue?: Answerable<E>): {
|
|
45
|
+
soThat: (simplifiedPredicate: (actualValue: A, expectedValue: E) => Promise<boolean> | boolean) => Expectation<A>,
|
|
47
46
|
} {
|
|
48
47
|
return ({
|
|
49
|
-
soThat: (
|
|
50
|
-
|
|
48
|
+
soThat: (simplifiedPredicate: (actualValue: A, expectedValue: E) => Promise<boolean> | boolean): Expectation<A> => {
|
|
49
|
+
const subject = relationshipName + ' ' + d`${expectedValue}`;
|
|
50
|
+
|
|
51
|
+
return new Expectation<A>(
|
|
52
|
+
subject,
|
|
53
|
+
async (actor: AnswersQuestions, actualValue: Answerable<A>): Promise<ExpectationOutcome<E, A>> => {
|
|
54
|
+
const expected = await actor.answer(expectedValue);
|
|
55
|
+
const actual = await actor.answer(actualValue);
|
|
56
|
+
|
|
57
|
+
const result = await simplifiedPredicate(actual, expected);
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
? new ExpectationMet(subject, expected, actual)
|
|
61
|
+
: new ExpectationNotMet(subject, expected, actual);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
51
64
|
},
|
|
52
65
|
});
|
|
53
66
|
}
|
|
@@ -77,60 +90,39 @@ export abstract class Expectation<Expected, Actual = Expected>
|
|
|
77
90
|
*
|
|
78
91
|
* @returns {"soThat": function(...expectations: Array<Expectation<any, A>>): Expectation<any, A>}
|
|
79
92
|
*/
|
|
80
|
-
static to<A>(relationshipName: string): {
|
|
81
|
-
soThatActual: (...expectations: Array<Expectation<
|
|
93
|
+
static to<E, A>(relationshipName: string): {
|
|
94
|
+
soThatActual: (...expectations: Array<Expectation<A>>) => Expectation<A>,
|
|
82
95
|
} {
|
|
83
96
|
return {
|
|
84
|
-
soThatActual: (expectation: Expectation<
|
|
85
|
-
return new
|
|
97
|
+
soThatActual: (expectation: Expectation<A>): Expectation<A> => {
|
|
98
|
+
return new Expectation<A>(
|
|
99
|
+
relationshipName,
|
|
100
|
+
async (actor: AnswersQuestions, actualValue: Answerable<A>): Promise<ExpectationOutcome<E, A>> => {
|
|
101
|
+
const outcome = await actor.answer(expectation.isMetFor(actualValue));
|
|
102
|
+
|
|
103
|
+
return outcome as ExpectationOutcome<E, A>;
|
|
104
|
+
}
|
|
105
|
+
);
|
|
86
106
|
},
|
|
87
107
|
};
|
|
88
108
|
}
|
|
89
109
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @package
|
|
95
|
-
*/
|
|
96
|
-
class DynamicallyGeneratedExpectation<Expected, Actual> extends Expectation<Expected, Actual> {
|
|
97
|
-
|
|
98
|
-
constructor(
|
|
99
|
-
private readonly description: string,
|
|
100
|
-
private readonly predicate: Predicate<Actual, Expected>,
|
|
101
|
-
private readonly expectedValue: Answerable<Expected>,
|
|
110
|
+
protected constructor(
|
|
111
|
+
private subject: string,
|
|
112
|
+
private readonly predicate: Predicate<Actual>,
|
|
102
113
|
) {
|
|
103
|
-
super(`${ description } ${ formatted `${ expectedValue }` }`);
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return (actual: Actual) => actor.answer(this.expectedValue)
|
|
109
|
-
.then(expected => {
|
|
110
|
-
return this.predicate(actual, expected)
|
|
111
|
-
? new ExpectationMet(this.toString(), expected, actual)
|
|
112
|
-
: new ExpectationNotMet(this.toString(), expected, actual);
|
|
113
|
-
});
|
|
116
|
+
isMetFor(actual: Answerable<Actual>): QuestionAdapter<ExpectationOutcome<unknown, Actual>> {
|
|
117
|
+
return Question.about(this.subject, actor => this.predicate(actor, actual));
|
|
114
118
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @package
|
|
119
|
-
*/
|
|
120
|
-
class ExpectationAlias<Actual> extends Expectation<any, Actual> {
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
subject
|
|
124
|
-
|
|
125
|
-
) {
|
|
126
|
-
super(subject);
|
|
120
|
+
describedAs(subject: string): this {
|
|
121
|
+
this.subject = subject;
|
|
122
|
+
return this;
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return (actual: Actual) =>
|
|
132
|
-
this.expectation.answeredBy(actor)(actual).then(_ => _ instanceof ExpectationMet
|
|
133
|
-
? new ExpectationMet(this.subject, _.expected, _.actual)
|
|
134
|
-
: new ExpectationNotMet(_.message, _.expected, _.actual));
|
|
125
|
+
toString(): string {
|
|
126
|
+
return this.subject;
|
|
135
127
|
}
|
|
136
128
|
}
|