Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
feat(core):
q
makes Question<string> templates as easy as regular s…
…tring templates
- Loading branch information
Showing
2 changed files
with
124 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import 'mocha'; | ||
import { given } from 'mocha-testdata'; | ||
|
||
import { q, Question, Serenity } from '../../../src'; | ||
import { Actor } from '../../../src/screenplay'; | ||
import { Cast } from '../../../src/stage'; | ||
import { expect } from '../../expect'; | ||
|
||
/** @test {q} */ | ||
describe('q', () => { | ||
|
||
let serenity: Serenity, | ||
Quentin: Actor; | ||
|
||
class Actors implements Cast { | ||
prepare(actor: Actor): Actor { | ||
return actor; | ||
} | ||
} | ||
|
||
beforeEach(() => { | ||
serenity = new Serenity(); | ||
|
||
serenity.configure({ | ||
crew: [ ], | ||
actors: new Actors(), | ||
}); | ||
|
||
Quentin = serenity.theActorCalled('Quentin'); | ||
}); | ||
|
||
/** @test {q} */ | ||
it('returns the original string value if no parameters are provided', () => { | ||
const question = q `some value`; | ||
|
||
return expect(Quentin.answer(question)).to.eventually.equal('some value') | ||
}); | ||
|
||
given([ | ||
{ description: 'string', value: 'World' }, | ||
{ description: 'Promise<string>', value: Promise.resolve('World') }, | ||
{ description: 'Question<string>', value: Question.about('name', actor => 'World') }, | ||
{ description: 'Question<Promise<string>>', value: Question.about('name', actor => Promise.resolve('World')) }, | ||
]). | ||
it('should inject an answer to an Answerable<string> into the template', ({ value }) => { | ||
const question = q `Hello ${ value }!`; | ||
|
||
return expect(Quentin.answer(question)).to.eventually.equal('Hello World!') | ||
}); | ||
|
||
given([ | ||
{ description: 'number', value: 42 }, | ||
{ description: 'Promise<number>', value: Promise.resolve(42) }, | ||
{ description: 'Question<number>', value: Question.about('value', actor => 42) }, | ||
{ description: 'Question<Promise<number>>', value: Question.about('value', actor => Promise.resolve(42)) }, | ||
]). | ||
it('should inject an answer to an Answerable<number> into the template', ({ value }) => { | ||
const question = q `The answer is: ${ value }!`; | ||
|
||
return expect(Quentin.answer(question)).to.eventually.equal('The answer is: 42!') | ||
}); | ||
|
||
/** @test {q} */ | ||
it('provides a sensible description of the question being asked', () => { | ||
const question = q `/products/${ 1 }/attributes/${ Promise.resolve(2) }`; | ||
|
||
return expect(question.toString()).to.equal('/products/{}/attributes/{}') | ||
}); | ||
|
||
/** @test {q} */ | ||
it('can have the default description overridden', () => { | ||
const question = q `/products/${ 1 }/attributes/${ Promise.resolve(2) }`.describedAs('/products/:productId/attributes/:attributeId'); | ||
|
||
return expect(question.toString()).to.equal('/products/:productId/attributes/:attributeId') | ||
}); | ||
}); |
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,48 @@ | ||
import { Answerable } from '../Answerable'; | ||
import { Question } from '../Question'; | ||
|
||
/** | ||
* @desc | ||
* A Screenplay-flavour of a [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates), | ||
* `q` is a tag function capable of resolving any `Answerable<string | number>` you parametrise it with (i.e. a {@link Question}). | ||
* | ||
* @example <caption>Interpolating `Questions`</caption> | ||
* import { q, actorCalled } from '@serenity-js/core'; | ||
* import { Send, DeleteRequest } from '@serenity-js/rest'; | ||
* | ||
* actorCalled('Alice').attemptsTo( | ||
* Send.a(DeleteRequest.to( | ||
* q `/articles/${ Text.of(Article.id()) }` | ||
* )) | ||
* ) | ||
* | ||
* @example <caption>Using a custom description</caption> | ||
* import { q, actorCalled } from '@serenity-js/core'; | ||
* import { Send, DeleteRequest } from '@serenity-js/rest'; | ||
* | ||
* actorCalled('Alice').attemptsTo( | ||
* Send.a(DeleteRequest.to( | ||
* q `/articles/${ Text.of(Article.id()) }`.describedAs('/articles/:id') | ||
* )) | ||
* ) | ||
* | ||
* @param {TemplateStringsArray} templates | ||
* @param {Array<Answerable<string | number>>} parameters | ||
* | ||
* @returns {Question<Promise<string>>} | ||
* | ||
* @see {@link Question} | ||
*/ | ||
export function q(templates: TemplateStringsArray, ...parameters: Array<Answerable<string | number>>): Question<Promise<string>> { | ||
return Question.about(templates.join('{}'), actor => | ||
Promise.all(parameters.map(parameter => actor.answer(parameter))) | ||
.then(answers => | ||
templates | ||
.map((template, i) => i < answers.length | ||
? [ template, answers ] | ||
: [ template ]) | ||
.reduce((acc, tuple) => acc.concat(tuple)) | ||
.join('') | ||
) | ||
); | ||
} |