@serenity-js/core 3.2.1 → 3.3.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 +16 -0
- package/lib/Serenity.d.ts +2 -2
- package/lib/Serenity.d.ts.map +1 -1
- package/lib/events/EmitsDomainEvents.d.ts +11 -0
- package/lib/events/EmitsDomainEvents.d.ts.map +1 -0
- package/lib/events/EmitsDomainEvents.js +3 -0
- package/lib/events/EmitsDomainEvents.js.map +1 -0
- package/lib/events/index.d.ts +1 -0
- package/lib/events/index.d.ts.map +1 -1
- package/lib/events/index.js +1 -0
- package/lib/events/index.js.map +1 -1
- package/lib/io/asyncMap.js +2 -2
- package/lib/io/asyncMap.js.map +1 -1
- package/lib/screenplay/Actor.d.ts +3 -3
- package/lib/screenplay/Actor.d.ts.map +1 -1
- package/lib/screenplay/Actor.js +14 -86
- package/lib/screenplay/Actor.js.map +1 -1
- package/lib/screenplay/abilities/AnswerQuestions.d.ts +21 -0
- package/lib/screenplay/abilities/AnswerQuestions.d.ts.map +1 -0
- package/lib/screenplay/abilities/AnswerQuestions.js +37 -0
- package/lib/screenplay/abilities/AnswerQuestions.js.map +1 -0
- package/lib/screenplay/abilities/PerformActivities.d.ts +28 -0
- package/lib/screenplay/abilities/PerformActivities.d.ts.map +1 -0
- package/lib/screenplay/abilities/PerformActivities.js +66 -0
- package/lib/screenplay/abilities/PerformActivities.js.map +1 -0
- package/lib/screenplay/abilities/index.d.ts +2 -0
- package/lib/screenplay/abilities/index.d.ts.map +1 -1
- package/lib/screenplay/abilities/index.js +2 -0
- package/lib/screenplay/abilities/index.js.map +1 -1
- package/lib/screenplay/time/abilities/ScheduleWork.d.ts +2 -4
- package/lib/screenplay/time/abilities/ScheduleWork.d.ts.map +1 -1
- package/lib/screenplay/time/abilities/ScheduleWork.js +0 -6
- package/lib/screenplay/time/abilities/ScheduleWork.js.map +1 -1
- package/lib/screenplay/time/models/Clock.d.ts +24 -0
- package/lib/screenplay/time/models/Clock.d.ts.map +1 -1
- package/lib/screenplay/time/models/Clock.js +41 -1
- package/lib/screenplay/time/models/Clock.js.map +1 -1
- package/lib/screenplay/time/models/Scheduler.d.ts +1 -10
- package/lib/screenplay/time/models/Scheduler.d.ts.map +1 -1
- package/lib/screenplay/time/models/Scheduler.js +65 -103
- package/lib/screenplay/time/models/Scheduler.js.map +1 -1
- package/lib/stage/Stage.d.ts +2 -2
- package/lib/stage/Stage.d.ts.map +1 -1
- package/lib/stage/Stage.js +4 -2
- package/lib/stage/Stage.js.map +1 -1
- package/package.json +2 -2
- package/src/Serenity.ts +2 -2
- package/src/events/EmitsDomainEvents.ts +11 -0
- package/src/events/index.ts +1 -0
- package/src/io/asyncMap.ts +2 -2
- package/src/screenplay/Actor.ts +32 -131
- package/src/screenplay/abilities/AnswerQuestions.ts +41 -0
- package/src/screenplay/abilities/PerformActivities.ts +88 -0
- package/src/screenplay/abilities/index.ts +2 -0
- package/src/screenplay/time/abilities/ScheduleWork.ts +2 -10
- package/src/screenplay/time/models/Clock.ts +47 -1
- package/src/screenplay/time/models/Scheduler.ts +89 -136
- package/src/stage/Stage.ts +15 -7
package/src/screenplay/Actor.ts
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { AssertionError, ConfigurationError, ImplementationPendingError, TestCompromisedError } from '../errors';
|
|
4
|
-
import { ActivityRelatedArtifactGenerated, InteractionFinished, InteractionStarts, TaskFinished, TaskStarts } from '../events';
|
|
1
|
+
import { ConfigurationError, TestCompromisedError } from '../errors';
|
|
2
|
+
import { ActivityRelatedArtifactGenerated } from '../events';
|
|
5
3
|
import { typeOf } from '../io';
|
|
6
|
-
import {
|
|
7
|
-
ActivityDetails,
|
|
8
|
-
Artifact,
|
|
9
|
-
ExecutionCompromised,
|
|
10
|
-
ExecutionFailedWithAssertionError,
|
|
11
|
-
ExecutionFailedWithError,
|
|
12
|
-
ExecutionSuccessful,
|
|
13
|
-
ImplementationPending,
|
|
14
|
-
Name,
|
|
15
|
-
ProblemIndication,
|
|
16
|
-
} from '../model';
|
|
17
|
-
import { Ability, AbilityType, Answerable, Discardable, Initialisable, Interaction } from '../screenplay';
|
|
4
|
+
import { Artifact, Name, } from '../model';
|
|
18
5
|
import { Stage } from '../stage';
|
|
19
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Ability,
|
|
8
|
+
AbilityType,
|
|
9
|
+
AnswerQuestions,
|
|
10
|
+
CanHaveAbilities,
|
|
11
|
+
Discardable,
|
|
12
|
+
Initialisable,
|
|
13
|
+
PerformActivities,
|
|
14
|
+
UsesAbilities
|
|
15
|
+
} from './abilities';
|
|
20
16
|
import { PerformsActivities } from './activities';
|
|
21
17
|
import { Activity } from './Activity';
|
|
18
|
+
import { Answerable } from './Answerable';
|
|
22
19
|
import { CollectsArtifacts } from './artifacts';
|
|
23
|
-
import { Question } from './Question';
|
|
24
20
|
import { AnswersQuestions } from './questions';
|
|
25
21
|
|
|
26
22
|
/**
|
|
@@ -82,18 +78,23 @@ import { AnswersQuestions } from './questions';
|
|
|
82
78
|
*
|
|
83
79
|
* @group Screenplay Pattern
|
|
84
80
|
*/
|
|
85
|
-
export class Actor implements
|
|
86
|
-
PerformsActivities,
|
|
81
|
+
export class Actor implements PerformsActivities,
|
|
87
82
|
UsesAbilities,
|
|
88
83
|
CanHaveAbilities<Actor>,
|
|
89
84
|
AnswersQuestions,
|
|
90
|
-
CollectsArtifacts
|
|
91
|
-
|
|
85
|
+
CollectsArtifacts {
|
|
86
|
+
private readonly abilities: Map<AbilityType<Ability>, Ability> = new Map<AbilityType<Ability>, Ability>();
|
|
87
|
+
|
|
92
88
|
constructor(
|
|
93
89
|
public readonly name: string,
|
|
94
90
|
private readonly stage: Stage,
|
|
95
|
-
|
|
91
|
+
abilities: Ability[] = [],
|
|
96
92
|
) {
|
|
93
|
+
[
|
|
94
|
+
new PerformActivities(this, stage),
|
|
95
|
+
new AnswerQuestions(this),
|
|
96
|
+
...abilities
|
|
97
|
+
].forEach(ability => this.acquireAbility(ability));
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
/**
|
|
@@ -111,16 +112,9 @@ export class Actor implements
|
|
|
111
112
|
const found = this.findAbilityTo(abilityType);
|
|
112
113
|
|
|
113
114
|
if (! found) {
|
|
114
|
-
if (this.abilities.size > 0) {
|
|
115
|
-
throw new ConfigurationError(
|
|
116
|
-
`${ this.name } can ${ Array.from(this.abilities.keys()).map(type => type.name).join(', ') }. ` +
|
|
117
|
-
`They can't, however, ${ abilityType.name } yet. ` +
|
|
118
|
-
`Did you give them the ability to do so?`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
115
|
throw new ConfigurationError(
|
|
123
|
-
`${ this.name } can
|
|
116
|
+
`${ this.name } can ${ Array.from(this.abilities.keys()).map(type => type.name).join(', ') }. ` +
|
|
117
|
+
`They can't, however, ${ abilityType.name } yet. ` +
|
|
124
118
|
`Did you give them the ability to do so?`
|
|
125
119
|
);
|
|
126
120
|
}
|
|
@@ -137,15 +131,9 @@ export class Actor implements
|
|
|
137
131
|
*/
|
|
138
132
|
attemptsTo(...activities: Activity[]): Promise<void> {
|
|
139
133
|
return activities
|
|
140
|
-
.map(activity => new TrackedActivity(activity, this.stage))
|
|
141
134
|
.reduce((previous: Promise<void>, current: Activity) => {
|
|
142
135
|
return previous
|
|
143
|
-
|
|
144
|
-
.then(() => this.stage.waitForNextCue())
|
|
145
|
-
.then(() =>{
|
|
146
|
-
/* todo: add an execution strategy */
|
|
147
|
-
return current.performAs(this);
|
|
148
|
-
});
|
|
136
|
+
.then(() => PerformActivities.as(this).perform(current));
|
|
149
137
|
}, this.initialiseAbilities());
|
|
150
138
|
}
|
|
151
139
|
|
|
@@ -176,23 +164,7 @@ export class Actor implements
|
|
|
176
164
|
* The answer to the Answerable
|
|
177
165
|
*/
|
|
178
166
|
answer<T>(answerable: Answerable<T>): Promise<T> {
|
|
179
|
-
|
|
180
|
-
return Object.prototype.hasOwnProperty.call(v, 'then');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function isDefined<V>(v: Answerable<V>) {
|
|
184
|
-
return ! (v === undefined || v === null);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (isDefined(answerable) && isAPromise(answerable)) {
|
|
188
|
-
return answerable;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (isDefined(answerable) && Question.isAQuestion(answerable)) {
|
|
192
|
-
return this.answer(answerable.answeredBy(this));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return Promise.resolve(answerable as T);
|
|
167
|
+
return AnswerQuestions.as(this).answer(answerable);
|
|
196
168
|
}
|
|
197
169
|
|
|
198
170
|
/**
|
|
@@ -237,7 +209,7 @@ export class Actor implements
|
|
|
237
209
|
|
|
238
210
|
private initialiseAbilities(): Promise<void> {
|
|
239
211
|
return this.findAbilitiesOfType<Initialisable>('initialise', 'isInitialised')
|
|
240
|
-
.filter(ability => !
|
|
212
|
+
.filter(ability => !ability.isInitialised())
|
|
241
213
|
.reduce(
|
|
242
214
|
(previous: Promise<void>, ability: (Initialisable & Ability)) =>
|
|
243
215
|
previous
|
|
@@ -254,7 +226,7 @@ export class Actor implements
|
|
|
254
226
|
Array.from(map.values());
|
|
255
227
|
|
|
256
228
|
const abilitiesWithDesiredMethods = (ability: Ability & T): boolean =>
|
|
257
|
-
methodNames.every(methodName => typeof(ability[methodName]) === 'function');
|
|
229
|
+
methodNames.every(methodName => typeof (ability[methodName]) === 'function');
|
|
258
230
|
|
|
259
231
|
return abilitiesFrom(this.abilities)
|
|
260
232
|
.filter(abilitiesWithDesiredMethods) as Array<Ability & T>;
|
|
@@ -267,7 +239,7 @@ export class Actor implements
|
|
|
267
239
|
}
|
|
268
240
|
|
|
269
241
|
private acquireAbility(ability: Ability): void {
|
|
270
|
-
if (!
|
|
242
|
+
if (!(ability instanceof Ability)) {
|
|
271
243
|
throw new ConfigurationError(`Custom abilities must extend Ability from '@serenity-js/core'. Received ${ typeOf(ability) }`);
|
|
272
244
|
}
|
|
273
245
|
|
|
@@ -280,7 +252,7 @@ export class Actor implements
|
|
|
280
252
|
abilityType: AbilityType<Specific_Ability>
|
|
281
253
|
): AbilityType<Generic_Ability> {
|
|
282
254
|
const parentType = Object.getPrototypeOf(abilityType);
|
|
283
|
-
return !
|
|
255
|
+
return !parentType || parentType === Ability
|
|
284
256
|
? abilityType
|
|
285
257
|
: this.mostGenericTypeOf(parentType)
|
|
286
258
|
}
|
|
@@ -297,74 +269,3 @@ export class Actor implements
|
|
|
297
269
|
: maybeName;
|
|
298
270
|
}
|
|
299
271
|
}
|
|
300
|
-
|
|
301
|
-
class ActivityDescriber {
|
|
302
|
-
|
|
303
|
-
describe(activity: Activity, actor: { name: string }): Name {
|
|
304
|
-
const template = activity.toString() === ({}).toString()
|
|
305
|
-
? `#actor performs ${ activity.constructor.name }`
|
|
306
|
-
: activity.toString();
|
|
307
|
-
|
|
308
|
-
return new Name(
|
|
309
|
-
this.includeActorName(template, actor),
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private includeActorName(template: string, actor: { name: string }) {
|
|
314
|
-
return template.replace('#actor', actor.name);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
class OutcomeMatcher {
|
|
319
|
-
outcomeFor(error: Error | any): ProblemIndication {
|
|
320
|
-
return match<Error, ProblemIndication>(error)
|
|
321
|
-
.when(ImplementationPendingError, _ => new ImplementationPending(error))
|
|
322
|
-
.when(TestCompromisedError, _ => new ExecutionCompromised(error))
|
|
323
|
-
.when(AssertionError, _ => new ExecutionFailedWithAssertionError(error))
|
|
324
|
-
.when(Error, _ =>
|
|
325
|
-
/AssertionError/.test(error.constructor.name) // mocha
|
|
326
|
-
? new ExecutionFailedWithAssertionError(error)
|
|
327
|
-
: new ExecutionFailedWithError(error))
|
|
328
|
-
.else(_ => new ExecutionFailedWithError(error));
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
class TrackedActivity extends Activity {
|
|
333
|
-
|
|
334
|
-
protected static readonly describer = new ActivityDescriber();
|
|
335
|
-
protected static readonly outcomes = new OutcomeMatcher();
|
|
336
|
-
|
|
337
|
-
constructor(
|
|
338
|
-
protected readonly activity: Activity,
|
|
339
|
-
protected readonly stage: Stage,
|
|
340
|
-
) {
|
|
341
|
-
super(activity.toString(), activity.instantiationLocation());
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
performAs(actor: (PerformsActivities | UsesAbilities | AnswersQuestions) & { name: string }): Promise<void> {
|
|
345
|
-
const sceneId = this.stage.currentSceneId();
|
|
346
|
-
const details = new ActivityDetails(
|
|
347
|
-
TrackedActivity.describer.describe(this.activity, actor),
|
|
348
|
-
this.activity.instantiationLocation(),
|
|
349
|
-
);
|
|
350
|
-
const activityId = this.stage.assignNewActivityId(details);
|
|
351
|
-
|
|
352
|
-
const [ activityStarts, activityFinished] = this.activity instanceof Interaction
|
|
353
|
-
? [ InteractionStarts, InteractionFinished ]
|
|
354
|
-
: [ TaskStarts, TaskFinished ];
|
|
355
|
-
|
|
356
|
-
return Promise.resolve()
|
|
357
|
-
.then(() => this.stage.announce(new activityStarts(sceneId, activityId, details, this.stage.currentTime())))
|
|
358
|
-
.then(() => this.activity.performAs(actor))
|
|
359
|
-
.then(() => {
|
|
360
|
-
const outcome = new ExecutionSuccessful();
|
|
361
|
-
this.stage.announce(new activityFinished(sceneId, activityId, details, outcome, this.stage.currentTime()));
|
|
362
|
-
})
|
|
363
|
-
.catch(error => {
|
|
364
|
-
const outcome = TrackedActivity.outcomes.outcomeFor(error);
|
|
365
|
-
this.stage.announce(new activityFinished(sceneId, activityId, details, outcome, this.stage.currentTime()));
|
|
366
|
-
|
|
367
|
-
throw error;
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Answerable } from '../Answerable';
|
|
2
|
+
import { Question } from '../Question';
|
|
3
|
+
import { AnswersQuestions } from '../questions';
|
|
4
|
+
import { Ability } from './Ability';
|
|
5
|
+
import { UsesAbilities } from './UsesAbilities';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This {@apilink Ability} enables an {@apilink Actor} to resolve the value of a given {@apilink Answerable}.
|
|
9
|
+
*
|
|
10
|
+
* {@apilink AnswerQuestions} is used internally by {@apilink Actor.answer}, and it is unlikely you'll ever need to use it directly in your code.
|
|
11
|
+
* That is, unless you're building a custom Serenity/JS extension and want to override the default behaviour of the framework,
|
|
12
|
+
* in which case you should check out the [Contributor's Guide](/contributing).
|
|
13
|
+
*
|
|
14
|
+
* @group Abilities
|
|
15
|
+
*/
|
|
16
|
+
export class AnswerQuestions extends Ability {
|
|
17
|
+
constructor(protected readonly actor: AnswersQuestions & UsesAbilities) {
|
|
18
|
+
super();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
answer<T>(answerable: Answerable<T>): Promise<T> {
|
|
22
|
+
|
|
23
|
+
if (AnswerQuestions.isDefined(answerable) && AnswerQuestions.isAPromise(answerable)) {
|
|
24
|
+
return answerable;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (AnswerQuestions.isDefined(answerable) && Question.isAQuestion(answerable)) {
|
|
28
|
+
return this.answer(answerable.answeredBy(this.actor));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Promise.resolve(answerable as T);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private static isAPromise<V>(v: Answerable<V>): v is Promise<V> {
|
|
35
|
+
return Object.prototype.hasOwnProperty.call(v, 'then');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private static isDefined<V>(v: Answerable<V>) {
|
|
39
|
+
return !(v === undefined || v === null);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { match } from 'tiny-types';
|
|
2
|
+
|
|
3
|
+
import { AssertionError, ImplementationPendingError, TestCompromisedError } from '../../errors';
|
|
4
|
+
import { EmitsDomainEvents, InteractionFinished, InteractionStarts, TaskFinished, TaskStarts } from '../../events';
|
|
5
|
+
import {
|
|
6
|
+
ActivityDetails,
|
|
7
|
+
ExecutionCompromised,
|
|
8
|
+
ExecutionFailedWithAssertionError,
|
|
9
|
+
ExecutionFailedWithError,
|
|
10
|
+
ExecutionSuccessful,
|
|
11
|
+
ImplementationPending,
|
|
12
|
+
Name,
|
|
13
|
+
Outcome,
|
|
14
|
+
ProblemIndication
|
|
15
|
+
} from '../../model';
|
|
16
|
+
import { PerformsActivities } from '../activities/PerformsActivities';
|
|
17
|
+
import { Activity } from '../Activity';
|
|
18
|
+
import { Interaction } from '../Interaction';
|
|
19
|
+
import { Ability } from './index';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An {@apilink Ability} that enables an {@apilink Actor} to perform a given {@apilink Activity}.
|
|
23
|
+
*
|
|
24
|
+
* {@apilink PerformActivities} is used internally by {@apilink Actor.perform}, and it is unlikely you'll ever need to use it directly in your code.
|
|
25
|
+
* That is, unless you're building a custom Serenity/JS extension and want to override the default behaviour of the framework,
|
|
26
|
+
* in which case you should check out the [Contributor's Guide](/contributing).
|
|
27
|
+
*
|
|
28
|
+
* @group Abilities
|
|
29
|
+
*/
|
|
30
|
+
export class PerformActivities extends Ability {
|
|
31
|
+
constructor(
|
|
32
|
+
protected readonly actor: PerformsActivities & { name: string },
|
|
33
|
+
protected readonly stage: EmitsDomainEvents,
|
|
34
|
+
) {
|
|
35
|
+
super();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async perform(activity: Activity): Promise<void> {
|
|
39
|
+
const sceneId = this.stage.currentSceneId();
|
|
40
|
+
const details = this.detailsOf(activity);
|
|
41
|
+
const activityId = this.stage.assignNewActivityId(details);
|
|
42
|
+
|
|
43
|
+
const [ activityStarts, activityFinished ] = activity instanceof Interaction
|
|
44
|
+
? [ InteractionStarts, InteractionFinished ]
|
|
45
|
+
: [ TaskStarts, TaskFinished ];
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
this.stage.announce(new activityStarts(sceneId, activityId, details, this.stage.currentTime()))
|
|
49
|
+
|
|
50
|
+
await activity.performAs(this.actor);
|
|
51
|
+
|
|
52
|
+
this.stage.announce(new activityFinished(sceneId, activityId, details, new ExecutionSuccessful(), this.stage.currentTime()));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
this.stage.announce(new activityFinished(sceneId, activityId, details, this.outcomeFor(error), this.stage.currentTime()));
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await this.stage.waitForNextCue();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
protected outcomeFor(error: Error | any): Outcome {
|
|
63
|
+
return match<Error, ProblemIndication>(error)
|
|
64
|
+
.when(ImplementationPendingError, _ => new ImplementationPending(error))
|
|
65
|
+
.when(TestCompromisedError, _ => new ExecutionCompromised(error))
|
|
66
|
+
.when(AssertionError, _ => new ExecutionFailedWithAssertionError(error))
|
|
67
|
+
.when(Error, _ =>
|
|
68
|
+
/AssertionError/.test(error.constructor.name) // mocha
|
|
69
|
+
? new ExecutionFailedWithAssertionError(error)
|
|
70
|
+
: new ExecutionFailedWithError(error))
|
|
71
|
+
.else(_ => new ExecutionFailedWithError(error));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private detailsOf(activity: Activity): ActivityDetails {
|
|
75
|
+
return new ActivityDetails(
|
|
76
|
+
new Name(this.nameOf(activity)),
|
|
77
|
+
activity.instantiationLocation(),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected nameOf(activity: Activity): string {
|
|
82
|
+
const template = activity.toString() === ({}).toString()
|
|
83
|
+
? `#actor performs ${ activity.constructor.name }`
|
|
84
|
+
: activity.toString();
|
|
85
|
+
|
|
86
|
+
return template.replace('#actor', this.actor.name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './Ability';
|
|
2
2
|
export * from './AbilityType';
|
|
3
|
+
export * from './AnswerQuestions';
|
|
3
4
|
export * from './CanHaveAbilities';
|
|
4
5
|
export * from './Discardable';
|
|
5
6
|
export * from './Initialisable';
|
|
7
|
+
export * from './PerformActivities';
|
|
6
8
|
export * from './UsesAbilities';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ability, Discardable
|
|
1
|
+
import { Ability, Discardable } from '../../abilities';
|
|
2
2
|
import { Clock, DelayedCallback, Duration, RepeatUntilLimits, Scheduler } from '../models';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -11,7 +11,7 @@ import { Clock, DelayedCallback, Duration, RepeatUntilLimits, Scheduler } from '
|
|
|
11
11
|
*
|
|
12
12
|
* @group Time
|
|
13
13
|
*/
|
|
14
|
-
export class ScheduleWork extends Ability implements
|
|
14
|
+
export class ScheduleWork extends Ability implements Discardable {
|
|
15
15
|
|
|
16
16
|
private readonly scheduler: Scheduler;
|
|
17
17
|
|
|
@@ -20,14 +20,6 @@ export class ScheduleWork extends Ability implements Initialisable, Discardable
|
|
|
20
20
|
this.scheduler = new Scheduler(clock, interactionTimeout);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
initialise(): void {
|
|
24
|
-
this.scheduler.start();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
isInitialised(): boolean {
|
|
28
|
-
return this.scheduler.isRunning();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
/**
|
|
32
24
|
* @param callback
|
|
33
25
|
* @param limits
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { ensure, isDefined } from 'tiny-types';
|
|
2
|
+
|
|
3
|
+
import { Duration } from './Duration';
|
|
1
4
|
import { Timestamp } from './Timestamp';
|
|
2
5
|
|
|
3
6
|
/**
|
|
@@ -15,14 +18,57 @@ import { Timestamp } from './Timestamp';
|
|
|
15
18
|
* @group Time
|
|
16
19
|
*/
|
|
17
20
|
export class Clock {
|
|
21
|
+
private static resolution: Duration = Duration.ofMilliseconds(10);
|
|
22
|
+
private timeAdjustment: Duration = Duration.ofMilliseconds(0);
|
|
18
23
|
|
|
19
24
|
constructor(private readonly checkTime: () => Date = () => new Date()) {
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Sets the clock ahead to force early resolution of promises
|
|
29
|
+
* returned by {@apilink Clock.waitFor};
|
|
30
|
+
*
|
|
31
|
+
* Useful for test purposes to avoid unnecessary delays.
|
|
32
|
+
*
|
|
33
|
+
* @param duration
|
|
34
|
+
*/
|
|
35
|
+
setAhead(duration: Duration): void {
|
|
36
|
+
this.timeAdjustment = ensure('duration', duration, isDefined());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns a Promise that resolves after one tick of the clock.
|
|
41
|
+
*
|
|
42
|
+
* Useful for test purposes to avoid unnecessary delays.
|
|
43
|
+
*/
|
|
44
|
+
async tick(): Promise<void> {
|
|
45
|
+
return new Promise(resolve => setTimeout(resolve, Clock.resolution.inMilliseconds()));
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
/**
|
|
23
49
|
* Returns current time
|
|
24
50
|
*/
|
|
25
51
|
now(): Timestamp {
|
|
26
|
-
return new Timestamp(this.checkTime());
|
|
52
|
+
return new Timestamp(this.checkTime()).plus(this.timeAdjustment);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns a Promise that will be resolved after the given duration
|
|
57
|
+
*
|
|
58
|
+
* @param duration
|
|
59
|
+
*/
|
|
60
|
+
async waitFor(duration: Duration): Promise<void> {
|
|
61
|
+
const stopAt = this.now().plus(duration);
|
|
62
|
+
|
|
63
|
+
let timer: NodeJS.Timer;
|
|
64
|
+
|
|
65
|
+
return new Promise<void>(resolve => {
|
|
66
|
+
timer = setInterval(() => {
|
|
67
|
+
if (this.now().isAfterOrEqual(stopAt)) {
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
return resolve();
|
|
70
|
+
}
|
|
71
|
+
}, Clock.resolution.inMilliseconds());
|
|
72
|
+
});
|
|
27
73
|
}
|
|
28
74
|
}
|