Skip to content

Commit

Permalink
feat(core): conditional activities
Browse files Browse the repository at this point in the history
affects: @serenity-js/core

Make the actor perform alternative sets of activities depending on the condition (boolean,
Question<boolean> or Question<PromiseLike<boolean>>)

Check.whether(condition).andIfSo(...activities).otherwise(...otherActivities)
This enables basic
flow control.

ISSUES CLOSED: Closes #159
  • Loading branch information
jan-molak committed Apr 24, 2018
1 parent 836700d commit 3883ece
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 0 deletions.
47 changes: 47 additions & 0 deletions packages/core/spec/tasks/check.spec.ts
@@ -0,0 +1,47 @@
import 'mocha';
import { given } from 'mocha-testdata';
import * as sinon from 'sinon';

import { Actor, Check, Interaction, Question } from '../../src/screenplay';
import expect = require('../expect');

describe('Check', () => {

const Nick = Actor.named('Nick');

const Call = (fn: () => void) => Interaction.where(`#actor triggers a function`, actor => {
return Promise.resolve(fn());
});

const QuestionReturning = <T>(v: T) => Question.about<T>(`testing`, actor => v);

const expectYays = (spy: sinon.SinonSpy) => expect(spy).to.have.been.calledWith('Yay!').callCount(3);
const expectNoes = (spy: sinon.SinonSpy) => expect(spy).to.have.been.calledWith('Oh noes!').callCount(2);

const examples: Array<{ 0: boolean | Question<boolean> | Question<PromiseLike<boolean>>, 1: any }> = [
[ true, expectYays ],
[ false, expectNoes ],
[ QuestionReturning(true), expectYays ],
[ QuestionReturning(false), expectNoes ],
[ QuestionReturning(Promise.resolve(true)), expectYays ],
[ QuestionReturning(Promise.resolve(false)), expectNoes ],
];

given(examples).it(`triggers activities in the appropriate branch depending on the condition`, (condition, expectation) => {

const spy = sinon.spy();

return expect(Nick.attemptsTo(
Check.whether(condition)
.andIfSo(
Call(() => spy(`Yay!`)),
Call(() => spy(`Yay!`)),
Call(() => spy(`Yay!`)),
)
.otherwise(
Call(() => spy(`Oh noes!`)),
Call(() => spy(`Oh noes!`)),
),
)).to.be.eventually.fulfilled.then(() => expectation(spy));
});
});
1 change: 1 addition & 0 deletions packages/core/src/screenplay/index.ts
Expand Up @@ -3,3 +3,4 @@ export * from './activities';
export * from './actor';
export * from './interactions';
export * from './question';
export * from './tasks';
69 changes: 69 additions & 0 deletions packages/core/src/screenplay/tasks/check.ts
@@ -0,0 +1,69 @@
import { Activity, Task } from '../activities';
import { PerformsTasks, UsesAbilities } from '../actor';
import { Question } from '../question';

const
isBoolean = (v: any): v is boolean => v === true || v === false,
isQuestion = (v: any): v is Question<any> => !! v.answeredBy;

export abstract class Check {
static whether(condition: boolean | Question<boolean> | Question<PromiseLike<boolean>>) {
return ({
andIfSo: (...activities: Activity[]): Activity & Check => {
if (isBoolean(condition)) {
return new ActivitiesConditionalOnBoolean(condition, activities, []);
}
if (isQuestion(condition)) {
return new ActivitiesConditionalOnQuestion(condition, activities, []);
}
},
});
}

abstract otherwise(...activities: Activity[]): Activity & Check;
}

class ActivitiesConditionalOnBoolean implements Task, Check {
constructor(private readonly condition: boolean,
private readonly activitiesWhenConditionIsMet: Activity[],
private readonly activitiesWhenConditionIsNotMet: Activity[]) {
}

otherwise(...activities: Activity[]): Activity & Check {
return new ActivitiesConditionalOnBoolean(this.condition, this.activitiesWhenConditionIsMet, activities);
}

performAs(actor: PerformsTasks): PromiseLike<void> {
return actor.attemptsTo(...(this.condition
? this.activitiesWhenConditionIsMet
: this.activitiesWhenConditionIsNotMet
));
}
}

class ActivitiesConditionalOnQuestion implements Task, Check {
constructor(private readonly condition: Question<boolean> | Question<PromiseLike<boolean>>,
private readonly activitiesWhenConditionIsMet: Activity[],
private readonly activitiesWhenConditionIsNotMet: Activity[]) {
}

otherwise(...activities: Activity[]): Activity & Check {
return new ActivitiesConditionalOnQuestion(this.condition, this.activitiesWhenConditionIsMet, activities);
}

performAs(actor: PerformsTasks & UsesAbilities): PromiseLike<void> {
const answer = this.condition.answeredBy(actor);

if (isBoolean(answer)) {
return actor.attemptsTo(...(answer
? this.activitiesWhenConditionIsMet
: this.activitiesWhenConditionIsNotMet
));
}

return answer.then(result => actor.attemptsTo(...(result
? this.activitiesWhenConditionIsMet
: this.activitiesWhenConditionIsNotMet
)));
}
}
1 change: 1 addition & 0 deletions packages/core/src/screenplay/tasks/index.ts
@@ -0,0 +1 @@
export * from './check';

0 comments on commit 3883ece

Please sign in to comment.