Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(screenplay): The actor can now
TakeNotes
to assert on their co…
…ntents later. This change adds: - an ability to `TakeNotes` - a task to `TakeNote.of(q: Question) - a task to `CompareNotes.toSeeIf(actual: Question, expectation: Expectation, topic: string)` Where: - `Expectation<T> = (expected: T) => Assertion<T>;` - `Assertion<T> = (actual: T) => PromiseLike<void>;` For example: ```typescript const includes = (expected: string) => (actual: string[]) => expect(actual).to.eventually.include(expected); const actor = Actor.named('Benjamin').whoCan(TakeNotes.using(notepad)); actor.attemptsTo( TakeNote.of(Text.of(MarketingPopup.Voucher_Code), /* perform some other tasks */ CompareNotes.toSeeIf(Text.ofAll(Checkout.Applied_Vouchers), includes, MarketingPopup.Voucher_Code), ); ``` Also, the signature of the task to `See` is compatible with `CompareNotes` so that the assertions could be reused: ```typescript const includes = (expected: string) => (actual: string[]) => expect(actual).to.eventually.include(expected); const actor = Actor.named('Benjamin'); actor.attemptsTo( See.that(Text.ofAll(Checkout.Applied_Vouchers), includes('my voucher')), ); ``` Closes #24
- Loading branch information
Showing
13 changed files
with
247 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import sinon = require('sinon'); | ||
import expect = require('../../../expect'); | ||
import { | ||
Actor, | ||
Notepad, | ||
Question, | ||
TakeNote, | ||
TakeNotes, | ||
UsesAbilities, | ||
} from '../../../../src/serenity/screenplay'; | ||
import { Performable } from '../../../../src/serenity/screenplay/performables'; | ||
|
||
import { step } from '../../../../src/serenity/recording/step_annotation'; | ||
|
||
import { AnswersQuestions } from '../../../../src/serenity/screenplay/actor'; | ||
import { Expectation } from '../../../../src/serenity/screenplay/expectations'; | ||
import { OneOrMany } from '../../../../src/serenity/screenplay/lists'; | ||
|
||
describe('Abilities', () => { | ||
|
||
describe('TakeNotes', () => { | ||
|
||
let notepad: Notepad; | ||
|
||
const include = (expected: string) => (actual: string[]) => expect(actual).to.eventually.include(expected), | ||
includeAllOf = (expected: string[]) => (actual: string[]) => expect(actual).to.eventually.include.members(expected), | ||
equals = <T>(expected: T) => (actual: T) => expect(actual).to.eventually.equal(expected); | ||
|
||
beforeEach(() => notepad = {}); | ||
|
||
it ('stores notes in a notepad as promises to be resolved', () => { | ||
let displayedVoucher = MyVoucherCode.shownAs('SUMMER2017'); | ||
|
||
let actor = Actor.named('Benjamin').whoCan(TakeNotes.using(notepad)); | ||
|
||
return actor.attemptsTo( | ||
TakeNote.of(displayedVoucher).as('my voucher'), | ||
). | ||
then(() => notepad['my voucher']). | ||
then(voucher => expect(voucher).to.equal('SUMMER2017')); | ||
}); | ||
|
||
it('stores notes using a topic name derived from the question', () => { | ||
let displayedVoucher = MyVoucherCode.shownAs('SUMMER2017'), | ||
availableVoucher = AvailableVoucher.of('SUMMER2017'); | ||
|
||
let actor = Actor.named('Benjamin').whoCan(TakeNotes.usingAnEmptyNotepad()); | ||
|
||
return actor.attemptsTo( | ||
TakeNote.of(displayedVoucher), | ||
CompareNotes.toSeeIf(availableVoucher, equals, displayedVoucher), | ||
); | ||
}); | ||
|
||
it ('allows the Actor to remember a thing they\'ve seen', () => { | ||
let displayedVoucher = MyVoucherCode.shownAs('SUMMER2017'), | ||
availableVoucher = AvailableVoucher.of('SUMMER2017'); | ||
|
||
let actor = Actor.named('Benjamin').whoCan(TakeNotes.using(notepad)); | ||
|
||
return actor.attemptsTo( | ||
TakeNote.of(displayedVoucher), | ||
/* perform some other tasks */ | ||
CompareNotes.toSeeIf(availableVoucher, equals, displayedVoucher), | ||
); | ||
}); | ||
|
||
it ('allows the Actor to remember several things they\'ve seen one after another', () => { | ||
let displayedVoucher = MyVoucherCode.shownAs('SUMMER2017'), | ||
otherDisplayedVoucher = MyVoucherCode.shownAs('50_OFF'), | ||
availableVouchers = AvailableVouchers.of('SUMMER2017', '50_OFF'); | ||
|
||
let actor = Actor.named('Benjamin').whoCan(TakeNotes.using(notepad)); | ||
|
||
return actor.attemptsTo( | ||
TakeNote.of(displayedVoucher).as('my voucher'), | ||
/* some other tasks */ | ||
TakeNote.of(otherDisplayedVoucher).as('my other voucher'), | ||
/* some other tasks */ | ||
CompareNotes.toSeeIf(availableVouchers, include, 'my voucher'), | ||
/* some other tasks */ | ||
CompareNotes.toSeeIf(availableVouchers, include, 'my other voucher'), | ||
); | ||
}); | ||
|
||
it ('allows the Actor to remember several things they\'ve seen at once', () => { | ||
let displayedVouchers = MyVoucherCodes.shownAs('SUMMER2017', 'SPRINGCLEANING'), | ||
availableVouchers = AvailableVouchers.of('SUMMER2017', '50_OFF', 'SPRINGCLEANING'); | ||
|
||
let actor = Actor.named('Benjamin').whoCan(TakeNotes.using(notepad)); | ||
|
||
return actor.attemptsTo( | ||
TakeNote.of(displayedVouchers).as('my vouchers'), | ||
CompareNotes.toSeeIf(availableVouchers, includeAllOf, 'my vouchers'), | ||
); | ||
}); | ||
|
||
it ('allows the Actor to complain if you ask them about the thing they have no notes on', () => { | ||
let availableVouchers = AvailableVouchers.of('SUMMER2017', '5OFF', 'SPRINGCLEANING'); | ||
|
||
let actor = Actor.named('Benjamin').whoCan(TakeNotes.using(notepad)); | ||
|
||
return expect(actor.attemptsTo( | ||
CompareNotes.toSeeIf(availableVouchers, include, 'my voucher'), | ||
)).to.be.rejectedWith('I don\'t have any notes on the topic of "my voucher"'); | ||
}); | ||
}); | ||
}); | ||
|
||
export class CompareNotes<S> implements Performable { | ||
static toSeeIf<A>(actual: Question<OneOrMany<A>>, expectation: Expectation<OneOrMany<A>>, topic: { toString: () => string }) { | ||
return new CompareNotes<A>(actual, expectation, topic.toString()); | ||
} | ||
|
||
@step('{0} compares notes on #actual') | ||
performAs(actor: UsesAbilities & AnswersQuestions): PromiseLike<void> { | ||
return TakeNotes. | ||
as(actor). | ||
read(this.topic). | ||
then(expected => this.expect(expected)(actor.toSee(this.actual))); | ||
} | ||
|
||
constructor(private actual: Question<OneOrMany<S>>, private expect: Expectation<OneOrMany<S>>, private topic: string) { | ||
} | ||
} | ||
|
||
class MyVoucherCode implements Question<string> { | ||
static shownAs = (someValue: string) => new MyVoucherCode(someValue); | ||
answeredBy = (actor: UsesAbilities) => Promise.resolve(this.value); | ||
toString = () => 'My voucher code'; | ||
constructor(private value: string) { | ||
} | ||
} | ||
|
||
class MyVoucherCodes implements Question<string[]> { | ||
static shownAs = (...someValues: string[]) => new MyVoucherCodes(someValues); | ||
answeredBy = (actor: UsesAbilities) => Promise.resolve(this.values); | ||
toString = () => 'My voucher codes'; | ||
constructor(private values: string[]) { | ||
} | ||
} | ||
|
||
class AvailableVoucher implements Question<string> { | ||
static of = (someValue: string) => new AvailableVoucher(someValue); | ||
answeredBy = (actor: UsesAbilities) => Promise.resolve(this.value); | ||
toString = () => 'Available voucher'; | ||
constructor(private value: string) { | ||
} | ||
} | ||
|
||
class AvailableVouchers implements Question<string[]> { | ||
static of = (...someValues: string[]) => new AvailableVouchers(someValues); | ||
answeredBy = (actor: UsesAbilities) => Promise.resolve(this.values); | ||
toString = () => 'Available vouchers'; | ||
constructor(private values: string[]) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './take_notes'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Ability, UsesAbilities } from '../actor'; | ||
|
||
export interface Notepad { | ||
[x: string]: PromiseLike<any>; | ||
} | ||
|
||
export class TakeNotes implements Ability { | ||
|
||
static usingAnEmptyNotepad = () => TakeNotes.using({}); | ||
static using = (notepad: Notepad) => new TakeNotes(notepad); | ||
|
||
static as(actor: UsesAbilities): TakeNotes { | ||
return actor.abilityTo(TakeNotes); | ||
} | ||
|
||
note = (topic: string, contents: PromiseLike<any> | any) => (this.notepad[topic] = contents, Promise.resolve()); | ||
|
||
read(topic: string): PromiseLike<any> { | ||
return !! this.notepad[topic] | ||
? Promise.resolve(this.notepad[topic]) | ||
: Promise.reject(new Error(`I don\'t have any notes on the topic of "${ topic }"`)); | ||
} | ||
|
||
constructor(private notepad: Notepad) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export type Assertion<T> = (actual: T) => PromiseLike<void>; | ||
export type Expectation<T> = (expected: T) => Assertion<T>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
export * from './abilities'; | ||
export * from './actor'; | ||
export * from './interactions'; | ||
export * from './performables'; | ||
export * from './question'; | ||
export * from './tasks'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './take_note'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { step } from '../../recording/step_annotation'; | ||
import { Interaction, Question, TakeNotes, UsesAbilities } from '../index'; | ||
|
||
export class TakeNote<T> implements Interaction { | ||
static of = <Answer>(question: Question<Answer>) => new TakeNote<Answer>(question, question); | ||
|
||
as = (topic: string) => (this.topic = topic, this); | ||
|
||
@step('{0} takes a note of #topic') | ||
performAs(actor: UsesAbilities): PromiseLike<void> { | ||
return TakeNotes.as(actor).note(this.topic.toString(), this.question.answeredBy(actor)); | ||
} | ||
|
||
constructor(private question: Question<T>, private topic: { toString: () => string }) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export type List<Item> = Item[]; | ||
export type OneOrMany<T> = T | List<T>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { step } from '../../recording/step_annotation'; | ||
import { TakeNotes } from '../abilities'; | ||
import { AnswersQuestions, UsesAbilities } from '../actor'; | ||
import { Expectation } from '../expectations'; | ||
import { OneOrMany } from '../lists'; | ||
import { Performable } from '../performables'; | ||
import { Question } from '../question'; | ||
|
||
export class CompareNotes<S> implements Performable { | ||
static toSeeIf<A>(actual: Question<OneOrMany<A>>, expectation: Expectation<OneOrMany<A>>, topic: { toString: () => string }) { | ||
return new CompareNotes<A>(actual, expectation, topic.toString()); | ||
} | ||
|
||
@step('{0} compares notes on #actual') | ||
performAs(actor: UsesAbilities & AnswersQuestions): PromiseLike<void> { | ||
return TakeNotes. | ||
as(actor). | ||
read(this.topic). | ||
then(expected => this.expect(expected)(actor.toSee(this.actual))); | ||
} | ||
|
||
constructor(private actual: Question<OneOrMany<S>>, private expect: Expectation<OneOrMany<S>>, private topic: string) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './compare_notes' | ||
export * from './see'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,17 @@ | ||
import { AnswersQuestions, Performable, Question } from '..'; | ||
import { step } from '../../recording/step_annotation'; | ||
|
||
export type Expectation<S> = (subject: S) => PromiseLike<void>; | ||
import { Assertion } from '../expectations'; | ||
|
||
export class See<S> implements Performable { | ||
static that<S>(subject: Question<S>, verifier: Expectation<S>) { | ||
return new See(subject, verifier); | ||
static that<T>(question: Question<T>, assertion: Assertion<T>) { | ||
return new See<T>(question, assertion); | ||
} | ||
|
||
@step('{0} looks at #question') | ||
performAs(actor: AnswersQuestions): PromiseLike<void> { | ||
return this.expect(actor.toSee(this.question)); | ||
return this.assert(actor.toSee(this.question)); | ||
} | ||
|
||
constructor(private question: Question<S>, private expect: Expectation<S>) { | ||
constructor(private question: Question<S>, private assert: Assertion<S>) { | ||
} | ||
} |