@serenity-js/core 3.23.2 → 3.24.1
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 +32 -0
- package/lib/errors/ErrorFactory.js +4 -4
- package/lib/errors/ErrorFactory.js.map +1 -1
- package/lib/errors/RaiseErrors.d.ts +2 -2
- package/lib/errors/RaiseErrors.js +2 -2
- package/lib/io/index.d.ts +0 -1
- package/lib/io/index.d.ts.map +1 -1
- package/lib/io/index.js +0 -1
- package/lib/io/index.js.map +1 -1
- package/lib/io/inspectedObject.js +1 -1
- package/lib/io/inspectedObject.js.map +1 -1
- package/lib/io/reflection/ValueInspector.d.ts +56 -0
- package/lib/io/reflection/ValueInspector.d.ts.map +1 -0
- package/lib/io/reflection/ValueInspector.js +149 -0
- package/lib/io/reflection/ValueInspector.js.map +1 -0
- package/lib/io/reflection/index.d.ts +1 -2
- package/lib/io/reflection/index.d.ts.map +1 -1
- package/lib/io/reflection/index.js +1 -2
- package/lib/io/reflection/index.js.map +1 -1
- package/lib/io/stringified.js +7 -90
- package/lib/io/stringified.js.map +1 -1
- package/lib/screenplay/Activity.d.ts +5 -12
- package/lib/screenplay/Activity.d.ts.map +1 -1
- package/lib/screenplay/Activity.js +3 -14
- package/lib/screenplay/Activity.js.map +1 -1
- package/lib/screenplay/Actor.js +1 -1
- package/lib/screenplay/Actor.js.map +1 -1
- package/lib/screenplay/Interaction.d.ts +4 -3
- package/lib/screenplay/Interaction.d.ts.map +1 -1
- package/lib/screenplay/Interaction.js +2 -2
- package/lib/screenplay/Interaction.js.map +1 -1
- package/lib/screenplay/Question.d.ts +73 -21
- package/lib/screenplay/Question.d.ts.map +1 -1
- package/lib/screenplay/Question.js +237 -30
- package/lib/screenplay/Question.js.map +1 -1
- package/lib/screenplay/Task.d.ts +16 -15
- package/lib/screenplay/Task.d.ts.map +1 -1
- package/lib/screenplay/Task.js +14 -14
- package/lib/screenplay/Task.js.map +1 -1
- package/lib/screenplay/abilities/Ability.d.ts +8 -6
- package/lib/screenplay/abilities/Ability.d.ts.map +1 -1
- package/lib/screenplay/abilities/Ability.js +8 -6
- package/lib/screenplay/abilities/Ability.js.map +1 -1
- package/lib/screenplay/abilities/AbilityType.d.ts +3 -3
- package/lib/screenplay/abilities/AnswerQuestions.d.ts +0 -1
- package/lib/screenplay/abilities/AnswerQuestions.d.ts.map +1 -1
- package/lib/screenplay/abilities/AnswerQuestions.js +2 -4
- package/lib/screenplay/abilities/AnswerQuestions.js.map +1 -1
- package/lib/screenplay/abilities/PerformActivities.d.ts +5 -3
- package/lib/screenplay/abilities/PerformActivities.d.ts.map +1 -1
- package/lib/screenplay/abilities/PerformActivities.js +12 -10
- package/lib/screenplay/abilities/PerformActivities.js.map +1 -1
- package/lib/screenplay/artifacts/CollectsArtifacts.d.ts +2 -2
- package/lib/screenplay/notes/NotepadAdapter.d.ts.map +1 -1
- package/lib/screenplay/notes/NotepadAdapter.js +44 -4
- package/lib/screenplay/notes/NotepadAdapter.js.map +1 -1
- package/lib/screenplay/questions/Describable.d.ts +27 -0
- package/lib/screenplay/questions/Describable.d.ts.map +1 -0
- package/lib/screenplay/questions/Describable.js +40 -0
- package/lib/screenplay/questions/Describable.js.map +1 -0
- package/lib/screenplay/questions/DescriptionFormattingOptions.d.ts +14 -0
- package/lib/screenplay/questions/DescriptionFormattingOptions.d.ts.map +1 -0
- package/lib/screenplay/questions/DescriptionFormattingOptions.js +3 -0
- package/lib/screenplay/questions/DescriptionFormattingOptions.js.map +1 -0
- package/lib/screenplay/questions/Expectation.d.ts +6 -10
- package/lib/screenplay/questions/Expectation.d.ts.map +1 -1
- package/lib/screenplay/questions/Expectation.js +12 -15
- package/lib/screenplay/questions/Expectation.js.map +1 -1
- package/lib/screenplay/questions/List.d.ts +1 -3
- package/lib/screenplay/questions/List.d.ts.map +1 -1
- package/lib/screenplay/questions/List.js +6 -31
- package/lib/screenplay/questions/List.js.map +1 -1
- package/lib/screenplay/questions/Unanswered.d.ts +1 -0
- package/lib/screenplay/questions/Unanswered.d.ts.map +1 -1
- package/lib/screenplay/questions/Unanswered.js +3 -0
- package/lib/screenplay/questions/Unanswered.js.map +1 -1
- package/lib/screenplay/questions/expectations/ExpectationDetails.js +1 -1
- package/lib/screenplay/questions/expectations/ExpectationDetails.js.map +1 -1
- package/lib/screenplay/questions/index.d.ts +3 -1
- package/lib/screenplay/questions/index.d.ts.map +1 -1
- package/lib/screenplay/questions/index.js +3 -1
- package/lib/screenplay/questions/index.js.map +1 -1
- package/lib/screenplay/questions/tag-functions.d.ts +228 -0
- package/lib/screenplay/questions/tag-functions.d.ts.map +1 -0
- package/lib/screenplay/questions/tag-functions.js +115 -0
- package/lib/screenplay/questions/tag-functions.js.map +1 -0
- package/lib/screenplay/time/activities/Wait.d.ts.map +1 -1
- package/lib/screenplay/time/activities/Wait.js +4 -3
- package/lib/screenplay/time/activities/Wait.js.map +1 -1
- package/package.json +6 -6
- package/src/errors/ErrorFactory.ts +5 -5
- package/src/errors/RaiseErrors.ts +2 -2
- package/src/io/index.ts +0 -1
- package/src/io/inspectedObject.ts +2 -2
- package/src/io/reflection/ValueInspector.ts +165 -0
- package/src/io/reflection/index.ts +1 -2
- package/src/io/stringified.ts +7 -103
- package/src/screenplay/Activity.ts +6 -17
- package/src/screenplay/Actor.ts +2 -2
- package/src/screenplay/Interaction.ts +5 -4
- package/src/screenplay/Question.ts +299 -49
- package/src/screenplay/Task.ts +18 -17
- package/src/screenplay/abilities/Ability.ts +8 -6
- package/src/screenplay/abilities/AbilityType.ts +3 -3
- package/src/screenplay/abilities/AnswerQuestions.ts +2 -5
- package/src/screenplay/abilities/PerformActivities.ts +35 -18
- package/src/screenplay/artifacts/CollectsArtifacts.ts +2 -2
- package/src/screenplay/notes/NotepadAdapter.ts +57 -6
- package/src/screenplay/questions/Describable.ts +48 -0
- package/src/screenplay/questions/DescriptionFormattingOptions.ts +13 -0
- package/src/screenplay/questions/Expectation.ts +19 -19
- package/src/screenplay/questions/List.ts +7 -41
- package/src/screenplay/questions/Unanswered.ts +4 -0
- package/src/screenplay/questions/expectations/ExpectationDetails.ts +2 -2
- package/src/screenplay/questions/index.ts +3 -1
- package/src/screenplay/questions/tag-functions.ts +313 -0
- package/src/screenplay/time/activities/Wait.ts +4 -3
- package/lib/io/isPlainObject.d.ts +0 -7
- package/lib/io/isPlainObject.d.ts.map +0 -1
- package/lib/io/isPlainObject.js +0 -25
- package/lib/io/isPlainObject.js.map +0 -1
- package/lib/io/reflection/isPrimitive.d.ts +0 -8
- package/lib/io/reflection/isPrimitive.d.ts.map +0 -1
- package/lib/io/reflection/isPrimitive.js +0 -24
- package/lib/io/reflection/isPrimitive.js.map +0 -1
- package/lib/io/reflection/typeOf.d.ts +0 -7
- package/lib/io/reflection/typeOf.d.ts.map +0 -1
- package/lib/io/reflection/typeOf.js +0 -35
- package/lib/io/reflection/typeOf.js.map +0 -1
- package/lib/screenplay/questions/q.d.ts +0 -66
- package/lib/screenplay/questions/q.d.ts.map +0 -1
- package/lib/screenplay/questions/q.js +0 -77
- package/lib/screenplay/questions/q.js.map +0 -1
- package/src/io/isPlainObject.ts +0 -24
- package/src/io/reflection/isPrimitive.ts +0 -20
- package/src/io/reflection/typeOf.ts +0 -31
- package/src/screenplay/questions/q.ts +0 -82
|
@@ -16,7 +16,7 @@ import { ExpectationMet } from './expectations';
|
|
|
16
16
|
*
|
|
17
17
|
* @group Questions
|
|
18
18
|
*/
|
|
19
|
-
export abstract class List<Item_Type> extends Question<Promise<Item_Type
|
|
19
|
+
export abstract class List<Item_Type> extends Question<Promise<Array<Item_Type>>> {
|
|
20
20
|
protected subject?: string;
|
|
21
21
|
|
|
22
22
|
static of<IT, CT, RQT extends (Question<Promise<Array<IT>>> | Question<Array<IT>>)>(collection: Answerable<Array<IT>> & ChainableMetaQuestion<CT, RQT>): MetaList<CT, IT>;
|
|
@@ -30,7 +30,7 @@ export abstract class List<Item_Type> extends Question<Promise<Item_Type[]>> {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
constructor(protected readonly collection: Answerable<Array<Item_Type>>) {
|
|
33
|
-
super();
|
|
33
|
+
super(d`${ collection }`);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
forEach(callback: (current: CurrentItem<Item_Type>, index: number, items: Array<Item_Type>) => Promise<void> | void): Task {
|
|
@@ -64,15 +64,6 @@ export abstract class List<Item_Type> extends Question<Promise<Item_Type[]>> {
|
|
|
64
64
|
return collection;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
describedAs(subject: string): this {
|
|
68
|
-
this.subject = subject;
|
|
69
|
-
return this;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
toString(): string {
|
|
73
|
-
return this.subject ?? d`${ this.collection }`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
67
|
/**
|
|
77
68
|
* @param {number} index
|
|
78
69
|
*/
|
|
@@ -287,21 +278,17 @@ export class MetaList<Supported_Context_Type, Item_Type>
|
|
|
287
278
|
class Where<Item_Type, Answer_Type>
|
|
288
279
|
extends Question<Promise<Array<Item_Type>>>
|
|
289
280
|
{
|
|
290
|
-
private subject: string;
|
|
291
|
-
|
|
292
281
|
constructor(
|
|
293
282
|
protected readonly collection: Answerable<Array<Item_Type>>,
|
|
294
283
|
protected readonly question: MetaQuestion<Item_Type, Question<Promise<Answer_Type> | Answer_Type>>,
|
|
295
284
|
protected readonly expectation: Expectation<Answer_Type>,
|
|
296
285
|
originalSubject: string,
|
|
297
286
|
) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const prefix = this.collection instanceof Where
|
|
287
|
+
const prefix = collection instanceof Where
|
|
301
288
|
? ' and'
|
|
302
289
|
: ' where';
|
|
303
290
|
|
|
304
|
-
|
|
291
|
+
super(originalSubject + prefix + d` ${ question } does ${ expectation }`);
|
|
305
292
|
}
|
|
306
293
|
|
|
307
294
|
async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Array<Item_Type>> {
|
|
@@ -319,19 +306,11 @@ class Where<Item_Type, Answer_Type>
|
|
|
319
306
|
}
|
|
320
307
|
|
|
321
308
|
return results;
|
|
322
|
-
}
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
323
311
|
throw new LogicError(d`Couldn't check if ${ this.question } of an item of ${ this.collection } does ${ this.expectation }: ` + error.message, error);
|
|
324
312
|
}
|
|
325
313
|
}
|
|
326
|
-
|
|
327
|
-
describedAs(subject: string): this {
|
|
328
|
-
this.subject = subject;
|
|
329
|
-
return this;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
toString(): string {
|
|
333
|
-
return this.subject;
|
|
334
|
-
}
|
|
335
314
|
}
|
|
336
315
|
|
|
337
316
|
/**
|
|
@@ -356,16 +335,12 @@ class MetaWhere<Supported_Context_Type, Item_Type, Answer_Type>
|
|
|
356
335
|
*/
|
|
357
336
|
class EachMappedTo<Item_Type, Mapped_Item_Type> extends Question<Promise<Array<Mapped_Item_Type>>> {
|
|
358
337
|
|
|
359
|
-
private subject: string;
|
|
360
|
-
|
|
361
338
|
constructor(
|
|
362
339
|
protected readonly collection: Answerable<Array<Item_Type>>,
|
|
363
340
|
protected readonly mapping: MetaQuestion<Item_Type, Question<Promise<Mapped_Item_Type> | Mapped_Item_Type>>,
|
|
364
341
|
originalSubject: string,
|
|
365
342
|
) {
|
|
366
|
-
super();
|
|
367
|
-
|
|
368
|
-
this.subject = originalSubject + d` mapped to ${ this.mapping }`;
|
|
343
|
+
super(originalSubject + d` mapped to ${ mapping }`);
|
|
369
344
|
}
|
|
370
345
|
|
|
371
346
|
async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<Array<Mapped_Item_Type>> {
|
|
@@ -379,15 +354,6 @@ class EachMappedTo<Item_Type, Mapped_Item_Type> extends Question<Promise<Array<M
|
|
|
379
354
|
|
|
380
355
|
return mapped;
|
|
381
356
|
}
|
|
382
|
-
|
|
383
|
-
describedAs(subject: string): this {
|
|
384
|
-
this.subject = subject;
|
|
385
|
-
return this;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
toString(): string {
|
|
389
|
-
return this.subject;
|
|
390
|
-
}
|
|
391
357
|
}
|
|
392
358
|
|
|
393
359
|
/**
|
|
@@ -14,6 +14,10 @@ import * as util from 'util'; // eslint-disable-line unicorn/import-style
|
|
|
14
14
|
* @group Questions
|
|
15
15
|
*/
|
|
16
16
|
export class Unanswered extends TinyType {
|
|
17
|
+
static isUnanswered(value: unknown): value is Unanswered {
|
|
18
|
+
return value instanceof Unanswered;
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
[util.inspect.custom](): string {
|
|
18
22
|
return `<<unanswered>>`;
|
|
19
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JSONObject, JSONValue} from 'tiny-types';
|
|
2
2
|
import { ensure, isArray, isDefined, TinyType } from 'tiny-types';
|
|
3
3
|
|
|
4
|
-
import { inspected,
|
|
4
|
+
import { inspected, ValueInspector } from '../../../io';
|
|
5
5
|
import { Name } from '../../../model';
|
|
6
6
|
import { Unanswered } from '../Unanswered';
|
|
7
7
|
|
|
@@ -55,7 +55,7 @@ export class ExpectationDetails extends TinyType {
|
|
|
55
55
|
return {
|
|
56
56
|
name: this.name.value,
|
|
57
57
|
args: this.args.map(arg => ({
|
|
58
|
-
type: typeOf(arg),
|
|
58
|
+
type: ValueInspector.typeOf(arg),
|
|
59
59
|
value: arg['toJSON']
|
|
60
60
|
? (arg as any).toJSON()
|
|
61
61
|
: arg,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export * from './AnswersQuestions';
|
|
2
2
|
export * from './ChainableMetaQuestion';
|
|
3
3
|
export * from './Check';
|
|
4
|
+
export * from './Describable';
|
|
5
|
+
export * from './DescriptionFormattingOptions';
|
|
4
6
|
export * from './Expectation';
|
|
5
7
|
export * from './expectations';
|
|
6
8
|
export * from './List';
|
|
7
9
|
export * from './Masked';
|
|
8
10
|
export * from './MetaQuestion';
|
|
9
|
-
export * from './
|
|
11
|
+
export * from './tag-functions';
|
|
10
12
|
export * from './Unanswered';
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { asyncMap, ValueInspector } from '../../io';
|
|
2
|
+
import type { UsesAbilities } from '../abilities';
|
|
3
|
+
import type { Answerable } from '../Answerable';
|
|
4
|
+
import type { MetaQuestionAdapter, QuestionAdapter } from '../Question';
|
|
5
|
+
import { Question } from '../Question';
|
|
6
|
+
import type { AnswersQuestions } from './AnswersQuestions';
|
|
7
|
+
import { Describable } from './Describable';
|
|
8
|
+
import type { DescriptionFormattingOptions } from './DescriptionFormattingOptions';
|
|
9
|
+
import type { MetaQuestion } from './MetaQuestion';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a single-line description of an {@apilink Activity} by transforming
|
|
13
|
+
* a [template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates),
|
|
14
|
+
* parameterised with [primitive data types](https://developer.mozilla.org/en-US/docs/Glossary/Primitive),
|
|
15
|
+
* [complex data structures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#objects),
|
|
16
|
+
* or any other {@apilink Answerable|Answerables}, into a {@link QuestionAdapter|`QuestionAdapter<string>`}
|
|
17
|
+
* that can be used with {@apilink Task.where} and {@apilink Interaction.where} methods.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* const dial = (phoneNumber: Answerable<string>) =>
|
|
21
|
+
* Task.where(the `#actor dials ${ phoneNumber }`, /* *\/)
|
|
22
|
+
*
|
|
23
|
+
* await actorCalled('Alice').attemptsTo(
|
|
24
|
+
* dial('(555) 123-4567'),
|
|
25
|
+
* // reported as: Alice dials "(555) 123-4567"
|
|
26
|
+
* )
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ## Trimming the output
|
|
30
|
+
*
|
|
31
|
+
* Use {@apilink DescriptionFormattingOptions} to trim the descriptions of template parameters.
|
|
32
|
+
* By default, the output is displayed in full.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { actorCalled, Task, the } from '@serenity-js/core'
|
|
36
|
+
*
|
|
37
|
+
* const dial = (phoneNumber: Answerable<string>) =>
|
|
38
|
+
* Task.where(dial({ maxLength: 10 }) `#actor dials ${ phoneNumber }`, /* *\/)
|
|
39
|
+
*
|
|
40
|
+
* await actorCalled('Alice').attemptsTo(
|
|
41
|
+
* dial('(555) 123-4567'),
|
|
42
|
+
* // reported as: Alice dials "(555) 123-...'
|
|
43
|
+
* )
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* ## Using with Questions
|
|
47
|
+
*
|
|
48
|
+
* When `the` is parameterised with {@apilink Question|Questions},
|
|
49
|
+
* it retrieves their description by calling {@apilink Question.describedBy}
|
|
50
|
+
* in the context of the {@apilink Actor} performing the {@apilink Activity}.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { actorCalled, Question, Task, the } from '@serenity-js/core'
|
|
54
|
+
*
|
|
55
|
+
* const phoneNumber = (areaCode: string, centralOfficeCode: string, lineNumber: string) =>
|
|
56
|
+
* Question.about('phone number', actor => {
|
|
57
|
+
* return `(${ this.areaCode }) ${ this.centralOfficeCode }-${ this.lineNumber }`
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* const dial = (phoneNumber: Answerable<string>) =>
|
|
61
|
+
* Task.where(dial({ maxLength: 10 }) `#actor dials ${ phoneNumber }`, /* *\/)
|
|
62
|
+
*
|
|
63
|
+
* await actorCalled('Alice').attemptsTo(
|
|
64
|
+
* dial(phoneNumber('555', '123', '4567'),
|
|
65
|
+
* // reported as: Alice dials phone number
|
|
66
|
+
* )
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* If you'd like the question to be described using its formatted value instead of its description, use {@apilink Question.formattedValue}.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* import { actorCalled, Question, Task, the } from '@serenity-js/core'
|
|
73
|
+
*
|
|
74
|
+
* const phoneNumber = (areaCode: string, centralOfficeCode: string, lineNumber: string) =>
|
|
75
|
+
* Question.about('phone number', actor => {
|
|
76
|
+
* return `(${ this.areaCode }) ${ this.centralOfficeCode }-${ this.lineNumber }`
|
|
77
|
+
* }).describedAs(Question.formattedValue())
|
|
78
|
+
*
|
|
79
|
+
* const dial = (phoneNumber: Answerable<string>) =>
|
|
80
|
+
* Task.where(dial({ maxLength: 10 }) `#actor dials ${ phoneNumber }`, /* *\/)
|
|
81
|
+
*
|
|
82
|
+
* await actorCalled('Alice').attemptsTo(
|
|
83
|
+
* dial(phoneNumber('555', '123', '4567'),
|
|
84
|
+
* // reported as: Alice dials "(555) 123-4567"
|
|
85
|
+
* )
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* ## Using with objects with a custom `toString` method
|
|
89
|
+
*
|
|
90
|
+
* When `the` is parameterised with objects that have
|
|
91
|
+
* a custom [`toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) method,
|
|
92
|
+
* or {@link Answerable|Answerables} resolving to such objects, the `toString()` method is called to produce the resulting description.
|
|
93
|
+
*
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { actorCalled, description, Task } from '@serenity-js/core'
|
|
96
|
+
*
|
|
97
|
+
* class PhoneNumber {
|
|
98
|
+
* constructor(
|
|
99
|
+
* private readonly areaCode: string,
|
|
100
|
+
* private readonly centralOfficeCode: string,
|
|
101
|
+
* private readonly lineNumber: string,
|
|
102
|
+
* ) {}
|
|
103
|
+
*
|
|
104
|
+
* toString() {
|
|
105
|
+
* return `(${ this.areaCode }) ${ this.centralOfficeCode }-${ this.lineNumber }`
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* const dial = (phoneNumber: Answerable<PhoneNumber>) =>
|
|
110
|
+
* Task.where(description `#actor dials ${ phoneNumber }`, /* *\/)
|
|
111
|
+
*
|
|
112
|
+
* await actorCalled('Alice').attemptsTo(
|
|
113
|
+
* dial(new PhoneNumber('555', '123', '4567')),
|
|
114
|
+
* // reported as: Alice dials (555) 123-4567
|
|
115
|
+
* )
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* ## Using with objects without a custom `toString` method
|
|
119
|
+
*
|
|
120
|
+
* When `the` is parameterised with complex objects that don't have a custom `toString()` method,
|
|
121
|
+
* or {@link Answerable}s resolving to such objects,
|
|
122
|
+
* the resulting description will contain a JSON-like string representation of the object.
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { actorCalled, description, Task } from '@serenity-js/core'
|
|
126
|
+
*
|
|
127
|
+
* interface PhoneNumber {
|
|
128
|
+
* areaCode: string;
|
|
129
|
+
* centralOfficeCode: string;
|
|
130
|
+
* lineNumber: string;
|
|
131
|
+
* }
|
|
132
|
+
*
|
|
133
|
+
* const dial = (phoneNumber: Answerable<PhoneNumber>) =>
|
|
134
|
+
* Task.where(the `#actor dials ${ phoneNumber }`, /* *\/)
|
|
135
|
+
*
|
|
136
|
+
* await actorCalled('Alice').attemptsTo(
|
|
137
|
+
* dial({ areaCode: '555', centralOfficeCode: '123', lineNumber: '4567' }),
|
|
138
|
+
* // reported as: Alice dials { areaCode: "555", centralOfficeCode: "123", lineNumber: "4567" }
|
|
139
|
+
* )
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* ## Using with masked values
|
|
143
|
+
*
|
|
144
|
+
* When `the` is parameterised with {@apilink Masked} values,
|
|
145
|
+
* the resulting description will contain a masked representation of the values.
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { actorCalled, description, Task } from '@serenity-js/core'
|
|
149
|
+
*
|
|
150
|
+
* const dial = (phoneNumber: Answerable<string>) =>
|
|
151
|
+
* Task.where(description `#actor dials ${ phoneNumber }`, /* *\/)
|
|
152
|
+
*
|
|
153
|
+
* await actorCalled('Alice').attemptsTo(
|
|
154
|
+
* dial(Masked.valueOf('(555) 123-4567')),
|
|
155
|
+
* // reported as: Alice dials [a masked value]
|
|
156
|
+
* )
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* ## Learn more
|
|
160
|
+
*
|
|
161
|
+
* - {@apilink Answerable}
|
|
162
|
+
* - {@apilink Question}
|
|
163
|
+
* - {@apilink Question.describedAs}
|
|
164
|
+
* - {@apilink QuestionAdapter}
|
|
165
|
+
* - {@apilink Masked}
|
|
166
|
+
*
|
|
167
|
+
* @group Questions
|
|
168
|
+
*/
|
|
169
|
+
export function the(options: DescriptionFormattingOptions): <Supported_Context_Type>(templates: TemplateStringsArray, ...placeholders: Array<MetaQuestion<Supported_Context_Type, any> | any>) => MetaQuestionAdapter<Supported_Context_Type, string>
|
|
170
|
+
export function the<Supported_Context_Type>(templates: TemplateStringsArray, ...parameters: Array<MetaQuestion<Supported_Context_Type, any> | any>): MetaQuestionAdapter<Supported_Context_Type, string>
|
|
171
|
+
export function the(...args: any[]): any {
|
|
172
|
+
if (ValueInspector.isPlainObject(args[0])) {
|
|
173
|
+
const descriptionFormattingOptions = args[0] as DescriptionFormattingOptions;
|
|
174
|
+
|
|
175
|
+
return (templates: TemplateStringsArray, ...parameters: Array<any>) =>
|
|
176
|
+
templateToQuestion(templates, parameters, createParameterToDescriptionMapper(descriptionFormattingOptions), createParameterValueToDescriptionMapper(descriptionFormattingOptions));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return templateToQuestion(args[0], args.slice(1), createParameterToDescriptionMapper(), createParameterValueToDescriptionMapper());
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A Serenity/JS Screenplay Pattern-flavour
|
|
184
|
+
* of a [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates),
|
|
185
|
+
* `q` is a tag function capable of resolving any `Answerable<string>` or `Answerable<number>`
|
|
186
|
+
* you parametrise it with, and returning a `QuestionAdapter<string>`.
|
|
187
|
+
*
|
|
188
|
+
* Use `q` to concatenate `string` and `number` values returned from synchronous an asynchronous sources.
|
|
189
|
+
*
|
|
190
|
+
* ## Interpolating questions
|
|
191
|
+
*
|
|
192
|
+
* ```ts
|
|
193
|
+
* import { q, actorCalled } from '@serenity-js/core'
|
|
194
|
+
* import { Send, DeleteRequest } from '@serenity-js/rest'
|
|
195
|
+
* import { Text } from '@serenity-js/web'
|
|
196
|
+
*
|
|
197
|
+
* await actorCalled('Alice').attemptsTo(
|
|
198
|
+
* Send.a(DeleteRequest.to(
|
|
199
|
+
* q `/articles/${ Text.of(Article.id()) }`
|
|
200
|
+
* ))
|
|
201
|
+
* )
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* ## Using a custom description
|
|
205
|
+
*
|
|
206
|
+
* ```ts
|
|
207
|
+
* import { q, actorCalled } from '@serenity-js/core'
|
|
208
|
+
* import { Send, DeleteRequest } from '@serenity-js/rest'
|
|
209
|
+
*
|
|
210
|
+
* await actorCalled('Alice').attemptsTo(
|
|
211
|
+
* Send.a(DeleteRequest.to(
|
|
212
|
+
* q `/articles/${ Text.of(Article.id()) }`.describedAs('/articles/:id')
|
|
213
|
+
* ))
|
|
214
|
+
* )
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* ## Transforming the interpolated string
|
|
218
|
+
*
|
|
219
|
+
* The mechanism presented below relies on {@apilink QuestionAdapter}.
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* import { q, actorCalled } from '@serenity-js/core'
|
|
223
|
+
* import { Send, DeleteRequest } from '@serenity-js/rest'
|
|
224
|
+
*
|
|
225
|
+
* await actorCalled('Alice').attemptsTo(
|
|
226
|
+
* Send.a(DeleteRequest.to(
|
|
227
|
+
* q `/articles/${ Text.of(Article.id()) }`.toLocaleLowerCase()
|
|
228
|
+
* ))
|
|
229
|
+
* )
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* ## Learn more
|
|
233
|
+
*
|
|
234
|
+
* - {@apilink Answerable}
|
|
235
|
+
* - {@apilink Question}
|
|
236
|
+
* - {@apilink Question.describedAs}
|
|
237
|
+
* - {@apilink QuestionAdapter}
|
|
238
|
+
*
|
|
239
|
+
* @group Questions
|
|
240
|
+
*
|
|
241
|
+
* @param templates
|
|
242
|
+
* @param parameters
|
|
243
|
+
*/
|
|
244
|
+
export function q(templates: TemplateStringsArray, ...parameters: Array<Answerable<string | number>>): QuestionAdapter<string> {
|
|
245
|
+
return templateToQuestion(
|
|
246
|
+
templates,
|
|
247
|
+
parameters,
|
|
248
|
+
(parameter: Answerable<string | number>) => {
|
|
249
|
+
// return static string and number parameter values as is
|
|
250
|
+
if (typeof parameter === 'string' || typeof parameter === 'number') {
|
|
251
|
+
return String(parameter);
|
|
252
|
+
}
|
|
253
|
+
// for Questions, Promises and other Answerables, return their description
|
|
254
|
+
return `{${ createParameterToDescriptionMapper()(parameter) }}`
|
|
255
|
+
},
|
|
256
|
+
createParameterValueMapper()
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function createParameterToDescriptionMapper(options?: DescriptionFormattingOptions) {
|
|
261
|
+
return (parameter: any) =>
|
|
262
|
+
parameter === undefined
|
|
263
|
+
? 'undefined'
|
|
264
|
+
: Question.formattedValue(options).of(parameter).toString();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function createParameterValueToDescriptionMapper(options?: DescriptionFormattingOptions) {
|
|
268
|
+
return async (actor: AnswersQuestions & UsesAbilities & { name: string }, parameter: any) =>
|
|
269
|
+
parameter instanceof Describable
|
|
270
|
+
? parameter.describedBy(actor)
|
|
271
|
+
: actor.answer(Question.formattedValue(options).of(parameter))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function createParameterValueMapper() {
|
|
275
|
+
return async (actor: AnswersQuestions & UsesAbilities & { name: string }, parameter: Answerable<string | number>) =>
|
|
276
|
+
actor.answer(parameter)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function templateToQuestion(
|
|
280
|
+
templates: TemplateStringsArray,
|
|
281
|
+
parameters: Array<any>,
|
|
282
|
+
descriptionMapper: (parameter: any) => string,
|
|
283
|
+
valueMapper: (actor: AnswersQuestions & UsesAbilities & { name: string }, parameter: any) => Promise<any> | any,
|
|
284
|
+
) {
|
|
285
|
+
const description = interpolate(templates, parameters.map(parameter => descriptionMapper(parameter)));
|
|
286
|
+
|
|
287
|
+
return Question.about<string, any>(description,
|
|
288
|
+
async (actor: AnswersQuestions & UsesAbilities & { name: string }) => {
|
|
289
|
+
const descriptions = await asyncMap(parameters, parameter => valueMapper(actor, parameter));
|
|
290
|
+
|
|
291
|
+
return interpolate(templates, descriptions);
|
|
292
|
+
},
|
|
293
|
+
(context: any) =>
|
|
294
|
+
templateToQuestion(
|
|
295
|
+
templates,
|
|
296
|
+
parameters.map(parameter =>
|
|
297
|
+
Question.isAMetaQuestion(parameter)
|
|
298
|
+
? parameter.of(context)
|
|
299
|
+
: parameter
|
|
300
|
+
),
|
|
301
|
+
descriptionMapper,
|
|
302
|
+
valueMapper,
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function interpolate(templates: TemplateStringsArray, parameters: Array<any>): string {
|
|
308
|
+
return templates.flatMap((template, i) =>
|
|
309
|
+
i < parameters.length
|
|
310
|
+
? [ template, parameters[i] ]
|
|
311
|
+
: [ template ],
|
|
312
|
+
).join('');
|
|
313
|
+
}
|
|
@@ -5,7 +5,8 @@ import { d } from '../../../io';
|
|
|
5
5
|
import type { UsesAbilities } from '../../abilities';
|
|
6
6
|
import type { Answerable } from '../../Answerable';
|
|
7
7
|
import { Interaction } from '../../Interaction';
|
|
8
|
-
import type { AnswersQuestions, Expectation, ExpectationOutcome
|
|
8
|
+
import type { AnswersQuestions, Expectation, ExpectationOutcome} from '../../questions';
|
|
9
|
+
import { the } from '../../questions';
|
|
9
10
|
import { ExpectationMet } from '../../questions';
|
|
10
11
|
import { ScheduleWork } from '../abilities';
|
|
11
12
|
import { Duration } from '../models';
|
|
@@ -202,7 +203,7 @@ export class Wait {
|
|
|
202
203
|
*/
|
|
203
204
|
class WaitFor extends Interaction {
|
|
204
205
|
constructor(private readonly duration: Answerable<Duration>) {
|
|
205
|
-
super(
|
|
206
|
+
super(the`#actor waits for ${ duration }`);
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
async performAs(actor: UsesAbilities & AnswersQuestions): Promise<void> {
|
|
@@ -231,7 +232,7 @@ export class WaitUntil<Actual> extends Interaction {
|
|
|
231
232
|
private readonly pollingInterval: Duration,
|
|
232
233
|
private readonly timeout?: Duration,
|
|
233
234
|
) {
|
|
234
|
-
super(
|
|
235
|
+
super(the`#actor waits until ${ actual } does ${ expectation }`);
|
|
235
236
|
|
|
236
237
|
if (timeout) {
|
|
237
238
|
ensure('Timeout', timeout.inMilliseconds(), isGreaterThanOrEqualTo(Wait.minimumTimeout.inMilliseconds()));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isPlainObject.d.ts","sourceRoot":"","sources":["../../src/io/isPlainObject.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAkBrD"}
|
package/lib/io/isPlainObject.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPlainObject = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Checks if the value has a good chance of being a plain JavaScript object
|
|
6
|
-
*
|
|
7
|
-
* @param v
|
|
8
|
-
*/
|
|
9
|
-
function isPlainObject(v) {
|
|
10
|
-
// Basic check for Type object that's not null
|
|
11
|
-
if (typeof v === 'object' && v !== null) {
|
|
12
|
-
// If Object.getPrototypeOf supported, use it
|
|
13
|
-
if (typeof Object.getPrototypeOf === 'function') {
|
|
14
|
-
const proto = Object.getPrototypeOf(v);
|
|
15
|
-
return proto === Object.prototype || proto === null;
|
|
16
|
-
}
|
|
17
|
-
// Otherwise, use internal class
|
|
18
|
-
// This should be reliable as if getPrototypeOf not supported, is pre-ES5
|
|
19
|
-
return Object.prototype.toString.call(v) === '[object Object]';
|
|
20
|
-
}
|
|
21
|
-
// Not an object
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
exports.isPlainObject = isPlainObject;
|
|
25
|
-
//# sourceMappingURL=isPlainObject.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isPlainObject.js","sourceRoot":"","sources":["../../src/io/isPlainObject.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,SAAgB,aAAa,CAAC,CAAU;IAEpC,8CAA8C;IAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE;QAErC,6CAA6C;QAC7C,IAAI,OAAO,MAAM,CAAC,cAAc,KAAK,UAAU,EAAE;YAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;YACvC,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;SACvD;QAED,gCAAgC;QAChC,yEAAyE;QACzE,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,iBAAiB,CAAC;KAClE;IAED,gBAAgB;IAChB,OAAO,KAAK,CAAC;AACjB,CAAC;AAlBD,sCAkBC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns true if `value` is a [JavaScript primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive),
|
|
3
|
-
* false otherwise.
|
|
4
|
-
*
|
|
5
|
-
* @param value
|
|
6
|
-
*/
|
|
7
|
-
export declare function isPrimitive(value: unknown): boolean;
|
|
8
|
-
//# sourceMappingURL=isPrimitive.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isPrimitive.d.ts","sourceRoot":"","sources":["../../../src/io/reflection/isPrimitive.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAanD"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPrimitive = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Returns true if `value` is a [JavaScript primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive),
|
|
6
|
-
* false otherwise.
|
|
7
|
-
*
|
|
8
|
-
* @param value
|
|
9
|
-
*/
|
|
10
|
-
function isPrimitive(value) {
|
|
11
|
-
if (value === null) {
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
return [
|
|
15
|
-
'string',
|
|
16
|
-
'number',
|
|
17
|
-
'bigint',
|
|
18
|
-
'boolean',
|
|
19
|
-
'undefined',
|
|
20
|
-
'symbol'
|
|
21
|
-
].includes(typeof value);
|
|
22
|
-
}
|
|
23
|
-
exports.isPrimitive = isPrimitive;
|
|
24
|
-
//# sourceMappingURL=isPrimitive.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"isPrimitive.js","sourceRoot":"","sources":["../../../src/io/reflection/isPrimitive.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,KAAc;IACtC,IAAI,KAAK,KAAK,IAAI,EAAE;QAChB,OAAO,IAAI,CAAC;KACf;IAED,OAAO;QACH,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,SAAS;QACT,WAAW;QACX,QAAQ;KACX,CAAC,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;AAC7B,CAAC;AAbD,kCAaC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"typeOf.d.ts","sourceRoot":"","sources":["../../../src/io/reflection/typeOf.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAuB7C"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.typeOf = void 0;
|
|
4
|
-
const util_1 = require("util");
|
|
5
|
-
/**
|
|
6
|
-
* Describes the type of the provided value.
|
|
7
|
-
*
|
|
8
|
-
* @param value
|
|
9
|
-
*/
|
|
10
|
-
function typeOf(value) {
|
|
11
|
-
switch (true) {
|
|
12
|
-
case value === null:
|
|
13
|
-
return 'null';
|
|
14
|
-
case util_1.types.isProxy(value):
|
|
15
|
-
return `Proxy<${Reflect.getPrototypeOf(value).constructor.name}>`;
|
|
16
|
-
case typeof value !== 'object':
|
|
17
|
-
return typeof value;
|
|
18
|
-
case value instanceof Date:
|
|
19
|
-
return `Date`;
|
|
20
|
-
case Array.isArray(value):
|
|
21
|
-
return `Array`;
|
|
22
|
-
case value instanceof RegExp:
|
|
23
|
-
return `RegExp`;
|
|
24
|
-
case value instanceof Set:
|
|
25
|
-
return 'Set';
|
|
26
|
-
case value instanceof Map:
|
|
27
|
-
return 'Map';
|
|
28
|
-
case !!value.constructor && value.constructor !== Object:
|
|
29
|
-
return value.constructor.name;
|
|
30
|
-
default:
|
|
31
|
-
return 'object';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
exports.typeOf = typeOf;
|
|
35
|
-
//# sourceMappingURL=typeOf.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"typeOf.js","sourceRoot":"","sources":["../../../src/io/reflection/typeOf.ts"],"names":[],"mappings":";;;AAAA,+BAA6B;AAE7B;;;;GAIG;AACH,SAAgB,MAAM,CAAC,KAAc;IACjC,QAAQ,IAAI,EAAE;QACV,KAAK,KAAK,KAAK,IAAI;YACf,OAAO,MAAM,CAAC;QAClB,KAAK,YAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACrB,OAAO,SAAU,OAAO,CAAC,cAAc,CAAC,KAAe,CAAC,CAAC,WAAW,CAAC,IAAK,GAAG,CAAC;QAClF,KAAK,OAAO,KAAK,KAAK,QAAQ;YAC1B,OAAO,OAAO,KAAK,CAAC;QACxB,KAAK,KAAK,YAAY,IAAI;YACtB,OAAO,MAAM,CAAC;QAClB,KAAK,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACrB,OAAO,OAAO,CAAC;QACnB,KAAK,KAAK,YAAY,MAAM;YACxB,OAAO,QAAQ,CAAA;QACnB,KAAK,KAAK,YAAY,GAAG;YACrB,OAAO,KAAK,CAAC;QACjB,KAAK,KAAK,YAAY,GAAG;YACrB,OAAO,KAAK,CAAC;QACjB,KAAK,CAAC,CAAE,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM;YACrD,OAAO,KAAK,CAAC,WAAW,CAAC,IAAI,CAAA;QACjC;YACI,OAAO,QAAQ,CAAC;KACvB;AACL,CAAC;AAvBD,wBAuBC"}
|