Skip to content
Permalink
Browse files
feat(core): Actor.named('name') allows for instantiating an Actor wit…
…hout explicitly providing the S
  • Loading branch information
jan-molak committed Apr 1, 2019
1 parent 6c4315c commit 581a6ba88d2dc0f33a974dec3812e9300f01565c
@@ -1,4 +1,4 @@
import { Activity, AnswersQuestions, KnowableUnknown, PerformsTasks, Task } from '@serenity-js/core';
import { Activity, AnswersQuestions, KnowableUnknown, PerformsActivities, Task } from '@serenity-js/core';
import { formatted } from '@serenity-js/core/lib/io';
import { Expectation } from './Expectation';
import { ExpectationMet } from './outcomes';
@@ -23,7 +23,7 @@ export class Check<Actual> extends Task {
return new Check<Actual>(this.actual, this.expectation, this.activities, alternativeActivities);
}

performAs(actor: AnswersQuestions & PerformsTasks): PromiseLike<void> {
performAs(actor: AnswersQuestions & PerformsActivities): PromiseLike<void> {
return Promise.all([
actor.answer(this.actual),
actor.answer(this.expectation),
@@ -2,11 +2,11 @@ import 'mocha';

import * as sinon from 'sinon';

import { InteractionFinished, InteractionStarts } from '../../../src/events';
import { ExecutionSuccessful, Name, Timestamp } from '../../../src/model';
import { Ability, Actor, See } from '../../../src/screenplay';
import { Stage } from '../../../src/stage';
import { expect } from '../../expect';
import { InteractionFinished, InteractionStarts } from '../../src/events';
import { ExecutionSuccessful, Name, Timestamp } from '../../src/model';
import { Ability, Actor, See } from '../../src/screenplay';
import { Stage } from '../../src/stage';
import { expect } from '../expect';
import {
AcousticGuitar,
Chords,
@@ -16,7 +16,7 @@ import {
PlayAChord,
PlayAGuitar,
PlayASong,
} from '../example-implementation';
} from './example-implementation';

const equals = (expected: number) => (actual: PromiseLike<number>) => expect(actual).to.equal(expected);

@@ -103,6 +103,14 @@ describe('Actor', () => {
PlayAChord.of(Chords.AMajor),
)).to.be.eventually.rejectedWith(`Ben can't PlayAGuitar yet. Did you give them the ability to do so?`));

/** @test {Actor} */
it(`can be instantiated without explicitly specifying the Stage`, () => {

const anActor: Actor = Actor.named('Bob').whoCan(PlayAGuitar.suchAs(guitar));

expect(anActor.name).to.equal('Bob');
});

describe('DomainEvent handling', () => {

let Bob: Actor;
@@ -139,8 +147,5 @@ describe('Actor', () => {
expect(secondEvent).to.have.property('timestamp').equal(now);
}));
});

/** @test {Actor} */
it('reacts to events and changes its behaviour'); // todo
});
});
@@ -1,4 +1,4 @@
import { PerformsTasks, Task } from '../../../../../core/src/screenplay';
import { PerformsActivities, Task } from '../../../../../core/src/screenplay';
import { PlayAChord } from '../interactions';
import { MusicSheet } from '../MusicSheet';

@@ -11,7 +11,7 @@ export class PlayASong extends Task {
super();
}

performAs(actor: PerformsTasks): PromiseLike<void> {
performAs(actor: PerformsActivities): PromiseLike<void> {
return actor.attemptsTo(
...this.musicSheet.chords.map(chord => PlayAChord.of(chord)),
);
@@ -1,6 +1,6 @@
import { AnswersQuestions, PerformsTasks, UsesAbilities } from './actor';
import { AnswersQuestions, PerformsActivities, UsesAbilities } from './actor';

export interface Activity {
performAs(actor: PerformsTasks | UsesAbilities | AnswersQuestions): PromiseLike<any>;
performAs(actor: PerformsActivities | UsesAbilities | AnswersQuestions): PromiseLike<any>;
toString(): string;
}
@@ -1,6 +1,6 @@
import { ImplementationPendingError } from '../errors';
import { Activity } from './Activity';
import { PerformsTasks } from './actor';
import { PerformsActivities } from './actor';

export abstract class Task implements Activity {
static where(description: string, ...activities: Activity[]): Task {
@@ -9,15 +9,15 @@ export abstract class Task implements Activity {
: new NotImplementedTask(description);
}

abstract performAs(actor: PerformsTasks): PromiseLike<void>;
abstract performAs(actor: PerformsActivities): PromiseLike<void>;
}

class DynamicallyGeneratedTask extends Task {
constructor(private description: string, private activities: Activity[]) {
super();
}

performAs(actor: PerformsTasks): PromiseLike<void> {
performAs(actor: PerformsActivities): PromiseLike<void> {
return actor.attemptsTo(...this.activities);
}

@@ -31,7 +31,7 @@ class NotImplementedTask extends Task {
super();
}

performAs(actor: PerformsTasks): PromiseLike<void> {
performAs(actor: PerformsActivities): PromiseLike<void> {
return Promise.reject(
new ImplementationPendingError(`A task where "${ this.toString() }" has not been implemented yet`),
);
@@ -2,7 +2,7 @@ import { InteractionFinished, InteractionStarts, TaskFinished, TaskStarts } from
import { ActivityDetails, CorrelationId, ExecutionSuccessful } from '../../model';
import { Stage } from '../../stage';
import { Activity } from '../Activity';
import { AnswersQuestions, PerformsTasks, UsesAbilities } from '../actor';
import { AnswersQuestions, PerformsActivities, UsesAbilities } from '../actor';
import { Interaction } from '../Interaction';
import { ActivityDescriber } from './ActivityDescriber';
import { OutcomeMatcher } from './OutcomeMatcher';
@@ -19,7 +19,7 @@ export class TrackedActivity implements Activity {
) {
}

performAs(actor: (PerformsTasks | UsesAbilities | AnswersQuestions) & { name: string }): PromiseLike<void> {
performAs(actor: (PerformsActivities | UsesAbilities | AnswersQuestions) & { name: string }): PromiseLike<void> {
const details = new ActivityDetails(
TrackedActivity.describer.describe(this.activity, actor),
CorrelationId.create(),
@@ -1,20 +1,100 @@
import { ArtifactGenerated } from '../../events';
import { Ability, AbilityType, KnowableUnknown, TestCompromisedError } from '../../index';
import { Artifact, Name } from '../../model';
import { Stage } from '../../stage';
import { TrackedActivity } from '../activities';
import { Activity } from '../Activity';
import { Question } from '../Question';
import { AnswersQuestions } from './AnswersQuestions';
import { CollectsArtifacts } from './CollectsArtifacts';
import { PerformsTasks } from './PerformsTasks';
import { UsesAbilities } from './UsesAbilities';

export class Actor implements PerformsTasks, UsesAbilities, AnswersQuestions, CollectsArtifacts {
import { ArtifactGenerated } from '../events';
import { Ability, AbilityType, DressingRoom, KnowableUnknown, serenity, TestCompromisedError } from '../index';
import { Artifact, Name } from '../model';
import { Stage } from '../stage';
import { TrackedActivity } from './activities';
import { Activity } from './Activity';
import { Question } from './Question';

/**
* @desc
* Enables the {@link Actor} to answer a {@link Question} about the system under test
*
* @public
* @interface
*/
export interface AnswersQuestions {
answer<T>(knownUnknown: KnowableUnknown<T>): Promise<T>;
}

/**
* @desc
* Enables the {@link Actor} to collect {@link Artifact}s while the scenario is being executed
*
* @public
* @interface
*/
export interface CollectsArtifacts {
/**
* @desc
* Makes the {@link Actor} collect an {@link Artifact} so that it's included in the test report.
*
* @param {Artifact} artifact - The artifact to be collected, such as {@link JSONData}
* @param {Name} [name] - The name of the artifact to make it easy to recognise in the test report
*/
collect(artifact: Artifact, name?: Name): void;
}

/**
* @desc
* Enables the {@link Actor} to perform an {@link Activity}, such as a {@link Task} or an {@link Interaction}
*
* @public
* @interface
*/
export interface PerformsActivities {
attemptsTo(...tasks: Activity[]): Promise<void>;
}

/**
* @desc
* Enables the {@link Actor} to have an {@link Ability} or Abilities to perform some {@link Activity}.
*
* @public
* @interface
*/
export interface CanHaveAbilities<Returned_Type = UsesAbilities> {
/**
* @param {Ability[]} abilities
* @returns {Actor}
*/
whoCan(...abilities: Ability[]): Returned_Type;
}

/**
* @desc
* Enables the {@link Actor} to use an {@link Ability} to perform some {@link Activity}.
*
* @public
* @interface
*/
export interface UsesAbilities {

/**
* @desc
* Grants access to the Actor's ability
*
* @param {AbilityType<T extends Ability>} doSomething
* @returns {T}
*/
abilityTo<T extends Ability>(doSomething: AbilityType<T>): T;
}

export class Actor implements PerformsActivities, UsesAbilities, CanHaveAbilities<Actor>, AnswersQuestions, CollectsArtifacts {
// todo: Actor should have execution strategies
// todo: the default one executes every activity
// todo: there could be a dry-run mode that default to skip strategy

static named(name: string): CanHaveAbilities<Actor> {
return {
whoCan: (...abilities): Actor => {
const stage = serenity.callToStageFor(DressingRoom.whereEveryoneCan(...abilities));

return stage.theActorCalled(name);
},
};
}

constructor(
public readonly name: string,
private readonly stage: Stage,
@@ -32,16 +112,14 @@ export class Actor implements PerformsTasks, UsesAbilities, AnswersQuestions, Co
}

attemptsTo(...activities: Activity[]): Promise<void> {
// todo: if there are no activities, make it a PendingActivity
// todo: only change the execution strategy for the duration of the current task; tasks from afterhooks should still get executed
return activities
.map(activity => new TrackedActivity(activity, this.stage)) // todo: TrackedInteraction, TrackedTask
.reduce((previous: Promise<void>, current: Activity) => {
return previous.then(() => {
/* todo: add an execution strategy */
return current.performAs(this);
});
}, Promise.resolve(void 0));
}, Promise.resolve(void 0));
}

whoCan(...abilities: Ability[]): Actor {

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -1,4 +1,4 @@
import { Actor } from '../screenplay/actor';
import { Ability, Actor } from '../screenplay';

/**
* @desc
@@ -20,5 +20,21 @@ import { Actor } from '../screenplay/actor';
* @interface
*/
export abstract class DressingRoom {
static whereEveryoneCan(...abilities: Ability[]): DressingRoom {
return new GenericDressingRoom(abilities);
}

abstract prepare(actor: Actor): Actor;
}

/**
* @package
*/
class GenericDressingRoom implements DressingRoom {
constructor(private readonly abilities: Ability[]) {
}

prepare(actor: Actor): Actor {
return actor.whoCan(...this.abilities);
}
}
@@ -8,7 +8,7 @@ import { StageCrewMember } from './StageCrewMember';
import { StageManager } from './StageManager';

export class Stage {
private readonly actorsOnStage: { [name: string]: Actor } = {};
private actorsOnStage: { [name: string]: Actor } = {};
private actorInTheSpotlight: Actor = null;

constructor(
@@ -89,6 +89,8 @@ export class Stage {
ensure('DressingRoom', actors, isDefined());

this.dressingRoom = actors;
this.actorsOnStage = {};
this.actorInTheSpotlight = null;

return this;
}

0 comments on commit 581a6ba

Please sign in to comment.