@serenity-js/core 3.25.4 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/lib/events/actor/ActorEntersStage.d.ts +17 -0
  3. package/lib/events/actor/ActorEntersStage.d.ts.map +1 -0
  4. package/lib/events/actor/ActorEntersStage.js +29 -0
  5. package/lib/events/actor/ActorEntersStage.js.map +1 -0
  6. package/lib/events/actor/ActorStageExitAttempted.d.ts +19 -0
  7. package/lib/events/actor/ActorStageExitAttempted.d.ts.map +1 -0
  8. package/lib/events/actor/ActorStageExitAttempted.js +29 -0
  9. package/lib/events/actor/ActorStageExitAttempted.js.map +1 -0
  10. package/lib/events/actor/ActorStageExitCompleted.d.ts +20 -0
  11. package/lib/events/actor/ActorStageExitCompleted.d.ts.map +1 -0
  12. package/lib/events/actor/ActorStageExitCompleted.js +30 -0
  13. package/lib/events/actor/ActorStageExitCompleted.js.map +1 -0
  14. package/lib/events/actor/ActorStageExitFailed.d.ts +17 -0
  15. package/lib/events/actor/ActorStageExitFailed.d.ts.map +1 -0
  16. package/lib/events/actor/ActorStageExitFailed.js +25 -0
  17. package/lib/events/actor/ActorStageExitFailed.js.map +1 -0
  18. package/lib/events/actor/ActorStageExitStarts.d.ts +20 -0
  19. package/lib/events/actor/ActorStageExitStarts.d.ts.map +1 -0
  20. package/lib/events/actor/ActorStageExitStarts.js +32 -0
  21. package/lib/events/actor/ActorStageExitStarts.js.map +1 -0
  22. package/lib/events/actor/index.d.ts +6 -0
  23. package/lib/events/actor/index.d.ts.map +1 -0
  24. package/lib/events/actor/index.js +22 -0
  25. package/lib/events/actor/index.js.map +1 -0
  26. package/lib/events/index.d.ts +1 -0
  27. package/lib/events/index.d.ts.map +1 -1
  28. package/lib/events/index.js +1 -0
  29. package/lib/events/index.js.map +1 -1
  30. package/lib/screenplay/Actor.d.ts +8 -1
  31. package/lib/screenplay/Actor.d.ts.map +1 -1
  32. package/lib/screenplay/Actor.js +14 -10
  33. package/lib/screenplay/Actor.js.map +1 -1
  34. package/lib/screenplay/SerialisedActor.d.ts +9 -0
  35. package/lib/screenplay/SerialisedActor.d.ts.map +1 -0
  36. package/lib/screenplay/SerialisedActor.js +3 -0
  37. package/lib/screenplay/SerialisedActor.js.map +1 -0
  38. package/lib/screenplay/abilities/Ability.d.ts +39 -0
  39. package/lib/screenplay/abilities/Ability.d.ts.map +1 -1
  40. package/lib/screenplay/abilities/Ability.js +65 -0
  41. package/lib/screenplay/abilities/Ability.js.map +1 -1
  42. package/lib/screenplay/abilities/AbilityType.d.ts +5 -1
  43. package/lib/screenplay/abilities/AbilityType.d.ts.map +1 -1
  44. package/lib/screenplay/abilities/SerialisedAbility.d.ts +10 -0
  45. package/lib/screenplay/abilities/SerialisedAbility.d.ts.map +1 -0
  46. package/lib/screenplay/abilities/SerialisedAbility.js +3 -0
  47. package/lib/screenplay/abilities/SerialisedAbility.js.map +1 -0
  48. package/lib/screenplay/abilities/index.d.ts +1 -0
  49. package/lib/screenplay/abilities/index.d.ts.map +1 -1
  50. package/lib/screenplay/abilities/index.js +1 -0
  51. package/lib/screenplay/abilities/index.js.map +1 -1
  52. package/lib/screenplay/index.d.ts +1 -0
  53. package/lib/screenplay/index.d.ts.map +1 -1
  54. package/lib/screenplay/index.js +1 -0
  55. package/lib/screenplay/index.js.map +1 -1
  56. package/lib/screenplay/notes/TakeNotes.d.ts +2 -1
  57. package/lib/screenplay/notes/TakeNotes.d.ts.map +1 -1
  58. package/lib/screenplay/notes/TakeNotes.js +8 -0
  59. package/lib/screenplay/notes/TakeNotes.js.map +1 -1
  60. package/lib/screenplay/time/abilities/ScheduleWork.d.ts +2 -1
  61. package/lib/screenplay/time/abilities/ScheduleWork.d.ts.map +1 -1
  62. package/lib/screenplay/time/abilities/ScheduleWork.js +8 -0
  63. package/lib/screenplay/time/abilities/ScheduleWork.js.map +1 -1
  64. package/lib/screenplay/time/models/Clock.d.ts +2 -0
  65. package/lib/screenplay/time/models/Clock.d.ts.map +1 -1
  66. package/lib/screenplay/time/models/Clock.js +5 -0
  67. package/lib/screenplay/time/models/Clock.js.map +1 -1
  68. package/lib/screenplay/time/models/Scheduler.d.ts +2 -0
  69. package/lib/screenplay/time/models/Scheduler.d.ts.map +1 -1
  70. package/lib/screenplay/time/models/Scheduler.js +6 -0
  71. package/lib/screenplay/time/models/Scheduler.js.map +1 -1
  72. package/lib/stage/Stage.d.ts +6 -7
  73. package/lib/stage/Stage.d.ts.map +1 -1
  74. package/lib/stage/Stage.js +15 -5
  75. package/lib/stage/Stage.js.map +1 -1
  76. package/package.json +5 -5
  77. package/src/events/actor/ActorEntersStage.ts +32 -0
  78. package/src/events/actor/ActorStageExitAttempted.ts +34 -0
  79. package/src/events/actor/ActorStageExitCompleted.ts +34 -0
  80. package/src/events/actor/ActorStageExitFailed.ts +27 -0
  81. package/src/events/actor/ActorStageExitStarts.ts +35 -0
  82. package/src/events/actor/index.ts +5 -0
  83. package/src/events/index.ts +1 -0
  84. package/src/screenplay/Actor.ts +19 -28
  85. package/src/screenplay/SerialisedActor.ts +9 -0
  86. package/src/screenplay/abilities/Ability.ts +73 -0
  87. package/src/screenplay/abilities/AbilityType.ts +8 -1
  88. package/src/screenplay/abilities/SerialisedAbility.ts +10 -0
  89. package/src/screenplay/abilities/index.ts +1 -0
  90. package/src/screenplay/index.ts +1 -0
  91. package/src/screenplay/notes/TakeNotes.ts +10 -1
  92. package/src/screenplay/time/abilities/ScheduleWork.ts +11 -2
  93. package/src/screenplay/time/models/Clock.ts +7 -1
  94. package/src/screenplay/time/models/Scheduler.ts +9 -0
  95. package/src/stage/Stage.ts +50 -25
@@ -0,0 +1,34 @@
1
+ import { ensure, isDefined, type JSONObject } from 'tiny-types';
2
+
3
+ import { CorrelationId, Name } from '../../model';
4
+ import { Timestamp } from '../../screenplay';
5
+ import { AsyncOperationCompleted } from '../AsyncOperationCompleted';
6
+
7
+ /**
8
+ * Emitted when an [`Actor`](https://serenity-js.org/api/core/class/Actor/) and its abilities
9
+ * are correctly [released](https://serenity-js.org/api/core/interface/Discardable/) either
10
+ * upon the [`SceneFinishes`](https://serenity-js.org/api/core-events/class/SceneFinishes/) event
11
+ * for actors initialised within the scope of a test scenario,
12
+ * or upon the [`TestRunFinishes`](https://serenity-js.org/api/core-events/class/TestRunFinishes/) event
13
+ * for actors initialised within the scope of a test suite.
14
+ *
15
+ * @group Events
16
+ */
17
+ export class ActorStageExitCompleted extends AsyncOperationCompleted {
18
+ static fromJSON(o: JSONObject): ActorStageExitCompleted {
19
+ return new ActorStageExitCompleted(
20
+ CorrelationId.fromJSON(o.correlationId as string),
21
+ Name.fromJSON(o.actor as string),
22
+ Timestamp.fromJSON(o.timestamp as string),
23
+ );
24
+ }
25
+
26
+ constructor(
27
+ correlationId: CorrelationId,
28
+ public readonly actor: Name,
29
+ timestamp?: Timestamp,
30
+ ) {
31
+ super(correlationId, timestamp);
32
+ ensure('actor', actor, isDefined());
33
+ }
34
+ }
@@ -0,0 +1,27 @@
1
+ import type { JSONObject } from 'tiny-types';
2
+
3
+ import { ErrorSerialiser } from '../../errors';
4
+ import { CorrelationId } from '../../model';
5
+ import { Timestamp } from '../../screenplay';
6
+ import { AsyncOperationFailed } from '../AsyncOperationFailed';
7
+
8
+ /**
9
+ * Emitted when [releasing](https://serenity-js.org/api/core/interface/Discardable/) an
10
+ * [`Actor`](https://serenity-js.org/api/core/class/Actor/) or its abilities
11
+ * resulted in an error either
12
+ * upon the [`SceneFinishes`](https://serenity-js.org/api/core-events/class/SceneFinishes/) event
13
+ * for actors initialised within the scope of a test scenario,
14
+ * or upon the [`TestRunFinishes`](https://serenity-js.org/api/core-events/class/TestRunFinishes/) event
15
+ * for actors initialised within the scope of a test suite.
16
+ *
17
+ * @group Events
18
+ */
19
+ export class ActorStageExitFailed extends AsyncOperationFailed {
20
+ static fromJSON(o: JSONObject): ActorStageExitFailed {
21
+ return new ActorStageExitFailed(
22
+ ErrorSerialiser.deserialise(o.error as string),
23
+ CorrelationId.fromJSON(o.correlationId as string),
24
+ Timestamp.fromJSON(o.timestamp as string),
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,35 @@
1
+ import type { JSONObject } from 'tiny-types';
2
+ import { ensure, isDefined } from 'tiny-types';
3
+
4
+ import { CorrelationId } from '../../model';
5
+ import { type SerialisedActor, Timestamp } from '../../screenplay';
6
+ import { DomainEvent } from '../DomainEvent';
7
+
8
+ /**
9
+ * Emitted upon the [`SceneFinishes`](https://serenity-js.org/api/core-events/class/SceneFinishes/) and
10
+ * [`TestRunFinishes`](https://serenity-js.org/api/core-events/class/TestRunFinishes/) events
11
+ * to notify the [stage crew members](https://serenity-js.org/api/core/interface/StageCrewMember/)
12
+ * about the final state of the [actors](https://serenity-js.org/api/core/class/Actor/) and their abilities
13
+ * before they're [released](https://serenity-js.org/api/core/interface/Discardable/).
14
+ *
15
+ * @group Events
16
+ */
17
+ export class ActorStageExitStarts extends DomainEvent {
18
+ static fromJSON(o: JSONObject): ActorStageExitStarts {
19
+ return new ActorStageExitStarts(
20
+ CorrelationId.fromJSON(o.sceneId as string),
21
+ o.actor as unknown as SerialisedActor,
22
+ Timestamp.fromJSON(o.timestamp as string),
23
+ );
24
+ }
25
+
26
+ constructor(
27
+ public readonly sceneId: CorrelationId,
28
+ public readonly actor: SerialisedActor,
29
+ timestamp?: Timestamp,
30
+ ) {
31
+ super(timestamp);
32
+ ensure('sceneId', sceneId, isDefined());
33
+ ensure('actor', actor, isDefined());
34
+ }
35
+ }
@@ -0,0 +1,5 @@
1
+ export * from './ActorEntersStage';
2
+ export * from './ActorStageExitAttempted';
3
+ export * from './ActorStageExitCompleted';
4
+ export * from './ActorStageExitFailed';
5
+ export * from './ActorStageExitStarts';
@@ -2,6 +2,7 @@ export * from './ActivityFinished';
2
2
  export * from './ActivityRelatedArtifactArchived';
3
3
  export * from './ActivityRelatedArtifactGenerated';
4
4
  export * from './ActivityStarts';
5
+ export * from './actor';
5
6
  export * from './ArtifactArchived';
6
7
  export * from './ArtifactGenerated';
7
8
  export * from './AsyncOperationAborted';
@@ -1,26 +1,17 @@
1
1
  import { ConfigurationError, TestCompromisedError } from '../errors';
2
2
  import { ActivityRelatedArtifactGenerated } from '../events';
3
3
  import { ValueInspector } from '../io';
4
- import type { Artifact} from '../model';
4
+ import type { Artifact } from '../model';
5
5
  import { Name, } from '../model';
6
6
  import type { Stage } from '../stage';
7
- import type {
8
- AbilityType,
9
- CanHaveAbilities,
10
- Discardable,
11
- Initialisable,
12
- UsesAbilities
13
- } from './abilities';
14
- import {
15
- Ability,
16
- AnswerQuestions,
17
- PerformActivities
18
- } from './abilities';
7
+ import type { AbilityType, CanHaveAbilities, Discardable, Initialisable, UsesAbilities } from './abilities';
8
+ import { Ability, AnswerQuestions, PerformActivities } from './abilities';
19
9
  import type { PerformsActivities } from './activities';
20
10
  import type { Activity } from './Activity';
21
11
  import type { Answerable } from './Answerable';
22
12
  import type { CollectsArtifacts } from './artifacts';
23
13
  import type { AnswersQuestions } from './questions';
14
+ import type { SerialisedActor } from './SerialisedActor';
24
15
  import type { TellsTime, Timestamp } from './time';
25
16
 
26
17
  /**
@@ -217,6 +208,19 @@ export class Actor implements PerformsActivities,
217
208
  return `Actor(name=${ this.name }, abilities=[${ abilities.join(', ') }])`;
218
209
  }
219
210
 
211
+ /**
212
+ * Returns a JSON representation of the actor and its current state.
213
+ *
214
+ * The purpose of this method is to enable reporting the state of the actor in a human-readable format,
215
+ * rather than to serialise and deserialise the actor itself.
216
+ */
217
+ toJSON(): SerialisedActor {
218
+ return {
219
+ name: this.name,
220
+ abilities: Array.from(this.abilities.values()).map(ability => ability.toJSON())
221
+ }
222
+ }
223
+
220
224
  private initialiseAbilities(): Promise<void> {
221
225
  return this.findAbilitiesOfType<Initialisable>('initialise', 'isInitialised')
222
226
  .filter(ability => !ability.isInitialised())
@@ -243,9 +247,7 @@ export class Actor implements PerformsActivities,
243
247
  }
244
248
 
245
249
  private findAbilityTo<T extends Ability>(doSomething: AbilityType<T>): T | undefined {
246
- const abilityType = this.mostGenericTypeOf(doSomething);
247
-
248
- return this.abilities.get(abilityType) as T;
250
+ return this.abilities.get(doSomething.abilityType()) as T;
249
251
  }
250
252
 
251
253
  private acquireAbility(ability: Ability): void {
@@ -253,18 +255,7 @@ export class Actor implements PerformsActivities,
253
255
  throw new ConfigurationError(`Custom abilities must extend Ability from '@serenity-js/core'. Received ${ ValueInspector.typeOf(ability) }`);
254
256
  }
255
257
 
256
- const abilityType = this.mostGenericTypeOf(ability.constructor as AbilityType<Ability>);
257
-
258
- this.abilities.set(abilityType, ability);
259
- }
260
-
261
- private mostGenericTypeOf<Generic_Ability extends Ability, Specific_Ability extends Generic_Ability>(
262
- abilityType: AbilityType<Specific_Ability>
263
- ): AbilityType<Generic_Ability> {
264
- const parentType = Object.getPrototypeOf(abilityType);
265
- return !parentType || parentType === Ability
266
- ? abilityType
267
- : this.mostGenericTypeOf(parentType)
258
+ this.abilities.set(ability.abilityType(), ability);
268
259
  }
269
260
 
270
261
  /**
@@ -0,0 +1,9 @@
1
+ import type { SerialisedAbility } from './abilities';
2
+
3
+ /**
4
+ * @group Actors
5
+ */
6
+ export interface SerialisedActor {
7
+ name: string;
8
+ abilities: Array<SerialisedAbility>;
9
+ }
@@ -1,4 +1,5 @@
1
1
  import type { AbilityType } from './AbilityType';
2
+ import type { SerialisedAbility } from './SerialisedAbility';
2
3
  import type { UsesAbilities } from './UsesAbilities';
3
4
 
4
5
  /**
@@ -404,4 +405,76 @@ export abstract class Ability {
404
405
  ): A {
405
406
  return actor.abilityTo(this) as A;
406
407
  }
408
+
409
+ /**
410
+ * Returns a JSON representation of the ability and its current state, if available.
411
+ * The purpose of this method is to enable reporting the state of the ability in a human-readable format,
412
+ * rather than to serialise and deserialise the ability itself.
413
+ */
414
+ toJSON(): SerialisedAbility {
415
+ const abilityClass = this.constructor.name;
416
+ const abilityType = this.abilityType().name;
417
+
418
+ if (abilityClass !== abilityType) {
419
+ return {
420
+ class: abilityClass,
421
+ type: abilityType,
422
+ };
423
+ }
424
+ return {
425
+ type: abilityType,
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Returns the most abstract type of this Ability class,
431
+ * specifically the first class in the inheritance hierarchy that directly extends the `Ability` class.
432
+ *
433
+ * ```ts
434
+ * import { Ability } from '@serenity-js/core';
435
+ *
436
+ * class MyAbility extends Ability {}
437
+ * class MySpecialisedAbility extends MyAbility {}
438
+ *
439
+ * MyAbility.abilityType(); // returns MyAbility
440
+ * MySpecialisedAbility.abilityType(); // returns MyAbility
441
+ * ```
442
+ */
443
+ static abilityType(): AbilityType<Ability> {
444
+ return Ability.abilityTypeOf(this);
445
+ }
446
+
447
+ /**
448
+ * Returns the most abstract type of this Ability instance,
449
+ * specifically the first class in the inheritance hierarchy that directly extends the `Ability` class.
450
+ *
451
+ * ```ts
452
+ * import { Ability } from '@serenity-js/core';
453
+ *
454
+ * class MyAbility extends Ability {}
455
+ * class MySpecialisedAbility extends MyAbility {}
456
+ *
457
+ * new MyAbility().abilityType(); // returns MyAbility
458
+ * new MySpecialisedAbility().abilityType(); // returns MyAbility
459
+ * ```
460
+ */
461
+ abilityType(): AbilityType<Ability> {
462
+ return Ability.abilityTypeOf(this.constructor as AbilityType<Ability>);
463
+ }
464
+
465
+ private static abilityTypeOf(abilityType: AbilityType<Ability>): AbilityType<Ability> {
466
+ const abilityTypes = Ability.ancestorTypes(abilityType);
467
+ return [
468
+ ...abilityTypes,
469
+ abilityType
470
+ ][0];
471
+ }
472
+
473
+ private static ancestorTypes(abilityType: AbilityType<Ability>, ancestors: Array<AbilityType<Ability>> = []): Array<AbilityType<Ability>> {
474
+ const parentType = Object.getPrototypeOf(abilityType);
475
+
476
+ return ! parentType || parentType === Ability
477
+ ? ancestors
478
+ : this.ancestorTypes(parentType, [ parentType, ...ancestors ]);
479
+ }
407
480
  }
@@ -1,4 +1,5 @@
1
1
  import type { Ability } from './Ability';
2
+ import type { UsesAbilities } from './UsesAbilities';
2
3
 
3
4
  /**
4
5
  * An interface describing the static access method that every [`Ability`](https://serenity-js.org/api/core/class/Ability/) class
@@ -43,4 +44,10 @@ import type { Ability } from './Ability';
43
44
  * @group Abilities
44
45
  */
45
46
  export type AbilityType<A extends Ability> =
46
- (abstract new (... args: any[]) => A);
47
+ (abstract new (... args: any[]) => A) & {
48
+ as<S extends Ability>(
49
+ this: AbilityType<S>,
50
+ actor: UsesAbilities
51
+ ): S;
52
+ abilityType(): AbilityType<Ability>
53
+ };
@@ -0,0 +1,10 @@
1
+ import type { JSONValue } from 'tiny-types';
2
+
3
+ /**
4
+ * @group Abilities
5
+ */
6
+ export interface SerialisedAbility {
7
+ type: string;
8
+ class?: string;
9
+ options?: JSONValue;
10
+ }
@@ -5,4 +5,5 @@ export * from './CanHaveAbilities';
5
5
  export * from './Discardable';
6
6
  export * from './Initialisable';
7
7
  export * from './PerformActivities';
8
+ export * from './SerialisedAbility';
8
9
  export * from './UsesAbilities';
@@ -12,6 +12,7 @@ export * from './Optional';
12
12
  export * from './Question';
13
13
  export * from './questions';
14
14
  export * from './RecursivelyAnswered';
15
+ export * from './SerialisedActor';
15
16
  export * from './Task';
16
17
  export * from './time';
17
18
  export * from './WithAnswerableProperties';
@@ -1,4 +1,4 @@
1
- import { Ability } from '../abilities';
1
+ import { Ability, type SerialisedAbility } from '../abilities';
2
2
  import { Notepad } from './Notepad';
3
3
 
4
4
  /**
@@ -254,4 +254,13 @@ export class TakeNotes<Notes_Type extends Record<any, any>> extends Ability {
254
254
  constructor(public readonly notepad: Notepad<Notes_Type>) {
255
255
  super();
256
256
  }
257
+
258
+ toJSON(): SerialisedAbility {
259
+ return {
260
+ ...super.toJSON(),
261
+ options: {
262
+ notepad: this.notepad.toJSON(),
263
+ }
264
+ };
265
+ }
257
266
  }
@@ -1,6 +1,6 @@
1
- import type { Discardable } from '../../abilities';
1
+ import type { Discardable, SerialisedAbility } from '../../abilities';
2
2
  import { Ability } from '../../abilities';
3
- import type { Clock, DelayedCallback, Duration, RepeatUntilLimits} from '../models';
3
+ import type { Clock, DelayedCallback, Duration, RepeatUntilLimits } from '../models';
4
4
  import { Scheduler } from '../models';
5
5
 
6
6
  /**
@@ -40,4 +40,13 @@ export class ScheduleWork extends Ability implements Discardable {
40
40
  discard(): void {
41
41
  this.scheduler.stop();
42
42
  }
43
+
44
+ override toJSON(): SerialisedAbility {
45
+ return {
46
+ ...super.toJSON(),
47
+ options: {
48
+ scheduler: this.scheduler.toJSON(),
49
+ },
50
+ };
51
+ }
43
52
  }
@@ -1,4 +1,4 @@
1
- import { ensure, isDefined } from 'tiny-types';
1
+ import { ensure, isDefined, type JSONObject } from 'tiny-types';
2
2
 
3
3
  import { Duration } from './Duration';
4
4
  import { Timestamp } from './Timestamp';
@@ -24,6 +24,12 @@ export class Clock {
24
24
  constructor(private readonly checkTime: () => Date = () => new Date()) {
25
25
  }
26
26
 
27
+ toJSON(): JSONObject {
28
+ return {
29
+ timeAdjustment: this.timeAdjustment.toJSON(),
30
+ };
31
+ }
32
+
27
33
  /**
28
34
  * Sets the clock ahead to force early resolution of promises
29
35
  * returned by [`Clock.waitFor`](https://serenity-js.org/api/core/class/Clock/#waitFor).
@@ -1,3 +1,5 @@
1
+ import type { JSONObject } from 'tiny-types';
2
+
1
3
  import { OperationInterruptedError, TimeoutExpiredError } from '../../../errors';
2
4
  import type { Clock } from './Clock';
3
5
  import type { DelayedCallback } from './DelayedCallback';
@@ -23,6 +25,13 @@ export class Scheduler {
23
25
  ) {
24
26
  }
25
27
 
28
+ toJSON(): JSONObject {
29
+ return {
30
+ clock: this.clock.toJSON(),
31
+ interactionTimeout: this.interactionTimeout.toJSON(),
32
+ }
33
+ }
34
+
26
35
  /**
27
36
  * Schedules a callback function to be invoked after a delay
28
37
  *
@@ -1,22 +1,20 @@
1
1
  import { ensure, isDefined } from 'tiny-types';
2
2
 
3
- import type { ErrorFactory, ErrorOptions, RuntimeError } from '../errors';
4
- import { ConfigurationError, LogicError, RaiseErrors } from '../errors';
5
- import type {
6
- DomainEvent,
7
- EmitsDomainEvents} from '../events';
3
+ import { ConfigurationError, type ErrorFactory, type ErrorOptions, LogicError, RaiseErrors,type RuntimeError } from '../errors';
8
4
  import {
9
- AsyncOperationAttempted,
10
- AsyncOperationCompleted,
11
- AsyncOperationFailed,
5
+ ActorEntersStage,
6
+ ActorStageExitAttempted,
7
+ ActorStageExitCompleted,
8
+ ActorStageExitFailed,
9
+ ActorStageExitStarts,
10
+ type DomainEvent,
11
+ type EmitsDomainEvents,
12
12
  SceneFinishes,
13
13
  SceneStarts,
14
14
  TestRunFinishes
15
15
  } from '../events';
16
- import type { ActivityDetails} from '../model';
17
- import { CorrelationId, Description, Name } from '../model';
18
- import type { Clock, Duration, Timestamp } from '../screenplay';
19
- import { Actor, ScheduleWork } from '../screenplay';
16
+ import { type ActivityDetails, CorrelationId, Name } from '../model';
17
+ import { Actor, type Clock, type Duration, ScheduleWork, type Timestamp } from '../screenplay';
20
18
  import type { ListensToDomainEvents } from '../stage';
21
19
  import type { Cast } from './Cast';
22
20
  import type { StageManager } from './StageManager';
@@ -39,7 +37,7 @@ import type { StageManager } from './StageManager';
39
37
  */
40
38
  export class Stage implements EmitsDomainEvents {
41
39
 
42
- private static readonly unknownSceneId = new CorrelationId('unknown')
40
+ public static readonly unknownSceneId = new CorrelationId('unknown')
43
41
 
44
42
  /**
45
43
  * Actors instantiated after the scene has started,
@@ -111,9 +109,6 @@ export class Stage implements EmitsDomainEvents {
111
109
  ]);
112
110
 
113
111
  actor = this.cast.prepare(newActor);
114
-
115
- // todo this.manager.notifyOf(ActorStarts)
116
- // todo: map this in Serenity BDD Reporter so that the "cast" is recorded
117
112
  }
118
113
  catch (error) {
119
114
  throw new ConfigurationError(`${ this.typeOf(this.cast) } encountered a problem when preparing actor "${ name }" for stage`, error);
@@ -123,7 +118,23 @@ export class Stage implements EmitsDomainEvents {
123
118
  throw new ConfigurationError(`Instead of a new instance of actor "${ name }", ${ this.typeOf(this.cast) } returned ${ actor }`);
124
119
  }
125
120
 
126
- this.actorsOnStage.set(name, actor)
121
+ this.actorsOnStage.set(name, actor);
122
+
123
+ this.announce(
124
+ new ActorEntersStage(
125
+ this.currentScene,
126
+ actor.toJSON(),
127
+ )
128
+ )
129
+ }
130
+
131
+ if (this.actorsOnBackstage.has(name)) {
132
+ this.announce(
133
+ new ActorEntersStage(
134
+ this.currentScene,
135
+ this.actorsOnBackstage.get(name).toJSON(),
136
+ )
137
+ )
127
138
  }
128
139
 
129
140
  this.actorInTheSpotlight = this.instantiatedActorCalled(name);
@@ -192,6 +203,10 @@ export class Stage implements EmitsDomainEvents {
192
203
  this.actorsOnStage = this.actorsOnFrontStage;
193
204
  }
194
205
 
206
+ if (event instanceof SceneFinishes || event instanceof TestRunFinishes) {
207
+ this.notifyOfStageExit(this.currentSceneId());
208
+ }
209
+
195
210
  this.manager.notifyOf(event);
196
211
 
197
212
  if (event instanceof SceneFinishes) {
@@ -298,6 +313,16 @@ export class Stage implements EmitsDomainEvents {
298
313
  : this.actorsOnFrontStage.get(name)
299
314
  }
300
315
 
316
+ private notifyOfStageExit(sceneId: CorrelationId): void {
317
+ for (const actor of this.actorsOnStage.values()) {
318
+ this.announce(new ActorStageExitStarts(
319
+ sceneId,
320
+ actor.toJSON(),
321
+ this.currentTime(),
322
+ ));
323
+ }
324
+ }
325
+
301
326
  private async dismiss(activeActors: Map<string, Actor>): Promise<void> {
302
327
  const actors = Array.from(activeActors.values());
303
328
 
@@ -311,10 +336,9 @@ export class Stage implements EmitsDomainEvents {
311
336
  const actorsToDismiss = new Map<Actor, CorrelationId>(actors.map(actor => [actor, CorrelationId.create()]));
312
337
 
313
338
  for (const [ actor, correlationId ] of actorsToDismiss) {
314
- this.announce(new AsyncOperationAttempted(
315
- new Name(this.constructor.name),
316
- new Description(`Dismissing ${ actor.name }...`),
339
+ this.announce(new ActorStageExitAttempted(
317
340
  correlationId,
341
+ new Name(actor.name),
318
342
  this.currentTime(),
319
343
  ));
320
344
  }
@@ -324,13 +348,14 @@ export class Stage implements EmitsDomainEvents {
324
348
  try {
325
349
  await actor.dismiss();
326
350
 
327
- this.announce(new AsyncOperationCompleted(
328
- correlationId,
329
- this.currentTime(),
330
- ));
351
+ this.announce(new ActorStageExitCompleted(correlationId, new Name(actor.name), this.currentTime()));
331
352
  }
332
353
  catch (error) {
333
- this.announce(new AsyncOperationFailed(error, correlationId, this.currentTime())); // todo: serialise the error!
354
+ this.announce(new ActorStageExitFailed(
355
+ error,
356
+ correlationId,
357
+ this.currentTime()
358
+ ));
334
359
  }
335
360
  }
336
361