Skip to content
Permalink
Browse files
feat(core): q makes Question<string> templates as easy as regular s…
…tring templates
  • Loading branch information
jan-molak committed Dec 7, 2020
1 parent 6f10c0b commit 9db29f8
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
@@ -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')
});
});
@@ -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('')
)
);
}

0 comments on commit 9db29f8

Please sign in to comment.